SitePrism

A Page Object Model DSL for Capybara

SitePrism gives you a simple, clean and semantic DSL for describing your site using the Page Object Model pattern, for use with Capybara in automated acceptance testing.

Find the pretty documentation here: http://rdoc.info/gems/site_prism/frames

Synopsis

Here's an overview of how SitePrism is designed to be used:

# define our site's pages

class Home < SitePrism::Page
  set_url "http://www.google.com"
  set_url_matcher /google.com\/?/

  element :search_field, "input[name='q']"
  element :search_button, "button[name='btnK']"
  elements :footer_links, "#footer a"
  section :menu, MenuSection, "#gbx3"
end

class SearchResults < SitePrism::Page
  set_url_matcher /google.com\/results\?.*/

  section :menu, MenuSection, "#gbx3"
  sections :search_results, SearchResultSection, "#results li"
end

# define sections used on multiple pages or multiple times on one page

class MenuSection < SitePrism::Section
  element :search, "a.search"
  element :images, "a.image-search"
  element :maps, "a.map-search"
end

class SearchResultSection < SitePrism::Section
  element :title, "a.title"
  element :blurb, "span.result-decription"
end

# now for some tests

When /^I navigate to the google home page$/ do
  @home = Home.new
  @home.load
end

Then /^the home page should contain the menu and the search form$/ do
  @home.wait_for_menu # menu loads after a second or 2, give it time to arrive
  @home.should have_menu
  @home.should have_search_field
  @home.should have_search_button
end

When /^I search for Sausages$/ do
  @home.search_field.set "Sausages"
  @home.search_button.click
end

Then /^the search results page is displayed$/ do
  @results_page = SearchResults.new
  @results_page.should be_displayed
end

Then /^the search results page contains 10 individual search results$/ do
  @results_page.wait_for_search_results
  @results_page.should have_search_results
  @results_page.search_results.size.should == 10
end

Then /^the search results contain a link to the wikipedia sausages page$/ do
  @results_page.search_results.map {|sr| sr.title['href']}.should include "http://en.wikipedia.org/wiki/Sausage"
end

Now for the details...

Setup

Installation

To install SitePrism:

gem install site_prism

Using SitePrism with Cucumber

If you are using cucumber, here's what needs requiring:

require 'capybara'
require 'capybara/dsl'
require 'capybara/cucumber'
require 'selenium-webdriver'
require 'site_prism'

Using SitePrism with RSpec

If you're using rspec instead, here's what needs requiring:

require 'capybara'
require 'capybara/dsl'
require 'capybara/rspec'
require 'selenium-webdriver'
require 'site_prism'

Introduction to the Page Object Model

The Page Object Model is a test automation pattern that aims to create an abstraction of your site's user interface that can be used in tests. The most common way to do this is to model each page as a class, and to then use instances of those classes in your tests.

If a class represents a page then each element of the page is represented by a method that, when called, returns a reference to that element that can then be acted upon (clicked, set text value), or queried (is it enabled? visible?).

SitePrism is based around this concept, but goes further as you'll see below by also allowing modelling of repeated sections that appear on muliple pages, or many times on a page using the concept of sections.

Pages

As you might be able to guess from the name, pages are fairly central to the Page Object Model. Here's how SitePrism models them:

Creating a Page Model

The simplest page is one that has nothing defined in it. Here's an example of how to begin modelling a home page:

class Home < SitePrism::Page
end

The above has nothing useful defined, only the name.

Adding a URL

A page usually has a URL. If you want to be able to navigate to a page, you'll need to set its URL. Here's how:

class Home < SitePrism::Page
  set_url "http://www.google.com"
end

Note that setting a URL is optional - you only need to set a url if you want to be able to navigate directly to that page. It makes sense to set the URL for a page model of a home page or a login page, but probably not a search results page.

Once the URL has been set (using set_url), you can navigate directly to the page using #load:

@home_page = Home.new
@home_page.load

This will tell which ever capybara driver you have configured to navigate to the URL set against that page's class.

Verifying that a particular page is displayed

Automated tests often need to verify that a particular page is displayed. Intuitively you'd think that simply checking that the URL defined using set_url is the current page in the browser would be enough, but experience shows that it's not. It is far more robust to check to see if the browser's current url matches a regular expression. For example, though account/1 and account/2 are the same page, their URLs are different. To deal with this, SitePrism provides the ability to set a URL matcher.

