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 = EtsyAdvancedSearchPage.new @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
end

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
    page.click_add_to_cart
  end
end

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 = Watir::Browser.new ENV['WEB_DRIVER'] ||:firefox

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

  def on page_class, visit=false, &block
    page = page_class.new BROWSER, visit
    block.call page if block
    page
  end
end

World Browser

Summary

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
  end
end

The on method:

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

An automated testing journey

I did a presentation this evening in Brisbane on an automated testing journey that one may embark on. The whole thing was centered around this tube map style diagram I came up with recently: (download in PDF)

Here’s a link to my prezi slides and it should appear below (if you have flash enabled that is).  You can also download them in very printable PDF if you so choose.

I feel the presentation was well received, but I really shouldn’t have tried to squeeze three days of thinking into 30 mins. Oh well.

As always, I welcome your feedback.

Introducing the Watir Page Helper gem

I’ve very recently released a watir-page-helper gem that provides some useful helper methods when creating page models when using Watir-WebDriver (I chose not to support Watir as it doesn’t work on Firefox 4, therefore not on non-Windows machines).

This is loosely based upon the great work that Jeff Morgan did in his series of UI testing blog posts, but takes the concept a lot further IMO.

I wanted it to be solid before I released it as a gem, so I wrote unit tests for every method, and wrote yard tagged documentation for each method also. You can see the documentation automatically generated on rdoc.info.

Installation

gem install watir-page-helper

Example

require 'watir-webdriver'
require 'watir-page-helper'

class MyPage
  include WatirPageHelper

  direct_url "http://www.google.com"
  expected_element :text_field, :name => "q"
  expected_title "Google"
  text_field :search_box, :name => "q"
  button :search, :name => "btnG"

  def initialize browser, visit = false
    @browser = browser
    goto if visit

    expected_element if respond_to? :expected_element
    has_expected_title? if respond_to? :has_expected_title?
  end
end

browser = Watir::Browser.new :chrome
page = MyPage.new browser, true
page.search_box = "Watirmelon" #This method is created by WatirPageHelper
page.search #This method is created by WatirPageHelper also
browser.close

The above example is very basic, but shows the concept well. You’re much better off putting the initialize method into a base page class so you don’t need to call the same methods for every page.

What you get

Page Methods

  • direct_url: allows you to navigate to a page upon initialization, if visit is set to true
  • expected_title: allows you to automatically assert the expected title of the page when it is initialized
  • expected_element: allows you to initialize the page by looking for a certain element. This is useful for pages that load dynamic content.

Element Methods

Element methods for a majority of the Watir-WebDriver supported elements which generate useful helper methods.
For example: text_field, select_list, radio_button, form, div, span, h1..h6 etc.

Summary

I’ve been using a watir page helper on a few different projects now and I’m sick of copy-and-pasting the code each time, so I thought it’d be nice to package it as a gem.

I also think it’s important to have unit tests for this kind of stuff, so it was a good opportunity to write unit tests for every method in these classes. The documentation was an added bonus too.

I hope you find this useful, please let me know if anything doesn’t make any sense.

Links/Further Info

Watir-WebDriver & Cucumber Examples for Etsy.com

I’ve had a go at implementing my Etsy.com gherkin scenarios using Watir-WebDriver in Ruby rather than C#. I like the flexibility of dynamic languages like Ruby, but I tend to make more silly mistakes that are picked up straight away when I am writing static typed code like C#, but I guess that’s the downside to dynamic languages, less error checking built into IDEs due to the flexibility the language offers.

You can check out and download my ruby code on my Github project page.

SpecDriver now includes Etsy.com examples

A fellow ThoughtWorker, Paul Hammant, recently announced his intent for QuickTest Professional. And his method? Provide some new technology tutorials for newbies, in the form of a series of open source automated tests for craft site Etsy.com.

He asked me to assist by providing some SpecDriver examples, and since I enjoy doing this stuff I am more than happy to help out.

So, I’ve updated the SpecDriver repository on Github to include some Etsy.com feature files, and working tests. It was a lot of fun to write these tests, because Etsy.com is awesomely testable. Its nicely formatted code with proper ids and class names makes automating against it a real treat.

