我正在尝试使用 winappdriver 从 WPF 项目中的 GridView 获取单元格值。
我在这行遇到问题:
string name = row.FindElementByName("Name1").Text;
使用给定的搜索无法在页面上找到元素 参数。
您可以查看我的以下代码吗:
<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);
您显然选择了错误的工具来完成您的任务。 自动化旨在与 UI 元素配合使用,但您需要该任务的数据。 查看 DataGrid 的可视化树是什么样子的:
DataGrid 继承自 ItemsControl。在他的想象中只有行。没有专栏。 从特定单元格中提取数据是可能的,但这非常困难并且没有意义。
您需要创建一个普通的数据源。 首先,采用 INotifyPropertyChanged 的某种实现。 例如,这个:
/// <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);
}
}
在此基础上,您可以创建集合元素的类型:
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); }
}
以及带有集合的 ViewModel:
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]"},
};
}
将其连接到 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>
现在您的任务简化为在人员集合中查找所需的项目。
如果您知道网格中单元格的确切位置(例如 x 行、y 列),请使用以下自定义代码。
它对我有用,我必须得到第三行第二列的数字。网格有 6 列。
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
以下是函数定义。 (虽然不是完美的代码)
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;
}
我还必须获得最大数量。所以,我做了以下事情。
allIds.Sort();
allIds.Reverse();
var maxId = allIds[0];
使用inspect.exe(通过WinAppDriver下载),我发现在DataGridView中可以访问DataGridView每个单元格中的文本,如下所示
string text = driver.FindElementByName( "<ColumnName> Row <x>" ).Text
其中 ColumnName 是列的名称,x 是行号(从 0 开始)
但是我发现上述方法非常慢,更快的方法是定位 DataGridView 并使用 XPath 来定位其所有元素(单元格),如下所示
var Results_DGV = Driver.FindElementByAccessibilityId( "DGV_Results" );
var DGV_Cells = Results_DGV.FindElementsByXPath("//*");
for ( for loop controls ) {
loop over the cells
}
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;
}
}