I think it’s time for xpath and css selectors to identify elements in automated tests to die. I am sick of seeing them in automated tests, and I don’t know of any good reasons why we should use them.
Let me demonstrate.
Randomly looking through some example automated tests for Etsy.com on github, there’s a couple of xpath selectors included.
One is on this page: http://www.etsy.com/buy?ref=so_buy
@browser.find_element(:xpath, "//ul[@id='user-nav']/li/a[text()='Buy']").click()
The other is on this page: http://www.etsy.com/treasury
def element = findElement(By.xpath("//div[@class='item-treasury-info-box']/h3/a"))
These are by no means complicated xpath expressions, but I just don’t like using them.
In Watir-WebDriver I’d do the first example as:
@browser.link(:text => 'Buy').click
# or
@browser.link(:title => 'Buy on Etsy').click
or Selenium-WebDriver (ruby) as:
driver.find_element(:link_text, 'Buy').click
# Selenium-WebDriver doesn't support identifying elements by #title
In Watir-WebDriver I’d do the second example as:
@browser.div(:class => 'item-treasury-info-box').link.click
or Selenium-WebDriver (ruby) as:
b.find_element(:class, 'item-treasury-info-box').find_element(:partial_link_text, '').click
No xpath. Not needed. Why bother?
I wrote the Etsy.com examples recently in Watir-WebDriver without using a single xpath selector, but the same example in Selenium-WebDriver uses xpath on every page. Why?
Selenium-WebDriver (the official ruby Selenium bindings) has limitations that means it’s easy to revert to using xpath and css selectors:
- It’s not possible to use more than one identifier to identify a single element
- You can use :index as a method to find elements (but you can find all elements and use the array)
- There is no such thing as an element type in Selenium-WebDriver: find_element returns all element types
- The identifier tags you can use is limited, for example, you can’t identify elements using :title
In Watir-WebDriver, there are no such limitations. It is a lot easier to nest multiple elements to clearly articulate what you’re looking for.
For example, this
@browser.div(:class => 'item-treasury-info-box').link.click
could be written as:
@browser.div(:class => 'item-treasury-info-box').h3.link.click
which shows the full DOM chain if you like to articulate it this way. I like the fact that each element type is explicit, I would want my test to fail it a span was changed to a div or vice-versa. It would mean I would go and manually check the layout to ensure it still looks okay. An element is an element type for a reason.
The absence of :index methods, and the ability to only use one identifier means that this chaining is not possible in Selenium-WebDriver, without using an xpath or css selector.
Summary
I struggle to understand why anyone would choose to use Selenium-WebDriver over Watir-WebDriver, considering its limitations. But people do, and its limitations mean writing complex xpath or css selectors in your tests.
I avoid writing xpath and css selectors; I would much rather use the Watir-WebDriver API to clearly express what I am expecting.