Thursday, January 6, 2011

Xpath In All It's Glory

I thought it would be a good idea to write up a little bit about Xpath, why it rocks, and why you SHOULD use it.  I know many "experts" in selenium tell you that Xpath is bad, it's slow, and it needs to be avoided at all costs.  I disagree completely.  I will, of course, try to reference an item in the most clean and concise method possible.  So if an item has a unique id or name, you should use that and avoid an xpath expression.  However, there are so many times when the element you're trying to locate does not have a unique identifier, and you need to find it.

The problem with Xpath is the way most xpath generating programs work.  They generate a string of absolute locators a million miles long.  So something like this:  /body/td/tr[2]/div[3]/a.  This is NOT how you want to use Xpath.  This style of xpath expression should be your very LAST resort on locating an element.  But what is the alternative? Start with the parent.

First off, I strongly recommend the use of // instead of /.  It may be slightly slower locating an element, but it allows you to construct a much more transparent, and reliable xpath expression. 

So something like this would be a more appropriate xpath from the above example:  //div[@class='object1']/a  This looks through the entire page for a div with attribute "class" equal to "object1".  It then looks for a child <a> element.  This is fine as long as there is only one div with class=object1.  However, class is not a unique identifier, and it's quite possible there is more than one item on the page that matches our expression.  What is the solution?  Add another parent. 

//td[@title='Row1']//div[@class='object1']/a.  Neither title nor class are unique, but hopefully the combination of both is.  However, maybe not. 

If you can't find a parent or grandparent to start with that is unique, you can also try a sibling node.  Suppose we have a table where nothing has attributes.  We want to type into the text field in an adjacent row to a link.  We can find the link, because the link text is unique.  But we can't find the text field without resorting to some caveman xpath expression.  The easiest way i've found to do this is to start with the parent node, and in the predicate (the []) look for a child node, then select another node.  For example:
This expression select a row, looks for a descendant link with text "User1", then selects any descendant input.  You have to use the period inside to select the current node.

//tr[.//a[text()="User1"]]//input

1 comment:

  1. Some additional XPath examples that you don't get with CSS selectors:

    (//someXPathThatReturnsMultipleMatches)[n]

    where it returns the nth match in the set of elements defined by Xpath within parenthesis

    //div[span[a[@title='foo']]]

    where we match & return the div (not the hyperlink) which contains a span which in turn contains a hyperlink with title of "foo". Another example to what you already posted.

    //div[@class='some value']/following-sibling::div[5]

    or

    //div[@class='some value']/preceding-sibling::div[5]

    which return the 5th sibling div before or after the div with the given class, that is the indicated indexed sibling div that has same parent as the div with the specified class

    And from what you pointed out in http://mrselenium.blogspot.com/2011/01/quick-xpath-for-dummies.html

    contains() and the ".." parent are very useful features of XPath.

    Out of all that I mention here in this comment, following-sibling or just "following" is the only thing CSS does offer. contains() is only partially offered as a pseudo CSS selector/feature, which generally means use of jQuery or Sizzle library to use it, as it is not part of native CSS3.

    ReplyDelete