Removing local page references from Cucumber steps

I’ve been giving some thought about the maintainability of having local page object model references in Cucumber steps. To explain what I mean, here’s some code of mine from an Etsy page model step:

When /^an item is added to the cart$/ do
  @advanced_search_page = @browser, true
  @search_results_page = @advanced_search_page.search_for 'hat'
  @etsy_item_page = @search_results_page.click_first_result
  @item_title = @etsy_item_page.item_title
  @etsy_cart_page = @etsy_item_page.click_add_to_cart

Here I am using instance variables in Cucumber steps to store references to pages that I am interacting with. As you can see, it’s quite easy to lose track of which page is which.

If I want to use one of these pages in another step, I have to refer to the same variable name. The key to this concept is having pages return pages, so you don’t have to initialize pages in your steps (except for the first visit, which normally happens only once per scenario).

A colleague and good friend of mine has been showing me some alternatives. One alternative to this is to actually dynamically refer to pages only when you need them. This means you don’t need to return a page from a page, as they are always initialized when needed. The above method would look like:

When /^an item is added to the cart$/ do
  visit EtsyAdvancedSearchPage do |page| page.search_for 'hat' end
  on EtsySearchResultsPage do |page| page.click_first_result end
  on EtsyItemPage do |page|
    @item_title = page.item_title

This introduces two new methods at the Cucumber step level that need to be created: on, and visit. The on method simply creates a new page object so you can call methods from it. As each page initialize checks expected title and expected element, it will raise an error automatically if either are incorrect. The visit method is an extension of on, which actually initalizes the page and visits it.

These are defined as a module in our Cucumber env.rb file, and then mixed into the Cucumber World, so that steps automatically have access to these.

module Browser
  BROWSER = ENV['WEB_DRIVER'] ||:firefox

  def visit page_class, &block
    on page_class, true, &block

  def on page_class, visit=false, &block
    page = BROWSER, visit page if block

World Browser


By introducing the on and visit methods, it means that we no longer need to have instance variables for page classes in our Cucumber steps. We also no longer need to worry about state transformations in pages, as these can be done in a modular way in the steps themselves. It means that when an error occurs initializing a page, it is more likely to to occur in the correct place. I find the steps more readible, as you only have to initialize the page once using the on block, and then can refer to page.

I have updated my set of Etsy Watir-WebDriver steps if you’d like to take a look.

What do you think? How will this scale?

Update: I have updated the WatirMelonCucumber project to use this style. It’s slightly different in that it supports two sites, and therefore the on method dynamically switches between these.

Cucumber Steps:

When /^I search for a? ?"([^"]*)"$/ do |term|
  on :home do |page|
    page.search_for term

The on method:

def on page, visit=false, &block
  page_class = Object.const_get "#{@site}#{page.capitalize}Page"
  page = BROWSER, visit page if block

Author: Alister Scott

Alister is an Excellence Wrangler for Automattic.

7 thoughts on “Removing local page references from Cucumber steps”

  1. Hi Alister,
    Wonderful post again. Just tried to quickly execute my scripts using the new visit and on methods after taking a look at your WatirMelon project. Wanted to ask, how should I change the “on” method if I have Page classes split up in different modules. For example I have several modules and the class hierarchy looks like this:
    module MyAccount
    module MyTransactions
    class Transactions

    in the “on” method when I do page = it doesn’t work as I have to specify the full path like I can’t hardcode the path in the method as it will be different path for each class as they lie in different modules. Any suggestions?


  2. This is truly amazing idea!!! I have been playing around with this implementation for atleast a week now and I should say this resolves almost every common issues with UI automated testing.

    I am implementing this in my forthcoming project.

    Kudos to you and Jeff Morgan as well!!!



  3. Two questions about this:

    1) This model requires navigating to your target pages by directly putting the target page URL into the browser address bar. This bypasses the site’s navigation objects (buttons, links, etc.) completely. Wouldn’t you want to incorporate exercising those in your tests?

    2) How is this model possible in large web sites where page URLs are dynamically generated, and you thus don’t necessarily know what the page’s target URL is going to be, ahead of time? I assume the answer to this question is: It isn’t possible.


    1. No, I think you’ve misunderstood.
      The direct url is optional, and only for non-dynamic pages you directly hit (often only a logon-page).
      You would call visit for only the logon page.
      Then you would only ever call on for the other pages, and let the methods of pages do the navigation via clicking links, buttons, forms etc.


  4. Congratulations for this fancy solution. I have implemented this pattern – as you did at EtsyWatirWebDriver – and it helped to have a cleaner and more maintainable code.

    By the way I faced a challenge to handle 2 different windows. There are system’s page and Facebook pop-up page just for login. I tried to use `@browser.window(:title => ‘log in | Facebook’).use, watir was able to fill, login but then the pop-up closes and watir loose the system’s window (page) and ends not able to run the steps anymore.

    Any clue on that?


Comments are closed.