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

C# 如何使用编码的UI测试通过自定义HTML属性搜索元素,c#,asp.net,.net,coded-ui-tests,C#,Asp.net,.net,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-

我在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">
其他一些细节:

  • 视窗7
  • Internet Explorer 11
  • localhost
    交付的网站,在Internet选项安全设置中标记为“受信任”


更新:感谢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站点中使用它,我有一个更详细的故事:因为Angular甚至使用自定义属性,如ng-*

,您可以通过使用ControlDefinition作为propertyname进行搜索来实现这一点。以下代码行为我实现了这一技巧:

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和direct解决方案,在这里的其他答案中进行了解释

我发现,虽然
ControlDefinition
属性最初使用Visual Studio Marketplace(v1.7)上的开箱即用功能无法跨浏览器(使用Chrome)工作,但在
chromedriver.exe
(到v2.41)之后,一切都开始按预期工作

请注意,升级的
chromedriver.exe
必须“解锁”,才能正常工作。我使用PowerShell的
取消阻止文件
命令来执行此操作,注意必须使用
以管理员身份运行才能成功

实用方法:

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";
publicpropertyExpression GetByCustomAttribute(string attributeName,string value)
{
返回新的PropertyExpression(HtmlControl.PropertyNames.ControlDefinition,
$“{attributeName}=\”{value}\”,
PropertyExpressionOperator.Contains);
}
公共静态T GetControlByCustomAttribute(此BrowserWindow BrowserWindow,
字符串attributeName,
字符串值,
HtmlControl上下文=空)
其中T:HtmlControl
{
字符串queryContext=(上下文!=null?“参数[0]”:“文档”);
字符串脚本=$“返回{queryContext}.querySelector(\”[{attributeName}=\'{value}\']\”)”;
将browserWindow.ExecuteScript(脚本、上下文)返回为T;
}
用法:

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";
BrowserWindow.CurrentBrowser=“chrome”;
var browser=BrowserWindow.Launch(新Uri(“index.html”);
//首先查找具有自定义数据属性的项的父项
var div=新的HtmlControl(浏览器);
添加(HtmlControl.PropertyNames.Id,“myDiv”);
//使用ControlDefinition解决方案:
HTMLEDIT1=新HtmlEdit(div);
edit1.SearchProperties.Add(GetByItemId(“4711”);
//或使用JavaScript解决方案:
HTMLEDIT2=browser.GetControlByItemId(“4711”,div);
//使用控制。。。
edit1.Text=“Text”;
edit2.Text=“Text”;

ExecuteScript
方法总是为我返回
null
。我读了这篇博文,使用
ControlDefinition
作为过滤器也找不到元素。需要进行更多挖掘我暂停调试器并使用
window.ExecuteScript(“document.querySelector('body');”)
,甚至返回
null
(“window”字段是BrowserWindow对象)。我尝试了
window.ExecuteScript(“alert('foo');”)
,它显示的浏览器警报很好。JavaScript正在执行,它只是没有捕获脚本的返回值。我找到了答案。无论出于什么愚蠢的原因,我必须
返回eval'd JavaScript中的document.querySelector(…)
。我编辑了你的答案以反映这一点(如果可以的话)+1并标记为答案。谢谢我理解,但请注意这有一个明显的缺点。您无法运行AfroMogli的跨浏览器提供的解决方案。因此,当您在chrome或Firefox上运行此测试时,测试将失败,因为跨浏览器播放仍然不支持控件定义,我必须尝试一下。理想情况下,我想要一个不需要在浏览器上运行JavaScript的解决方案。太好了,请回复我们它是如何运行的:)这太好了!我还喜欢它是一个纯编码的UI测试解决方案。我累坏了。我希望我能把你和marcel de vries的答案作为解决方案。太好了!是的,当有纯编码的UI测试解决方案作为替代方案时,您不想执行JS代码。但是JS解决方案也很有效,所以我知道你很痛苦:)请注意,使用这个解决方案,你无法在chrome和Firefox上运行它,因为跨浏览器插件不支持使用ControlDefinition。这是我多次要求团队解决的问题,但尚未改变。我也希望这个解决方案更好,但更喜欢javascrip解决方案,这样我就可以跨浏览器运行它了。