The benefit of doing this exercise is that it’s meaty enough for me to get some insight into how other people code real automated tests. For example, I’ve had a look into a couple of the code samples and I see a lot of XPath selectors. I personally really dislike XPath stuff, just like I dislike XML as I find it hard to read, and less intuitive than using straight identifiers. I am proud that there isn’t a single XPath used in my Etsy.com examples.

Another thing that stood out to me was how embedded some of the stories are. For example, in the JBehave example the features are located under //etsy-stories/src/main/resources/stories, which took me some time to locate at first, and every time I forget where they are. I much prefer having a features folder in root that makes it pretty obvious what’s in there.

One thing I am very interested in is seeing how people specify tests. I found two of Paul’s scenarios repetitious so I moved them into a single scenario outline. Moving them into a scenario outline made me think about some different ways to test, so I added a new scenario, which didn’t require much more code, but strengthened my feature.

For example, Paul’s two scenarios:


Scenario: Advanced Search for a hat
  Given I am searching on Etsy.com
  When I specify the Knitting sub category
  And I search for hat
  Then there are search results

Scenario: Advanced Search for a ring
  Given I am searching on Etsy.com
  When I specify the Jewelry sub category
  And I search for ring
  Then there are search results

became my:

Scenario Outline: Advanced Search for items in various categories that exist and don't exist
  Given I am searching on Etsy.com
    When I specify the <Sub Category> sub category
    And I search for <Search Term>
    Then I should see <Search Results> search results for <Search Term> 

Examples:
  | Sub Category | Search Term  | Search Results |
  | Knitting     | 'hat'        | some           |
  | Jewelry      | 'necklace'   | some           |
  | Jewelry      | 'specdriver' | no             | 

While I was at it, I noticed there was a spelling correction feature of the advanced search. I also thought it was a good idea to test that this was working correctly.


Scenario: Misspelling a word corrects search automatically
  Given I am searching on Etsy.com
  When I specify the Knitting sub category
  And I search for 'scalf'
  Then I should see some search results for 'scarf'
  And I should see that the search was for 'scarf' instead of 'scalf'

Now I’ve done this exercise using SpecFlow and WebDriver in C#, I’m keen to do it again using Cucumber & Watir (WebDriver) in Ruby, to compare the implementation. When I am done I will post it on here for you all to see. Enjoy.

TrySelenium.org in Watir-WebDriver

Jason Huggins put out a call for some example scripts to tweet some stuff using Selenium.
Whilst I don’t agree with this being done through the Twitter GUI (there’s an API for this), being a Watir core team member and ruby guy, I thought I would use Watir-WebDriver.

My resulting script is:

require 'rubygems'
require 'highline/import'
require 'watir-webdriver'

begin
 username = ask("What is your twitter username?")
 password = ask("and your password?") { |q| q.echo = false }
 browser=  Watir::Browser.start "twitter.com", :firefox
 browser.span(:text => "Sign in").click
 browser.text_field(:id => "username").set username
 browser.text_field(:id => "password").set password
 browser.button(:id => "signin_submit").click
 raise "Could not log into Twitter: either your password is wrong or a CAPTCHA is needed."   if browser.url.include? "sessions"
 1.times do |attempt|
 browser.text_field(:class => "twitter-anywhere-tweet-box-editor").when_present.set "#SeConf is awesome x #{attempt}! http://tryselenium.org @Watir-WebDriver is even more awesome!"
 browser.link(:class => "tweet-button button").when_present.click
 browser.goto "twitter.com"
 end
ensure
 browser.close
end

And the resulting tweet:

Thanks for Jari Bakken for discussing this with me.

SpecDriver: A simple, open-source, page object model framework for C# automated web testing

There doesn’t seem to be a lot of material available in the C# .NET automated testing space, so I thought I would create and share my own page object model centered framework: SpecDriver.

It uses SpecFlow to define features, scenarios and steps, and then WebDriver to actually drive the browser to automate these steps, with a page object model in between to ensure maximum maintainability of the solution.

