如何使用编码 UI 测试通过自定义 HTML 属性搜索元素

How to search for an element by custom HTML attribute using Coded UI Tests

我在 GridView 中有一堆 input[type=submit] 按钮。这些按钮的 ids 和 names 虽然可以预测,因为它们使用索引号,但不太适合使用 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 属性是唯一的。我想使用此属性和值来标识要单击的按钮。我正在尝试使用 SearchPropertiesFilterProperties 但我不断收到异常:

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";

一些额外的细节:


更新:感谢 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";