There is a lot of XPath hate in the Selenium world. Some of it justifiable, but a lot of it is bandwagon-ism and results in hatred for all things XPath. Here’s the rub though, sometimes you really do need XPath and when you do, its power is pretty impressive. Yes, more powerful than CSS Selectors.
x ='//td[@class="ql-account-requests-title_author"]/a[contains(translate(text(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), translate("' + material_row.get("author") + '", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"))]/../../td[@class="ql-account-requests-pickup_location" and contains(text(), "' + branch_row.get("branch") + '")]/..//input[@class="ql-account-select-checkbox"]';
Yes. That is a 408 character XPath. And it is awesome.
Context.
This is from a BrowserMob script for a library site revamp. At the end of the script I needed to find the material I had reserved and cancel that reservation. The list of reservations is a tabular data and so was logically in a table. But the accounts are reused during the run so I needed to find the material by the author and title that was randomly chosen for this run. This meant that I needed to find a cell, go back up and over, find a cell, got back up and over and click a checkbox.
This is not a time machine; its a locator. It can’t go back.[1]
When you need a plain top-to-bottom structural locator then CSS Selectors should be your default approach, but it cannot go back up the DOM. XPath can.
CSS cannot also deal with the inevitable matching problems that come from using real-life data. And unfortunately Firefox’s implementation of XPath does not have the lower-case function so you have to use the nasty looking translate. CSS Selectors do not also have substring matching for of descendent text in the standard. Sizzle solves that, but that is not standard. XPath has contains though.
Circling back to this particular XPath.
- Does it solve the problem? Yes
- Is it more inherently brittle than another structural locator? No
- Does it provide guards around case differences in data? Yes
- Would it be pretty easy to modify if the way that the identifying bits change? Yes
Awesome.
The point of this little rant is to not be afraid of large XPath when it is the proper locator for the task and reserve your judgemental sneers for those use of bad used XPath poorly.
Oh, and remember, your ‘but it relies on the structure of the HTML’ hatred should really be directed at both XPath and CSS Selectors. They are both structural location schemes and therefore suffer the same problems.
[1] Stolen blatantly from Back To The Future by Pagano
Comments 2
Long, non-brittle, XPath locators can be a very good idea. But I always get nervous when I see “/../” inside a table. For starters, it’s more navigational than structural, and for finishers, many (not Adam, but many) folks aren’t capable of understanding the side aspects of that. So I usually try to write something more like the following. It has the advantage that you can see the relationship between the two TD elements, and realize that they’re siblings within the same TR, and that the INPUT element floats free beneath the TR, two things that I don’t find obvious from the “up and over to the right” version.
x = ‘//tr[td[@class=”ql-account-requests-title_author”]/a[contains(translate(text(), “ABCDEFGHIJKLMNOPQRSTUVWXYZ”, “abcdefghijklmnopqrstuvwxyz”), translate(“‘ + material_row.get(“author”) + ‘”, “ABCDEFGHIJKLMNOPQRSTUVWXYZ”, “abcdefghijklmnopqrstuvwxyz”))] and td[@class=”ql-account-requests-pickup_location” and contains(text(), “‘ + branch_row.get(“branch”) + ‘”)]]/td/input[@class=”ql-account-select-checkbox”]’;
Posted 15 Feb 2012 at 3:15 pm ¶Adam, thank you for posting this; I have not understood all the negative feelings towards xpath in our community, and it is a relief to have some of my personal feelings confirmed.
And Ross, that’s brilliant, I didn’t know you could have child elements as conditionals.
Posted 17 Feb 2012 at 11:49 am ¶Post a Comment