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
- Cheezy’s Page Object gem for Watir-webdriver and Selenium
- The rSmart Test-Factory gem for Page & Data objects using Watir-webdriver
- WatirPump: Page Object gem - a fresh approach from 2018
Blog postings related to Page Objects and Watir-webdriver
- cheezyworld’s series on UI testing part 1, part 2, part 3, part 4, part 5
- Watermelon blog article on ‘roll your own’ page objects
- WatirPump: Page Object library for Ruby and Watir
Blog postings related to Page Objects and webdriver/Selenium