I have previously documented the steps to getting SpecFlow up and running with Visual Studio C# Express which is free to use for both commercial and non-commercial reasons. You should follow these if you would like to play around with SpecDriver.

You can access all the source code on the github repository, and please feel free to fork/improve it as you see fit.

I will explain the various elements here and how they fit together.

Feature Files for Test Specifications

SpecFlow uses .feature files exactly the same as Cucumber, so it’s pretty easy to create these. I actually used my feature files from my Cucumber framework for this example; the benefits of writing these in a non-technical style!

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 centimetres = 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 100 results

Step Definitions that call page objects

The step definitions are small, granular methods that call methods on page objects and do assertions against expected results.

namespace Project1.StepDefinitions
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using TechTalk.SpecFlow;
    using NUnit.Framework;
    using Project1.Pages;

    [Binding]
    public class GoogleSearchStepDefinitions : BaseStepDefinitions
    {

        [Given(@"I am on the Google Home Page")]
        public void GivenIAmOnTheGoogleHomePage()
        {
            GoogleHomePage = new GoogleHomePageModel(Driver);
            Assert.AreEqual("Google", GoogleHomePage.Title);
        }

        [When(@"I search for ""(.+)""")]
        public void WhenISearchForSomething(string searchTerm)
        {
            GoogleSearchResultsPage = GoogleHomePage.Search(searchTerm);
        }

        [When(@"I search for a ridiculously small number of results")]
        public void WhenISearchForARidiculouslySmallNumberOfResults()
        {
            GoogleSearchResultsPage = GoogleHomePage.Search("macrocryoglobulinemia marvel");
        }

        [When(@"I convert (.+)")]
        public void WhenIConvertSomething(string ConversionString)
        {
            GoogleSearchResultsPage = GoogleHomePage.Search("convert " + ConversionString);
        }

        [Then(@"I should see at most ([\d,]+) results")]
        public void ThenIShouldSeeAtMostNumberOfResults(string expMaxNumberResults)
        {
            expMaxNumberResults = expMaxNumberResults.Replace(",", "");
            Assert.LessOrEqual(Convert.ToInt32(GoogleSearchResultsPage.NumberOfResults), Convert.ToInt32(expMaxNumberResults));
        }

        [Then(@"I should see at least ([\d,]+) results")]
        public void ThenIShouldSeeAtLeastNumberOfResults(string expMinNumberResults)
        {
            expMinNumberResults = expMinNumberResults.Replace(",", "");
            Assert.GreaterOrEqual(Convert.ToInt32(GoogleSearchResultsPage.NumberOfResults), Convert.ToInt32(expMinNumberResults));
        }

        [Then(@"I should see the conversion result ""(.+)""")]
        public void ThenIShouldSeeTheConversionResult(string expectedConversionResult)
        {
            Assert.AreEqual(expectedConversionResult, GoogleSearchResultsPage.ConversionResult);
        }
    }
}

Page Object Model

Each page in the application under test is represented by a page class (that inherits from a base page class), and this page class has elements and methods associated with it. The pages are the things that actually use WebDriver to interact with browsers. You can see, like my ruby page object pattern, methods that change pages return an instance of that new page. The other thing to note is that there is a base page class that has a constructor, that requires an known element to instantiate the page. This is a way of knowing where you are in your application and constantly checking it is in the right place. This also ensures consistent syncronization, especially when pages contain dynamic content such as AJAX calls.

namespace Project1.Pages
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using OpenQA.Selenium;

    public class GoogleHomePageModel : BasePageModel
    {
        private static readonly By SearchEditSelector = By.Name("q");
        private static readonly By SearchButtonSelector = By.Name("btnG");

        public GoogleHomePageModel(IWebDriver driver)
            : base(driver, SearchEditSelector)
        {
        }

        private IWebElement SearchEditElement
        {
             get { return Driver.FindElement(SearchEditSelector); }
        }

        private IWebElement SearchButtonElement
        {
            get { return Driver.FindElement(SearchButtonSelector); }
        }

        public GoogleSearchResultsPageModel Search(string term)
        {
            this.SearchEditElement.Set(term);
            this.SearchEditElement.SendKeys(Keys.Escape);
            this.SearchButtonElement.Click();
            return new GoogleSearchResultsPageModel(Driver);
        }
    }
}

