My simple Cucumber + Watir page object pattern framework

Introduction

I was very impressed with Jeff Morgan, known as Cheezy, who recently wrote a series of blog posts about how to use Cucumber and Watir, and shared his code on Github.

I love it when people share their ideas this like, so I thought I would share what I have found useful when setting up a very simple Cucumber with Watir (Celerity & Watir-WebDriver) page object pattern framework, and how this compares to what Jeff has proposed.

Show me the code!

Before we begin, I’ll show you my code. It’s all on Github, right now, as we speak, and you can easily fork and clone this repository to play around with it. It uses a simple google search example.

Project Folder Structure

Google Search Feature

Feature: Google Search
  As a casual internet user
  I want to find some information about watir, and do a quick conversion
  So that I can be knowledgeable being

Scenario: Search for Watir
  Given I am on the Google Home Page
  When I search for "Watir"
  Then I should see at least 100,000 results

Scenario: Do a unit conversion
  Given I am on the Google Home Page
  When I convert 10 cm to inches
  Then I should see the conversion result 
            "10 centimeters = 3.93700787 inches"

Scenario: Do a search using data specified externally
  Given I am on the Google Home Page
  When I search for a ridiculously small number of results
  Then I should see at most 5 results

Page Object Pattern

For our example, we have two page objects. The page classes delegate any methods that don’t exist on the page to the Browser object that is passed in from Cucumber. This ensures you can call browser specific methods (.title, .url etc.) at the page object level without duplicating methods.

class GoogleHomePage

  attr_accessor :search_field, :google_search_button

  URLS = { :production => "http://www.google.com/" }

  def initialize(browser)
    @browser = browser
    @search_field         = @browser.text_field(:name => "q")
    @google_search_button = @browser.button(:name => "btnG")
  end

  def method_missing(sym, *args, &block)
    @browser.send sym, *args, &block
  end

  def visit
    @browser.goto URLS[:production]
  end

  def page_title
    @browser.title
  end

  def search_for term
    self.search_field.set term
    self.google_search_button.click
    google_results_page = GoogleResultsPage.new(browser)
    google_results_page.results.wait_until_present if WEBDRIVER
    google_results_page
  end

end

Cucumber Initialization

I have everything to do with initialization in support/env.rb. This initializes a browser once, and then uses the same browser instance for each Cucumber scenario. This is different from Jeff’s hooks.rb file that launches a new browser for every scenario. I have found by reusing the browser, you get much quicker test execution time.

The INDEX_OFFSET is a constant that allows me to use ‘index’ when referring to browser elements across both celerity with its 1-based index and watir-webdriver with its 0-based index.

TEST_DATA_DIR = "./features/test_data"

if ENV["HEADLESS"] then
  require "celerity"
  browser = Celerity::Browser.new
  INDEX_OFFSET = 0
  WEBDRIVER = false
else
  require 'watir-webdriver'
  require 'watir-webdriver/extensions/wait'
  browser = Watir::Browser.new :firefox
  INDEX_OFFSET = -1
  WEBDRIVER = true
end

Before do
  @browser = browser
end

at_exit do
  browser.close
end

Calling the page objects from the steps

As you can see, most of my steps are in a declarative style, so these normally align pretty well to a method on the page object.
I use a given step to create a page object (in our case @google_home_page), and then call methods on this object for when and then steps. I have created a dynamic invocation of the page creation, so it will work for all pages specified in the cucumber step itself. I use the search_for_term method to transition between page objects, as this method returns the new page. As a rule of thumb, I try to keep my step definition code to 1 or 2 lines, and at a fairly high level, which makes it much more readable.

Given /^I am on the (.+)$/ do |page_name|
  @google_home_page = Object.const_get(page_name.gsub(" ","")).new(@browser)
  @google_home_page.visit
end

When /^I search for a? ?"([^"]*)"$/ do |term|
  @google_results_page = @google_home_page.search_for term
end

When /^I search for a?n? ?([^"].+[^"])$/ do |term|
  term = Common.get_search_term_data term
  @google_results_page = @google_home_page.search_for term
end

Then /^I should see at least ([\d,]+) results$/ do |exp_num_results|
  @google_results_page.number_search_results.should >= exp_num_results.gsub(",","").to_i
end

Then /^I should see at most ([\d,]+) results$/ do |exp_num_results|
  @google_results_page.number_search_results.should <= exp_num_results.gsub(",","").to_i
end

When /^I convert (.+)$/ do |conversion_statement|
  @google_results_page = @google_home_page.search_for "convert #{conversion_statement}"
end

Then /^I should see the conversion result "([^"]*)"$/ do |exp_conversion_result|
  @google_results_page.conversion_result.text.should == exp_conversion_result
end

Expressing test data external to the features/pages

Unlike Jeff’s recommendation of using default data on page objects, I prefer to store test data externally to the page objects in YAML files. That way I can describe the test data which is directly used in my Cucumber step. For example:

Scenario: Do a search using data specified externally
  Given I am on the Google Home Page
  When I search for a ridiculously small number of results
  Then I should see at most 5 results

The phrase ridiculously small number of results matches directly to a section of my YAML test data file. This keeps the scenario highly readable, with the detailed data specified in the YAML file.

