Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

  • Blog Navigation
Acceptance Testing with Page Objects

An acceptance test suite goes through stages of complexity. Our first acceptance tests started off using a browser DSL like capybara directly:

fill_in "tweet", with: "hi!"
click_button "Tweet"

(Note: For the purposes of this article, I’m going to pretend I’m working with a team to build Twitter. I’ve never actually worked on Twitter, but I like to use it since it’s a domain readers are likely to already know.)

As our test suites grew, we began to feel pain. All of the sudden, we had twenty tests clicking the “Tweet” button, and every single one of them did it slightly differently:

click_button "Tweet"
click_link_or_button "Tweet"
click_on "Tweet"
click_on "#tweet"

When our product owner decided that users needed to enter a captcha every time they submit a tweet, guess what – we suddenly had twenty tests breaking, and twenty tests to update manually.

We realized that we were dealing with knowledge duplication, so we decided to abstract it behind an intent-revealing method that submits a tweet and fills in the captcha:

tweet "hi!"

Lesson learned, we continued to develop our application, making sure to expose the “what”, not the “how”, in our tests, and isolate knowledge of the DOM behind these intent revealing “helper” methods. Our Cucumber step definition became teleportation devices.

Our application grew. And grew. And grew. Pretty soon we had dozens of features, and dozens more helper methods for our acceptance test suite. We tried organizing our helper methods into modules, but we were still stuck with a large, procedural DSL. And then it dawned on us – we’re writing code in an object oriented language – why not use objects to encapsulate the behavior of our application?

When /Alice tweets/ do
  session_for(alice) do
    tweet_form.tweet "hi"

Then /Bob, Alice’s follower, should see that tweet in his timeline/ do
  session_for(bob) do
    timeline.tweets.should have_content "hi"

And now we had a more logical place to encapsulate all of the DOM elements, CSS selectors, and behaviors of the various features of our application.

Although these objects are typically referred to as “Page” objects, they don’t necessarily have to correspond to an actual page on your site. In the previous example, we had a “tweet_form” object and a “timeline” object – but if you look at, you’ll see that both the form for submitting tweets and the timeline can exist on the same page. And that’s OK. In fact, it’s even desired – we’re no longer hardcoding the knowledge of the specific UI organization directly into our step definitions, but rather encapsulating that knowledge into our page objects.

As an aside, if you’ve never used the `World` object in Cucumber, you might be scratching your head about where these objects come from. It’s as simple as creating the appropriate page classes, then adding helper methods that memoize the objects for every scenario run and then mixing those methods into the “World”:

class Timeline
  TIMELINE_SELECTOR = "#timeline"

  def initialize(browser: nil)
    @browser = browser

  def tweets

  def open
    browser.visit browser.timeline_path unless opened?

  def opened?
    browser.current_path == browser.timeline_path

module PageHelpers
  def timeline
    timelines[session_name] ||= browser: self

  def timelines
    @timelines ||= {}

World PageHelpers

And if you’re _really_ curious, you might wonder how the “session_for” helper method works. Underneath the hood, it’s simply a matter of switching out the Capybara session_name for the duration of the block:

def session_for(user)
  old_session_name = Capybara.session_name
  Capybara.session_name =
  Capybara.session_name = old_session_name

def session_name
  1. John Barker says:

    This is a well intentioned idea, any maybe you’ll have more luck with it than I did but in my experience it causes more harm than good.

    In my situation instead of having a number of different methods for tweeting in world, we just had those same methods reimplemented on various page objects. So you have duplication AND you have an OO hierarchy you have to reason about. Then the objects started accumulating complexity. The levels of abstraction were broken so you couldn’t tell what did what. The tests were difficult to change, unreliable and a mess. We ripped the page model framework out (site_prism if you’re curious). Factored down into simple declarative methods. We’ve never looked back.

    OO is sadly not a panacea for bad software design and is no substitute for disciplined refactoring. I think I’ll stick with the teleportation devices ;)

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *

Share This