Putting it all together

I have two batch files that I use in Visual Studio 2010 C# Express, one to generate the SpecFlow NUnit tests, the other to execute them and provide a visual report. I add these as external tools in VS2010 and run them from the menu. Neat.

C# ATDD on a shoestring (or the complete guide to SpecFlow & NUnit in Visual Studio 2010 Express)

Introduction

When I’m working on something new at work, I like to be able to work on it in my own time at nights and on the weekend to polish my skills through experimentation, and share what I learn. I’ve recently been working on setting up Acceptance Test Driven Development in C# at work, but unfortunately Microsoft makes it really, really hard to polish your skills in C# development at home, unless you’re willing to part with large sums of cash (say from $20,775, and I’d prefer a new car). This is probably one of the reasons why I find the Microsoft .NET testing community (and the dev community too) quite insular, Microsoft just doesn’t encourage their users to share their craft.

Sure, Microsoft provides Visual Studio 2010 (VS2010) Express Edition – which can be used free of charge, even for commercial purposes, but the limitations on its functionality are borderline ridiculous. For example, it doesn’t support Microsoft’s own unit testing framework (Microsoft obviously don’t encourage TDD), and all add-ins are banned (no ReSharper for you). It also doesn’t work with Microsoft’s own version control system (TFS).

So, Microsoft it basically makes it really hard to do anything you want to do, but there are ways to get stuff done, even if they’re convoluted.

ATDD Frameworks in C#

I know of three main open source ATDD/BDD frameworks that work in C#: SpecFlow, Cuke4Nuke and StoryQ. Only two of these use Gherkin (StoryQ uses its own slightly modified DSL), and I like Gherkin, so I’ll ignore StoryQ for now. Cuke4Nuke still requires some Ruby glue, and to keep things pure, I like SpecFlow as it’s a pure .NET implementation of ATDD with full Gherkin syntax support. For this reason, SpecFlow seems to be the most prevalent ATDD framework in the .NET community and this is what I will use for this example.

SpecFlow as an Acceptance Test Driven Development Framework

SpecFlow is an open source framework that is delivered as a Windows installer, and this installs support for feature and step definition files in Visual Studio through an Add-In that automatically generates unit tests for you from the feature files. Unfortunately, as VS2010 Express doesn’t allow add-ins, SpecFlow won’t work without some tinkering.

Install SpecFlow & NUnit

Before we get started, we need to install VS2010 Express, SpecFlow and NUnit.

  1. Install VS2010 C# Express if you don’t already have it
  2. Install the latest SpecFlow version from the SpecFlow GitHub downloads page.
  3. Install the latest NUnit version from the NUnit download page.
  4. Add the Specflow directory (C:\Program Files\TechTalk\SpecFlow or similar) and the NUnit directory (C:\Program Files\NUnit 2.5.9\bin\net-2.0 or similar) to your Windows path.

Adding SpecFlow templates to Visual Studio 2010 Express

When installing SpecFlow to VS2010 (non-express) it creates templates that can be accessed through the “New Item” menu. Fortunately, it is fairly trivial to add these to VS2010 Express. Just download the templates I have uploaded here, unzip this directory and place the .zip files (SpecFlowFeature.zip, SpecFlowStepDefinition.zip etc.) into your Visual Studio 2010 Express C# templates directory (usually: C:\Users\username\Documents\Visual Studio 2010\Templates\ItemTemplates\Visual C# or similar).

This means you now have access to the SpecFlow templates when adding a new item to your C# project.



Generating SpecFlow NUnit tests from Feature Files

