Locating elements in web applications created by Ext JS framework could be a total nightmare for UI automators, as Ext JS generates one of the most complex DOM structures which have dynamic IDs with a large amount of duplicate class names.
Unlike automating applications with simple DOM structures, Selenium WebDriver's built-in methods like
By.Namewould barely identify anything in Ext JS applications. In most of the cases,
By.XPathwill be neccessary, even though using them are not that straightforward either.
Hopefully this article would help developers write the most concise meaningful and human readable locators, so that maintenance cost can be kept minimum when automating ExtJS applications.
Ext JS ExampleHere Sencha Ext JS' Ticket App is used as an example:
Don't match only IDsUnless IDs are explicitly defined in application's source code, ExtJS will produce IDs for each elements dynamically, like
button-1016-btnInnerEl. Matching elements using those dynamic numbers will make project unmaintainable. Even matching on IDs partially like
input[id$='inputEl']wouldn't help either, as there will be lots of others elements have the same id structures.
Don't use highly position-dependent XPathsHighly position-dependent XPaths, or even worse, absolute XPaths should be avoided no matter what, even for web applications with simple DOM structures. Because any tiny bit of DOM change will result in XPath becoming invalid. XPaths like
//div[contains(@class, 'x-panel-body')]/div/div/div/div/div/div/inputare too fragile in terms of UI automation.
Don't match single class name onlyJust like matching IDs, matching single class name won't help either. As ExtJS generates class names in a similar naming convention for all elements,
.x-form-fieldwill mostly likely result in multiple elements.
Don't perform exact match on multiple classesThis happens mostly to XPath locators instead of CSS selectors.
When matching multiple class names using CSS selectors, people would normally use something like
a.x-btn.btn-submit, which matches an anchor that has class
btn-submit. This is absolutely fine without any problems.
However, for XPaths, a common usage
.//a[contains(@class, 'x-btn btn-submit')]doesn't do the same thing, as it matches exactly class attributes
x-btn btn-submitwith exact one space and class order. This XPath is actually equivalent to CSS selector
a[class*='x-btn btn-submit']. Matching class names by exact string
x-btn btn-submitshould be avoided unless the order is important in that particular case.
Imagine we have few elements as the followings:
<input class="x-form-field x-form-text">
<input class="x-form-field x-form-text "><!-- Note the trailing space -->
<input class="x-form-required-field x-form-field x-form-text x-form-text-default ">
<input class="x-form-text x-form-field">
- Match only #1 (exact match)
- Match #1, #2 and #3 (match class contains
x-form-field x-form-text, class order matters)
- Match #1, #2, #3 and #4 (as long as elements have class
Don't use tools to generate locatorsThere are XPath generating extensions in Chrome or Firefox to create XPaths. Never ever use them against web applications with complex DOM. Some tools generate absolute or position-based XPaths, which are totally rubbish and shouldn't be used in any Selenium WebDriver code at all. Some extensions are smarter that generates some relative selectors based on class names or IDs. Due to the nature of Ext JS as explained above, class names or IDs are unlikely to be sufficient for locating elements in Ext JS applications most of the time.
Q: What makes a locator good locator?
A: In my opinion, good locators are unique and concise, not random or fragile.
Use meaningful class namesAmong all those Ext JS generated class names, sometimes some meaningful ones can be used as good locators. In the example above, there are two labels for text fields. To locate the 'Password' label,
.password labelwould be a nice and easy one, as
passwordis the meaningful and unique class name among those text fields.
Use unique attributesIf there are no meaningful class names generated by Ext JS can be found, the next step is to find some unique attributes. Sometimes this is even better than meaningful class names depending how unique they are.
Take the Ticket App login screen as an example, there is only one button and one dropdown combo box. It is fairly easy locate them using either CSS or XPath locators.
Find a unique ancestorSometimes one locator matches multiple elements, but each element is within a unique ancestor. In this case, try locate the unique ancestor and then match the element based on this context.
For example, here are few 'Cancel' buttons which are almost identical except for one is in a container
<div id='header'>, while the other two are in
Then instead of matching by ID, class names or anything else, each button can be uniquely identified by their ancestors.
Match element textMatching element by text is commonly used to locate elements. Personally I rarely use it, but there is nothing wrong with the approach itself. However, to match elements with text, there are things to be considered first!
If you are doing something like
//div[contains(@class, 'password')]/label//span[text()='Password:']to find the
Password:label, it won't match if there are extra whitespaces around it!
In this case, normalize whitespace in XPath would be a good idea.
Many web applications nowadays support internationalization. Matching text will make the tests dependent on the displaying language of the application. This is one major reason that I personally always avoid writing text matching locators. Even if there is only one language supported in the application at the moment, it's still good to keep Selenium code extensible in the future.
Say for example, the only display language of the application is Simplified Chinese. If the test code uses text matching, it might run into encoding problems, especially for those Selenium WebDriver Python binding users.
Use indexSometimes it is not easy to find one perfect locator that matches exact one element. Therefore many developers like to find few elements by one locator and index the elements, or use
in CSS Selectors or XPaths.
There is nothing wrong with these index-type locators, but they can cause problems potentially. Using them to locate the first or number X item of a list is totally acceptable, where the items are actually structured inside a list (options, lists, etc.) logically. But using them against some elements that are not logically related, like three buttons called 'Cancel' in different containers from the example above, might make tests really fragile that any text change or position change would fail the tests.
Web designers can add class names to HTML in order to make styling easier. Developers should add class names to HTML for UI testing purposes too!Q: None of the tips above seem to be sufficient. Is there any better way to write locators for Ext JS apps?
A: Yes, there is! Personally, I believe this is ultimate solution.
Ext JS API allows users to set class names in most of their components with great flexibility, which can make locating elements so much easier.
will generate the following HTML with user defined class names for UI testing purposes.