如何使用编码 UI 测试通过自定义 HTML 属性搜索元素
How to search for an element by custom HTML attribute using Coded UI Tests
我在 GridView 中有一堆 input[type=submit]
按钮。这些按钮的 id
s 和 name
s 虽然可以预测,因为它们使用索引号,但不太适合使用 SpecFlow 和编码 UI 测试的自动化。我发现很难搜索那些按钮元素。
发送到浏览器的 HTML 片段:
<input type="submit" id="abc_xyz_0" data-task-id="123" value="Apply">
<input type="submit" id="qrs_tuv_0" data-task-id="345" value="Renew">
按钮文本在每一行中都是通用的("Apply" 和 "Renew"),但 data-task-id
属性是唯一的。我想使用此属性和值来标识要单击的按钮。我正在尝试使用 SearchProperties
和 FilterProperties
但我不断收到异常:
System.NotSupportedException: The property DataTaskId is not supported for this control.
我是如何尝试找到控件的:
HtmlInputButton button = new HtmlInputButton(document);
button.SearchProperties["data-task-id"] = "123";
// or button.SearchProperties["DataTaskId"] = "123";
一些额外的细节:
- Windows 7
- Internet Explorer 11
- 从
localhost
提供的网站,并在 Internet 选项安全设置中标记为 "trusted"
更新:感谢 marcel de vries 和 AfroMogli 的回答。 Marcel 使用 JavaScript 查找元素,AfroMogli 使用纯 C# 和 CodedUI API。这两个答案同样有效,我没有注意到任何一个之间的性能差异。两个同样好的解决方案。
执行此操作的最简单方法是使用简单的 javascript 执行来根据您提供的任何属性名称和值获取控件。像这样:
const string javascript = "return document.querySelector('{0}');";
var bw = BrowserWindow.Launch("your page");
string selector = "[data-task-id]='123']";
var control = bw.ExecuteScript(string.Format(javascript,selector));`
变量控件现在包含您要查找的控件并且类型正确。所以如果它是例如和 HtmlHyperLink,您可以直接使用它。
关于如何在 Angular 站点中使用它,我有一个更长的故事:http://fluentbytes.com/testing-angular-sites-with-codedui/ 因为 angular 甚至使用像 ng-*
这样的自定义属性
您可以通过将 ControlDefinition 作为属性名进行搜索来完成此操作。以下代码行对我有用:
HtmlInputButton button = new HtmlInputButton(document);
button.SearchProperties.Add(new PropertyExpression(HtmlControl.PropertyNames.ControlDefinition, "data-task-id=\"123\"", PropertyExpressionOperator.Contains));
允许您传入任何属性名称和值的示例方法:
public HtmlInputButton InputButton(string attributeName, string attributeValue, UITestControl container = null)
{
string controlDefinition = string.Format("{0}=\"{1}\"", attributeName, attributeValue);
HtmlInputButton button = new HtmlInputButton(container ?? document);
button.SearchProperties.Add(new PropertyExpression(HtmlControl.PropertyNames.ControlDefinition, controlDefinition, PropertyExpressionOperator.Contains));
return button;
}
编辑:
或者如果属性名称没有更改,请执行以下操作?
public PropertyExpression GetByTaskId(string value)
{
return new PropertyExpression(
HtmlControl.PropertyNames.ControlDefinition,
$"data-task-id=\"{value}\"",
PropertyExpressionOperator.Contains
);
}
HtmlInputButton button = new HtmlInputButton(document);
button.SearchProperties.Add(GetByTaskId("123"));
我编写这些实用方法是为了测试 JavaScript 和其他答案中解释的直接解决方案。
我发现虽然 ControlDefinition
属性 最初不能使用开箱即用的 Selenium Cross-Browser Components available on the Visual Studio Marketplace (v1.7), everything started working as expected after upgrading 跨浏览器(使用 Chrome) chromedriver.exe
(到 v2.41)。
请注意升级后的 chromedriver.exe
必须是 'unblocked' 才能工作。我使用 PowerShell 的 Unblock-File
命令来执行此操作,注意 这必须使用 Run as Administrator
启动才能成功。
实用方法:
public PropertyExpression GetByCustomAttribute(string attributeName, string value)
{
return new PropertyExpression(HtmlControl.PropertyNames.ControlDefinition,
$"{attributeName}=\"{value}\"",
PropertyExpressionOperator.Contains);
}
public static T GetControlByCustomAttribute<T>(this BrowserWindow browserWindow,
string attributeName,
string value,
HtmlControl context = null)
where T : HtmlControl
{
string queryContext = (context != null ? "arguments[0]" : "document");
string script = $"return {queryContext}.querySelector(\"[{attributeName}=\'{value}\']\");";
return browserWindow.ExecuteScript(script, context) as T;
}
用法:
BrowserWindow.CurrentBrowser = "chrome";
var browser = BrowserWindow.Launch(new Uri("index.html"));
// first find parent of item with custom data attribute
var div = new HtmlControl(browser);
div.SearchProperties.Add(HtmlControl.PropertyNames.Id, "myDiv");
// Either using ControlDefinition solution:
HtmlEdit edit1 = new HtmlEdit(div);
edit1.SearchProperties.Add(GetByItemId("4711"));
// Or using JavaScript solution:
HtmlEdit edit2 = browser.GetControlByItemId<HtmlEdit>("4711", div);
// use control...
edit1.Text = "text";
edit2.Text = "text";
我在 GridView 中有一堆 input[type=submit]
按钮。这些按钮的 id
s 和 name
s 虽然可以预测,因为它们使用索引号,但不太适合使用 SpecFlow 和编码 UI 测试的自动化。我发现很难搜索那些按钮元素。
发送到浏览器的 HTML 片段:
<input type="submit" id="abc_xyz_0" data-task-id="123" value="Apply">
<input type="submit" id="qrs_tuv_0" data-task-id="345" value="Renew">
按钮文本在每一行中都是通用的("Apply" 和 "Renew"),但 data-task-id
属性是唯一的。我想使用此属性和值来标识要单击的按钮。我正在尝试使用 SearchProperties
和 FilterProperties
但我不断收到异常:
System.NotSupportedException: The property DataTaskId is not supported for this control.
我是如何尝试找到控件的:
HtmlInputButton button = new HtmlInputButton(document);
button.SearchProperties["data-task-id"] = "123";
// or button.SearchProperties["DataTaskId"] = "123";
一些额外的细节:
- Windows 7
- Internet Explorer 11
- 从
localhost
提供的网站,并在 Internet 选项安全设置中标记为 "trusted"
更新:感谢 marcel de vries 和 AfroMogli 的回答。 Marcel 使用 JavaScript 查找元素,AfroMogli 使用纯 C# 和 CodedUI API。这两个答案同样有效,我没有注意到任何一个之间的性能差异。两个同样好的解决方案。
执行此操作的最简单方法是使用简单的 javascript 执行来根据您提供的任何属性名称和值获取控件。像这样:
const string javascript = "return document.querySelector('{0}');";
var bw = BrowserWindow.Launch("your page");
string selector = "[data-task-id]='123']";
var control = bw.ExecuteScript(string.Format(javascript,selector));`
变量控件现在包含您要查找的控件并且类型正确。所以如果它是例如和 HtmlHyperLink,您可以直接使用它。
关于如何在 Angular 站点中使用它,我有一个更长的故事:http://fluentbytes.com/testing-angular-sites-with-codedui/ 因为 angular 甚至使用像 ng-*
这样的自定义属性您可以通过将 ControlDefinition 作为属性名进行搜索来完成此操作。以下代码行对我有用:
HtmlInputButton button = new HtmlInputButton(document);
button.SearchProperties.Add(new PropertyExpression(HtmlControl.PropertyNames.ControlDefinition, "data-task-id=\"123\"", PropertyExpressionOperator.Contains));
允许您传入任何属性名称和值的示例方法:
public HtmlInputButton InputButton(string attributeName, string attributeValue, UITestControl container = null)
{
string controlDefinition = string.Format("{0}=\"{1}\"", attributeName, attributeValue);
HtmlInputButton button = new HtmlInputButton(container ?? document);
button.SearchProperties.Add(new PropertyExpression(HtmlControl.PropertyNames.ControlDefinition, controlDefinition, PropertyExpressionOperator.Contains));
return button;
}
编辑:
或者如果属性名称没有更改,请执行以下操作?
public PropertyExpression GetByTaskId(string value)
{
return new PropertyExpression(
HtmlControl.PropertyNames.ControlDefinition,
$"data-task-id=\"{value}\"",
PropertyExpressionOperator.Contains
);
}
HtmlInputButton button = new HtmlInputButton(document);
button.SearchProperties.Add(GetByTaskId("123"));
我编写这些实用方法是为了测试 JavaScript 和其他答案中解释的直接解决方案。
我发现虽然 ControlDefinition
属性 最初不能使用开箱即用的 Selenium Cross-Browser Components available on the Visual Studio Marketplace (v1.7), everything started working as expected after upgrading 跨浏览器(使用 Chrome) chromedriver.exe
(到 v2.41)。
请注意升级后的 chromedriver.exe
必须是 'unblocked' 才能工作。我使用 PowerShell 的 Unblock-File
命令来执行此操作,注意 这必须使用 Run as Administrator
启动才能成功。
实用方法:
public PropertyExpression GetByCustomAttribute(string attributeName, string value)
{
return new PropertyExpression(HtmlControl.PropertyNames.ControlDefinition,
$"{attributeName}=\"{value}\"",
PropertyExpressionOperator.Contains);
}
public static T GetControlByCustomAttribute<T>(this BrowserWindow browserWindow,
string attributeName,
string value,
HtmlControl context = null)
where T : HtmlControl
{
string queryContext = (context != null ? "arguments[0]" : "document");
string script = $"return {queryContext}.querySelector(\"[{attributeName}=\'{value}\']\");";
return browserWindow.ExecuteScript(script, context) as T;
}
用法:
BrowserWindow.CurrentBrowser = "chrome";
var browser = BrowserWindow.Launch(new Uri("index.html"));
// first find parent of item with custom data attribute
var div = new HtmlControl(browser);
div.SearchProperties.Add(HtmlControl.PropertyNames.Id, "myDiv");
// Either using ControlDefinition solution:
HtmlEdit edit1 = new HtmlEdit(div);
edit1.SearchProperties.Add(GetByItemId("4711"));
// Or using JavaScript solution:
HtmlEdit edit2 = browser.GetControlByItemId<HtmlEdit>("4711", div);
// use control...
edit1.Text = "text";
edit2.Text = "text";