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:
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 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.