Friday, August 21, 2015

xpath fun

At work we use Robot and Selenium to do automated testing. Most of the page selectors use xpath; at first I didn't like that (vs using css selectors), because it seemed like another thing to learn without adding value (I mean most of us know CSS already but this is the only place we were using xpath) but the fact that xpath can do a contains(text(),'Some Text') makes it worth its conceptual weight vs CSS.

In one of our tests, a menu clicker like
//span[@class='x-menu-item-text'][contains(text(),'Flights')]
was failing because the matching item wasn't visible, even though to the casual observer it certainly seemed visible, with the parent menu open on the page.

The previous solution was to have the robot test close the browser and re-navigate to the page... obviously overkill, and it worked, but we weren't sure why.

It turned out that the ext.js code was creating a new instance of the menu without destroying the old one as robot navigated back and forth through screens in this one-page app- it was just hiding the old menu ("display:none")

We probably should hunt that code down in the source and fix it, but in the meanwhile
(//span[@class='x-menu-item-text'][contains(text(),'Flights')])[last()]
finds the last and relevant instance to click on. Although this last() required some extra parens (to make sure we were finding the last of the matching set, and not indicating we wanted the something that was the last among its siblings in the DOM) it seems easier than rigging up an xpath to indicate "the visible one" - that gets complicated.

Another thing I learned is that Firefox default developer tools console gives you a $x() function "for free" that can evaluate xpath expressions... so when
$x( "(//span[@class='x-menu-item-text'][contains(text(),'Flights')])[last()]" ).length
in the console returned 1 instead of 2 or 0, we knew we had finally constructed the xpath properly, and we were able to iterate on variants of the syntax much more quickly than if we were waiting for robot to churn through the test.

Of course, one additional takeaway (especially relevant for the unwary UI developer turned QA engineer) is the empirical observation that Robot's use of xpath selectors is very different from jQuery's use of CSS selectors when multiple items are hit; much of jQuery's power is effortlessly applying operations to all matching nodes, while evidently Robot will just take the first match in the set.

FOLLOWUP:
Our Robot tests also were made excessively brittle by long, exact match classpaths:
//div[@class="classa classb classc classd"]/em/button
A test was failing because the order of the classes applied to the element had changed.
The best work around seems to be picking one (or two) of the most relevant classes and use contains for a partial match :
//div[contains(@class,"classa") and contains(@class,"classb")]/em/button

No comments:

Post a Comment