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.


Nice – downloaded the source and will try it out. Thanks for sharing
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
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.
Thanks for your feedback Ben.
I wrote a new post on this idea: http://watirmelon.com/2011/01/23/reducing-step-explosion-by-decoupling-cucumber-steps-from-your-gui/
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 will check it out.
Really enjoying reading your writing, Alister. You might be interested in Gizmo, which provides a way of creating page models for your Cucumber features. I’ve just started using it, and it’s working well.
https://github.com/icaruswings/gizmo
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.
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.
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!
Pingback: Quality Shepherd » Blog Archive » Javascript Page Object Pattern for Sahi - software testing