How To Access GridView Cells using Winappdriver?
Asked Answered
T

4

6

I am trying to get Cell Value From GridView In WPF Project with winappdriver.

I get an issue with this line:

 string name = row.FindElementByName("Name1").Text;

An element could not be located on the page using the given search parameters.

Could you please check my follwing code:

 <Grid>
        <ListView Margin="10" Name="lvUsers" AutomationProperties.AutomationId="lvUsers">
                <ListView.View>
                <GridView x:Name="ListViewItem"  AutomationProperties.AutomationId="ListViewItem">
                        <GridViewColumn x:Name="Name1" AutomationProperties.Name="Name1" AutomationProperties.AutomationId="Name1" Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
                        <GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
                        <GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" />
                    </GridView>
                </ListView.View>
            </ListView>
  </Grid>

 var listBox = session.FindElementByAccessibilityId("lvUsers");
            var comboBoxItems = listBox.FindElementsByClassName("ListViewItem");
             foreach (var row  in  comboBoxItems)
             {
                string name = row.FindElementByName("Name1").Text;
                if (name == "John Doe")
                {                     
                   findName = true;
                   break;
                }
         }
        Assert.AreEqual(findName, true);
Thiazine answered 27/6, 2020 at 10:25 Comment(2)
Probably not what you want to hear but... It's usual to automate tests on wpf viewmodels and not views. Iterating through a datagrid or listview to find content of a cell is always going to be painful. A gridviewcolumn is an abstract thing. You won't find it in the visual tree. I recommend you download snoop ( there's a different version for .net core ) and use that to explore what you're actually getting in your UI when you run.Silas
WinAppDrive uses UI Automation. Use inspect.exe from the Windows SDK to check what UI Automation "sees" from your running application. github.com/microsoft/WinAppDriver/blob/master/Docs/… Only UIElement-derived classes can support UI Automation. GridViewColumn is not a UIElementAryl
E
0

You have obviously chosen the wrong tool to accomplish your task. Automation is designed to work with UI elements, but you need data for the task. See what the Visual Tree of your DataGrid looks like:

enter image description here

DataGrid is inherited from ItemsControl. And in his visualization there are only rows. No columns. It is possible to extract data from a specific cell, but it is very difficult and does not make sense.

You need to create a normal Data source. To get started, take some kind of implementation of INotifyPropertyChanged. For example, this:

/// <summary>Base class implementing INotifyPropertyChanged.</summary>
public abstract class BaseINPC : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>Called AFTER the property value changes.</summary>
    /// <param name="propertyName">The name of the property.
    /// In the property setter, the parameter is not specified. </param>
    public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    /// <summary> A virtual method that defines changes in the value field of a property value. </summary>
    /// <typeparam name = "T"> Type of property value. </typeparam>
    /// <param name = "oldValue"> Reference to the field with the old value. </param>
    /// <param name = "newValue"> New value. </param>
    /// <param name = "propertyName"> The name of the property. If <see cref = "string.IsNullOrWhiteSpace (string)" />,
    /// then ArgumentNullException. </param> 
    /// <remarks> If the base method is not called in the derived class,
    /// then the value will not change.</remarks>
    protected virtual void Set<T>(ref T oldValue, T newValue, [CallerMemberName] string propertyName = "")
    {
        if (string.IsNullOrWhiteSpace(propertyName))
            throw new ArgumentNullException(nameof(propertyName));

        if ((oldValue == null && newValue != null) || (oldValue != null && !oldValue.Equals(newValue)))
            OnValueChange(ref oldValue, newValue, propertyName);
    }

    /// <summary> A virtual method that changes the value of a property. </summary>
    /// <typeparam name = "T"> Type of property value. </typeparam>
    /// <param name = "oldValue"> Reference to the property value field. </param>
    /// <param name = "newValue"> New value. </param>
    /// <param name = "propertyName"> The name of the property. </param>
    /// <remarks> If the base method is not called in the derived class,
    /// then the value will not change.</remarks>
    protected virtual void OnValueChange<T>(ref T oldValue, T newValue, string propertyName)
    {
        oldValue = newValue;
        RaisePropertyChanged(propertyName);
    }

}

