Page Objects  

The Page Object pattern is a way to represent pages and their elements in reusable classes. 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.

The examples below assumes you are familiar with RSpec way of doing test assertions.

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:

Page Object gems that work with Watir-webdriver

Blog postings related to Page Objects and Watir-webdriver

Blog postings related to Page Objects and webdriver/Selenium

Last Updated: September 11, 2018