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?

Using Watir-WebDriver with Safari (at last!)

Well, it’s official: Watir-WebDriver now supports Safari thanks to the release of SafariDriver.

The downside is the set up is quite lengthy at the moment as it requires a Safari extension (version 5+) and until someone publishes the extension to the online gallery, you’ll have to build it yourself.

Steps to build the extension

  1. First, create and install a Safari extension certificate at Apple. You’ll need to sign up for a (free) Safari developer account, and download your certificate locally.
  2. Now, you’ll need to build the extension. First, you’ll need to check out the selenium source code to do so:
    svn co http://selenium.googlecode.com/svn/trunk selenium
  3. Then change into this directory and build the extension
    cd selenium
    ./go safari
  4. Finally, install your extension:
    1. Launch Safari
    2. Ensure develop menu is shown by setting it in Advanced Preferences
    3. Open the Extension Builder (Develop > Show Extension Builder)
    4. Add your new extension from: $SELENIUM_CHECKOUT_LOCATION/build/javascript/safari-driver/SafariDriver.safariextension

Using Safari with Watir-WebDriver

It’s exactly the same as any other browser:

require 'watir-webdriver'
b = Watir::Browser.new :safari

Caveat Emptor

  • browser.execute_script is a little flakey
  • Can’t use local html files as extensions don’t load
  • Not sure how to configure browser options programatically like user agent strings

Enjoy!

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.

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.