Reputation: 18783
I have a bunch of input[type=submit]
buttons inside a GridView. The id
s and name
s of these buttons, while predictable because they use an index number, are not well suited to automation using SpecFlow and Coded UI Tests. I'm finding it difficult to search for those button elements.
A snippet of the HTML delivered to the browser:
<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">
The button text is generic in each row ("Apply" and "Renew"), but the data-task-id
attribute is unique. I would like to use this attribute and value to identify a button to click on. I'm trying to use the SearchProperties
and FilterProperties
but I keep getting exceptions:
System.NotSupportedException: The property DataTaskId is not supported for this control.
How I'm attempting to find the control:
HtmlInputButton button = new HtmlInputButton(document);
button.SearchProperties["data-task-id"] = "123";
// or button.SearchProperties["DataTaskId"] = "123";
Some additional details:
localhost
and is marked "trusted" in the Internet Options Security SettingsUpdate: Thanks to both marcel de vries and AfroMogli for their answers. Marcel's uses JavaScript to find the element, and AfroMogli's uses pure C# and the CodedUI API. Both answers worked equally well, and I didn't notice a performance difference between either one. Two equally good solutions.
Upvotes: 4
Views: 5521
Reputation: 7463
I wrote these utility methods to test both the JavaScript and direct solutions explained in other answers here.
I found that although the ControlDefinition
property did not initially work cross-browser (with Chrome) using the out-of-the-box Selenium Cross-Browser Components available on the Visual Studio Marketplace (v1.7), everything started working as expected after upgrading the chromedriver.exe
(to v2.41).
Be aware the upgraded chromedriver.exe
must be 'unblocked' before it will work. I used PowerShell's Unblock-File
command to do this, note this must be started using Run as Administrator
to succeed.
Utility methods:
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;
}
Usage:
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";
Upvotes: 0
Reputation: 587
You can accomplish this by searching with the ControlDefinition as propertyname. The following line of code did the trick for me:
HtmlInputButton button = new HtmlInputButton(document);
button.SearchProperties.Add(new PropertyExpression(HtmlControl.PropertyNames.ControlDefinition, "data-task-id=\"123\"", PropertyExpressionOperator.Contains));
Example method allowing you to pass in any attribute name and value:
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;
}
EDIT:
Or if the attribute name doesn't change, do the following?
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"));
Upvotes: 7
Reputation: 128
The simplest way of doing this is by using a simple javascript execute to get the control based on any attribute name and value you provide. something like this:
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));`
The variable control now contains the control you are looking for and is of the correct type. So if it is e.g. and HtmlHyperLink you can use it as such right a way.
I have a longer story about how to use this in Angular sites here: http://fluentbytes.com/testing-angular-sites-with-codedui/ since angular uses even custom attributes like ng-*
Upvotes: 2