One of the key challenges with user interface (UI) level testing is identifying which objects on screen you want to interact with. How does your automated test know that you want to click on button X? Or type text into textbox Y?
Although some tools such as Sikuli try to identify objects by image recognition, most major web automation tools try to interrogate the page’s Document Object Model (DOM). There are three main methods that can be used by examining the DOM – Object IDs, CSS Selectors and XPath.
It’s worth noting that solutions like CodeFuse utilise all three. If one fails, it falls back on the next to try and identify the object.
Object IDs
An object ID is usually an ID that a web developer has attached to an object to allow easy identification of that object. If this is the case, then this is your best option to locate on-screen objects as they are usually unique.
Sometimes object IDs are generated dynamically by the web server or client-side JavaScript. This unfortunately renders the ID useless for the purpose of automation.
It is also worth noting that while IDs are meant to be unique, this is not strictly enforced by most browsers. It is however considered best practice by most in the development community.
CSS Selectors
Cascading Style Sheets (CSS) is used to style web content. For that styling to be applied, a set of rules is needed to identify which objects should use the style. It is this mechanism that can be leveraged to find on-screen objects for other purposes. Like test automation.
Pro’s
– Often more robust than XPath – less likelihood of parent objects or structure causing problems for individual objects
– Often more concise than XPath
– Some evidence that they are faster than XPath
– More widespread browser support than XPath
Con’s
– Trickier to learn and master than XPath
Operators
This table shows the most common CSS Selector operators.
Name | Operator | Example | Description |
Direct child | > | div > select | |
Child or sub child | whitespace | div select | |
ID | # | div select#myid | |
Class | . | div select.myclass | |
Next sibling | + | div select + input | Get the next adjacent matching element inside the same parent |
General sibling | ~ | div select ~ input | Get any matching element inside the same parent |
Attribute | [x] | div[id] | Searches for one or more elements with an id attribute |
Attribute value | [x=’y’] | Div[id=’myid’] | Searches for an attribute/value pair |
No attribute | :not[] | img:not[pic] | Matches all img’s without a pic attribute |
Child match | nth-of-type
nth-child |
ul:nth-of-type(3)
ul:nth-child(3) *nth-child(3) |
Find 4th ul
Return 4th item only if ul Return 4th child |
Sub string match | ^=
$= *= |
li[id^=’id-prefix’]
li[id$=’id-suffix’] li[id*=’id-ss’] |
Match any id with prefix
Match any id with suffix Match any id that contains |
Match by inner text | contains | li:contains(‘myword’) | Matches any object which contains the specified innertext |
Is checked? | :checked | input:checked | Matches any input that is selected |
Helpful Hints
– Make selectors unique by combining multiple classes. E.g. button.green-btn.primary-btn
– Make selectors unique by combining multiple attribute/value pairs. E.g. div[id=’myid’][name=’myname’]
– You can still make use of IDs to add a degree of robustness to your CSS Selectors even if the actual object CSS is not very useful. If you can identify a parent, then you can find a parent with an ID then work down. For example div#myid>div>select.
XPath
Pro’s
– Easy to understand – it’s easy to follow the Xpath in the DOM
– Precision – Can uniquely identify any object
– More text recognition operators than CSS
Con’s
– Can get very long and unwieldy
– Some browsers have limited XPath support
– More brittle than CSS selectors in many circumstances
Operators
This table shows the most common XPath operators.
Name | Operator | Example | Description |
Node name | name | myname | Selects all nodes named ‘myname’ |
Root node | / | myname/books | Selects all the books under the node myname |
Start search from current node | // | //books | Selects all books |
Current node | . | ./myname | Selects all nodes named ‘myname’ |
Parent node | .. | //book[@title=’book1’]/.. | Select the parent of book1 |
Attribute selection | @ | books[@style] | Selects all books with a style attribute |
Occurrence | [x] | .//book[2] | Returns the second book |
OR | or | //input[@type=’submit’ or @class=’Login’] | Selects all inputs with type=submit or class=login |
AND | and | //input[@type=’submit’ and @class=’Login’] | Selects all inputs with type=submit and class=login |
NOT | not | //a[not(contains(@id, ‘xx’))] | Selects all ‘a’ elements which have id’s that don’t contain ‘xx’ |
Starts with | starts-with | //input[starts-with(@class,’tbl_’)] | Returns any input whose class name starts with ‘tbl_’ |
Ends with | ends-with | //input[ends-with(@class,’_name’)] | Returns any input whose class name ends with ‘_name’ |
Contains | contains | //input[contains(@id,’username’)] | Returns any input with an id that contains the string ‘username’ |
Match value to any attribute | *= | //input[@*= ‘username’)] | Returns any input with any attribute with a value of ‘username’ |
Axes | child::
parent::
following::
following-sibling::
preceding::
preceding-sibling::
|
child::table
//input[@id=’email’]/parent::*
//input[@id=’email’]/following::*
//select[@id=’month’]/following-sibling::*
//input[@id=’pass’]/preceding::tr
//select[@id=’day’]/preceding-sibling::* |
Child nodes of table
Parent of input with id=email Node immediately following input where id=email Return sibling nodes that occur after the current node Node immediately preceding input where id=email Return sibling nodes that occur before the current node |
Helpful Hints
1. Make use of relative XPaths, not absolute ones. One of the core problems with absolute Xpaths is that any change to a parent node structure will break your selector. By using relative xpath you can minimise this. For example:
<html>
<div>
<div id=’mydiv’>
<a>hello</a>
</div>
</div>
</html>
The absolute XPath would look like html/div/div/a but the relative XPath may look like //div[@id=’mydiv’]/a. This has largely removed the threat of any changes above ‘mydiv’ breaking your selector.
2. When matching an object which has multiple classes, you will find that an object <div class=”tag1 tag2” will not match with //*[@class=’tag1’]. You will need to use contains in the following way //div[contains(@class, ‘atag’) or contains(@class ,’btag’)]
General Helpful Hints
These hints are guidelines for both XPath and CSS Selectors!
– Generalise as far as possible. If you are identifying objects using values that may alter, is there a part of them which will remain consistent? Make use of contains, starts-with and ends-with for XPath and substring functions for CSS Selectors.
– Minimise the elements you need to validate. If there is more than one class included for an object, use only the ones you need. Otherwise unnecessary updates will need to be made.
– Overall, try to make your selectors as short as possible while still uniquely identifying the object.
Performance
When discussing XPath vs CSS Selectors a comment is often made about performance. Are CSS selectors faster at locating an object than XPath? Elemental Selenium looked into this and found that there were no significant time differences. However, some research by Santigo Suarez Ordonez here did show CSS selectors to be faster.
Conclusion
We hope that this article has given you some insight into how to write better XPath and CSS Selectors.
On balance from the positives and negatives, it is worth trying to utilise Object IDs followed by CSS Selectors. If that is not possible, then fall back on XPath.