Web element extensions in C# that I find useful

I really miss the Watir API when working with WebDriver in C#. Whilst the C# WebDriver bindings are fantastic (thanks Se team!), simple things I like to do with the Watir API aren’t present, so I find myself writing web element extension methods to make my life easier (and code cleaner). Here’s some of my favourites: (wouldn’t it be neat to see them in WebDriver one day. I can dream…)

IWebElement.Set(text)

I have always found .SendKeys to be an unusual method, as I haven’t come across an instance where I want to fire keys at a field instead of just setting it to a value. So .Set(text) is usually one of the first web element extensions that I write:

namespace WebDriverExtensions
{
  using Microsoft.VisualStudio.TestTools.UnitTesting;
  using OpenQA.Selenium;
  using OpenQA.Selenium.Firefox;
  
  public static class WebElementExtensions
  {
    public static void Set(this IWebElement e, string text)
    {
      e.Clear();
      e.SendKeys(text);
    }
  }
  
  [TestClass]
  public class ExtensionUnitTests
  {
    [TestMethod]
    public void SetTextField()
    {
      var driver = new FirefoxDriver();
      driver.Navigate().GoToUrl("data:text/html,<input type=\"text\" id=\"t\" />");
      var textField = driver.FindElement(By.Id("t"));
      textField.SendKeys("a");
      Assert.AreEqual("a", textField.GetAttribute("value"));
      textField.SendKeys("b");
      Assert.AreEqual("ab", textField.GetAttribute("value"));
      textField.Set("b");
      Assert.AreEqual("b", textField.GetAttribute("value"));
      driver.Quit();
    }
  }
}

SelectElement.SelectBySubText(subText)

The Selenium Support library provides some useful select element specific methods, but unfortunately the .SelectByText(partial) method doesn’t select by subtext (as advertised), so I wrote a .SelectBySubText(subText) method to actually do this.

namespace WebDriverExtensions
{
  using System.Linq;
  using Microsoft.VisualStudio.TestTools.UnitTesting;
  using OpenQA.Selenium;
  using OpenQA.Selenium.Firefox;
  using OpenQA.Selenium.Support.UI;
  
  public static class WebElementExtensions
  {
    public static string SelectBySubText(this SelectElement me, string subText)
    {
      foreach (var option in me.Options.Where(option =&gt; option.Text.Contains(subText)))
      {
        var optionText = option.Text;
        option.Click();
        return optionText;
      }
      me.SelectByText(subText);
      return subText;
    }
  }
  
  public class ExtensionUnitTests
  {
    [TestMethod]
    public void SelectBySubTextTest()
    {
      var driver = new FirefoxDriver();
      driver.Navigate().GoToUrl("data:text/html,VolvoSaab");
      var selectList = new SelectElement(driver.FindElement(By.Id("cars")));
      selectList.SelectByText("Volvo");
      Assert.AreEqual(selectList.SelectedOption.Text, "Volvo");
      selectList.SelectBySubText("Saa");
      Assert.AreEqual(selectList.SelectedOption.Text, "Saab");
      driver.Quit();
    }
  }
}

IWebElement.FindVisibleElement(by) and IWebElement.VisibleElementExists(by)

Web Apps I test often hide elements that are available to use, but unfortunately WebDriver still locates these (even though it won’t allow you to interact with them). So, I wrote two extensions: one to only find an element matching a selector that is visible, and another to check if a visible element exists matching a selector.

namespace WebDriverExtensions
{
  using System;
  using System.Linq;
  using Microsoft.VisualStudio.TestTools.UnitTesting;
  using OpenQA.Selenium;
  using OpenQA.Selenium.Firefox;
  
  public static class WebElementExtensions
  {
    public static IWebElement FindVisibleElement(this IWebDriver driver, By by)
    {
      var elements = driver.FindElements(by);
      foreach (var element in elements.Where(e => e.Displayed))
      {
        return element;
      }
      throw new NoSuchElementException("Unable to find visible element with " + @by);
    }

    public static bool VisibleElementExists(this IWebDriver driver, By by, Int32 implicitWait=10)
    {
      driver.Manage().Timeouts().ImplicitlyWait(new TimeSpan(0, 0, 0));
      var elements = driver.FindElements(by);
      var visibleElements = elements.Count(e => e.Displayed);
      driver.Manage().Timeouts().ImplicitlyWait(new TimeSpan(0, 0, implicitWait));
      return visibleElements != 0;
    }
  }

