Using XPath Axes for locating elements in Selenium

XPath is a very powerful and widely used mechanism in Selenium for locating elements on a Web Page. XPath is used for finding nodes in an XML document, and it can also be used for finding HTML elements in XHTML. Selenium supports XPath along with various other locator strategies. XPath in Selenium extends beyond the simple locator methods by id or name attributes, and opens up all sorts of new possibilities for locating complex & dynamically rendered elements.

Developers do not always follow best practices or testability guidelines while building the applications. XPath comes to your help when you don’t have a suitable id or name attribute for the element you wish to find on a page. You can use XPath to either find the element in absolute terms or relative to an element that does have an id or name attribute.

Recently a team approached me for solving a locator problem in Selenium. They were testing an e-commerce web application. They had difficulty in selecting & adding an item to the shopping cart. The table listing the items was rendered dynamically using dynamic ids and complex HTML structure. The following figure shows how shopping cart looks like:

ShoppingCart
Shopping Cart Page

The textbox to enter the quantity and image to add item was deep under layer of div elements inside a td (table column) element. The id’s for these elements are generated dynamically and it was difficult to add an item from the test script. Here is HTML code for the quantity textbox:

For Product 1

<input type="text" id="count_670756" value="" maxlength="3" size="3" name="qty">

For Product 2

<input type="text" id="count_670759" value="" maxlength="3" size="3" name="qty">

As you can see in the above code, test cannot depend on the id attribute as it changes every time page is refreshed and name of the textbox is not unique.

This where XPath Axes comes to a help to resolve problem to find the element as well as develop a generic locator strategy. In simple terms, XPath Axes helps to find elements based on element’s relationship with other elements in a document. For more information, there is a very informative tutorial available on W3Schools on XPath & XPath Axes.

Coming back to the problem, first we need to find a unique way to identify a product inside the table. There are two columns which contain unique values namely Product & Article column. The Article column contains primary key from the database so it is highly recommended to use this value as basis to locate the product and will be always unique. Following XPath query will find the cell containing the specified Article using XPath functions contains() & text():

xpath=//td[contains(text(),'0002')]

Now we need to find the table cell which contains the desired elements. Here we will use following-sibling axis and find the third cell from the current cell. Following query will return the cell containing Quantity & Add to cart image:

xpath=//td[contains(text(),'0002')]/following-sibling::td[3]

In the next step we need to get the actual elements which are located inside the layer of div elements. Here we need to use descendent axis to find the child elements in the desired cell:

xpath=//td[contains(text(),'0002')]/following-sibling::td[3]/descendant::div[2]/input[@name='qty']

xpath=//td[contains(text(),'0002')]/following-sibling::td[3]/descendant::div[3]/input[@name='add']

Following screen-shot shows the Selenium IDE commands to enter the Quantity and click on the Shopping Cart image:

seleide
Selenium IDE

These actions will be used to add multiple items in various test cases. We can turn these actions into a reusable function and remove the hardcoded Article Id used in above example. Following example shows the custom command implemented in Selenium User Extension and Selenium IDE:

Selenium.prototype.doAddItemToShoppingCart = function(locator, value){
     this.doType("xpath=//td[contains(text(),'" + locator + "')]/following-sibling::td[3]/descendant::div[2]/input[@name='qty']", value);
     this.doClick("xpath=//td[contains(text(),'" + locator + "')]/following-sibling::td[3]/descendant::div[3]/input[@name='add']");
}
custcommand
Custom Command in Selenium IDE

We can also design a reusable function using these actions in Selenium RC for Java:

public void addItemToShoppingCart(String ArticleId, String Qty) throws Exception
{
	selenium.type("xpath=//td[contains(text(),'" + ArticleId + "')]/following-sibling::td[3]/descendant::div[2]/input[@name='qty']", Qty);
	selenium.click("xpath=//td[contains(text(),'" + ArticleId + "')]/following-sibling::td[3]/descendant::div[3]/input[@name='add']");
}

While using XPath we need to consider the fact that these locators are dependent on structure of the page and the position of elements in the DOM. Any changes in the position will affect the locators and tests will result into failures.


	

4 thoughts on “Using XPath Axes for locating elements in Selenium

  1. Thanks for this great post on xpath axes locators for selenium.

    A good trick to reference a specific row or item in a list is to surround your target xpath context node in parens and then you can refer to the element by it’s index relative to the context node. You need the parens because of the order of operations of xpath or this won’t work.

    In your example above, if it suited your test purposes, you could could change this expression for the qty field in the second row: xpath=//td[contains(text(),’0002′)]/following-sibling::td[3]/descendant::div[2]/input[@name=’qty’]

    to this :

    xpath=(//input[@name=’qty’])[2]

    This is good if you want to pick out the element by row/index rather than your text value ‘0002’.

    Using these strategies together is really nice to create dynamic and robust tests especially if you use the storeXpathCount command and then loop until that count is hit.

    This code in the IDE should add a qty to the last row:
    storeXpathCount | xpath=//input[@name=’qty’] | xcount
    type | xpath=(//input[@name=’qty’])[storedVars.xcount] | 100
    click | xpath=(//input[@name=’add’])[storedVars.xcount]

    Of course you would need a control flow extension to loop with the IDE if you wanted to do that or you can use the RC commands in a programming language.

    The xpath checker firefox addon helps me to test these xpath expressions out. I use firebug to help figure out the axes expressions and then test them in xpath checker and that helps ease the pain.

    Thanks again for sharing your knowledge on this tricky selenium xpath locator craziness.

Leave a Reply to unmesh Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s