On its basis you can create a type for collection edements:

public class PersonVM : BaseINPC
{
    private string _name;
    private uint _age;
    private string _mail;

    public string Name { get => _name; set => Set(ref _name, value); }
    public uint Age { get => _age; set => Set(ref _age, value); }
    public string Mail { get => _mail; set => Set(ref _mail, value); }
}

And ViewModel with collection:

public class ViewModel
{
    public ObservableCollection<PersonVM> People { get; } 
        = new ObservableCollection<PersonVM>()
        {
            new PersonVM(){Name="Peter", Age=20, Mail="[email protected]"},
            new PersonVM(){Name="Alex", Age=30, Mail="[email protected]"},
            new PersonVM(){Name="Nina", Age=25, Mail="[email protected]"},
        };
}

Connect it to the DataContext Windows:

<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
<Grid>
    <ListView Margin="10" ItemsSource="{Binding People}">
        <ListView.View>
            <GridView x:Name="ListViewItem" >
                <GridViewColumn x:Name="Name1" Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
                <GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
                <GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" />
            </GridView>
        </ListView.View>
    </ListView>
</Grid>

Now your task is reduced to finding the desired item in the People collection.

Esotropia answered 2/7, 2020 at 16:50 Comment(0)
F
0

If you know the exact location of the cell (e.g. x row, y column) in the grid, use following custom code.
It worked for me, I had to get the number in 3rd row, 2nd column. The grid has 6 columns.

var gridItemsCollection = grid.FindElementsByXPath("//ListItem/Text");
List<int> allIds = HelperClass.GetColumnValuesFromGrid(gridItemsCollection, 6,2).ConvertAll(int.Parse);
var myId = allIds[2];//3rd row. 3-1

Following is the function definition. (Not a perfect code though)

public static List<string> GetColumnValuesFromGrid(IReadOnlyCollection<AppiumWebElement> gridItemsCollection, int gridColumns, int selectColumn)
    {
        List<string> list = new List<string>();
    List<string> selectList = new List<string>();

int index = selectColumn - 1;
if (index < 0 || gridItemsCollection.Count == 0)
{
return null;
}

foreach (var element in gridItemsCollection)
{
    list.Add(element.Text);
}            

while (index < list.Count)
{
    selectList.Add(list[index]);
    index += gridColumns;
}

return selectList;
}

I also had to get the maximum number. So, I did the following.

allIds.Sort();
allIds.Reverse();
var maxId = allIds[0];
Fletafletch answered 22/9, 2020 at 2:37 Comment(0)
C
0

Using inspect.exe (downloaded with WinAppDriver), I found that in a DataGridView one can access the text in each Cell of the DataGridView as follows

string text = driver.FindElementByName( "<ColumnName> Row <x>" ).Text

where ColumnName is the name of the Column and x is the Row number (starting from 0)

However I found the above method to be very slow a much quicker method is to locate the DataGridView and them use XPath to locate all its elements (cells), as illustrate below

var Results_DGV = Driver.FindElementByAccessibilityId( "DGV_Results" );

var DGV_Cells = Results_DGV.FindElementsByXPath("//*");

for ( for loop controls ) {
     loop over the cells
}                                                           
Calcification answered 14/4, 2023 at 8:1 Comment(0)
A
0
var listBox = session.FindElementByAccessibilityId("lvUsers");
var comboBoxItems =listBox.FindElementsByClassName("ListViewItem");
             foreach (var row  in  comboBoxItems)
             {  
              // Find by Name with Columname + Row + Row Index as value                    
               var name = row.FindElementByName("Name1 Row 2").Text;
                if (name == "John Doe")
                {                     
                   findName = true;
                   break;
                }
         }
Alumina answered 14/3, 2024 at 13:25 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Yuki

© 2022 - 2025 — McMap. All rights reserved.