  public class ExtensionUnitTests
  {
    [TestMethod]
    public void FindVisibleElements()
    {
      var driver = new FirefoxDriver();
      driver.Navigate().GoToUrl("data:text/html,<input  type=\"hidden\" class=\"myfield\" name=\"date-submitted\" value=\"Hidden\"><input class=\"myfield\" name=\"date-submitted\" value=\"Visible\">");
      var textField = driver.FindElement(By.ClassName("myfield"));
      Assert.IsFalse(textField.Displayed);
      var visibleTextField = driver.FindVisibleElement(By.ClassName("myfield"));
      Assert.IsTrue(visibleTextField.Displayed);
      Assert.AreEqual("Visible", visibleTextField.GetAttribute("value"));
      driver.Quit();
    }
    
    [TestMethod]
    public void VisibleElementExists()
    {
      var driver = new FirefoxDriver();
      driver.Navigate().GoToUrl("data:text/html,<input  type=\"hidden\" class=\"myfield\" name=\"date-submitted\" value=\"Hidden\"><input class=\"myfield\" name=\"date-submitted\" value=\"Visible\">");
      Assert.IsTrue(driver.VisibleElementExists(By.ClassName("myfield")));
      driver.Quit();
    }
    
    [TestMethod]
    public void VisibleElementDoesNotExist()
    {
      var driver = new FirefoxDriver();
      driver.Navigate().GoToUrl("data:text/html,<input  type=\"hidden\" class=\"myfield\" name=\"date-submitted\" value=\"Hidden\">");
      Assert.IsFalse(driver.VisibleElementExists(By.ClassName("myfield")));
      driver.Quit();
    }
  }
}

Summary

These are some web element extensions that I find useful. Do you have any? What would you like to see built into WebDriver?

Writing your own WebDriver selectors in C#

I am working on a C# project at the moment, writing tests using WebDriver, and one of the things I miss most about Watir-WebDriver is its variety of selectors, for example, being able to specify a value for value. Since the application I am testing uses value a huge amount, I started using a css selector for each element I was interacting with:

var driver = new FirefoxDriver();
driver.Navigate().GoToUrl("data:text/html,<div value=\"Home\">Home</div>");
var divByCss = driver.FindElement(By.CssSelector("[value=\"Home\"]"));

But I got sick of typing out this fairly unattractive CSS selector each time I came across a new element.

I wanted to write an extension method so that I can use something like By.Value(“Home”) instead of By.CssSelector(…) but soon realized that you can’t write extension methods for static classes in C#, as you need an instance of the class to extend.

So, instead of extending the original By class, I wrote my own custom MyBy class that I can use in addition to the original.

namespace WebDriverExtensions
{
  using Microsoft.VisualStudio.TestTools.UnitTesting;
  using OpenQA.Selenium;
  using OpenQA.Selenium.Firefox;

  public static class MyBy
  {
    public static By Value(string text)
    {
      return By.CssSelector("[value=\"" + text + "\"]");
    }
  }

  [TestClass]
  public class WebElementExtensionTests
  {
    [TestMethod]
    public void ByValue()
    {
      var driver = new FirefoxDriver();
      driver.Navigate().GoToUrl("data:text/html,<div value=\"Home\">Home</div>");
      var divByValue = driver.FindElement(MyBy.Value("Home"));
      var divByCss = driver.FindElement(By.CssSelector("[value=\"Home\"]"));
      Assert.AreEqual(divByCss, divByValue);
      Assert.AreEqual("Home", divByValue.Text);
      Assert.AreEqual("Home", divByValue.GetAttribute("value"));
      driver.Quit();
    }
  }
}

I think this solves the problem fairly nicely. Do you do something similar? Is there a better way?

Introducing webdriver-user-agent gem: a quick way to run ruby webdriver tests emulating mobile devices

Update: As @David points out below, Chrome window resizing does not seem to work at all on Windows. I am not sure why but I will update this blog if I fix this issue.

After chatting to Jari Bakken at the Test Automation Bazaar in Austin, I wrote and released the webdriver-user-agent gem.

