Sunday, July 26, 2009

Relative locators for elements that change their position with time

Web applications are full of elements that are part of a list/table and their position in the list/table is bound to change with time. When identifying such elements static information such as the row number that they are on at the current moment is not the correct strategy. For example the vcdquaity website located here displays a long list of movie titles.



New movies are added every now and then and these new ones will appear at the top pushing the existing ones down. Next to every movie title (apart from a few exceptions) you can see three small buttons namely JPG, nFO and IMDB respectively.

Let us record the step of clicking on the JPG button (highlighted in red square in the image above) next to the movie ‘Harry Potter The Half Blood Prince’ using selenium IDE.


clickAndWait //div[@id='content']/table/tbody/tr[17]/td[4]/a/img


As you can see the table row tr[17] is hard coded in the above locator. This will work as long as the table is unchanged but the moment a new title is added the above locator will point to the JPG button of some other movie title and not ‘Harry Potter The Half Blood Prince’.

For all such elements that change their position with time we need to devise locators that don’t have hard-coded positions in them and will work irrespective of their position in the list/table.

If the locators are xpath expressions then this can be done using the ‘xpath axes’.

W3school has an excellent tutorial on XPATH axes that can be found here.

I will try to demonstrate how to derive a relative xpath expression which will stand the test of time.

1. The first step should be to express the exact requirement in words. In this case we want the locator for the ‘jpg button that lies in the same row as the text Harry Potter The Half Blood Prince *XVID*’.

2. Now let us build a locator that point to the movie title text. Right click on the movie title ‘Harry Potter The Half Blood Prince *XVID*’and select ‘View XPath’. If you don’t see this option you don’t have XPath Checker installed in firefox. You can download XPath Checker here.

In XPath Checker window the XPath displayed is id('content')/table/tbody/tr[17]/td[3]/a.

3. We need to tweak this XPath expression to our requirement. Please remember that the XPath expression that we need must not have hard-coded row position but it’s ok to have a hard-coded column position because the column position will never change.

So lets start by removing the row position from the xpath expression in the Xpath Checker window.

Now we are left with id('content')/table/tbody/tr/td[3]/a

This xpath expression matches all movie title on the page. You can see in the XPath Checker window all the movie title shown as matches.

We can also get rid of the ‘/a’ from the expression since we are more interested in the textual contents of that cell and not the anchor tag.

So now we have id('content')/table/tbody/tr/td[3]

The next step would be to tweak the expression further such that it matches only the required row i.e the row that contains ‘Harry Potter The Half Blood Prince *XVID*’ in the 3rd column and not all the rows. To achieve this a node() predicate can be used in the following way


id('content')/table/tbody/tr/td[3][node()='Harry Potter The Half Blood Prince *XVID*']      


Now we have uniquely identified the row that contains our element and are currently pointing to the column that has the text Harry Potter The Half Blood Prince *XVID*’ in that row.

The JPG image/button resides one cell away from here.

To move in same row one cell to the right we need to use the XPath axes ‘following-sibling’. This XPath axes will help us to move right on the same level i.e move right in the same row of the table. Also remember that we need to move one cell i.e one td.Adding this axes changes our XPath expression to


id('content')/table/tbody/tr/td[3][node()='Harry Potter The Half Blood Prince *XVID*']/following-sibling::td[1]              


The XPath Checker would show just one match that is the JPG image/button.

To make this XPath expression work in selenium IDE append ‘xpath=’ to the beginning of the XPath expression derived above and voila it works.

So finally the XPath expression that matches our requirement is


xpath=id('content')/table/tbody/tr/td[3][node()='Harry Potter The Half Blood Prince *XVID*']/following-sibling::td[1]         


ancestor, descendant, following, preceding, preceding-sibling are some of the other XPath axes that I find very useful. All of these axes and more are explained in the link given at the beginning of this post.

Also please note that 'Harry Potter The Half Blood Prince *XVID*' was in the list when I wrote this post and you may have to replace it with some other movie title that is there in the list at that time.

7 Comments:

Tarun K said...

Excellent example

Anonymous said...

Hi Tarun,

I found this post very useful in locating a specific row in a list view.
However i am facing difficulties in applying this same strategy in my application
I need to locate a particular row based on a column value
Can you please reply to ashgopi@gmail.com
so that i can tell the details

Thanks a lot

Anonymous said...

Hi Mahesh,

Please reply to ashgopi@gmail.com
I have issues in xpath for the same kind of scenario

Anonymous said...

This is very helpful. I am new to XPath and need to do almost this exact thing, except I would like to match on part of the string?
For example, to match on 'Harry Potter' instead of the full name of the video.

id('content')/table/tbody/tr/td[3][node()='Harry Potter The Half Blood Prince *XVID*']/following-sibling::td[1]

I found the contains() function but I can't get it to work. Thanks for your help!

Mahesh Narayanan said...

Hi Anonymous, for contains() function to work you need to add "xpath=/descendant::" to the beginning of your xpath. Refer to the post http://functionaltestautomation.blogspot.com/2009/07/xpath-not-working-in-selenium-rc.html for more details.

Anonymous said...

Hi Mahesh,

I am using ant to instantiate selenium-server. How do i pass user-extensions arguments in ant build file ?

Shruti Ramalingam said...

Thanks for the great information in your blog Selenium Training in Chennai