I am using the UIAutomation library from Microsoft for C#. I have an element with the following properties:
LocalizedControlType: "edit"
AutomationId: "Database"
Readonly: False
Text field
I am able to read these properties using Inspect. However, I am unable to find it with the following command in C#:
AutomationElement ae = root.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, "Database"), new PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "edit"));
In fact, the only way I can find it is via the following command (which follows the tree):
AutomationElement ae = root.FindAll(TreeScope.Children, Condition.TrueCondition)[0]
.FindAll(TreeScope.Children, Condition.TrueCondition)[0]
.FindAll(TreeScope.Children, Condition.TrueCondition)[0]
.FindAll(TreeScope.Children, Condition.TrueCondition)[0]
.FindAll(TreeScope.Children, Condition.TrueCondition)[2]
.FindAll(TreeScope.Children, Condition.TrueCondition)[0]
.FindAll(TreeScope.Children, Condition.TrueCondition)[0]
.FindAll(TreeScope.Children, Condition.TrueCondition)[0]
.FindAll(TreeScope.Children, Condition.TrueCondition)[2]
.FindAll(TreeScope.Children, Condition.TrueCondition)[0];
Unfortunately, when I attempt to edit the value, I am hit with an error message that the selected element is read only. However, the element is in fact not read-only. Also, the element does not have the automationId that it should have. None of the elements in the tree have an AutomationId when viewed with the UIAutomation.
The application that I am attempting to automate is a proprietary application, and this is the first time I've had this issue. I'm not sure what might be causing this, what would be something I could look into?
EDIT: Just as an update, I was able to successfully locate the elements required using indexing from root.FindAll(TreeScope.Descendants, Condition.TrueCondition).
However, I have the following code:
cachedElement = findAll[index];
Console.Write("cachedelement : "); cachedElement.ToConsole();
return 0;
If I just let the program run, it fails when it tries to write the text to the text field. Also, it shows that the selected element has no attributes (during the cachedelement.ToConsole).
Interestingly, if I put a breakpoint on return 0; (or use System.Diagnostics.Debugger.Break()), cachedElement.ToConsole() outputs the LCT as "edit" (still no automationId), and writes the text to the text field properly. Why I find this interesting, is that the breakpoint is set after the call to ToConsole. It shouldn't have an effect on the running code at all.
Based on your second code example the element you are looking for is nested pretty far down the tree so the first code example will never find the element because it is only looking at children of the desktop. If you want to look for the element from the root node you would need to use TreeScope.Descendants for the treescope. I don't recommend using descendants though because it will be slow and more than likely it will cause a stack overflow when used from the root element.
In the worst case scenario I do a FindFirst for my window using child scope from the root element then I use descendants from the applications root. This only works quickly though on small applications with trivial users interfaces. If you have something complex like a grid with cells I would suggest using FindFirst with child scope all the way down to the element you are looking for similar to how you used the find all.
If you can post the code you are using to edit the element I can update this answer with information on how to edit the control that is specific to your situation. In general though how you would edit a text box control would be to use the ValuePattern like this.
public void SetValue(AutomationElement element, string text)
{
var valuePattern = (ValuePattern) element.GetCurrentPattern(ValuePattern.Pattern);
valuePattern.SetValue(text);
}
Hopefully this helps.
Related
I have a curious issue for which I can not seem to find a solution on this forum (or elsewhere on the internet)
I am building a test automation application using Selenium and .NET Core 3.1. I am trying to select an element in the DOM using a OpenQA.Selenium.IWebDriver and previously selected OpenQA.Selenium.IWebElement and an Xpath selector like so:
IWebElement parent => driver.FindElement(By.CssSelector("#parent"));
IWebElement child(string t) = parent.FindElement(By.XPath($".//li[#data-original-title=\"{t}\"]"))
...
var el = child("'title-1'");
Where the DOM is the following:
<div id="parent">
... <!-- child element is nested multiple levels -->
<li title data-original-title="'title-1'"></li>
<li title data-original-title="'title-2'"></li>
...
</div>
When I try to perform this action i get the following error:
OpenQA.Selenium.NoSuchElementException : no such element: Unable to locate element: {"method":"xpath","selector":".//li[#data-original-title="'title-1'"]"}
(Session info: chrome=85.0.4183.102)
However when I copy this xpath which is apparently non existent and search the dom using chrome debugger it does match the element.
Then I tried to figure out what could be the cause, maybe I made a mistake with the parent or smth. So I try the following XPath for the child selector, matching the other attribute (with out value)
IWebElement child(string t) = parent.FindElement(By.XPath($".//li[#title]"))
This does return an element, and after checking the debugger it is my intended child element. Now I assume something must go wrong with the formatting of the string so I try the following selector (without the value)
IWebElement child(string t) = parent.FindElement(By.XPath($".//li[#data-original-title]"))
This produces (roughly) the same error as before:
OpenQA.Selenium.NoSuchElementException : no such element: Unable to locate element: {"method":"xpath","selector":".//li[#data-original-title]"}
(Session info: chrome=85.0.4183.102)
And again when I try to find the element in the DOM using the search function in the chrome debugger using the above xpath .//li[#data-original-title] it does match my expected child element.
So I am now thinking the hyphens must cause the problem. I tried some research on the issue but the only real thing I could find was this stackoverflow article
Here they recommend to use an CSS selector instead, which I tried and also did not work, furthermore I believe the relative to parent function does not work with the CSS selector.
In this article the poster comments that the problem in the end was caused by DOM structure, which does not help me very much and also makes me think I just made a big mistake somewhere along the line that I just have been overlooking.
Conclusion
Based on the above XPath selector is there anything I have done wrong that causes my mismatch, or is there maybe a difference in implementation of XPath in chrome debugger vs the .NET Core Selenium library that I should know of that could cause this issue?
Thanks in advance and sorry for the long post :$
So as it turned out it was something I overlooked. The data-original-tile attribute is set reactively, based on user mouse input (as it is used for conditionally displaying a tooltip) and title property is cleared. Since selenium only fires a click event and does not actually hover above the element without specifically clarifying so the attributes won't be swapped during automation.
Conclusion
I should have checked the source HTML more thoroughly for updates to the DOM on user interaction.
I am facing with problem when I tried to access Android element by ID in C#, I also tried with Appium-desktop version and seems not accessible for example:
I tried with and without package name and there is an exception every time which says could not find element. Also from Appium desktop selector.
AndroidElement selectPlant = driver.FindElementById("com.heidelbergcement.aom.stage.dev:id/loginForm-plantSelection")
You're misunderstanding something.
FindElementById refers to the unique identifier of the element which in most cases is the RuntimeId of the UI element.
What you should use is findElementByAccessibilityId, which refers to AutomationId of the UI element as shown in the inspect tool.
Total credit to this.
I have a control which is a TreeView node that will always be set to the name of the PC you're currently running the tested software on. Therefore I need the Search Property for the control's Name property to be set to `Environment.MachineName like so:
The problem with this is that inside UIMap.Designer.cs I can see the generated code that this makes and it's trying to use Environment.MachineName as a string:
this.SearchProperties[WinTreeItem.PropertyNames.Name] = "Environment.MachineName";
Obviously this approach won't work, and it's not possible to manually edit UIMap.Designer.cs to change this. How can I make this work then?
The general approach is to use the UI Map editor to remove that search item. This should be possible from the window shown in the question. Then, in the test method that needs to do the search, add a statement something like
this.uimap.controlNames.SearchProperties[WinTreeItem.PropertyNames.Name]
= Environment.MachineName;
or
this.uimap.controlNames.SearchProperties.Add(name, Environment.MachineName;
The precise statement depends on the structure of the controls, so where I wrote .controlNames. it may need a series of dot-separated control names.
I want to show the DOM as it is on the web browser with all comments and html, head, body, etc.. preserve its structure. Currently, I can only start from node html. Document.All didnt help.
The only way I can see is webBrowser1.Document.Body but I would miss the commentss, head etc.. Then if I go with Document.All then that gives me all the nodes.
I think the only choice with the WebBrowser control to get what you want is to use Document.All. Although this gives all elements not just top-level, each element has a .Parent element property so you can loop through them (or use Linq) and get only the ones that have <body> or <head> as the parent element.
Try using HTMLAgilityPack, it support Xpath so you can get any node as you want.
As suggested by hienvd_csuit, I think HTML Agility Pack is your best option. If you still want to use the WebBrowser, a possible solution is to access the unmanaged DOM directly, using dynamic (requires .NET 4+). For instance you can do something like this:
dynamic dom = wb.Document.DomDocument;
foreach (dynamic node in dom.childNodes)
{
Console.WriteLine ("{0} - {1} - {2}", node.nodeType, node.nodeName, node.nodeValue);
}
Of course, you need to know the structure of the DOM, since intellisense doesn't work on dynamic objects; you can find some information about it here.
You should be able to query (there is a property somewhere) if a particular item has a child node or not, also, you can query if it is a parent node or if a particular item has a parent or not, and if it does, discard, and you can keep querying for parent such as item.parent.parent (pls check intellisense for exact object/property names) and if it returns nothing, it means there is only one parent (assuming item.parent doesnt return nothing), and you can organize how many levels deep the nodes can/must be. So based on the child or parent checking method (or both) you can choose to either include it in your collection or discard it.
Of course, you might get many "P" tags or DIV/SPAN tag's as your top level nodes/items. So, i'm assuming there is a chance you will not want these, so feel free to discard them and query their children.
I have a RichTextBox created programmatically with the following code:
RichTextBox RT = new RichTextBox();
RT.Name = "asdf";
RT.Text = "blah";
TableLayoutPanel.Controls.Add(RT,0,0);
Now let's say I want to modify the text of RT, and it's name is "asdf", Visual Studio won't allow me to write asdf.Text = "haha" because asdf doesn't exist yet.
How can I grab "asdf" specifically and set its text? Because this RichTextBox is in a specific cell, can I grab it based on its cell coordinates?
You should be able to get a reference to it via the TableLayoutPanel.Controls property, which returns a TableLayoutControlCollection. That class provides two ways to locate a control by name: the Item property and the Find method. The Item property returns a control with the specified name, whereas the Find method returns a collection of controls. In both cases you would need to cast from a Control to a RichTextBox.
var rt = (RichTextBox)myTableLayoutPanel.Controls.Item["asdf"];
// or
var rts = myTableLayoutPanel.Controls.Find("asdf", false);
foreach (var rt in rts)
// (RichTextBox)rt ...
EDIT: be sure to check that the result is not null before using it in case the control is not found.
Well... you did instantiate the RichTextBox and have a reference that you can use; it's called "RT" in your example.
Now, likely you've done this in a method so it was locally scoped and is no longer available when you want it. So you save that reference somehow by assigning it to some member you can access. If you have a lot of them and want to differentiate by name somehow, you might stick it into a Dictionary<string, RichTextBox>, for example. Or you could put it in some static variable; there are numerous options, each with their own pros and cons.
The one thing you probably don't want to do is walk the control tree looking for the control with the name you specified. But you could also do that, if you really wanted to.