class Account < SitePrism::Page
  set_url_matcher /\account\/\d+/
end

Once a URL matcher is set for a page, you can test to see if it is displayed:

@account_page = Account.new
#...
@account_page.displayed? #=> true or false

Calling #displayed? will return true if the browser's current URL matches the regular expression for the page and false if it doesn't. So in the above example (account/1 and account/2), calling @account_page.displayed? will return true for both examples.

Testing for Page display

SitePrism's #displayed? predicate method allows for semantic code in your test:

Then /^the account page is displayed$/ do
  @account_page.should be_displayed
  @some_other_page.should_not be_displayed
end

Another example that demonstrates why using regex instead of string comparison for URL checking is when you want to be able to run your tests across multiple environments.

class Login < SitePrism::Page
  set_url "#{$test_environment}.example.com/login" #=> global var used for demonstration purposes only!!!
  set_url_matcher /(?:dev|test|www)\.example\.com\/login/
end

The above example would work for dev.example.com/login, test.example.com/login and www.example.com/login; now your tests aren't limited to one environment but can verify that they are on the correct page regardless of the environment the tests are being executed against.

Getting the Current Page's URL

SitePrism allows you to get the current page's URL. Here's how it's done:

class Account < SitePrism::Page
end

@account = Account.new
#...
@account.current_url #=> "http://www.example.com/account/123"
@account.current_url.should include "example.com/account/"

Page Title

Getting a page's title isn't hard:

class Account < SitePrism::Page
end

@account = Account.new
#...
@account.title #=> "Welcome to Your Account"

HTTP vs. HTTPS

You can easily tell if the page is secure or not by checking to see if the current URL begins with 'https' or not. SitePrism provides the secure? method that will return true if the current url begins with 'https' and false if it doesn't. For example:

class Account < SitePrism::Page
end

@account = Account.new
#...
@account.secure? #=> true/false
@account.should be_secure

Elements

Pages are made up of elements (text fields, buttons, combo boxes, etc), either individual elements or groups of them. Examples of individual elements would be a search field or a company logo image; examples of element collections would be items in any sort of list, eg: menu items, images in a carousel, etc.

Individual Elements

To interact with individual elements, they need to be defined as part of the relevant page. SitePrism makes this easy:

class Home < SitePrism::Page
  element :search_field, "input[name='q']"
end

Here we're adding a search field to the Home page. The element method takes 2 arguments: the name of the element as a symbol, and a css selector as a string.

Accessing the individual element

The element method will add a number of methods to instances of the particular Page class. The first method to be added is the name of the element. So using the following example:

class Home < SitePrism::Page
  set_url "http://www.google.com"

  element :search_field, "input[name='q']"
end

... the following shows how to get hold of the search field:

@home_page = Home.new
@home.load

@home.search_field #=> will return the capybara element found using the selector
@home.search_field.set "the search string" #=> since search_field returns a capybara element, you can use the capybara API to deal with it
@home.search_field.text #=> standard method on a capybara element; returns a string

Testing for the existence of the element

Another method added to the Page class by the element method is the has_<element name>? method. Using the same example as above:

class Home < SitePrism::Page
  set_url "http://www.google.com"

  element :search_field, "input[name='q']"
end

... you can test for the existence of the element on the page like this:

@home_page = Home.new
@home.load
@home.has_search_field? #=> returns true if it exists, false if it doesn't

...which makes for nice test code:

Then /^the search field exists$/ do
  @home.should have_search_field
end

Waiting for an element to appear on a page

The final method added by calling element is the wait_for_<element_name> method. Calling the method will cause the test to wait for the Capybara's default wait time for the element to exist. It is also possible to use a custom amount of time to wait. Using the same example as above:

class Home < SitePrism::Page
  set_url "http://www.google.com"

  element :search_field, "input[name='q']"
end

... you can wait for the search field to exist like this:

@home_page = Home.new
@home.load
@home.wait_for_search_field
# or...
@home.wait_for_search_field(10) #will wait for 10 seconds for the search field to appear

Summary of what the element method provides:

Given:

class Home < SitePrism::Page
  element :search_field, "input[name='q']"
end