I’m not convinced that running your automated functional tests on real or emulated mobile devices will yeild the appropriate defects for effort involved, so this gem makes it simple to run yourwatir-webdriver or selenium-webdriver tests on real browsers (:firefox and :chrome) but using mobile device user agents and resolutions, so your app thinks they’re a mobile device.

Installation

Add this line to your application’s Gemfile:

gem 'webdriver-user-agent'

And then execute:

$ bundle

Or install it yourself as:

$ gem install webdriver-user-agent

Examples

An example script in selenium-webdriver:

require 'selenium-webdriver'
require 'webdriver-user-agent'
driver = UserAgent.driver(:browser => :chrome, :agent => :iphone, :orientation => :landscape)
driver.get 'http://tiffany.com'
driver.current_url.should == 'http://m.tiffany.com/International.aspx'

and another example script in watir-webdriver:

require 'watir-webdriver'
require 'webdriver-user-agent'
driver = UserAgent.driver(:browser => :chrome, :agent => :iphone, :orientation => :landscape)
browser = Watir::Browser.new driver
browser.goto 'tiffany.com'
browser.url.should == 'http://m.tiffany.com/International.aspx' 

The available options are:

  • :browser
    • :firefox (default)
    • :chrome
  • :agent
    • :iphone (default)
    • :ipad
    • :android_phone
    • :android_tablet
  • :orientation
    • :portrait (default)
    • :landscape

This is what the screenshots look like:

Tiffany.com in iPhone portrait

Tiffany.com in iPad landscape



Summary

I think this will be a useful gem for those wanting to test mobile device functionality without having to set up complex infrastructure.

A tale of three ruby automated testing APIs (redux)

Redux Note: I originally wrote a similar article to this before going on parental leave about six weeks ago. Whilst I didn’t intend to offend, it seemed that a few people took my article the wrong way. I understand that a lot of effort goes into creating a web testing API, but that doesn’t mean that everyone will agree with what you’ve made.

Sadly, an anonymous coward attacked myself and the company who I work (even though I don’t mention that company on this blog), so for the first time in this blog’s history, I have had to turn comment moderation on. I am sorry to the other genuine commenters whose comments have been lost in transition, and now have to wait for their new comments to be approved.

Since then I have received numerous emails asking where my article went, and commenting that people found it interesting and worthwhile. So I have decided to repost this article, hopefully with a little less contention this time around, making it clear, this is my opinion and experience: YMMV.

Intro

As a consultant I get to see and work on a lot of automated testing solutions using different automated web testing APIs. Lately I’ve been thinking about how these APIs are different and what makes them so.

My main interest is in ruby, and fortunately ruby has three solid examples of three different kinds of web testing APIs, two of which extend the lowest level API: selenium-webdriver.

I’ll (try to) explain here what I consider to be three kinds of automated web testing APIs and where I consider the sweet spot to be and and why.

A meaty example

As a carnivore, I thought I would explain my concept in terms I can relate to. If you’re a beef eater, there are many different kinds of beef that you can use to make some tasty food to eat. I’ll use three different kinds of beef for my example. The first (rawest) kind would involve getting a beef carcass and filleting it yourself to eventually make some edible food. The second kind of beef you could use is beef that is already in a slightly usable form, but you can then use yourself to make some edible food. For example, you can buy minced beef at a butcher, and then make your own hamburger patties, taco fillings etc from it. The final type of beef you could use is beef that has already been prepared so you can directly consume it, for example, sausages which can be cooked and consumed as is.

I consider these three examples of different kinds of beef to roughly correlate to automated web testing APIs, of which I also consider to be three kinds of.

The first is a Web Driver API, which is the rawest form of an API, its job is to drive a browser by issuing it commands. It provides a high level of user control, but like filleting a beef carcass it’s more ‘work’. An example in ruby of this API is the selenium-webdriver API, which controls the browser using the webdriver drivers.

The second kind of automated web testing API is the Browser API, which is a higher level API but still provides user control. This is the minced beef of APIs, as whilst it’s in a more usable form than a carcass, you still have a lot of control (and potential to what you can do with it). An example in ruby of this API is the watir-webdriver API, which uses the underlying selenium-webdriver carcass to control the browser.

