How to Write XPath: Your Pocket Guide
Most web automation tools require you to specify locators for any web elements you’re referring to. There are different types of locators such as id, name, linkText, className, CSS selector, tagName, partialLinkText, and XPath. Initially, id or class name were the ones most commonly used for automation, but with new programming languages, e.g.: Angular, React or Go, the ids became dynamic. So automation won’t be able to depend on id or name anymore, since they change constantly. In that scenario, XPath gained popularity. Since writing CSS selectors was not that easy, everyone tended to move towards using XPath.
<html> <head> <body> <div> <a id="homeButton" rel="home" class="site-logo"> <img src="/sites/default/files/Logo2_1.png" alt="Home"> </a> </div> </body> </head> </html>
For this page, we can create an XPath like – /html/head/body/div/a
or //a[@id='homeButton']
The XPaths mentioned above are different. So there are two ways that we can create XPaths. Let’s look into the types of XPath.
Types of XPath
- Absolute XPath
- Relative XPath
Absolute XPath
Absolute XPath starts from the root of the element that needs to be located. Absolute XPath is the simplest form of XPath. The below-mentioned one is the absolute XPath /html/head/body/div/a
Absolute XPath is very easy to build. Usually, it starts with the root node, which will be from the “” tag. It’s always denoted by a single slash “/”. The major drawback of using absolute XPath is that it’s very lengthy. If there is any change in the page, like adding or modifying any elements, the XPath will fail, making it difficult to maintain. Therefore it is considered to be a bad practice to use absolute XPaths for your automated tests.
Relative XPath
Relative XPath starts with the node which we want to identify. Relative XPaths will be short and crisp, with minimal references to the target element. Here we begin by giving the double slash “//”. An example of a relative XPath is: //a[@id='homeButton']
//tag[@attribute='value']
- // : Select the current node.
- tag: tag name of a particular element like div, li, button, or span.
- @: Select attribute.
- Attribute: Attribute selected for that element like id, class, or text
- Value: Value of the attribute.
We can write relative XPaths using different methods. Let’s dive deeper into them.
Using a single attribute
We can write the XPath without referring to any other element. We are sure there will be no other element with a similar name or reference here.
<input type="email" maxlength="128" id="ap_email" name="email" tabindex="1" class="a-input-text a-span12 auth-autofocus auth-required-field">
So the XPath can be written directly like: //input[@id="ap_email"]
Using contains()
<input type="address" maxlength="128" id="address_18" name="address_18" tabindex="1" class="a-input-text a-span12 auth-autofocus auth-required-field">
<input type="address" maxlength="128" id="address_23" name="address_23" tabindex="1" class="a-input-text a-span12 auth-autofocus auth-required-field">
So, the id or name are always dynamic. Here we have to use the option Contains(), so we can write our XPath as //input[contains(@id, "address_")]
Using and() & or()
<button class="command-button" disabled="true" type="button" tabindex="-1">
And when enabled, appears as:
<button class="command-button" disabled="false" type="button" tabindex="-1">
//button[@class = "command-button" and @disabled="true" )]
//button[@class = "command-button" or @id="priceValue" )]
Here the element will be identified if any of the two options matches. Or will be used if there is an attribute with a constant value and another switching value.
Using not()
Another operator we use in dynamic XPath is not. Here we are making sure the element should not have that particular attribute. We can consider the above example itself; here, we are checking if the element is present and its disabled status is not false. So we can write it like so: //button[@class = "command-button" and not(@disabled="false" ))]
Using text()
Use text for an XPath, if the id is not mentioned and the class name is dynamic. In that case, we can use the text function of an XPath. So it won’t be challenging to get the XPath for that element.
<input type="address" maxlength="128" abindex="1" class="some_class" value="email">
So here, we can write the XPath as: //input[text(), "email")]
Using starts-with()
For an element, if its partial value gets changed frequently and doesn’t have any other attribute we can use for getting the XPath, then we can use the starts-with() function.
<span class="address_edcasdca"></span>
The partial class name changes
<span class="address_ewretrt"></span>
So here we can write the XPath as: //span[starts-with(@class,'address_')]
Using XPath axes
<div class="world"> <div class="major"> <ul id = "one"> <li id="name">raj</li> <li id="age">10</li> </ul> </div> <div class="minor"> <ul id="two"> <li id="name">anjali</li> <li id="age">10</li> </ul> </div> </div>
Ancestor
When using ancestor, it selects all the ancestors of the mentioned node. Here it will be like this: //div[@id="two"]/ancestor::div
This will return two matches: three divs with class names – minor, major, and world. So if we want to select only one – //div[@id="two"]/ancestor::div[@class="major"]/
Ancestor-or-self
This will return the self and all its ancestor nodes.
//div[@id="two"]/ancestor-or-self::div
Child
This will return all the child nodes of the mentioned element.
//div[@id="two"]/child::li
So here we will have two lis: name and age.
Descendant
This will return all the children and grandchildren of the current node.
div[@class="world"]/descendant::div
So here, it will return all the child and grandchild divs – major, one, minor, and two.
Descendant-or-self
This will return all the children and grandchildren of the current node and the current node itself.
div[@class="world"]/descendant::div
So here, it will return all ancestor, child, and grandchild divs – world, major, one, minor, and two.
Following
This will return all the nodes after the mentioned nodes, irrespective of child or grandchild.
div[@class="one"]/following::div
So here, it will return all the divs after the “one” – minor and two.
Following-sibling
This will return all the nodes after the mentioned node, sharing the same parent.
div[@class="one"]/following-sibling::li
So here, it will return all the li’s inside the “one” – name and age.
Preceding
This will return all the nodes before the mentioned node, irrespective of child or grandchild.
div[@class="two"]/preceding::div
So here, it will return all the div’s before the div-“two” – minor, one, major, and world.
Preceding-sibling
This will return all the nodes before the mentioned node sharing the same parent.
div[@class="minor"]/preceding::div
So here, it will return all the divs before the div-“minor” but sharing the same parent – “world” – so it will return the div “major.”
Parent
This will return the parent of the mentioned node.
div[@class="two"]/parent::div
So here, it will return the parent of div “two” – “minor”
Even though XPath is the most commonly used element locator, the chances of tests failing because of it are still high. A lot of legacy automation frameworks depend on XPath; any change in the element name or hierarchy might cause the XPath to fail, even if it’s written ingeniously. There are new smart automation tools on the market today, that do the job for you – analyzing all possible locators on the page, and combining them to identify elements. Let’s briefly touch on that to give you an idea on the possibilities.
testRigor
There are a lot of advantages of using testRigor, but the one relevant to this article is that tests do not rely on implementation details. What does this mean? You can pretty much forget about XPaths, CSS selectors, etc – and refer to elements as you see them on the screen. Behind the scenes, testRigor’s smart algorithms analyze all possible locators and make sure your tests won’t fail if any of these locators change later. The results? Much faster speed of test creation, extremely stable taste, and barely any test maintenance.
Here is a sample script.
click "Sign up" generate unique email and enter it into "Email", then save it as "generatedEmail" generate unique name and enter it into "Name", then save it as "generatedName" enter "PasswordSuperSecure" into "Password"
For example, for the element “Sign up,” the integrated AI feature of testRigor will not only identify that element with the name locator, but it will also get the position of an element, the hierarchy, and other factors. If the underlying XPath changes, the element will still be located correctly. You can check out the features of testRigor here.