Introduction
This article is for people who want to find robust locators used in Selenium and have basic HTML knowledge. In general, the main focus will be to get the XPath you need for Selenium Test Automation. We will be using https://reference.subject-7.com as a reference site for the examples throughout this document.
Tools
1- Google Chrome browser
2- Access to https://reference.subject-7.com which is the primary reference for examples in this tutorial. You can register for an account there so you can practice learning XPaths.
3- Subject7 Smart XPath Generation Engine, which is a Chrome Extension which we call Gutenberg because it “prints” smart XPaths!. Click here to navigate to the download page and it will help you with generating locators either XPath or CSS.
Inspecting Elements
Before understanding XPaths and how to generate them, we must first learn how to inspect elements. On Chrome, let’s open the Reference application and take a look at the Login link on the main page. In order to inspect this link, we can simply right-click it and select Inspect as shown below.
The Elements screen will appear on the right side with the HTML structure of the web page. As we can see from the image below, the Login link is automatically highlighted on the Elements screen. If it is not highlighted, simply right-click it and select Inspect again.
<a href="/#/login">LOGIN</a>
In order to test the XPaths we generate, we must click on the Console tab and begin by typing $x("enter XPath here"). Note that XPath is only the part which is inserted between the double quotes and everything else is a Console symbol and is not part of the XPath that Selenium can understand.
Additionally, we must pay close attention to the section that contains 'top' as this marks the location where the Console will be looking for objects in. If the object we are looking for is located inside a frame, then the frame must be selected in this section, otherwise, the XPath will not be located.
If the page contains too many results after we did multiple searches, we can clear it by using the Clear Console shown on the left side of the image below or by using the keyboard shortcut (CTRL + L). We can also control where the Console screen appears by clicking on the ellipsis (3 dots) shown on the right side of the image below and choosing the preferred place.
What is an XPath
XPath (also known as XML Path) is like a query language that helps to navigate an XML tree to locate nodes using an XPath expression (or simply an XPath). Selenium users need to target elements on an HTML page which is itself an XML document. Think of XPath as a way to query elements in a given web page (i.e. HTML). If you learn the main idea of how to query elements on the screen, you will be able to use most automation tools, as XPath is a standard that is used by most modern automation tools such as Selenium/WebDriver.
Take a look at the basic structure of the web page below, also referred to as a DOM (Document Object Model).
To locate the text box, we can use the XPath: /html/body/form/input. This XPath is considered absolute, which means that it traverses through the document tag after tag to reach the target in this case the input tag <input type="text" id="username"/>. However, if a small change is added to the HTML, this XPath will no longer be valid. For example, if an additional field is added before the input tag or if the input tag is wrapped with a span tag, the XPath will no longer be valid. For this reason, an absolute XPath must never be used. Generally, if an XPath is easily broken by changes in the HTML, we call it brittle XPath. Conversely, a robust or smart XPath is one that is resilient to changes in the DOM, has a minimal dependency on the structure of the DOM, and is relevant to the target as much as possible. For example, the same element can be targeted using //input[@id='username'] which is a relative XPath.
Relative vs. Absolute XPath
An absolute XPath starts from the beginning of the DOM and blindly follows the hierarchy tag by tag until it reaches the target element. Absolute XPath expression starts with '/'. A relative XPath starts with '//' and can point to target elements based on some more general things such as an attribute’s value which is less likely to change in the future. An absolute XPath is brittle and can be broken with the smallest change in the HTML. A relative XPath on the other hand is much more resilient to change.
Basix XPath Structure
In order to select nodes (aka elements or tags), you can use path expressions that are followed until this node is found. The following are notable path expressions:
Path Expression |
Details |
/ |
Selects from the root node (Absolute XPath) |
// |
Selects from anywhere in the HTML (Relative XPath) |
. |
Selects current node |
.. |
Selects the parents of the current node |
@ |
Selects attributes |
Live Example: on the Reference app, let’s find all elements with 'input' tag on the Login page.
We can use the XPath below which will show us two fields: 'Email' and 'Password'
$x("//input")
Selenium equivalent: find
Meaning: Get all the inputs on this page
More Specific XPaths
Targeting a node with a specific attribute and value
There are methods that can be used to target nodes by adding additional parameters to these XPaths.
The image above details the DOM showed previously and the XPath reads like this: “Find all input tags that have the attribute 'id' equal to 'username' ”. Here is the breakdown of this XPath:
1- Starting with '//' to indicate a relative XPath.
2- The tag name identifies the HTML tag in this case the node 'input'.
3- The '@' character allows us to select attributes. In this case, the attribute is 'id'.
4- The 'value' field, in this case, is 'username'.
Live Example: on the Reference app, let’s find the password field using 'id' attribute and 'password' value.
We can use the XPath below which locates an input tag with attribute 'id' with value 'password'
$x("//input[@id='password']")
Meaning: Get all the inputs that have id=’password’
*Best Practice Note*: If the ID changes every time you refresh the page, it will not be the best attribute for you to use as it will likely break your test case when it tries to find an ID that is no longer present. It’s best to find a static ID or another attribute that does not change and use it in your XPath for better results.
Targeting a node with a specific number in the DOM
In order to select a node using its number, you can wrap the XPath with parenthesis and add the number of the object in brackets. The object will be located depending on its number in the DOM tree.
$x("(XPath)[i]")
Live Example: on the Reference app, let’s find the first input tag on the page.
We can use the XPath below which locates the first tag using the number in brackets [1]
$x("(//input)[1]")
Meaning: Get the first input tag on the page
Targeting a node using the 'contains' function
In order to select a node by providing a partial value, we can use the 'contains' function and provide an attribute (id, etc) and a value for this attribute.
$x("//tag[contains(@attribute,'value')")
Live Example: on the Reference app registration page, let’s find all input tags that contain the attribute 'type' with value 'password'.
$x("//input[contains(@type,'password')]")
Meaning: Get an input tag where the attribute ‘type’ contains the value ‘password’
As shown below, we should be able to find both 'Password' and 'Confirm Password' fields:
Question: What If I don’t know the exact tag/attribute?
Answer: If you are not sure what the exact tag/attribute is, you can simply use a wildcard and the Console will locate any element it finds.
Example 1: the below XPath finds any object where the attribute 'type' has a value that contains 'password':
$x("//*[contains(@type,'password')]")
Example 2: The below XPath finds any object with an attribute that contains a value 'email':
$x("//*[contains(@*,'email')]")
Targeting a node using 'text()' function
In order to select a node by providing a specific text, we can use the 'contains' function combined with the 'text()' function and provide a value for this text.
$x("//tag[contains(text(),'value')]")
Live Example: on the Reference app, let’s find any element that contains the text with the value 'Login':
$x("//*[contains(text(),'Login')]")
Meaning: Get any tag that contains the text ‘Login’
As shown below, we should be able to find 'Login' text in three places.
*Note*: Please note that the Console search is case-sensitive. If the value of the attribute you are looking for is 'Text', then entering 'text' will not provide any results or may target a different object.
Targeting a node with an exact text match
In order to select a node by providing a specific exact text, we can use the 'text' function and provide a value for this text.
$x("//tag[text()='value']")
In order for this XPath to work, the text must appear in the DOM in black and the XPath should contain the exact characters, including spaces. The example below shows First name: value which needs to be specified in order for the XPath below to work:
$x("//label[text()='First name:']")
Live Example: on the Reference app, let’s find any element that contains the text with the value 'Login':
$x("//*[text()='Login']")
Meaning: Get any tag that has the exact text ‘Login’
As shown below, we should be able to find the exact 'Login' text in three places.
Targeting a node that starts with a specific text
In order to select a node by providing a text that starts with some letters, we can use the 'starts-with' function along with 'text()' function.
$x("//tag[starts-with(text(),'value')]")
Live Example: on the Reference app login page, let’s find any element that starts with the letters 'Re':
$x("//*[starts-with(text(),'Re')]")
Meaning: Get any tag that starts with the text ‘Re’
As highlighted below, we should be able to find only one object, which is the Register link.
Targeting a node with more than one attribute
We can select a node by providing one or more attributes and their values to find an exact match.
$x("//tag[@attribute='value' and @attribute='value']")
Live Example: on the Reference app, let’s find any element that has 'type' attribute with the value 'email' and 'placeholder' attribute with the value 'Email'.
$x("//*[@type='email' and @placeholder='Email']")
Meaning: Get any tag where the attribute ‘type’ contains the value ‘email’ or the attribute ‘placeholder’ contains the value ‘Email;
As shown below, only one object will match which is the Email text box.
*Note*: In order to find previously used XPath in Chrome Console, use the Up and Down arrows on your keyboard. This is an easy feature for you to see all XPaths you have used in the Console.
Targeting a node with one or another attribute
We can select a node by providing two attributes and their values to find an exact match using one or another attribute.
$x("//tag[@attribute='value' or @attribute='value']")
Live Example: on the Reference app, let’s find any element that has a 'type' attribute with value 'email' or 'placeholder' attribute with value 'Email'.
$x("//*[@type='email' or @placeholder='Email']")
Meaning: Get any tag that has a ‘type’ attribute with a value of ‘email’ or a ‘placeholder’ attribute with a value of ‘Email’
As shown below, only one object will match which is the Email text box.
Targeting the parent of a node
We can find the parent of a particular node by entering an XPath that finds the node and then enter /.. after the XPath. We can also enter /.. again to keep going higher in the DOM. For example:
$x("//tag[@attribute='value']/..") Finds a parent
$x("//tag[@attribute='value']/../..") Finds the parent and its parent
Instead of /.. we can also use parent::tag to locate any element as below:
$x("//tag[@attribute='value']/parent::tag")
Live Example: on the Reference app, let’s find the parent of the ‘Email’ text box.
$x("//input[@id='email']/..")
Meaning: Get the parent of an input tag that has an ‘id’ attribute with a value of ‘email’
Alt. Meaning: Find an input tag that has an ‘id’ attribute with a value of ‘email’ and get its parent
As you can see in the image above, using /.. shows the parent which is a 'div.form-group'. We can also use ‘parent’ instead of /.. as below:
Using a wildcard to look for elements with any tags:
$x("//input[@id='email']/parent::*") Finds a parent with any tags
$x("//input[@id='email']/parent::*/parent::*") Finds the parent of the parent with any tags
Targeting the children of a node
We can find the children of a particular node by entering an XPath that finds the node and then enter /* after the XPath. We can also enter /* again to keep going lower in the DOM. For example:
$x("//tag[@attribute='value']/*") Finds a child with any tag
$x("//tag[@attribute='value']/*/*") Finds a child and its child with any tag
Instead of /* we can also use child::tag to locate any element as below:
$x("//tag[@attribute='value']/child::tag")
Live Example: on the Reference app, let’s find the children of the login form.
$x("//form") Finds the form
$x("//form/*") Finds the children of the form
Meaning: Get the children of the form on the page
As you can see in the image above, using /* shows the children of the form which are
1. div.form-group
2. div.form-group
3. div.form-actions.pull-right
We can also use ‘child’ instead of /* as below:
$x("//form/child::*") Finds a child with any tags
$x("//form/child::*/child::*") Finds the child and its children
Targeting the ancestors of a node
We can find the ancestors (parents and/or grandparents) of a particular node by entering an XPath that finds the node and then enter ‘ancestor’ after the XPath. We can also enter ‘ancestor’ again to keep going higher in the DOM. For example:
$x("//tag[@attribute='value']/ancestor::*") Finds an ancestor
$x("//tag[@attribute='value']/ancestor::*/ancestor::*") Finds the ancestor and its ancestors
Live Example: on the Reference app, let’s find the ancestors of the ‘Login’ button
$x("//button[contains(text(),'Login')]/ancestor::*")
Meaning: Get the ancestor with any tag of "<a>" the button that contains the text ‘Login’
Alt. Meaning: Find a button that contains the text ‘Login’ and get its ancestor
The resulting XPath will provide us with 10 ancestors as we can see below:
Targeting the descendants of a node
We can target the descendants (children and/or grandchildren) of a node by entering an XPath that finds the node and then use 'descendant' to target the descendants.
$x("//tag/descendant::tag")
Live Example: on the Reference app, let’s find the 'Email' text box by using its parent form.
We can start by entering an XPath that finds the form (there is only one on the 'Login' page)
$x("//form")
After that, we use ‘descendant::tag’ where the tag for the ‘Email’ text box is ‘input’ along with an attribute and value:
$x("//form/descendant::input[@id='email']")
Meaning: Get the input tag that descends from the form using the ‘id’ attribute with the value ‘email’
Alt. Meaning: Find the form on the page, move to its descendant input tag which has an ‘id’ attribute with the value ‘email’
*Note*: You can use a wildcard to view any descendants of the node as in the XPath below:
$x("//form/descendant::*")
Targeting the siblings of a node
Siblings are nodes that are relative to other nodes on the DOM tree. We can find the siblings of a node by entering an XPath that finds the node and then using either following-sibling::tag or preceding-sibling::tag depending on which sibling we need. Generally, you should try not to use the following sibling as it is too generic. Try to use descendant and ancestor, in the long run, you will see they tend to be more stable.
$x("//tag/following-sibling::tag")
$x("//tag/preceding-sibling::tag")
Live Example: on the Reference app, let’s find the following sibling of the 'Email' label.
We can start by entering an XPath that finds the label:
$x("//label[contains(text(),'Email')]")
Then we can add 'following-sibling::*' to locate any following siblings for this node:
$x("//label[contains(text(),'Email')]/following-sibling::*")
Meaning: Find the label that contains the text ‘Email and get any following sibling for that label.
Alt. Meaning: Get any following sibling for the label that contains the text ‘Email’
As we can see in the image below, the following sibling of the ‘Email’ label is the 'Email' text box
Live Example 2: on the Reference app, let’s find the preceding sibling of the 'Password' text box.
We can start by entering an XPath that finds the text box:
$x("//input[@id='password']")
Then we can add 'preceding-sibling::*' to locate any preceding siblings for this node:
$x("//input[@id='password']/preceding-sibling::*")
Meaning: Find an input tag with ‘id’ attribute with value ‘password’ and get any preceding sibling
Alt. Meaning: Get any preceding sibling for the input tag with ‘id’ attribute with value ‘password’
As we can see in the image below, the preceding sibling of the 'Password' text box is the 'Password' label:
*Best Practice Note*: try not to use the following/preceding as they are too generic. Try to use ancestor/descendant/parent/child. In the long run, you will see they tend to be more stable.
Using Static Relevant Points as Anchors
If you are unable to generate an XPath because the attributes for the element are not static or for any other reason, you can use nearby relevant points as anchors to find this specific element you are looking for.
Live Example: on the Reference app, let’s find the number of followers for the Community with the name “Test Community 3”.
We could generate an XPath such as $x("(//span[contains(text(),'1 follower')])[1]") but this XPath will not work due to the following reasons:
1- The XPath points to the first span that contains the text ‘1 follower’, so if the order of community changes on the page, this XPath could point to any community.
2- Since the number of followers will change, this XPath will only try to find an element with the text ‘1 follower’, so the result will not be accurate.
In order for us to accurately find the number of followers for this particular community, let’s first find the community using the XPath below:
$x("//a[contains(text(),'Test Community 3')]")
Now let's look at the DOM which shows the structure of the elements:
Whole structure:
Div (Ancestor/Main)
Div 1 (child) - Name of Community
Div 2 (child) - Community status
Div 3 (child) - Number of community followers
Structure of Div 1
Div 1
Div 4
Span 1
Link 1
Based on the structure, we must generate an XPath that moves from the link to the ancestor/main div and then simply moves to the 3rd child div. In other words, we need to move from the link which contains the name of the community ‘Test Community 3’ to the main div which contains all 3 elements and get the 3rd child div which is the number of community followers.
The XPath will look as below:
$x("//a[contains(text(),'Test Community 3')]/ancestor::div[@class='panel-body']/child::div[3]")
Meaning: Find a link that contains the text ‘Test Community 3’, move to its ancestor div which has a class attribute containing the value ‘panel-body’, and get its 3rd child div.
Alt. Meaning: Get the 3rd child div of the ancestor of the link that contains the text ‘Test Community 3’
This is the result the XPath finds:
Table Structure
In table structures, you can generate XPaths that traverse to different parts of the table to locate neighboring elements. On the Reference app, let’s log into an account, click on the Profiles menu to see all profiles in the application. After that, click on Table View instead of ListView. You will see the table of registered users below that you can use to generate XPaths as we will demonstrate in the upcoming examples.
- To locate the table itself, we can use an XPath like the below:
$x("//table")
- For more specific results in case we are working on a page with several tables, we can use any of the available attributes to expand the XPath. Here are some examples from the Reference app:
$x("//table[@class='table table-bordered ng-scope']") Brings the table using 'class'
$x("(//table)[1]") Brings the first table in the page
Targeting table headers and cells
Live Example 1: on the Reference app, let’s find the table header with the text 'Id'.
We can find the table header simply by using the appropriate table header tag 'th':
$x("//th[contains(text(),'Id')]")
Meaning: Get a table header that contains the text ‘Id’
Live Example 2: on the Reference app, let’s find the table cell that contains the text 'nick@gmail.com'.
We can find the table cell simply by using the appropriate table cell tag 'td':
$x("//td[contains(text(),'nick@gmail.com')]")
Meaning: Get a table header that contains the text ‘nick@gmail.com’
Targeting siblings in tables
We can use any of the methods we learned previously for locating sibling elements in the tables.
Live Example 1: on the Reference app, let’s locate the Email Address of the user in ID # 39 by using 'following-sibling'.
We can start by entering an XPath to find ID # 39 using the table header tag 'th'
$x("//th[text()='39']")
Meaning: Get a table header that contains the text ‘39’
Afterward, we can add ‘following-sibling::tag’ to look for the siblings for this ID.
$x("//th[text()='39']/following-sibling::*") Brings any sibling objects which are Name, Email and Birthday
$x("//th[text()='39']/following-sibling::td[1]") Brings the 1st cell which is 'Name'
$x("//th[text()='39']/following-sibling::td[2]") Brings the 2nd cell which is 'Email'
$x("//th[text()='39']/following-sibling::td[3]") Brings the 3rd cell which is 'Birthday'
Live Example 2: on the Reference app, let’s locate the Id of the row which contains the email address “tom.strong@subject-7.com’.
We can start by entering an XPath to find the table cell which includes the email address
$x("//td[contains(text(),'tom.strong@subject-7.com')]")
Meaning: Get a table cell that contains the text ‘tom.strong@subject-7.com’
Afterward, we can add ‘preceding-sibling::tag’ to locate the Id.
$x("//td[contains(text(),'tom.strong@subject-7.com')]/preceding-sibling::*") Brings any sibling objects which are Name cell and Id header
$x("//td[contains(text(),'tom.strong@subject-7.com')]/preceding-sibling::td") Brings the Name cell
$x("//td[contains(text(),'tom.strong@subject-7.com')]/preceding-sibling::th") Brings the Id header
Targeting parents and children in tables (headers and rows)
We can use any of the methods we learned previously for locating ancestor/descendant elements in the tables.
Live Example 1: on the Reference app, let’s locate the ancestor of the Birthday table header.
We can start by entering an XPath to find the Birthday table header:
$x("//th[contains(text(),'Birthday')]")
Meaning: Get a table header that contains the text ‘Birthday’
Afterward, we can add 'ancestor::*' to the XPath to see all elements:
$x("//th[contains(text(),'Birthday')]/ancestor::*") Brings 10 objects where the first ancestor is the table row
Let’s fix the XPath to find the table row only:
$x("//th[contains(text(),'Birthday')]/ancestor::tr") Brings the table row that includes (Id, Full Name, Email and Birthday)
Live Example 2: on the Reference app, let’s use the last XPath in example (1) to move to the 'Full Name' table header.
This is the last XPath we used:
$x("//th[contains(text(),'Birthday')]/ancestor::tr")
Now, all we have to do is add ‘/descendant::tag’. We can be specific by using either the number or the table header or the text this header contains:
$x("//th[contains(text(),'Birthday')]/ancestor::tr/descendant::th[2]")
Meaning: Find a table header that contains the text ‘Birthday’, move to its ancestor row, and move to the second descending table header.
Alt. Meaning: Get the second descending table header for the table row which is the ancestor of the table header that contains the text ‘Birthday’.
$x("//th[contains(text(),'Birthday')]/ancestor::tr/descendant::th[contains(text(),'Full Name')]")
Meaning: Find a table header that contains the text ‘Birthday’, move to its ancestor row, and get the descending table header which contains the text ‘Full Name’
Alt. Meaning: Get the table header that contains the text ‘Full Name’ which descends from the table header which is the ancestor of the table header that contains the text ‘Birthday’.
Using tables as an anchor to find elements
In some cases, a web page may contain elements with shared values inside and outside of the table. For example, if we search for 'Tom' on the Reference app using XPath $x("//*[contains(text(),'Tom')]"), we will find 4 matches, one of them being the logged-in user’s first name as well as the other users which share the same first name. In this case, we can use the table itself as an anchor to locate objects inside this table only without looking on the whole web page. This can be especially useful in the case of two or more tables that contain similar values.
In order to use the table as an anchor, we simply generate an XPath to find this table. On the Reference app, there is only one table, so the XPath below will suffice:
$x("//table")
Meaning: Find a table on the page
Afterward, we enter '//' followed by any of the previous methods to locate any object within the table.
Live Example 1: on the Reference app, let’s find the birthday field for the second Tom in the table.
To begin, let’s generate an XPath to find any object with the text ‘Tom’ in the table:
$x("//table//*[contains(text(),'Tom')]")
Meaning: Get any element in the table that contains the text ‘Tom’
Alt. Meaning: Find the table on the page and get any element containing the text ‘Tom’
We will notice that there are 3 users in the table named ‘Tom’. Let’s pick the second one by wrapping the XPath in parentheses and adding 2 in brackets.
$x("(//table//*[contains(text(),'Tom')])[2]")
Meaning: Get the second element in the table that contains the text ‘Tom’
Alt. Meaning: Find the table on the page and get the second element containing the text ‘Tom’
Now that we have specified which Tom we need, we will notice that the tag we found is a hyperlink (a). When analyzing the tree, we will see that the hyperlink is located in a cell (td). In order to get to the Birthday field, we have to first move up in the tree to reach the cell and use ‘following-sibling’ to traverse to the next cell (td).
Step 1: Moving up in the tree to reach the 'td' which contains the hyperlink:
$x("(//table//*[contains(text(),'Tom')])[2]/ancestor::td")
Meaning: Find the table on the page, move to the second element that contains the text ‘Tom’, and get its ancestor table cell.
Step 2: Using ‘following-sibling::tag’ to move to the next sibling cells (tds):
$x("(//table//*[contains(text(),'Tom')])[2]/ancestor::td/following-sibling::td")
Meaning: Find the table on the page, move to the second element that contains the text ‘Tom’, move to the ancestor cell, and get its following sibling.
Step 3: Since the XPath in step (2) shows two cells (tds) which are (Email and Birthday), we simply add the number of the Birthday cell (td) in brackets:
$x("(//table//*[contains(text(),'Tom')])[2]/ancestor::td/following-sibling::td[2]")
Meaning: Find the table on the page, move to the second element that contains the text ‘Tom’, move to the ancestor cell, and get its second following sibling.
They look cryptic and ugly, but when you do a few and keep doing it, it becomes natural. Like breathing!
Counting Nodes
If you are planning on generating XPaths that count a number of nodes on a web page, you can use the Count function as below:
$x("count(//tag)")
Live Example: on the Reference app, let’s count the number of profiles in the table under the Profiles page.
To begin, let’s generate an XPath to find all the profile numbers using the Id header. We can use 'class' as an attribute.
$x("//th[@class='ng-binding']")
Meaning: Get the table header with ‘class’ attribute which has value ‘ng-binding;
Afterward, we simply wrap the XPath in parentheses and add the 'count' function ahead of the XPath.
$x("count(//th[@class='ng-binding'])")
Meaning: Count the table headers with ‘class’ attribute which has the value ‘ng-binding’.
As you can see below, we will be given the number of the profiles based on the Id header:
Invisible Elements
If you have successfully generated an XPath which is found in Console, but the element for this XPath is not visible, Selenium will not interact with it in the sense that since the user cannot see it, Selenium will not interact with it.
Live Example: on the Reference app, let’s find the link with the text ‘Mutual friends’ on the Edit Profile page after logging into a user.
$x("//a[contains(text(),'Mutual friends')]")
Meaning: Get a link that contains the text ‘Mutual Friends’
As we can see in the image above, the Console displays a result for this XPath, but when hovering with the mouse over the object, it is not highlighted on the page because it’s not visible. If Selenium is unable to find the object and highlight it, it will not interact with it. Let’s examine the object in the DOM by clicking on it to identify which part makes the element invisible.
As we can see from the DOM, the class “btn btn-link ng-hide” is responsible here. There can also be other identifiers such as HTML attribute ‘hidden’ or CSS property ‘visibility’ that control if an element is visible or not.
If we right-click on this part of the DOM and click Edit as HTML, we can modify this value to “btn btn-link ng-show” which should make the element visible. However, this is only for demonstration purposes, but the actual element will still be visible when we refresh the page.
Dynamic Elements
If we are targeting elements that change every time the page is reloaded or if we are working on multiple profiles where the profile names are different, then we must be careful to use an XPath that works on all pages and one that does not contain attribute values that constantly change.
Live Example: on the Reference app, let’s generate an XPath that is used to click on the profile name highlighted below without specifying the exact user name:
Upon inspecting the element, we will be seeing two spans, one for the First Name and one for the Last Name. The text values of spans are dynamic and will change when logging into a different profile. An XPath like //span[contains(text(),'Tom')] can be used, but will only work when the logged-in user has ‘Tom’ as the first name.
Possible Solutions
Solution (1): We can generate an XPath for the span that does not use the “text” function, but uses unchangeable attributes such as ‘ng-bind’. For example:
$x("//span[@ng-bind='principal.firstName']") interacts with the span for the first name, regardless of its text value
$x("//span[@ng-bind='principal.lastName']") interacts with the span for the last name, regardless of its text value
Solution (2): If we look closely at the DOM, we can find that the two spans are located under a hyperlink. We can generate an XPath for this hyperlink which should perform the needed interaction without the need to worry about the profile first and last names.
$x("//a[@class='dropdown-toggle']")
Meaning: Get a link which has ‘class’ attribute with value ‘dropdown-toggle’
Final Review
Now that you were able to see how XPaths are generated, let’s do a quick review:
- Starting with / means finding something absolute (not recommended).
- Starting with // means finding something relative (recommended).
- Find an anchor you can use. Good candidates might be some text on the page that would probably not change.
- From there, use relative XPath to navigate to get to the element(s) you want.
- Navigation is very important.
- ‘*’, ‘//’, ‘child’ and ‘descendent’ mean going down
- ‘..’, ‘parent’ and ‘ancestor’ mean going up
- ‘.’ selects the self node
- A sibling is on the same level
- Following-sibling goes forward
- Preceding-sibling goes backward
- Subject7 Smart XPath Generation Engine, which is a Chrome Extension which we call Gutenberg, because it “prints” smart XPaths!. Click here to navigate to the download page and it will help you with generating locators either XPath or CSS.
References
Subject7: https://www.subject-7.com/
Google Chrome Browser: https://www.google.com/chrome/
Subject7 Reference App (Social Network): https://reference.subject-7.com
Subject7 Knowledge Base: https://subject7.zendesk.com/hc/en-us
Subject7 Community: https://subject7.zendesk.com/hc/en-us/community/topics
Subject7 User Guide: https://subject7.atlassian.net/wiki/spaces/SD/pages/36700229/User+s+Guide
Subject7 Smart XPath Generation Tool (Gutenberg): https://subject7.zendesk.com/hc/en-us/articles/360008225273-Set-up-Gutenberg-Subject7-Smart-XPath-CSS-Generator-Engine-
Useful Community Posts:
3- https://subject7.zendesk.com/hc/en-us/community/posts/360048279674-How-to-capture-xpath-for-checkbox
Comments
Please sign in to leave a comment.