The final kind of automated web testing API is the Web Form DSL (Domain Specific Language) which is a very high level API that provides users with specific methods to automate web forms and their elements. This is the beef sausages of APIs as sometimes you feel like eating something else besides sausages, but it’s difficult to make anything else edible but sausages from sausages. An example in ruby of this Web Form DSL is the Capybara DSL.

Visually, this looks something like this:

Show me the code™

So exactly what do these APIs look like?

I knew you’d ask, that’s why I came prepared.

Say I want to accomplish a fairly basic scenario on my example Google Doc form:

  • Start a browser
  • Navigate to the watir-webdriver-demo form
  • Check whether text field with id ‘entry_0′ exists (this should exist)
  • Check whether text field with id ‘entry_99′ exists (this shouldn’t exist)
  • Set a text field with id ‘entry_0′ to ’1′
  • Set a text field with id ‘entry_0′ to ’2′
  • Select ‘Ruby’ from select list with id ‘entry_1′
  • Click the Submit button

This is how I would do it in the three different APIs:

# * Start browser
# * Navigate to watir-webdriver-demo form
# * Check whether text field with id 'entry_0' exists
# * Check whether text field with id 'entry_99' exists
# * Set text field with id 'entry_0' to '1'
# * Set text field with id 'entry_0' to '2'
# * Select 'Ruby' from select list with id 'entry_1'
# * Click the Submit button

require 'bench'

benchmark 'selenium-webdriver' do
  require 'selenium-webdriver'

  driver = Selenium::WebDriver.for :firefox
  driver.navigate.to 'http://bit.ly/watir-webdriver-demo'
  begin
    driver.find_element(:id, 'entry_0')
  rescue Selenium::WebDriver::Error::NoSuchElementError
    # doesn't exist
  end
  begin
    driver.find_element(:id, 'entry_99').displayed?
  rescue Selenium::WebDriver::Error::NoSuchElementError
    # doesn't exist
  end
  driver.find_element(:id, 'entry_0').clear
  driver.find_element(:id, 'entry_0').send_keys '1'
  driver.find_element(:id, 'entry_0').clear
  driver.find_element(:id, 'entry_0').send_keys '2'
  driver.find_element(:id, 'entry_1').find_element(:tag_name => 'option', :value => 'Ruby').click
  driver.find_element(:name, 'submit').click
  driver.quit
end

benchmark 'watir-webdriver' do
  require 'watir-webdriver'
  b = Watir::Browser.start 'bit.ly/watir-webdriver-demo', :firefox
  b.text_field(:id => 'entry_0').exists?
  b.text_field(:id => 'entry_99').exists?
  b.text_field(:id => 'entry_0').set '1'
  b.text_field(:id => 'entry_0').set '2'
  b.select_list(:id => 'entry_1').select 'Ruby'
  b.button(:name => 'submit').click
  b.close
end

benchmark 'capybara' do
  require 'capybara'
  session = Capybara::Session.new(:selenium)
  session.visit('http://bit.ly/watir-webdriver-demo')
  session.has_field?('entry_0') # => true
  session.has_no_field?('entry_99') # => true
  session.fill_in('entry_0', :with => '1')
  session.fill_in('entry_0', :with => '2')
  session.select('Ruby', :from => 'entry_1')
  session.click_button 'Submit'
  session.driver.quit
end

run 10

This is how long they took for me to run:

                        user     system      total        real
selenium-webdriver  1.810000   0.840000  22.130000 ( 73.123340)
watir-webdriver     1.940000   0.870000  24.380000 ( 79.388494)
capybara            1.950000   0.890000  24.080000 ( 79.920051)

Note: Capybara doesn’t always require a ‘session’, it’s only for non ruby rack applications, but since my example (Google) is not a rack application, as are most of the applications I test, my example must use the session.

When using ruby, why Watir-WebDriver is my sweet spot

I personally find Watir-WebDriver to be the most elegant solution, as the API is high enough for me to be highly readable/usable, but low enough to be powerful and for me to feel like I’m in control.

For example, being able to select an element by a explicit identifier (name, class name, id, anything) is a huge deal to me. I personally don’t like relying on the API to determine which selector to use: for example Capybara only supports name, id and label, but you can’t tell fill_in which specific one to choose: it appears to try each selector one by one until it finds it.

