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.

Watir-WebDriver tests on Firefox 7: getting rid of the send data to Mozilla message

Update 6 October 2011: The send data to Mozilla question will be turned off by default in the next release (2.8.0) of the selenium-webdriver gem which watir-webdriver uses.

I’ve been running Watir-WebDriver tests against Firefox 7, which works superbly. The biggest change is Firefox 7 now supports performance metrics, so this means you can use the watir-webdriver-peformance gem: yay! It also means my EtsyWatirWebDriver project now collects page metrics using Firefox.

The only slight annoyance is the presence of the ‘send data to Mozilla?’ dialog bar. Never fear, it’s easily dismissed.

require 'watir-webdriver'
profile = Selenium::WebDriver::Firefox::Profile.new
profile['toolkit.telemetry.prompted'] = true
b = Watir::Browser.new :firefox, :profile => profile

Enjoy.

Send data to mozilla

Watir-Page-Helper 0.3.0: now with added frames

I’ve just release version 0.3.0 of my watir-page-helper gem, with support for frames.

To use a frame, you define it as you would any other element:

class PageIFrame < BasePageClass
  direct_url TEST_URL
  frame :iframe, :id => "myiframe"
  link(:ilink) { |page|  page.iframe.link(:text => 'Link in an iFrame') }
end

and then you can use the frame, or any elements within that frame:

it "should support elements within a iframe" do
  page = PageIFrame.new @browser, true
  page.iframe.exist?.should be_true
  page.ilink_link.exist?.should be_true
  page.ilink
end

I hope you find this update useful.

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

Determining file MIME types to autosave using Firefox & Watir-WebDriver

Update: Jari Bakken points out you can do this on Mac/Linux with one terminal command:

curl -I http://dl.dropbox.com/u/18859962/temp.csv | grep Content-Type

Introduction

When using Firefox with Watir WebDriver, there’s a nifty trick to set certain file types to automatically download so it’s not necessary to deal with troublesome ‘save as’ dialog boxes.

The problem with it is that you need to explicitly tell Firefox which file types you want to automatically save. Whilst this seems like an easy idea in concept, in practice it’s a bit harder as the web site serving the file can affect the MIME type which Firefox uses to determine what to do.

I recently found a way to determine what MIME type Firefox uses for a particular file so that this can be automatically captured. Here’s how:

1) Navigate to the site in Firefox and click on the link to the file (here’s an example CSV file). This should display a dialog such as the one below, where you’ll want to click ‘Save File’ and ‘Do this automatically for files like this from now on’

2) What this does is create an entry in the Applications tab of the settings to always download the file. Unfortunately, this doesn’t show us the MIME type which is what we need to specify this in our Firefox WebDriver profile.

3) What we want to do is open a file in our Firefox profile called mimeTypes.rdf. To find your Firefox profile on your disk, first open the page ‘about:support’ in Firefox, and click the ‘Show in Finder’ or ‘Open Containing Folder’ button.

4) Then navigate into your profile directory and open mimeTypes.rdf in your favorite text editor. We simply need to find the MIME type we just added, which is my case is found by searching for ‘excel’. The following text is included:

 <RDF:Description RDF:about="urn:mimetype:text/csv"
                   NC:fileExtensions="csv"
                   NC:description="Microsoft Excel.app Document"
                   NC:value="text/csv"
                   NC:editable="true">
    <NC:handlerProp RDF:resource="urn:mimetype:handler:text/csv"/>
  </RDF:Description>

The thing we’re looking for is NC:value=”text/csv“. This is our MIME type.

5) Now we simply add this MIME type to our file types we want to automatically save. This is done when creating the Firefox Watir-WebDriver instance:

profile = Selenium::WebDriver::Firefox::Profile.new
profile['browser.download.folderList'] = 2 #custom location
profile['browser.download.dir'] = "#{Dir.pwd}/downloads"
profile['browser.helperApps.neverAsk.saveToDisk'] = "text/csv"

Note: you can add multiple MIME types to this value in the same string separated by commas.

Summary

While it’s a fairly long winded process, this takes the guess work out of ensuring you automatically download files of certain types.

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 your watir-webdriver tests in the cloud, for free!

  • What if you could run unlimited Watir WebDriver tests in the cloud? Check.
  • What if the Watir WebDriver tests would run automatically as soon as you pushed a change to github? Check.
  • What if you would have a full visual history of results with embedded screenshots on failure? Check.
  • What if all of this was free?* Checkmate.

