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 locating nodes in an XML document and it can also be used for locating HTML elements in XHTML. Selenium supports XPath along with various other locator strategies. XPath in Selenium extends beyond the simple methods of locating by id or name attributes, and opens up all sorts of new possibilities for locating complex & dynamically rendered elements.

Awkwardly, developers 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 locate. You can use XPath to either locate the element in absolute terms or relative to an element that does have an id or name attribute. XPath locators can also be used to specify elements via attributes other than id and name.

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

ShoppingCart

Shopping Cart Page

The textbox to enter the Quantity and image to add item was buried under layer of div elements inside a td element in a table. The ids for these elements were generated dynamically and it was difficult to add an item dynamically from the test script. Here is html code for 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, we cannot rely on id attribute as it changes every time page is refreshed and secondly name of textbox is not unique.

XPath Axes comes to a great help to resolve this problem and develop a generic location strategy. In simple terms XPath Axes helps to locate elements based on element’s relationship with other elements in a document. For more information, there is a nice 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 in the table. There are two columns which contain unique values namely Product & Article column. However Article column contains primary key from the database so it is highly recommended to use this value as basis to locate the product. We can also use Product column otherwise. Following XPath Query will locate the cell containing the specified Article using XPath functions contains() & text():


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

Now we need to find the cell which contains the 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 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 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 need to make these actions more generic and remove the hardcoded Article Id. 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 create reusable function around these actions in following way for Selenium RC in 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']");
	}

However while using XPath we need to consider the fact that these locators are dependent on structure of the page and the layout of the elements. Any changes to the structure or layout will affect the locators and tests will result into failures.

Update:

Sometimes XPath queries may not work well or tests run slow in browsers that do not have good support for XPaths. CSS selectors is a good alternative in such situations. CSS selectors are considerably faster than XPath. Selenium supports CSS 1.0, 2.0 & 3.0. Following code sample describes CSS selectors for locating the elements from above example:


css=td:contains('0002')+td+td+td>div>form>div>input[name='qty']
css=td:contains('0002')+td+td+td>div>form>div>input[name='add']
About these ads

4 Comments on “Using XPath Axes for locating elements in Selenium”

  1. [...] Using XPath Axes for locating elements in Selenium discusses some of the more advanced ways of finding elements via XPath [...]

  2. Mikael says:

    Great Article. We extensivly use Selenium with Xpath to test our web applications.

  3. Alex Everitt says:

    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

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 33 other followers