I have found that Watir-WebDriver also also provides lots of flexibility/neatness. For example: it’s the only API shown here that allows URLs to not have a ‘http://’ prefix (how many people do you know who type in http:// into a browser?).

In my opinion, the high level APIs like Capybara don’t provide enough control (for example – being able to specify the explicit selector), but the low level APIs like webdriver don’t provide enough functionality. This is evident when I am using a language other than ruby (like C#) when I find myself writing a large number of web element extension methods because webdriver doesn’t provide any of them. A .set method is a classic example, even Simon Stewart writes a clearAndType method in his examples even though he wrote webdriver which sadly misses it (you must call .clear, and .send_keys).

My biggest concern about high level field APIs

But my biggest issue with the high level APIs is that I’ve frequently seen them used to write test scripts that are step by step interactions with a web form. Instead of thinking of a business application as that, people see it as a series of forms that you ‘fill in’. This means people create scenarios like Aslak Hellesøy included in his recent post about cucumber web steps (which uses Capybara) and the problems it has created.

Scenario: Successful login
  Given a user "Aslak" with password "xyz"
  And I am on the login page
  And I fill in "User name" with "Aslak"
  And I fill in "Password" with "xyz"
  When I press "Log in"
  Then I should see "Welcome, Aslak"

I’m not saying it’s not possible to end up with something as ugly as above using other APIs, but I am saying the web form DSL style naturally relates to this: as the APIs look so similar to this style because that’s what the DSL was designed for: filling in forms. I’ve seen people frequently write generic, reusable cucumber steps to match the web form DSL like:

When /^I fill in "(.+)" with "(.+)"$/ do |value, field|
  fill_in field, :with => value
end

But this means you end up with less readable, less maintainable test scripts rather than business readable executable specifications.

Summary

Ultimately what I am looking for in an automated web testing API is simplicity and full control. I personally find browser APIs like Watir-WebDriver and Watir give me this, and this is why I love them so. Your mileage may vary, you may like different styles of APIs better, but I’ve seen other APIs so badly abused by people not even thinking about it, so it makes sense to think about what you’re trying to achieve and whether what you’re doing is the right way.

C#: Avoiding the WebDriverException: No response from server for url

When it comes to automated testing, there’s not much worse than intermittent failures, especially when they stem from the driver itself. The current version of the C# WebDriver bindings has such a failure, but I worked out a reasonable way to avoid it happening. Basically it involves creating a WebDriver extension method that I use instead of Driver.FindElement, which tries a number of times to find the element, ignoring the exception that is intermittently raised.

I hope you find this useful if you’re consuming WebDriver in C#.

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
namespace Extensions
{
    public static class WebDriverExtensions
    {
        public static SelectElement GetSelectElement(this IWebDriver driver, By by)
        {
            return new SelectElement(driver.GetElement(by));
        }
        public static IWebElement GetElement(this IWebDriver driver, By by)
        {
            for (int i = 1; i <= 5; i++ )
            {
                try
                {
                    return driver.FindElement(by);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception was raised on locating element: " + e.Message);
                }
            }
            throw new ElementNotVisibleException(by.ToString());
        }
    }
}

Setting Firefox download.dir with watir-webdriver on Windows

I was recently unsuccessfully trying to set the Firefox download directory on Windows using a Watir-WebDriver/Selenium-WebDriver profile. Thanks to a comment on this blog, it turns out there is a bug whereby if your download path contains a \n or \r, then it reverts back to the default location.

So, a download path like “C:\new\raw” will fail without telling you why.

Fortunately, it’s now been fixed, but hasn’t been released, so in the meantime, you can double escape it

require 'watir-webdriver'
p = Selenium::WebDriver::Firefox::Profile.new
p['browser.download.dir'] = "C:\\\\new\\\\raw"
p['browser.download.folderList'] = 2
p['browser.helperApps.neverAsk.saveToDisk'] = "application/pdf"
b = Watir::Browser.new :firefox, :profile => p

Disabling native events when using Firefox with WebDriver

Imagine this, you’ve got a whole suite of regression tests (thousands of steps) written in Watir-WebDriver that you run on a corporate Windows XP SOE using Firefox.

The tests have been run numerous times and are running perfectly without any intermittent failures.

A new version of selenium-webdriver is released with promised bug fixes and stability improvements, so you update your selenium-webdriver gem to 2.6.0 and re-run your test suite.

Red light: half of the tests fail. The suite takes longer than ever to run. Oh my.

After some investigation, Jari Bakken points out that it’s Firefox native events related. This causes text field sets to take a long time if they include capital letters, and locating elements seems to often intermittently fail.

I add a config option to disable native events to my Firefox profile, and my tests run perfectly again. Phew!

So, if you’re using Windows and Firefox and come across any of these problems, include this code to disable native events when you start your browser.

profile = Selenium::WebDriver::Firefox::Profile.new
profile.native_events = false
Watir::Browser.new WEB_DRIVER, :profile => profile

Running Watir-WebDriver tests on Travis CI: a distributed build system

I recently came across Travis CI, a distributed build system that has close links to Github. I’ve seen quite a few projects use it as a CI system, but none that run headless browser tests.

Leveraging off the work I had done recently setting up my own Jenkins server in the cloud to run headless Watir-WebDriver tests, I thought I would have a go at running my WatirMelonCucumber and EtsyWatirWebDriver headless browser tests using Travis CI.

What I didn’t realize is how easy it’d be. The only things I had to do was make sure my Gemfile included rake, and also make sure there was some file existance checking happening for some log files, and it pretty much ran straight away. Wow!

Caveat Emptor

This is pretty new territory, so there’s a few things to watch out for:

  • Every now and, Travis complains about not having Firefox installed. I am not sure why this happens, maybe something to do with the different agents in use;
  • The locale of the build agents seems to be German, so when running my Google tests, the page content is in German, so it fails because it can’t find my expected (English) results; and
  • I can’t seem to capture my test results html file nor screenshot pngs, so it’s a red/green only affair at the moment.

But still, neat distributed free headless CI!

Running headless Watir-WebDriver tests using a real browser

I seem to get quite a few questions from people trying to use Watir-WebDriver in headless mode using a WebDriver server and HTMLUnit. It seems to me that it’s problematic, especially when your web app contains JavaScript (who’s app doesn’t?).

The problem stems from HTMLUnit’s JavaScript support. Whilst it does support JavaScript, myself and many others have found the JavsScript support to be pretty poor as it uses its own JavaScript engine (Rhino) that no other ‘real’ browser actually uses, which sorta raises a question: are you really testing your app’s JavaScript?

The other thing about HTMLUnit is the lack of screenshot capability, as it’s purely headless, there’s no way to capture a screenshot when something goes wrong, so you’ll find yourself re-running failed tests using a different browser, which may or may not reproduce your problem. Not ideal!

Finally, running WebDriver headless tests requires you to run a Selenium Server, which is additional overhead for your tests.

But why headless?

Whilst running automated browser tests in a headless form can speed things up, the main reason I see people wanting headless watir-webdriver support is so that tests can be run on headless Linux machines, for example, a Jenkins Server. I’ve found running tests in parallel speeds things up more than headless execution can, so it’s mainly headless server support most people are after.

Enter the headless gem.

The headless gem is a ruby wrapper for Xvfb that makes it super easy to run graphical applications (such as real web browsers) on a headless machine. This gem is perfect for running headless watir-webdriver tests using a real browser.

How? It’s simple.

require 'watir-webdriver'
require 'headless'
headless = Headless.new
headless.start
b = Watir::Browser.start 'www.google.com'
puts b.title
b.close
headless.destroy

You’ll need Xvfb installed, which is as easy as:

sudo apt-get install xvfb

Or if you’re using cucumber like me, you just need the following code in your env.rb file:

if ENV['HEADLESS']
  require 'headless'
  headless = Headless.new
  headless.start
  at_exit do
    headless.destroy
  end
end

This then means you can still launch and use Firefox (or Chrome) on a headless machine.

The best thing about it though is that Watir-WebDriver’s inbuilt screenshots still work perfectly, so it captures a screenshot of the actual page even though it has no display. How awesome is that!

Summary

The headless gem makes it super easy to run real browsers on headless servers. This way you get real JavaScript support without the need to run a Selenium server, and is perfect for running your tests as part of continous integration.

Stay tuned as I’ll be writing a post soon about how to use an EC2 instance to run your headless watir-webdriver tests in the cloud, for free!

Death to xpath (and css) selectors in automated tests

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.