...then the following methods are available:

@home.search_field
@home.has_search_field?
@home.wait_for_search_field
@home.wait_for_search_field(10)

Element Collections

Sometimes you don't want to deal with an individual element but rather with a collection of similar elements, for example, a list of names. To enable this, SitePrism provides the elements method on the Page class. Here's how it works:

class Friends < SitePrism::Page
  elements :names, "ul#names li a"
end

Just like the element method, the elements method takes 2 arguments: the first being the name of the elements as a symbol, the second is the css selector that would return the array of capybara elements.

Accessing the elements

Just like the element method, the elements method adds a few methods to the Page class. The first one is of the name of the element collection which returns an array of capybara elements that match the css selector. Using the example above:

class Friends < SitePrism::Page
  elements :names, "ul#names li a"
end

You can access the element collection like this:

@friends_page = Friends.new
# ...
@friends_page.names #=> [<Capybara::Element>, <Capybara::Element>, <Capybara::Element>]

With that you can do all the normal things that are possible with arrays:

@friends_page.names.each {|name| puts name.text}
@friends_page.names.map {|name| name.text}.should == ["Alice", "Bob", "Fred"]
@friends_page.names.size.should == 3

Testing for the existence of the element collection

Just like the element method, the elements method adds a method to the page that will allow you to check for the existence of the collection, called has_<element collection name>?. As long as there is at least 1 element in the array, the method will return true, otherwise false. For example, with the following page:

class Friends < SitePrism::Page
  elements :names, "ul#names li a"
end

... the following method is available:

@friends_page.has_names? #=> returns true if at least one element is found using the relevant selector

...which allows for pretty test code:

Then /^there should be some names listed on the page$/ do
  @friends_page.should have_names
end

Waiting for the element collection

Just like for an individual element, the tests can be told to wait for the existence of the element collection. The elements method adds a wait_for_<element collection name> method that will wait for Capybara's default wait time until at least 1 element is found that matches the selector. For example, with the following page:

class Friends < SitePrism::Page
  elements :names, "ul#names li a"
end

... you can wait for the existence of a list of names like this:

@friends_page.wait_for_names

Again, you can customise the wait time by supplying a number of seconds to wait for:

@friends_page.wait_for_names(10)

Checking that all mapped elements are present on the page

Throughout my time in test automation I keep getting asked to provide the ability to check that all elements that should be on the page are on the page. Why people would want to test this, I don't know. But if that's what you want to do, SitePrism provides the #all_there? method that will return true if all mapped elements (and sections... see below) are present in the browser, false if they're not all there.

@friends_page.all_there? #=> true/false

# and...

Then /^the friends page contains all the expected elements$/ do
  @friends_page.should be_all_there
end

Sections

SitePrism allows you to model sections of a page that appear on multiple pages or that appear a number of times on a page separately from Pages. SitePrism provides the Section class for this task.

Individual Sections

In the same way that SitePrism provides element and elements, it provides section and sections. The first returns an instance of a page section, the secont returns an array of section instances, one for each capybara element found by the supplied css selector. What follows is an explanation of section.

Defining a Section

A section is similar to a page in that it inherits from a SitePrism class:

class MenuSection < SitePrism::Section
end

At the moment, this section does nothing.

Adding a section to a page

Pages include sections that's how SitePrism works. Here's a page that includes the above MenuSection section:

class Home < SitePrism::Page
  section :menu, MenuSection, "#gbx3"
end

The way to add a section to a page (or another section - SitePrism allows adding sections to sections) is to call the section method. It takes 3 arguments: the first is the name of the section as referred to on the page (sections that appear on multiple pages can be named differently). The second argument is the class of which an instance will be created to represent the page section, and the third argument is a css selector that identifies the root node of the section on this page (note that the css selector can be different for different pages as the whole point of sections is that they can appear in different places on different pages).

Accessing a page's section

The section method (like the element method) adds a few methods to the page or section class it was called against. The first method that is added is one that returns an instance of the section, the method name being the first argument to the section method. Here's an example:

# the section:

class MenuSection < SitePrism::Section
end

# the page that includes the section:

class Home < SitePrism::Page
  section :menu, MenuSection, "#gbx3"
end

# the page and section in action:

@home = Home.new
@home.menu #=> <MenuSection...>