When you add a SpecFlow feature to the full version of VS2010, it automatically generates a unit test file behind the feature file, so that it’s always up to date. This doesn’t work in Express edition, so you have to use the specflow.exe file to do this for you. Fortunately, this can be done in a batch file, and then added as an “external tool” to VS2010 Express, so you get the same functionality, but not quite as neat.

  1. Enable Expert Settings in VS2010 Express (if not already enabled), by choosing Tools->Settings->Expert Settings.
  2. Create a batch file in your project directory called something like “generatespecflowtests.bat” with the following content:

echo Starting to generate SpecFlow unit tests
SpecFlow generateAll %1 /force /verbose
echo Finished generating SpecFlow unit tests
  1. Add an external tool command in VS2010 Express, using Tools->External Tools->Add. Make sure you set the starting directory, and “show output”

  1. You can then generate your tests from the Tools menu or, if you like, you can add this command to a new Toolbar by using Tools -> Customize -> Toolbars -> New, then Tools -> Customize -> Commands -> Toolbar -> Add Command.

Running SpecFlow NUnit tests from Feature Files

Simon Whittemore from London wrote an excellent post about how to automatically run Specflow NUnit tests, and capture a pretty html result, on his blog. I’ve included his batch file here, as it’s what we’ll use to run our tests from within VS2010 Express.

  1. Create a batch file in your project directory named runspecflowtests.bat or similar.
  2. Include Simon’s content (below) in your batch file.
 
@echo off
nunit-console %1
specflow.exe nunitexecutionreport %2 /xmlTestResult:%3
if NOT %errorlevel% == 0 (
echo "Error generating report - %errorlevel%"
GOTO :exit
)
if %errorlevel% ==0 TestResult.html
:exit
  1. Create an external tool setting the same as we did for the generate unit tests, but with the parameters defined on this dialog:
    1. Command: Link to your batch file
    2. Arguments: $(TargetName)$(TargetExt)$(ProjectDir)$(ProjectFileName)$(BinDir)TestResult.xml
    3. Initial Directory: $(BinDir)
    4. Use Output Window: checked

  1. Optionally add this to your VS2010 Express Toolbar as above

When you run this command in VS2010, all SpecFlow features are run, and the resulting report is launched in your default web browser.

Summary

What I have shown is that although Microsoft makes it very hard, it is possible to use VS2010 Express and open source tools like SpecFlow so that you can polish your skills in your own time.

In this theme of ATDD using C# and .NET, over the coming days I will demonstrate how you can use SpecFlow to write and run automated web tests against a web browser using the open source tool WebDriver. Stay tuned!

Update: 20 Feb 2011

The original link to the templates was wrong, I have now updated it to the correct link.

Your automated acceptance tests needn’t be written in the same language as your system being tested

When selecting which tool to implement for business facing automated acceptance tests, I’ve often heard that it must use the same programming language as the system under test. Whilst this can sometimes work well, often it is better to choose a tool in another language that will ulimately deliver you better success as it can be adopted more passionately. Here are some of the reasons on why I think so.

The simplicity of some other languages mean it’s easier for less technical testers to understand them

Let me show you an example that compares the same test written in C# as in Ruby.

C# WebDriver Example – Inspired by David Burns

using OpenQA.Selenium;
using OpenQA.Selenium.IE;
 
using NUnit.Framework;
 
namespace Selenium.Two.DotNetExample
{
    [TestFixture]
    public class Test_Google
    {
	    IWebDriver driver;
	     
	    [SetUp]
	    public void Setup()
	    {
		    driver = new InternetExplorerDriver();
	    }
	    
	    [TearDown]
	    public void Teardown()
	    {
		    driver.Quit();
	    }
    		
        [Test]
        public void TestSearchGoogleForTheAutomatedTester()
	    {
	        //Navigate to the site
            driver.Navigate().GoToUrl("http://www.google.com");
            //Find the Element and create an object so we can use it
            IWebElement queryBox = driver.FindElement(By.Name("q"));
            //Work with the Element that's on the page
            queryBox.SendKeys("Watir");
			queryBox.SendKeys(Keys.ArrowDown);
            queryBox.Submit();
            //Check that the Title is what we are expecting
            Assert.True(driver.Title.IndexOf("Watir - Google Search") > -1);
        }
    }
}

