Fist of Senn

a developer's notebook

Grow Tasty Cucumbers Using PageObjects

Motivation

Cucumber is a wonderful tool but at the same time it can get you into deep trouble if you don’t use it correctly. Over the years I tried many different styles to write Cucumber Features but in the end most projects ended up with a verbose and brittle set of features, which was a pain to maintain.

I always liked Cucumber because you can write your thoughts without translating them into a programming language. This helps me to think more about the feature at hand and gets me into the mindset of the customer. I understood the goal of Cucumber from the beginning but as a programmer I always looked at it as a testing tool. It took me a long time (multiple years) to notice that the maintenance pain is directly coupled to that thinking.

Cucumber helps you think, specify and document your application in a structured way. That we can also use this documentation to drive our tests is convenient but should not be your primary goal. The distinction you need to make, is that you write the Cucumber Features to document and not to test your application. If you apply this thinking you’ll notice that you don’t “program” with cucumber anymore. You don’t try to create highly reusable step definitions but just write a different one. The end result is that you have a lot of step definitions, which do similar things but are not the same step. The features will be much more expressive but since you don’t tackle duplication on the text-level you need to deal with it in the implementation of the steps.

Introducing PageObjects

After I realized that the Cucumber Features are not the right level to abstract we started using the PageObject pattern to reduce the duplication in our step implementations. A PageObject as the name says is an object, which represents a page in your application. The object has methods to interact with your application and to assert that it is in a certain state (effectively what you are doing when you are acceptance testing). The PageObject is an abstraction for your tests that you can combine and reuse in your step implementations to fit your needs. In the beginning you have to put some more work into creating the PageObjects but as time passes you will have an API to navigate your application and it will save you a lot of time. It also allows you to have many steps, which vary only slightly but can be implemented with 1 or 2 lines, which is not a problem when you need to react to change.

Another benefit of PageObjects is that you have a shared place to put assertions about your application. You could for example verify that the correct tab is selected before performing an operation.

An Example

Imagine you are writing the next big blogging engine. You could have a Feature which looks like:

features/visitor/comment_on_article.feature
1
2
3
4
5
6
7
8
9
Feature: Comment on an article
  In order to share their opinion with the rest of the world
  Visitors should be able to
  Comment on an article

  Scenario: Submit a new comment
    Given I am looking at the article "Introduction to PageObjects"
    When I submit a new comment
    Then the article page should display my comment

Of course as always with examples it’s very simple but notice that we have descriptive steps, which likely won’t be reused. After writing such a Feature I got into the habit of just running it and copying all the needed step definitions:

features/step_definitions/comment_steps.rb
1
2
3
4
5
6
7
8
9
10
11
Given /^I am looking at the article "(.*?)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

When /^I submit a new comment$/ do
  pending # express the regexp above with the code you wish you had
end

Then /^the article page should display my comment$/ do
  pending # express the regexp above with the code you wish you had
end

Now we can apply the PageObject pattern to build an object, which represents the article page we are developing. The PageObject is very straight forward: a simple class and the necessary methods to implement our steps:

features/support/page_objects/article.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module PageObjects
  class Article

    # gives you access to the Capybara session methods
    include Capybara::DSL

    # gives you access to the rails path helpers
    include Rails.application.routes.url_helpers

    def open(article_record)
      visit article_path(article_record)
    end

    def comments
      all('.comment').map do |comment_node|
        comment_node.text
      end
    end

    def submit_comment(text)
      fill_in "#new_comment_text", :with => text
      click_button 'Comment'
    end
  end

  # helper method to access the page object
  def article
    PageObjects::Article.new
  end
end

We need to include the PageObjects module into our World that we can access the helper method article.

features/support/page_objects.rb
1
2
3
4
module PageObjects
end

World(PageObjects)

and the step implementations:

features/step_definitions/comment_steps.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
Given /^I am looking at the article "(.*?)"$/ do |article_title|
  # create the article however you want (factory_girl, machinist, plain AR)
  @article = Article.create!(:title => article_title)
  article.open(@article)
end

When /^I submit a new comment$/ do
  article.submit_comment('This article rocks!')
end

Then /^the article page should display my comment$/ do
  article.comments.should include('This article rocks!')
end

Even though this a tiny example you can see an API emerge around our page. If we need to write a feature to ensure that the comments are ordered by date they are created we could already reuse the comments method:

features/visitor/comment_on_article.feature
1
2
3
4
Scenario: The comments appear in order of creation
  Given an article with multiple comments
  When I visit that article
  Then I should see the comments in the order they were created
features/step_definitions/comment_steps.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Given /^an article with multiple comments$/ do
  @article = Article.create!(:title => 'Example')
  ['First', 'Second', 'Third'].each do |message|
    @article.comments.create!(:message => message)
  end
end

When /^I visit that article$/ do
  article.open(@article)
end

Then /^I should see the comments in the order they were created$/ do
  article.comments.should == ['First', 'Second', 'Third']
end

The examples illustrate only the tip of the iceberg. I plan to do a few follow-up posts to describe how we use PageObjects to make our step implementations less brittle, more helpful and how you can split your PageObjects into smaller reusable pieces.

If you have experience using the PageObject pattern or use a different approach to achieve the same goal please leave a comments.

Comments