When the menu method is called against @home, an instance of MenuSection (the second argument to the section method) is returned. The third argument that is passed to the section method is the css selector that will be used to find the root element of the section; this root node becomes the 'scope' of the section.

The following shows that though the same section can appear on multiple pages, it can take a different root node:

# define the section that appears on both pages

class MenuSection < SitePrism::Section
end

# define 2 pages, each containing the same section

class Home < SitePrism::Page
  section :menu, MenuSection, "#gbx3"
end

class SearchResults < SitePrism::Page
  section :menu, MenuSection, "#gbx48"
end

You can see that the MenuSection is used in both the Home and SearchResults pages, but each has slightly different root node. The capybara element that is found by the css selector becomes the root node for the relevant page's instance of the MenuSection section.

Adding elements to a section

This works just the same as adding elements to a page:

class MenuSection < SitePrism::Section
  element :search, "a.search"
  element :images, "a.image-search"
  element :maps, "a.map-search"
end

Note that the css selectors used to find elements are searched for within the scope of the root element of that section. The search for the element won't be page-wide but it will only look in the section.

When the section is added to a page...

class Home < SitePrism::Page
  section :menu, MenuSection, "#gbx3"
end

...then the section's elements can be accessed like this:

@home = Home.new
@home.load

@home.menu.search #=> returns a capybara element representing the link to the search page
@home.menu.search.click #=> clicks the search link in the home page menu
@home.menu.search['href'] #=> returns the value for the href attribute of the capybara element representing the search link
@home.menu.has_images? #=> returns true or false based on whether the link is present in the section on the page
@home.menu.wait_for_images #=> waits for capybara's default wait time until the element appears in the page section

...which leads to some pretty test code:

Then /^the home page menu contains a link to the various search functions$/ do
  @home.menu.should have_search
  @home.menu.search['href'].should include "google.com"
  @home.menu.should have_images
  @home.menu.should have_maps
end

Testing for the existence of a section

Just like elements, it is possible to test for the existence of a section. The section method adds a method called has_<section name>? to the page or section it's been added to - same idea as what the has_<element name>? method. Given the following setup:

class MenuSection < SitePrism::Section
  element :search, "a.search"
  element :images, "a.image-search"
  element :maps, "a.map-search"
end

class Home < SitePrism::Page
  section :menu, MenuSection, "#gbx3"
end

... you can check whether the section is present on the page or not:

@home = Home.new
#...
#home.has_menu? #=> returns true or false

Again, this allows pretty test code:

@home.should have_menu
@home.should_not have_menu

Waiting for a section to appear

The final method added to the page or section by the section method is wait_for_<section name>. Similar to what element does, this method waits for the section to appear - the test will wait up to capybara's default wait time until the root node of the element exists on the page/section that our section was added to. Given the following setup:

class MenuSection < SitePrism::Section
  element :search, "a.search"
  element :images, "a.image-search"
  element :maps, "a.map-search"
end

class Home < SitePrism::Page
  section :menu, MenuSection, "#gbx3"
end

... we can wait for the menu section to appear on the page like this:

@home.wait_for_menu
@home.wait_for_menu(10) # waits for 10 seconds instead of capybara's default timeout

Sections within sections

You are not limited to adding sections only to pages; you can nest sections within sections within sections within sections!


# define a page that contains an area that contains a section for both logging in and registration, then modelling each of the sub sections seperately

class Login < SitePrism::Section
  element :username, "#username"
  element :password, "#password"
  element :sign_in, "button"
end

class Registration < SitePrism::Section
  element :first_name, "#first_name"
  element :last_name, "#last_name"
  element :next_step, "button.next-reg-step"
end

class LoginRegistrationForm < SitePrism::Section
  section :login, Login, "div.login-area"
  section :registration, Registration, "div.reg-area"
end

class Home < SitePrism::Page
  section :login_and_registration, LoginRegistrationForm, "div.login-registration"
end

# how to login (fatuous, but demonstrates the point):

Then /^I sign in$/ do
  @home = Home.new
  @home.load
  @home.
  @home.should 
  @home..should have_username
  @home...username.set "bob"
  @home...password.set "p4ssw0rd"
  @home....click
end

# how to sign up:

When /^I enter my name into the home page's registration form$/ do
  @home = Home.new
  @home.load
  @home..should have_first_name
  @home..should have_last_name
  @home..first_name.set "Bob"
  # ...
end

Section Collections

An individual section represents a discrete section of a page, but often sections are repeated on a page, an example is a search result listing - each listing contains a title, a url and a description of the content. It makes sense to model this only once and then to be able to access each instance of a search result on a page as an array of SitePrism sections. To achieve this, SitePrism provides the sections method that can be called in a page or a section.

The only difference between section and sections is that whereas the first returns an instance of the supplied section class, the second returns an array containing as many instances of the section class as there are capybara elements found by the supplied css selector. This is better explained in code :)

Adding a Section collection to a page (or other section)

Given the following setup:

class SearchResultSection < SitePrism::Section
  element :title, "a.title"
  element :blurb, "span.result-decription"
end

class SearchResults < SitePrism::Page
  sections :search_results, SearchResultSection, "#results li"
end

... it is possible to access each of the search results:

@results_page = SearchResults.new
# ...
@results_page.search_results.each do |search_result|
  puts search_result.title.text
end

... which allows for pretty tests:

Then /^there are lots of search_results$/ do
  @results_page.search_results.size.should == 10
  @results_page.search_results.each do |search_result|
    search_result.should have_title
    search_result.blurb.text.should_not be_nil
  end
end

The css selector that is passed as the 3rd argument to the sections method ("#results li") is used to find a number of capybara elements. Each capybara element found using the css selector is used to create a new instance of the SearchResultSection and becomes its root element. So if the css selector finds 3 li elements, calling search_results will return an array containing 3 instances of SearchResultSection, each with one of the li elements as it's root element.

Testing for existence of Sections

Using the example above, it is possible to test for the existence of the sections. As long as there is at least one section in the array, the sections exist. The sections method adds a has_<sections name>? method to the page/section that our section has been added to. Given the following example:

class SearchResultSection < SitePrism::Section
  element :title, "a.title"
  element :blurb, "span.result-decription"
end

class SearchResults < SitePrism::Page
  sections :search_results, SearchResultSection, "#results li"
end

... here's how to test for the existence of the section:

@results_page = SearchResults.new
# ...
@results_page.has_search_results?

...which allows pretty tests:

Then /^there are search results on the page$/ do
  @results.page.should have_search_results
end

Waiting for sections to appear

The final method added by sections to the page/section we're adding our sections to is wait_for_<sections name>. It will wait for capybara's default wait time for there to be at least one instance of the section in the array of sections. For example:

class SearchResultSection < SitePrism::Section
  element :title, "a.title"
  element :blurb, "span.result-decription"
end

class SearchResults < SitePrism::Page
  sections :search_results, SearchResultSection, "#results li"
end

... here's how to wait for the section:

@results_page = SearchResults.new
# ...
@results_page.wait_for_search_results
@results_page.wait_for_search_results(10) #=> waits for 10 seconds instead of the default capybara timeout

Epilogue

So, we've seen how to use SitePrism to put together page objects made up of pages, elements and sections. But how to organise this stuff? There are a few ways of saving yourself having to create instances of pages all over the place. Here's an example of this common problem:

@home = Home.new
@home.load
@home.search_field.set "Sausages"
@home.search_field.search_button.click
@results_page = SearchResults.new
@results_page.should have_search_result_items

The annoyance (and, later, maintenance nightmare) is having to create @home and @results_page. It would be better to not have to create instances of pages all over your tests.

The way I've dealt with this problem is to create a class containing methods that return instances of the pages. Eg:

# our pages

class Home < SitePrism::Page
  #...
end

class SearchResults < SitePrism::Page
  #...
end

class Maps < SitePrism::Page
  #...
end

# here's the app class that represents our entire site:

class App
  def home
    Home.new
  end

  def results_page
      SearchResults.new
  end

  def maps
    Maps.new
  end
end

# and here's how to use it:

#first line of the test...
Given /^I start on the home page$/ do
  @app = App.new
  @app.home.load
end

When /^I search for Sausages$/ do
  @app.home.search_field.set "sausages"
  @app.home.search_button.click
end

# etc...

The only thing that needs instantiating is the App class - from then on pages don't need to be initialized, they are now returned by methods on @app. Maintenance win!