Ruby Watir Example – written by me

require 'watir-webdriver' #watir, celerity, firewatir
require "test/unit"
class GoogleTest < Test::Unit::TestCase
  def test_google
    b =Watir::Browser.start "www.google.com"
    b.text_field(:name => "q").set "watir"
    b.button(:name, "btnG").click
    assert_equal("Watir - Google Search", b.title)
    b.close
  end
end

I am slightly biased, but I think the ruby example is neater and easier to read, especially to someone with a less technical background. Sure, a C# developer might baulk at this premise, but for most people who don’t know C#, I imagine it’s the case.

Often times developers like learning another programming language to add to their experience
When I’ve been working on automated tests in ruby, I’ve often seen a strong interest in developers wanting to learn ruby and the automated testing framework, as it’s common for developers to specialize in a language (Java or .NET) and then focus solely on that. Don’t get me wrong, this isn’t always the case, but it happens more than you think.

Testers can embrace simpler programming languages such as ruby, and can become the “experts” in that domain

I’ve seen testers pick up a programming language like ruby and polish their skills in it, which helps their morale by becoming experts in it. If the automated acceptance tests were written in the same language as the developers, yes this brings benefits, but the testers might not become experts as quickly, and may lack ownership of the tests.

Automated acceptance test tools provide different mileage across languages
Some languages have particularly good tools for defining business facing automated tests, for example, Cucumber’s support for ruby is superb, whereas support for C# equivalents such as Cuke4Nuke and SpecFlow isn’t quite as good. RSpec is another example of a tool that is awesome in its domain.

Summary

I know this post has ended up sounding like a pro-ruby rant, but it is :) I am trying to point out that there’s simpler, easier languages for testers to pick up than your Javas and C-Sharps. The ruby and python programming languages are two great examples, and as I mentioned, it seems that ruby is quickly becoming the testing language of choice, because of its simplicity, and the wide variety of testing related gems available. Ruby also makes it extremely easy to bootstrap configurations across different environments, and its lack of licensing requirements or need for an IDE make it an excellent choice for testers.

One of the reasons that the Selenium project is so successful is that it supports various programming languages (and browsers), which appeals to the majority of developers who can use their established skills, but it’s common to meet testers who are passionate about Watir, because of its simplicity and focus: a tool for testers by testers. It is for this reason I believe Ruby & Watir & Cucumber is a winning combination, and will bring about many success stories for agile testing teams in the future.

Reducing Cucumber page object element duplication using mixins

The one thing that quickly happens when you start using a page object pattern to develop your Cucumber acceptance tests is you end up with duplicate page elements in multiple page objects. This happens because usually your pages often have elements common to every page and it doesn’t make sense defining these more than once, enter page mixins.

Page mixins are methods in ruby that you can mix into a class, so that each method in your mixin is then available in the instance of your class. We can use a mixin to define our common elements which are then available for every class we define.

For example, in our Google/Bing search example, the search text fields are conveniently given the same id in each of the search engine pages, therefore it makes sense to define this once and mix it in to each page.

We create a mixin file which is essentially a module with a bunch of methods. In our case, we define initialize to define our elements, and method missing as this is a common method we use for Browser object delegation.

module PageMixIn
  attr_accessor :search_field

  def initialize *args, &block
    @search_field = self.text_field(:name => "q")
  end

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

We then simply include this mixin into our page class, and the methods are mixed in. The super statement in the class’s initialize method is needed to ensure the instance variables of the mixin are available in the class.

class GoogleHomePage
  include PageMixIn

  attr_accessor :search_field, :google_search_button

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

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

  def visit
    @browser.goto URLS[:production]
  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

This means we eliminate a lot of duplication of page elements, such as the search field, as well as common methods such as the method_missing.

Summary and a quick tip

I have found using page mixins provides a flexible approach to reducing page object element and method duplication. It makes your pages easy to read and compact. A quick tip though: I thoroughly recommend storing all page objects and page mixins under the support directory in Cucumber, as this ensures they are loaded automatically by Cucumber and are consistently available to reference.