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.

Monday, December 15, 2008

Way to get XPATH in Internet Explorer

Are you stuck with an application that only works in IE?
Do you want the XPATH of an element that can be seen only IE? Unable to replicate the scenario in firefox. There are many firefox addons like xpather, xpath-checker and firebug that will give you the xpath of an element in a second. But sadly there is no addon or tool avaialable that will do this for IE. For most cases you can get the xpath of the elements that fall in your script using the above tools in firefox and tweak them a little (if required) to make them work in IE. But if you are testing an application that will work only in IE or the specific scenario or page that has this element will open-up/play-out only in IE then you cannot use any of the above mentione tools to find the XPATH.
Well the only thing that works in this case is the Bookmarklets that were coded just for this purpose. Bookmarklets are JavaScript code that you will add in IE as bookmarks and later use to get the XPATH of the element you desire. Using these you can get the XPATH as easily as you get using xpather or any other firefox addon.

STEPS TO INSTAL BOOKMARKLETS
1)Open IE
2)Type about:blank in the address bar and hit enter
3)From Favorites main menu select--->Add favorites
4) In the Add a favorite popup window enter name GetXPATH1.
5)Click add button in the add a favorite popup window.
6)Open the Favorites menu and right click the newly added favorite and select properties option.
7)GetXPATH1 Properties will open up. Select the web Document Tab.
8)Enter the following in the URL field.

javascript:function getNode(node){var nodeExpr=node.tagName;if(!nodeExpr)return null;if(node.id!=''){nodeExpr+="[@id='"+node.id+"']";return "/"+nodeExpr;}var rank=1;var ps=node.previousSibling;while(ps){if(ps.tagName==node.tagName){rank++;}ps=ps.previousSibling;}if(rank>1){nodeExpr+='['+rank+']';}else{var ns=node.nextSibling;while(ns){if(ns.tagName==node.tagName){nodeExpr+='[1]';break;}ns=ns.nextSibling;}}return nodeExpr;}

9)Click Ok. Click YES on the popup alert.
10)Add another favorite by following steps 3 to 5, Name this favorite GetXPATH2 (step4)
11)Repeat steps 6 and 7 for GetXPATH2 that you just created.
12)Enter the following in the URL field for GetXPATH2

javascript:function o__o(){var currentNode=document.selection.createRange().parentElement();var path=[];while(currentNode){var pe=getNode(currentNode);if(pe){path.push(pe);if(pe.indexOf('@id')!=-1)break;}currentNode=currentNode.parentNode;}var xpath="/"+path.reverse().join('/');clipboardData.setData("Text", xpath);}o__o();

13)Repeat Step 9.

You are all done!!

Now to get the XPATH of elements just select the element with your mouse. This would involve clicking the left mouse button just before the element (link, button, image, checkbox, text etc) begins and dragging it till the element ends. Once you do this first select the favorite GetXPATH1 from the favorites menu and then select the second favorite GetXPATH2. At his point you will get a confirmation, hit allow access button. Now open up a notepad file, right click and select paste option. This will give you the XPATH of the element you seek.


*I got this bookmarklets from some other site but can't remember which one. Credit goes to the guy who created these JS.