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.

Using Opera with Watir-Webdriver (for free)

Selenium 2.0 (WebDriver) was released today, and with it came support for Opera. Since Watir-WebDriver uses the WebDriver bindings, it now supports Opera out of the box.

How to get it working

  1. First make sure you have the latest watir-webdriver (>0.2.5) and selenium-webdriver (>2.0.0) gems installed.
  2. Install Opera if you haven’t already
  3. Download the latest Selenium Standalone Server JAR files to your computer
  4. Create an Environment Variable named SELENIUM_SERVER_JAR and set it to the path to your standalone server JAR file
    (On the mac, you can add something like this to your .bash_profile file:
    export SELENIUM_SERVER_JAR=/Library/Java/selenium-server-standalone-2.0.0.jar
  5. Open irb, require ‘watir-webdriver’, and type something like
    b = Watir::Browser.new :opera

    and ta-da: Opera automated testing.

Selenium-WebDriver vs Watir-WebDriver in Ruby

One question that I often see arise is what is the difference between using watir-webdriver and the selenium-webdriver ruby bindings?

I imagine that the best person to answer this is Jari Bakken, as he wrote both! He did an excellent demo at the Selenium Conference highlighting the differences between the ruby webdriver bindings (selenium-webdriver) and the watir webdriver bindings (watir-webdriver), which I’ve included below.

To do a Google Translate test in selenium-webdriver:

require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.get "http://translate.google.com/"

wait = Selenium::WebDriver::Wait.new(:timeout => 5)

language_button = wait.until {
  element = driver.find_element(:id => "gt-sl-gms")
  element if element.displayed?
}

language_button.find_element(:tag_name => "div").click

menu = wait.until {
  element = driver.find_element(:id => "gt-sl-gms-menu")
  element if element.displayed?
}

langs = menu.find_elements(:class => "goog-menuitem")

norwegian = langs.find { |lang| lang.text == "Norwegian" }
norwegian.find_element(:tag_name => "div").click

puts language_button.text

driver.find_element(:id => "source").send_keys("ost")

result = wait.until {
  result = driver.find_element(:id => "result_box").text
  result if result.length > 0
}

puts result
driver.quit

the same functionality in watir-webdriver looks like:

require 'watir-webdriver'

browser = Watir::Browser.new :firefox
browser.goto "http://translate.google.com/"

language_button = browser.span(:id => "gt-sl-gms")
language_button.when_present.div.click

menu = browser.div(:id => "gt-sl-gms-menu")
menu.when_present.div(
  :class => /goog-menuitem/,
  :text => "Norwegian"
).div.click

puts language_button.text

browser.text_field(:id => "source").set("ost")

result_box = browser.span(:id => "result_box")
browser.wait_until { result_box.text.length > 0 }

puts result_box.text
browser.close

So whilst these achieve the same thing, I find the selenium-webdriver example is more verbose, and and less readable too.

If I was implementing automated tests using WebDriver in ruby, there’s no doubt that I would choose watir-webdriver over selenium-webdriver.

TrySelenium.org in Watir-WebDriver

Jason Huggins put out a call for some example scripts to tweet some stuff using Selenium.
Whilst I don’t agree with this being done through the Twitter GUI (there’s an API for this), being a Watir core team member and ruby guy, I thought I would use Watir-WebDriver.

My resulting script is:

require 'rubygems'
require 'highline/import'
require 'watir-webdriver'

begin
 username = ask("What is your twitter username?")
 password = ask("and your password?") { |q| q.echo = false }
 browser=  Watir::Browser.start "twitter.com", :firefox
 browser.span(:text => "Sign in").click
 browser.text_field(:id => "username").set username
 browser.text_field(:id => "password").set password
 browser.button(:id => "signin_submit").click
 raise "Could not log into Twitter: either your password is wrong or a CAPTCHA is needed."   if browser.url.include? "sessions"
 1.times do |attempt|
 browser.text_field(:class => "twitter-anywhere-tweet-box-editor").when_present.set "#SeConf is awesome x #{attempt}! http://tryselenium.org @Watir-WebDriver is even more awesome!"
 browser.link(:class => "tweet-button button").when_present.click
 browser.goto "twitter.com"
 end
ensure
 browser.close
end

And the resulting tweet:

Thanks for Jari Bakken for discussing this with me.