This site uses cookies to offer you a better browsing experience. Find out more on Privacy Policy.

Better – Automated Tests in Ruby Using the SitePrism Gem for the Page Object Model Pattern

May 16, 2017 Sebastian Wcisło

Capybara is a simple Domain Specific Language (DSL), which is totally fine for a small number of automated tests. However, when the quantity of the tests grows, managing them becomes a major undertaking because even small UI changes can disrupt various tests.

The SitePrism gem, which is a Page Object Model DSL, comes to the rescue and can be used with Capybara.
Thanks to this tool, you can simply describe the page structure and elements in your application. This gem makes your tests easier to read, understand, and maintain. Easier maintenance is SitePrism’s biggest advantage because changes in page elements (for example, to id or class) will only have to be made in one place in the page class.

A remedy for your problems

SitePrism is a great tool to use when the application you work on does not clearly identify the page elements – especially when it is necessary to perform some kind of ugly voodoo with xpaths that cover multiple levels of nodes.

For example, you have a simple table without any ids or CSS and you want to find a table row with a specific client.

1
2
3
client_row = page.find(:xpath, "(//div[contains(text(), '#{client_name}')]/../../..)")
expect(client_row).to have_content("#{client_phone}")
expect(client_row).to have_content("#{client_balance}")

This simple code tests if the client’s phone number and balance are correct. The client name node must be found and the node that is three levels higher must be returned in order to get the entire row.
With SitePrism, the same code will appear as:

1
2
3
client_row = @app.clients_index.find_client_by_name(client_name)
expect(client_row.client_phone.text).to eq(client_phone)
expect(client_row.balance.text).to eq(client_balance)

The code directly above is much cleaner and easier to look at. You can test each row column precisely without encountering any problems. Finding nodes by their children elements is as simple as comparing the child node content with the expected result in the found block. No more xpath voodoo!
Another good example is the button, which can be found by its text – a small change to the button label forces you to fix many of your tests. With SitePrism, buttons are defined in one place; when the label text is altered, changes only need to be made in one place.

Another awesome feature of SitePrism allows you to test your application in detail using nested sections, e.g. an input row that can have a label, input, description, and a validation error message. Without SitePrism, it is difficult to test the correct elements when the tested page form has multiple inputs, which can have a variety of states.

Installation

Firstly, it is necessary to create a “page_objects” directory in your application project (for example, in a root or spec directory) and “pages” and “sections” directories inside “page_objects”.
You can require all page objects in your “spec_helper”. You should also remember to include section files before pages.

1
2
Dir["spec /page_objects/sections/**/*.rb",
  "spec/ page_objects /**/*.rb"].each {|f| require Rails.root.join(f)}

Pages

Pages describe your application’s elements. Best practice is to create the same page structure as the application’s views templates. For example:

1
2
3
4
5
6
7
8
9
10
11
12
Page_objects
 |-> pages
  |-> admins
   |-> index_page.rb
   |-> new_page.rb
   |-> edit_page.rb
  |-> clients
   |-> index_page.rb
   |-> new_page.rb
   |-> edit_page.rb
  |-> home
   |-> index_page.rb

Class definition

To create the page class, simply add SitePrism::Page inheritance.

1
2
3
4
5
6
7
8
9
10
11
module AppName
  module Pages
    module Clients
      class Index < SitePrism::Page
        set_url "clients/index.htm"
 
        # page elements and methods here
      end
    end
  end
end

The page class can contain an element, elements, a section, and sections. For example, you can easily map all page elements:

1
2
3
4
5
6
7
8
section :filters, "form[id='new_search']" do
   section :client_name, "div.search_name" do
     element :label, "label[for='search_name']"
     element :input, "input[id='search_name']"
     element :error, "span[class='help-block']"
   end
  element :search_button, "input[type='submit']"
end

This example describes filters with input and buttons. The client_name section describes the input row with all possible elements.

Sections

Sections are classes that describe small page elements, which can be presented on multiple pages. For example, below you can find the section that describes the page sidebar.

1
2
3
4
5
6
7
8
9
10
11
12
module AppName
  module Sections
    module Layouts
      module Shared
        class Sidebar < SitePrism::Section
          element :admins, "li[id='admins']"
          element :clients, "li[id='clients']"
        end
      end
    end
  end
end

You can then add this section to your page’s classes.

1
section :sidebar, AppName::Sections::Layouts::Shared::Sidebar, "#sidebar"

Good practice is to create one class containing methods that return instances of the page’s object classes. This allows you to avoid having to create instances for every page class, which you will be using in your tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
module AppName
  class Application
    def dashboard_index
      AppName::Pages::Dashboard::IndexPage.new
    end
    def clients_index
      AppName::Pages::Clients::IndexPage.new
    end
    def clients_new
      AppName::Pages::Clients::NewPage.new
    end
  end
end

An example

A sample test that uses the Page Object Model pattern, (which loads the page, enters text into input, filters results, and checks if the results are presented), can be seen below:

1
2
3
4
5
6
7
8
9
10
feature 'Clients filter' do
  let!(:app) { AppName::Application }
  scenario 'filtered client is displayed'
    app.clients_index.load
    app.clients_index.filters.client_name.input.set “Ruby”
    app.clients_index.filters.search_button.click
 
    expect(app.clients_index.results[0].text).to eq “Ruby”
  end
end

Conclusion

From my QA perspective, this gem helps me create very detailed tests without having to compromise their readability – I am able to test even the tiniest aspects, which used to be a headache to deal with. For example, I can check every validation error for the specific input of page forms. I am able to get rid of within blocks that overly complicate tests. And I love that the code I write is legible to everyone and not just to developers and me.
SitePrism has shown its strength on many levels in the project, on which I am currently working. Certain changes had been made to the table structure and they were fixed in a matter of just a few seconds by means of altering only the page’s element definitions. Nothing else!

Summary

SitePrism is a remedy for all of the UI changes that cause your tests to stop working. When something changes, you can simply update a page class element to new values and all of the tests will automatically use the new element.

This gem alleviates one of the biggest headaches – test maintenance. Maintaining created tests is a lot easier when changes can be made in only one page’s class element or in a given section’s definitions.

Links

Last posts