search terms:
  ridiculously small number of results:
    search term: 'macrocryoglobulinemia marvel'

A common method simply retrieves the appropriate test data, and provides it to the step to use. Note here that, as this step doesn’t contain quotation marks, this implies a description of data, rather than a literal string of data that would contain quotation marks around the string.

When /^I search for a?n? ?([^"].+[^"])$/ do |term|
  term = Common.get_search_term_data term
  @google_results_page = @google_home_page.search_for term
end

Putting it all together
I have set up two profiles in the cucumber.yml file, one is the default that uses Firefox under Watir-Webdriver, and one runs Celerity (headless) and is named ci. It’s then a case of calling "cucumber" or "cucumber -p ci" to run each of these profiles.

I have set up a ./go script that bootstraps the entire process on unix based systems (Mac OSX and Linux).

The final result

Running cucumber provides results as green as a cucumber.

How does this compare to Cheezy’s framework I mentioned?

  • I am using a similar page object pattern to define web application pages, but I haven’t created a helper class to mix into each like he has: I simply use instance variables on a page class to define the GUI elements, instead of creating a series of methods for each object.
  • My page objects also delegate methods to the main Browser object as required, so you can directly call things like .title, .url etc.
  • Instead of using Jeff’s default data pattern, I instead prefer to store this data described in external YAML files instead for maximum flexibility.
  • Jeff talks about using mixins to mix in common objects. I haven’t done this, but it’s easily doable with my framework, and should be done as it scales out.

Summary

Jeff has done an excellent job with his series of posts and code (thank you), and I am hoping here to build on that, and show how it’s easy to set up a simple Cucumber & Watir page object pattern framework.

19 thoughts on “My simple Cucumber + Watir page object pattern framework

  1. Since your using ruby, you can try out my page object factory project. It will work for both selenium and watir. Nokogiri is really the underlying tool for the generator. It builds and tests page objects for you. Really excellent work on the framework. We are doing something very similar here at HomeAway. https://github.com/scottcsims/SeleniumFury

  2. The thing I like about this pattern is you could run the feature against a completely different search engine by just creating a new page object impl.

    This prevents the common “step explosion” problem all too common with cucumber suites.

  3. This might be a good approach when you have one or few pages. When you start adding more pages there’ll be a lot of boiler plate code. You probably need a Page class for new pages to inherit from, in which case defining the page elements in the initalize block is probably not a good idea. I like the way the Watircrat framework implements the page object pattern.

    • Thanks.
      I’m not really a fan of Gizmo, as you end up with at least three lines of code for each Cucumber step definition using the page mixin approach.
      But I will investigate it further as it seems to be quite popular.

  4. This is cool — but the stuff I got off of GitHub looks only a little bit like what is shown in the contents of the blog post. File contents are different, directory structure is a tiny bit different. I assume this is because of improvements — and I haven’t read further in the blog yet. But it might be good to indicate this. Some people, at least like me, will tend to stick with one blog post to make sure they fully understand it before going on to more recent stuff.

    • Yes, the latest on Github includes all improvements from subsequent posts.
      I should have branched the code for each post, but I think it’s a bit late to retrofit it.
      I aimed to have enough concept code in each post that you should be able to implement it yourself for your own app.
      Cheers.

  5. Interestingly, I ran the sample project (latest from GitHub) and got a test failure:

    expected: “10 centimetres = 3.93700787 inches”
    got: “10 centimeters = 3.93700787 inches”

    At first I was confused, until I realized that you’re running your browser in Australia and I’m in the US!

  6. Pingback: Quality Shepherd » Blog Archive » Javascript Page Object Pattern for Sahi - software testing

  7. I’ve really enjoyed reading your blog. Is there some reason why you use separate variable names for each page (@google_home_page, @google_results_page) instead of something generic like @current_page?

    I’d assume this would couple the steps to specific pages. If there’s another page where you can do a search (say, Image Search), you cannot reuse the step ‘When I search for “Watir”‘, because it assumes you’re on the home page.

    Using something generic would allow this to be abstracted to the page object. The following would work on any page where the page object implements the search_for method:

    When /^I search for a? ?”([^"]*)”$/ do |term|
    @current_page = @current_page.search_for term
    end

    Are there some downsides to this methodology that I’m missing?

  8. I really enjoy your blog! Is there some reason why you use separate variable names for each page (@google_home_page, @google_results_page) instead of something generic like @current_page?

    I’d assume this would couple the steps to specific pages. If there’s another page where you can do a search (say, Image Search), you cannot reuse the step ‘When I search for “Watir”‘, because it assumes you’re on the home page.

    Using something generic would allow this to be abstracted to the page object. The following would work on any page where the page object implements the search_for method:

    When /^I search for a? ?”([^"]*)”$/ do |term|
    @current_page = @current_page.search_for term
    end

    Are there some downsides to this methodology that I’m missing?

  9. HI I have done setup with Netbeans and I want to create a project with sample google search app using your page object design patterns. Here I have one question. My question is whether I required Selenium Server or Glashfish Server?

  10. Pingback: Testing visual appearance with Cucumber + Watir | Code for Hire

Comments are closed.