Turn your manual testers into automation experts! Request a DemoStart testRigor Free

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.

XML Path or XPath is used to navigate in HTML or XML to locate an object. It’s a syntax or address of an element in the HTML tree. Writing XPath is very flexible and accessible. Let’s see how we can write an XPath for a sample page:
<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

There are two different types of XPaths:
  • 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']

Relative XPaths are written using reference attributes like id, name, or text for the particular element. Standard syntax of a relative XPath will be like this:
//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.

For example, the Amazon sign-in page has only one element with the id “ap_email” for the email input.
<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()

Basic XPath will only work in standard cases. There can be more than one element with similar ids or class names or parts of the similar class names. We need to put the reference to other elements or make the XPath more conditional. For the below-mentioned scenario, we cannot use a similar type of XPath mentioned above as the element value changes dynamically.
<input type="address" maxlength="128" id="address_18" name="address_18" tabindex="1" class="a-input-text a-span12 auth-autofocus auth-required-field">
For example, a website refresh may result in a slightly different output:
<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()

What is the use case for using and condition? For example, we have a link or a button that can be enabled or disabled. Here let’s say we would like to perform a certain action when the button is enabled. The button, when disabled, appears as:
<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">
In this case, we need to write XPath with and condition like this:

//button[@class = "command-button" and @disabled="true" )]

Consider a scenario, where we need to match either of one conditions, then we use or:
Like: //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

Using XPath axes means writing XPath by relating to another node. Let’s see the different axes available. Here we can take a common HTML snippet:
<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

So here it will be 4 divs: two, minor, major, and world.

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.