Essays

1 month ago

Monday, February 8, 2010

Using CSS Selectors Instead of XPath As the Default Locator Strategy in Selenium

We use Selenium to run in-browser acceptance tests in all our Rails apps via the Selenium on Rails plugin. With early versions of Selenium you had to use verbose and clunky XPath locators to reference DOM elements in the tests, such as:

//div[@class='content']/div[@class='sidebar']/a

For a while now, though, the cssQuery library has been integrated into Selenium Core, allowing you to use CSS selectors instead. For example, the above XPath locator could be written with CSS selectors as:

css=.content .sidebar a

In addition to being shorter, the Selenium documentation itself points out that most experienced users recommend CSS locators because they’re faster for the Javascript engine to parse, which means faster tests.

Making CSS Locators the Default

While the CSS locators themselves are great, it’s a minor annoyance of mine that they’re not the default locating strategy. As a result, every locator has to be prepended with css=. If you want to change this, apply the following patch to selenium-core/scripts/selenium-browserbot.js:

@@ -1107,7 +1107,7 @@
         if (locator.startsWith('document.')) {
             return this.locateElementByDomTraversal(locator, inDocument, inWindow);
         }
-        return this.locateElementByIdentifier(locator, inDocument, inWindow);
+        return this.locateElementByCss(locator, inDocument);
     };
 }

Minor Annoyances With CSS Locators

The only thing that I don’t like with CSS locators is that indexing specific sibling elements is more verbose—it must be done with nth-child():

.content .sidebar:nth-child(1) a

With XPath you can use a simple pair of brackets:

//div[@class='content']/div[@class='sidebar'][1]/a

It would be really nice if CSS selectors had the same bracket-style indexing syntax as XPath. (Of course, this is a limitation of CSS itself, not Selenium.)

In addition, nth-child() is a little more brittle in that it isn’t constrained to the current selection scope. For example, assume you have markup like so:

<div class="content">
  <div class="something_else">…</div>
  <div class="sidebar">…</div>
</div>

There is now a non-sidebar sibling <div> appearing before the sidebar <div>. In this case, there will be no element that matches .sidebar:nth-child(1)—the sidebar is matched by .sidebar:nth-child(2). This means that adding additional unrelated markup to your page can break your tests if you’re using CSS locators. If you were using the XPath locators in this case, the indexing of the element in question would remain constant because the index doesn’t refer to children, but to elements matching that specific XPath. (If you added additional sidebar <div>’s the XPath would break as well, but all the same it’s still less brittle.)

One final point to note is that there’s also a bug in the cssQuery library that prevents nth-child() from working correctly—it’s Selenium bug #698. Unfortunately, the patch posted on the bug does not fix the problem for me. Instead, you have to resort to suffixing any nth-child() selector with a child or sibling combinator like so:

.content .sidebar:nth-child(1) > a

Despite these minor wrinkles, I find that the CSS locators used with Selenium are less verbose in general and lead to faster running tests.

Trackback

Leave a Comment

Friday, March 19, 2010
09:58pm