Testing Page Objects with SitePrism

Share this article

Prism Spectrum Illustration on Blue Background

In this article, I am going to talk about SitePrism, a testing tool that I have recently learned about and been enjoying. Here is a description of SitePrism from its’ creators:

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.

Before we go into SitePrism, let’s talk about page objects, because page objects are central to understanding the philosophy behind SitePrism.

A Page Objects Primer

Page Objects encapsulate the implementation details of the page (in our example, an HTML document) and expose a specific API that allows you, the tester, to test elements on the page. This encapsulation of details make page objects especially handy as a testing tool.

Let’s illustrate this with an example. Here, we have a page object that inherits from the SitePrism::Page class:

class LoginPage < SitePrism::Page
  element :username_field, "input[name='username']"
  element :password_field, "input[name='password']"
  element :flash, "div.flash"

  def log_in(username, password)
    username_field.set(username)
    password_field.set(password)
    click_on('Log In')
  end
end

class ProfilePage < SitePrism::Page
  element :flash, "div.flash"

  def flash_message
    flash.text
  end
end

Let’s see how we can use our example page object:

feature 'Login' do
  let(:login_page) { LoginPage.new }

  before do
    login_page.load
  end

  scenario 'a successful login' do
    profile_page = login_page.log_in('admin', 'password')
    expect(profile_page.flash_message).to eq("Ohai! Welcome Admin.")
  end
end

The above code snippet illustrates how we can use SitePrism’s page objects in our RSpec feature tests. Ready to learn more?

Getting Site Prism Into Your Project

Installing Site Prism is a painless affair. In your Gemfile:

group :test do
  # ...
  gem 'rspec-rails', '~> 3.0.0'
  gem 'capybara-rails'
  gem 'site_prism'
  # ...
end

Note that for the example project, we are going to use RSpec 3. Also, you will need Capybara in order to use SitePrism.

Working with the Example Project

The best way to learn about SitePrism (and everything else, really) is to have some hands on experience. For this, I have prepared an example application that you can use to follow along this article.

The sample application is a Spree e-commerce store. At this point of writing, we are using the latest 2.3-stable branch and running it on Rails 4.1.4.

To get the project:

$ git clone git@github.com:benjamintanweihao/rails_store.git
$ cd rails_store
$ bundle install

The Task: Testing Out Search

In order to get our hands dirty and feet wet with SitePrism, we are going to test the search function of the store.

Some of the portions of the test have Spree-specific code. Don’t worry, I will point them out. In most cases, you can safely ignore them. The sample Spree store makes for a realistic platform to practice our testing skills.

Let’s go through the implementation of the HomePage page object. I store all my page object classes in a spec/support/pages directory. You are free to choose your own conventions. Just make sure that the directory is included in the tests.

Here’s how the page looks like:

home page

HomePage is subclassed from SitePrism::Page. element is used to specify one DOM element, while elements is used to refer to a collection of DOM elements. Here, we pass in a symbol (for example, :products), and then the CSS selector to target the various elements on the page.

Here’s the initial implementation of the HomePage page object:

class HomePage < SitePrism::Page
  elements :products,        "[data-hook='products_list_item']"
  elements :product_links,   "[data-hook='products_list_item'] a"
  element  :search_field,    "input#keywords"
  element  :search_dropdown, "select#taxon"
end

Let’s define the search_for method, which wraps actions such as selecting the drop down, filling in the search field, and clicking on the button. We are using Capybara’s methods, so if you are unfamiliar with them, you should definitely consult the documentation.

# rails_store/spec/support/pages/home_page.rb
class HomePage < SitePrism::Page
  # elements previously defined
  def search_for(query, scope='All departments')
    search_dropdown.select(scope)
    search_field.set(query)
    click_on('Search')
  end
end

Before we go carry on, I want to add another line to the code:

# rails_store/spec/support/pages/home_page.rb
class HomePage < SitePrism::Page
  # elements previously defined
  def search_for(query, scope='All departments')
    search_dropdown.select(scope)
    search_field.set(query)
    click_on('Search')
    SearchResults.new # <--------- Added this! -----
  end
end

What does this buy us? Having a method in the page object return another page object is a nice way to express the user’s journey through the site.

Let’s define the search results page:

class SearchResults < SitePrism::Page
  elements :products, "[data-hook='products_list_item']"
end

The search results page has a single elements that returns a collection of the elements that matches the "[data-hook='products_list_item']" selector.

Let’s take a look with the feature specs for more details:

#  rails_store/spec/features/home/search_products_spec.rb
require 'rails_helper'

feature 'Product Search' do
  # factories to load products into the test database
  include_context 'custom products'

  let(:home_page) { HomePage.new }

  before do
    home_page.load
  end

  scenario 'a search term is entered' do
    search_results = home_page.search_for('jersey')
    expect(search_results.products.size).to eq(3)
  end

  scenario 'search limited by dropdown' do
    search_results = home_page.search_for('mug', 'Categories')
    expect(search_results.products.size).to eq(1)
  end
end

We first set up the HomePage instance like so:

let(:home_page) { HomePage.new }

before do
  home_page.load
end

Note that we have to call #load on the page object in other to signal Capybara to load the page in a headless browser. Next, our first scenario:

scenario 'a search term is entered' do
  search_results = home_page.search_for('jersey')
  expect(search_results.products.size).to eq(3)
end

It tests a search term being entered into the search bar. Since it takes in only one argument (jersey), the selector box will be set to All Departments. The search button is clicked. Finally, a new SearchResult instance is returned.

Here’s an important point. Going by the user flow, once a user performs a search, she should be directed to another page. This is represented by the SearchResult instance. Since we have specified :products in the SearchResult class, we can use that in our assertion like so:

expect(search_results.products.size).to eq(3)

Learning More

I have only scratched the surface of what SitePrism can do. The SitePrism documentation is well-written. Spend 5 minutes reading those docs and I bet you will agree with me.

More Practice: Testing out the Home Page

In the GitHub repository, you will find rails_store/spec/features/home/list_products_spec.rb. Look at the methods used by the page object, and try to implement your own page object to make the tests pass.

scenario 'enters home page' do
  expect(home_page.product_permalinks).to include('ruby-on-rails-mug',
                                                  'ruby-on-rails-tote')
end

scenario 'clicks on a product' do
  product_link = home_page.product_links.first
  product_page = home_page.click_on_product_link(product_link)

  expect(product_page).to be_displayed

  expect(product_page.title).not_to be_empty
  expect(product_page.description).not_to be_empty
  expect(product_page.price).not_to be_empty
end

If you get stuck, you can always peek at the implementation I have at rails_store/spec/support/pages/home_page.rb.

Final Thoughts

I was never a big fan of Cucumber. I think SitePrism makes feature tests way more fun, and strikes a nice balance between readability and conciseness. I particularly like that I can represent a HTML page using an object, where interactions to the page are exposed through methods.

Another thing I like about page objects is the encapsulation they provide. For example, should I decide that I want to change the CSS selector of input#keywords to input#search, I would just need to modify the CSS selector in the respective element method.

That said, I think this is mainly a matter of personal taste. I’ve been enjoying using SitePrism on a couple of projects and encourage you to at least give it a shot.

Frequently Asked Questions (FAQs) about SitePrism and Page Objects Testing

What is SitePrism and why is it important in testing?

SitePrism is a Page Object Model (POM) DSL (Domain Specific Language) for Capybara, which is a web-based test automation software. It provides a simple, clean and semantic DSL for describing your site using the Page Object Model pattern, for use with Capybara in automated acceptance testing. It’s important in testing because it helps to create more maintainable and readable tests by separating the technical details of the UI from the interaction with the site.

How do I install SitePrism?

To install SitePrism, you need to have Ruby and RubyGems installed on your system. Once you have these prerequisites, you can install SitePrism by running the command gem install site_prism in your terminal. This will download and install the SitePrism gem onto your system.

How do I use SitePrism for testing?

SitePrism is used in conjunction with Capybara to create a robust testing framework. You start by defining a page object, which is a Ruby class that includes the SitePrism::Page module. This class represents a specific page on your site and contains the elements of that page. You can then use these elements in your tests to interact with your site.

What are the benefits of using SitePrism for testing?

SitePrism provides a number of benefits for testing. It allows you to create more maintainable and readable tests by separating the technical details of the UI from the interaction with the site. It also provides a simple, clean and semantic DSL for describing your site, making your tests easier to write and understand.

How do I define a page object in SitePrism?

In SitePrism, a page object is defined as a Ruby class that includes the SitePrism::Page module. This class represents a specific page on your site and contains the elements of that page. You define a page object by creating a new Ruby class, including the SitePrism::Page module, and then defining the elements of the page using the element method.

How do I interact with elements on a page using SitePrism?

Once you have defined a page object and its elements, you can interact with these elements in your tests. You do this by calling methods on the page object that correspond to the elements you want to interact with. For example, if you have an element defined as element :button, 'button', you can interact with this button by calling page.button.click.

Can I use SitePrism with other testing frameworks?

Yes, SitePrism is designed to work with Capybara, which is a web-based test automation software. However, it can also be used with other testing frameworks that support Capybara, such as RSpec and Cucumber.

How do I handle dynamic content with SitePrism?

SitePrism provides several methods for handling dynamic content, such as waiting for elements to appear or disappear. These methods allow you to write tests that can handle the asynchronous nature of modern web applications.

What is the site_prism-all_there gem?

The site_prism-all_there gem is an extension to SitePrism that provides additional methods for checking the presence of all defined elements on a page. This can be useful for ensuring that a page is fully loaded before interacting with it in your tests.

How do I contribute to the SitePrism project?

SitePrism is an open-source project, and contributions are welcome. You can contribute by reporting bugs, suggesting new features, or submitting pull requests. All contributions should follow the project’s contribution guidelines, which can be found on the project’s GitHub page.

Benjamin Tan Wei HaoBenjamin Tan Wei Hao
View Author

Benjamin is a Software Engineer at EasyMile, Singapore where he spends most of his time wrangling data pipelines and automating all the things. He is the author of The Little Elixir and OTP Guidebook and Mastering Ruby Closures Book. Deathly afraid of being irrelevant, is always trying to catch up on his ever-growing reading list. He blogs, codes and tweets.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week