I’ve spent a bit of time over the last week working out how to do this. Here are the basics of what you need to do:

And here’s the detailed instructions.

Set up an Amazon EC2 micro instance running Ubuntu.

  1. First you need to sign up for an Amazon AWS account. This means you’re eligible for a free-tier micro instance for a year.
  2. Once you have an account set up, you need to launch a new instance. I found a free tier eligible Ubuntu image (11.04 Natty 64 bit desktop) and launched that.
  3. You will also want to create an elastic IP and associate it to your instance so that if you reboot your machine, you will have the same IP address. This is done through the AWS console under Elastic IPs.
  4. While you’re here, you’ll want to edit your machine’s security group and open up port 22 for SSH, and 80 for HTTP.
  5. This gives you secure shell (SSH) access to this machine using the provided key, and user ‘ubuntu’:
    ssh -i your-key-name.pem ubuntu@your-ip-address
  6. Everything you will do to configure this machine will be through this SSH session, so polish up your unix command line skills!

Set up Jenkins on your machine

There is a useful page for installing Jenkins on Ubuntu.

wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo aptitude update
sudo aptitude install jenkins

Make Jenkins available on port 80 so that you don’t need to specify port

Jenkins installs by default on port 80. Ubuntu won’t let applications run on port 80 unless they’re running as root, so it’s best to set up an Apache 2 proxy to port 80 to 8080.

sudo aptitude install apache2
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod vhost_alias
sudo a2dissite default

Then create a file called jenkins in /etc/apache2/sites-available


	ServerAdmin webmaster@localhost
	ServerName ci.company.com
	ServerAlias ci
	ProxyRequests Off
	
		Order deny,allow
		Allow from all
	
	ProxyPreserveHost on
	ProxyPass / http://localhost:8080/

Then run the following commands:

sudo a2ensite jenkins
sudo apache2ctl restart

Password Protect Jenkins

You should go to your Jenkins site (accessible directly at your instance’s IP address through a web browser), and create an account, and then configure the security of Jenkins.

Install Jenkins Plugins

You will need to install the following Jenkins plugins

  • Github: to integrate to Github SCM
  • Rake: to run ruby rake tasks that run Watir-WebDriver tests
  • Green balls: because blue balls are just plain wrong

Install RVM for the Jenkins user

First we’ll need to install git

sudo apt-get install git

Jenkins will need to be able to run ruby, so we’ll install RVM as the Jenkins user.

To run as the jenkins user, we’ll use the sudo command, with the -Hiu arguments to load the home directory and bash profile:

sudo -Hiu jenkins

Once we are user Jenkins, we’ll install RVM using Git.

bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)

Now we need to work out what Ubuntu packages Ruby needs, which is easily done via RVM.

rvm notes

which gives me something like

For Ruby (MRI, Rubinius, & REE)  you should install the following OS dependencies:
/usr/bin/apt-get install build-essential bison openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake

So, we can log-out as the Jenkins user (control-D) and install the following as ubuntu

sudo apt-get install build-essential bison openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake

Once we’ve done this, we’ll want to run the following as the Jenkins user (
sudo -Hiu jenkins) to install Ruby 1.9.2.

rvm pkg install zlib
rvm install 1.9.2 --with-zlib-dir=$rvm_path/usr

Running headless Watir-WebDriver tests

I choose a desktop version of Ubuntu, so it’ll already have Firefox installed, but if you don’t, you can install it by:

sudo apt-get install firefox

To run our Watir WebDriver tests headlessly using the headless gem, we’ll need xvfb

sudo apt-get install xvfb

Configuring Jenkins to run tests via Rake

You add a new build in Jenkins where you can specify the github repository location.

As we’ve installed the rake plugin, we can configure a new Jenkins project to use an RVM ruby install (in my case ruby-1.9.2-p290@watirmelon-cucumber).

I simply set up a default task in rake, which runs all my cucumber tests. This generates a results.html file which is captured as an artifact, and also creates and captures junit xml results, which are used to show test summary information.I also capture any file created under the ‘screenshots’ directory.

Summary and Outcome

I have set up both my WatirMelonCucumber and EtsyWatirWebDriver projects on jenkins.watirmelon.com.

My Jenkins Dashboard looks something like this:

Please feel to leave a comment below and let me know what you think.

* Free for one year using an free tier EC2 micro instance

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!