Skip to content
markisisme edited this page Jun 5, 2011 · 19 revisions

Page Objects is a design pattern to model the application under test as objects in your code. Page Objects eliminate duplication by building an abstraction that allows you to write browser tests for maximum maintainability and robustness.

The Page Object pattern originated in WebDriver, and a great explanation (with examples in Java) can be found here.

You create a Page object for each page of your application, which has methods that represent the services available on a given page. You should encapsulate all the implementation details of the system (i.e. HTML elements, waiting for the DOM to update etc) in these objects. Your test code (i.e. RSpec code blocks, Cucumber step definitions) should never access the underlying Browser instance or deal with HTML elements directly.

In essence, you should have the mindset that your test code should not need to change if your web app was rewritten as a desktop app – you would simply need another implementation of the page object layer. Although strictly speaking a set of pages isn’t necessarily a good way to model a desktop application, having this in mind makes it easy to decide what should go where.

For example, consider this script:

browser = Watir::Browser.new
browser.goto "http://example.com/login"

browser.text_field(:name => "user").set "Mom"
browser.text_field(:name => "pass").set "s3cr3t"
browser.button(:id => "login").click

Watir::Wait.until { browser.title == "Your Profile" }
browser.div(:id => "logged-in").should exist

With page objects, this could become:

site = Site.new(Watir::Browser.new)

login_page = site.login_page.open
user_page = login_page.login_as "Mom", "s3cr3t"

user_page.should be_logged_in

An implementation of this could be:

class BrowserContainer
  def initialize(browser)
    @browser = browser
  end
end

class Site < BrowserContainer
  def login_page
    @login_page = LoginPage.new(@browser)
  end

  def user_page
    @user_page = UserPage.new(@browser)
  end

  def close
    @browser.close
  end
end # Site

class LoginPage < BrowserContainer
  URL = "http://example.com/login"

  def open
    @browser.goto URL
    self
  end

  def login_as(user, pass)
    user_field.set user
    password_field.set pass

    login_button.click

    next_page = UserPage.new(@browser)
    Watir::Wait.until { next_page.loaded? }

    next_page
  end

  private

  def user_field
    @browser.text_field(:name => "user")
  end

  def password_field
    @browser.text_field(:name => "pass")
  end

  def login_button
    @browser.button(:id => "login")
  end
end # LoginPage

class UserPage < BrowserContainer
  def logged_in?
    logged_in_element.exists?
  end

  def loaded?
    @browser.title == "Your Profile"
  end

  private

  def logged_in_element
    @browser.div(:id => "logged-in")
  end
end # UserPage

This can then be integrated with other tools. For example, using Cucumber you could have this in env.rb:

require "watir-webdriver"
require "/path/to/site"

module SiteHelper
  def site
    @site ||= (
      Site.new(Watir::Browser.new(:firefox))
    )
  end
end

World(SiteHelper)

And this step definition:

 Given /I have successfully logged in/ do
   login_page = site.login_page.open

   user_page = login_page.login_as "Mom", "s3cr3t"
   user_page.should be_logged_in
 end

Assertions/expectations should be kept in your test code. Don’t use assertions
in your page objects; instead ask them about their state, and assert on the result. E.g.:

#
# bad example
#

class SomePage
  def assert_loaded
    raise "not loaded" unless some_element.exists?
  end
end

it "should be loaded" do
  page.assert_loaded
end

#
# good example
#

class SomePage
  def loaded?
    some_element.exists?
  end
end

it "should be loaded" do
  page.should be_loaded
end

See also:

Clone this wiki locally