Contents
Overview
PrismChecker is an extension for rspec and minitest, built on top of the SitePrism gem and using its page object model. It allows you to write short, easy-to-read browser tests with clear error messages
Requires Ruby >= 3.3 and site_prism >= 5.0.
Let's assume your application has the following shop cart:

- Corresponding SitePrism PDO
class Cart < SitePrism::Page
set_url '/cart.html'
element :header, 'h1'
sections :cart_items, '[data-test="cart-item"]' do
element :name, '[data-test="cart-item-name"]'
element :image, '[data-test="cart-item-image"]'
element :price, '[data-test="cart-item-price"]'
element :quantity, '[data-test="cart-item-quantity"]'
element :total, '[data-test="cart-item-total"]'
end
section :checkout, '[data-test="cart-checkout"]' do
element :total, '[data-test="cart-checkout-total"]'
element :checkout_button, '[data-test="cart-checkout-button"]'
end
end
A typical test would look like this:
describe 'Cart' do
it 'is correct' do
page = Cart.new
page.load
expect(page.header.text).to eq('Shopping Cart')
expect(page.cart_items.size).to eq(2)
expect(page.cart_items[0].visible?).to be_truthy
expect(page.cart_items[0].name.text).to match('Cup')
expect(page.cart_items[0].quantity.value).to match('1')
expect(page.cart_items[0].image[:src]).to match('cup.png')
expect(page.cart_items[0].price.text).to match('19.00')
expect(page.cart_items[0].total.text).to match('19.00')
expect(page.cart_items[1].visible?).to be_truthy
expect(page.cart_items[1].name.text).to match('Cap')
expect(page.cart_items[1].quantity.value).to match('2')
expect(page.cart_items[1].image[:src]).to match('cap.png')
expect(page.cart_items[1].price.text).to match('24.00')
expect(page.cart_items[1].total.text).to match('48.00')
expect(page.checkout.visible?).to be_truthy
expect(page.checkout.total.text).to match('67.99')
expect(page.checkout..text).to match('Checkout')
end
end
Using gem prism_checker, the same test will look much cleaner and simpler:
describe 'Cart' do
it 'is correct' do
page = Cart.new
page.load
expect(page).to be_like(
header: 'Shopping Cart',
cart_items: [
{
name: 'Cup',
quantity: '1',
image: 'cup.png',
price: '19.00',
total: '19.00'
},
{
name: 'Cap',
quantity: '2',
image: 'cap.png',
price: '24.00',
total: '48.00'
}
],
checkout: {
total: '67.00', # deliberately wrong, to show the failure report below
checkout_button: 'Checkout'
}
)
end
end
In case of errors, an easy-to-read message will be displayed. Every key is reported with its own status, so the failing one is visible at a glance:

Install
To install PrismChecker:
RSpec
gem install prism_checker_rspec
MiniTest
gem install prism_checker_minitest
Setup
PrismChecker drives the browser through SitePrism and Capybara, so a Capybara driver has to be registered as usual. Only the last two lines are PrismChecker-specific.
RSpec
Adds the be_like matcher:
# Gemfile
gem 'prism_checker_rspec'
# spec/spec_helper.rb
require 'capybara/rspec'
require 'selenium-webdriver'
require 'site_prism'
require 'prism_checker_rspec'
Capybara.register_driver :prism_checker do |app|
= Selenium::WebDriver::Chrome::Options.new
.add_argument('--headless=new')
Capybara::Selenium::Driver.new(app, browser: :chrome, options: )
end
Capybara.configure do |config|
config.default_driver = :prism_checker
config.app_host = 'http://localhost:3000'
# Needed so that :invisible expectations can see hidden elements
config.ignore_hidden_elements = false
end
# spec/cart_spec.rb
describe 'Cart' do
it 'is correct' do
page = Cart.new
page.load
expect(page).to be_like(header: 'Shopping Cart')
end
end
MiniTest
Adds the assert_page_like assertion:
# Gemfile
gem 'prism_checker_minitest'
# test/test_helper.rb
require 'minitest/autorun'
require 'capybara/minitest'
require 'site_prism'
require 'prism_checker_minitest'
# ...same Capybara.register_driver / Capybara.configure as above
# test/cart_test.rb
class CartTest < Minitest::Test
include Capybara::DSL
include Capybara::Minitest::Assertions
def test_cart
page = Cart.new
page.load
assert_page_like page, header: 'Shopping Cart'
end
end
Cheat sheet
What gets checked depends on the type of the expectation and on the inspected item.
| Expectation | Checked against |
|---|---|
'text' |
text of an element, section or page; value of an input, textarea or select; src of an image |
/regexp/ |
the same values, matched as a regular expression |
42 |
size of elements/sections |
[...] |
size of elements/sections, then each entry in turn |
{...} |
element is visible, then each key in turn |
true / false |
checked? of a checkbox or radio |
:visible |
visible? |
:invisible |
visible? is false |
:absent |
has_no_<name>? |
:absent3 |
sleep 3 seconds, then has_no_<name>? |
:empty |
value is an empty string |
Inside a {...} expectation, keys map to Capybara methods (text, value, checked, disabled,
readonly, selected, multiple) or, failing that, to HTML attributes (class, id, src, alt,
data-*, ...).
A page or element is always checked for being loaded or visible before its value is compared, so those assertions never need to be spelled out.
Examples
Common cases
Page and Page Object Model:

class Cart < SitePrism::Page
element :header, 'h1'
sections :cart_items, '[data-test="cart-item"]' do
element :name, '[data-test="cart-item-name"]'
element :price, '[data-test="cart-item-price"]'
end
section :checkout, '[data-test="cart-checkout"]' do
element :total, '[data-test="cart-checkout-total"]'
element :checkout_button, '[data-test="cart-checkout-button"]'
end
end
Text check
@page = Cart.new
page.load
expect(page).to be_like('Shopping Cart')
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.text).to match('Shopping Cart')
Element and section check
@page = Cart.new
page.load
expect(page).to be_like(
header: 'Shopping Cart',
checkout: {
total: '67.99',
checkout_button: 'Checkout'
}
)
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.header.visible?).to eq(true)
# expect(page.header.text).to match('Shopping Cart')
# expect(page.checkout.visible?).to eq(true)
# expect(page.checkout.total.visible?).to eq(true)
# expect(page.checkout.total.text).to match('67.99')
# expect(page.checkout.checkout_button.visible?).to eq(true)
# expect(page.checkout.checkout_button.text).to match('Checkout')
Element check
@page = Cart.new
page.load
# page.header is inspected, not page
expect(page.header).to be_like('Shopping Cart')
# Same as
# expect(page.header.visible?).to eq(true)
# expect(page.header.text).to match('Shopping Cart')
@page = Cart.new
page.load
# page is inspected
expect(page).to be_like(header: 'Shopping Cart')
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.header.visible?).to eq(true)
# expect(page.header.text).to match('Shopping Cart')
Section check
expect(page.checkout).to be_like(
total: '67.99',
checkout_button: 'Checkout'
)
# Same as
# expect(page.checkout.visible?).to eq(true)
# expect(page.checkout.total.visible?).to eq(true)
# expect(page.checkout.total.text).to match('67.99')
# expect(page.checkout.checkout_button.visible?).to eq(true)
# expect(page.checkout.checkout_button.text).to match('Checkout')
expect(page).to be_like(
checkout: {
total: '67.99',
checkout_button: 'Checkout'
}
)
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.checkout.visible?).to eq(true)
# expect(page.checkout.total.visible?).to eq(true)
# expect(page.checkout.total.text).to match('67.99')
# expect(page.checkout.checkout_button.visible?).to eq(true)
# expect(page.checkout.checkout_button.text).to match('Checkout')
Sections check
expect(page).to be_like(
cart_items: [
{
name: 'Cup',
image: 'cup.png'
},
{
name: 'Cap',
image: 'cap.png'
}
])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.cart_items.size).to eq([{:name=>"Cup", :image=>"cup.png"}, {:name=>"Cap", :image=>"cap.png"}].size)
# expect(page.cart_items[0].visible?).to eq(true)
# expect(page.cart_items[0].name.visible?).to eq(true)
# expect(page.cart_items[0].name.text).to match('Cup')
# expect(page.cart_items[0].image.visible?).to eq(true)
# expect(page.cart_items[0].image['src']).to match('cup.png')
# expect(page.cart_items[1].visible?).to eq(true)
# expect(page.cart_items[1].name.visible?).to eq(true)
# expect(page.cart_items[1].name.text).to match('Cap')
# expect(page.cart_items[1].image.visible?).to eq(true)
# expect(page.cart_items[1].image['src']).to match('cap.png')
expect(page).to be_like(cart_items: 2)
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.cart_items.size).to eq(2)
expect(page).to be_like(cart_items: [{name: 'Cup'}, {}])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.cart_items.size).to eq([{:name=>"Cup"}, {}].size)
# expect(page.cart_items[0].visible?).to eq(true)
# expect(page.cart_items[0].name.visible?).to eq(true)
# expect(page.cart_items[0].name.text).to match('Cup')
# expect(page.cart_items[1].visible?).to eq(true)
Warning! It is not possible to pass directly array:
expect(page.cart_items).to be_like([{name: 'Cup'}, {name: 'Cap'}]) # Error
expect(page).to be_like(cart_items: [{name: 'Cup'}, {name: 'Cap'}]) # Ok
Elements check
expect(page).to be_like(
items: [
'Item 1',
'Item 2'
])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.items.size).to eq(["Item 1", "Item 2"].size)
# expect(page.items[0].visible?).to eq(true)
# expect(page.items[0].text).to match('Item 1')
# expect(page.items[1].visible?).to eq(true)
# expect(page.items[1].text).to match('Item 2')
expect(page).to be_like(items: 2)
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.items.size).to eq(2)
HTML elements
Page Object Model:
class HtmlElements < SitePrism::Page
set_url '/html_elements.html'
element :input, 'input.input'
element :textarea, 'textarea.textarea'
element :button, 'button'
element :button_input, 'input.button'
element :image, 'img.image'
elements :radios, 'input.radio'
elements :checkboxes, 'input.checkbox'
elements :selects, 'select.select'
end
Input
expect(page).to be_like(input: 'Some text')
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.input.visible?).to eq(true)
# expect(page.input.value).to match('Some text')
expect(page.input).to be_like(
value: 'Some text',
class: 'input',
readonly: false,
disabled: false
)
# Same as
# expect(page.input.visible?).to eq(true)
# expect(page.input.value).to match('Some text')
# expect(page.input[:class]).to match('input')
# expect(page.input.readonly?).to eq(false)
# expect(page.input.disabled?).to eq(false)
Button
expect(page).to be_like(button: 'Button')
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.button.visible?).to eq(true)
# expect(page.button.text).to match('Button')
expect(page.).to be_like(
text: 'Button',
disabled: false
)
# Same as
# expect(page.button.visible?).to eq(true)
# expect(page.button.text).to match('Button')
# expect(page.button.disabled?).to eq(false)
Textarea
expect(page).to be_like(textarea: 'Some text')
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.textarea.visible?).to eq(true)
# expect(page.textarea.value).to match('Some text')
expect(page.textarea).to be_like(
value: /Some text/,
class: 'textarea'
)
# Same as
# expect(page.textarea.visible?).to eq(true)
# expect(page.textarea.value).to match(/Some text/)
# expect(page.textarea[:class]).to match('textarea')
Image
expect(page).to be_like(image: /logo.png$/)
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.image.visible?).to eq(true)
# expect(page.image['src']).to match(/logo.png$/)
expect(page.image).to be_like(
src: 'logo.png',
class: 'logo',
alt: 'Logo'
)
# Same as
# expect(page.image.visible?).to eq(true)
# expect(page.image.src).to match('logo.png')
# expect(page.image[:class]).to match('logo')
# expect(page.image[:alt]).to match('Logo')
Radio
expect(page).to be_like(radios: [
{
checked: false,
name: 'radio1'
},
{
checked: true,
},
{
checked: false,
},
])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.radios.size).to eq([{:checked=>false, :name=>"radio1"}, {:checked=>true}, {:checked=>false}].size)
# expect(page.radios[0].visible?).to eq(true)
# expect(page.radios[0].checked?).to eq(false)
# expect(page.radios[0].name).to match('radio1')
# expect(page.radios[1].visible?).to eq(true)
# expect(page.radios[1].checked?).to eq(true)
# expect(page.radios[2].visible?).to eq(true)
# expect(page.radios[2].checked?).to eq(false)
expect(page).to be_like(radios: [
false,
true,
false
])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.radios.size).to eq([false, true, false].size)
# expect(page.radios[0].visible?).to eq(true)
# expect(page.radios[0].checked?).to eq(false)
# expect(page.radios[1].visible?).to eq(true)
# expect(page.radios[1].checked?).to eq(true)
# expect(page.radios[2].visible?).to eq(true)
# expect(page.radios[2].checked?).to eq(false)
Check box
expect(page).to be_like(checkboxes: [
{
checked: false,
id: "checkbox-not-checked"
},
{
checked: true,
}
])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.checkboxes.size).to eq([{:checked=>false, :id=>"checkbox-not-checked"}, {:checked=>true}].size)
# expect(page.checkboxes[0].visible?).to eq(true)
# expect(page.checkboxes[0].checked?).to eq(false)
# expect(page.checkboxes[0].id).to match('checkbox-not-checked')
# expect(page.checkboxes[1].visible?).to eq(true)
# expect(page.checkboxes[1].checked?).to eq(true)
expect(page).to be_like(checkboxes: [
false,
true
])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.checkboxes.size).to eq([false, true].size)
# expect(page.checkboxes[0].visible?).to eq(true)
# expect(page.checkboxes[0].checked?).to eq(false)
# expect(page.checkboxes[1].visible?).to eq(true)
# expect(page.checkboxes[1].checked?).to eq(true)
expect(page.checkboxes[0]).to be_like(
checked: false,
id: "checkbox-not-checked"
)
# Same as
# expect(page.checkboxes[0].visible?).to eq(true)
# expect(page.checkboxes[0].checked?).to eq(false)
# expect(page.checkboxes[0].id).to match('checkbox-not-checked')
Select
expect(page).to be_like(selects: [
{
value: /^$/,
id: 'select-not-selected'
},
{
value: 'option2',
id: 'select-selected'
}
])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.selects.size).to eq([{:value=>/^$/, :id=>"select-not-selected"}, {:value=>"option2", :id=>"select-selected"}].size)
# expect(page.selects[0].visible?).to eq(true)
# expect(page.selects[0].value).to match(/^$/)
# expect(page.selects[0].id).to match('select-not-selected')
# expect(page.selects[1].visible?).to eq(true)
# expect(page.selects[1].value).to match('option2')
# expect(page.selects[1].id).to match('select-selected')
Visibility
Page Object Model:
class Visibility < SitePrism::Page
set_url '/visibility.html'
elements :list_items, 'li'
section :article, 'article' do
# ...
end
end
Element or section
expect(page).to be_like(article: :invisible)
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.article.invisible?).to eq(true)
Elements or sections
expect(page).to be_like(list_items: [
'Element 1',
:invisible,
:invisible,
:visible
])
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.list_items.size).to eq(["Element 1", :invisible, :invisible, :visible].size)
# expect(page.list_items[0].visible?).to eq(true)
# expect(page.list_items[0].text).to match('Element 1')
# expect(page.list_items[1].invisible?).to eq(true)
# expect(page.list_items[2].invisible?).to eq(true)
# expect(page.list_items[3].visible?).to eq(true)
Absence
Page Object Model:
class Absence < SitePrism::Page
set_url '/absence.html'
element :article, 'article'
end
Element or section
expect(page).to be_like(article: :absent)
# Same as
# expect(page.loaded?).to eq(true)
# expect(page.has_no_article?).to eq(true)
Delay before checking absence
Sometimes we need to sleep before checking absence. Use :absent[delay in seconds]
expect(page).to be_like(article: :absent3)
# Same as
# expect(page.loaded?).to eq(true)
# sleep 3
# expect(page.has_no_article?).to eq(true)
Configuration
String comparison
Exact match and inclusion
By default the inclusion of a substring in a string is checked
PrismChecker.string_comparison = :inclusion # default value
expect(page.header).to be_like('Shopping Cart')
# Same as
# expect(page.header.visible?).to eq(true)
# expect(page.header.text).to match('Shopping Cart')
To compare strings exactly, use :exact
PrismChecker.string_comparison = :exact
expect(page.header).to be_like('Shopping Cart')
# Same as
# expect(page.header.visible?).to eq(true)
# expect(page.header.text).to eq('Shopping Cart')
Empty string
When the comparison method is :inclusion, and it is necessary to compare with an empty string, it can be done in this way
expect(page.element_without_text).to be_like(:empty)
# Same as
# expect(page.element_without_text.text.empty?).to eq(true)
or
expect(page.element_without_text).to be_like(/^$/)
# Same as
# expect(page.element_without_text.visible?).to eq(true)
# expect(page.element_without_text.text).to match(/^$/)
Colorizer
Failure reports are colorized through PrismChecker.colorizer. prism_checker_rspec sets one that
uses the RSpec console codes; the default of the core gem returns text unchanged.
A colorizer answers colorize(text, code), where code is one of :success, :failure,
:detail or :white:
class MyColorizer
CODES = { success: 32, failure: 31, detail: 33, white: 37 }.freeze
def self.colorize(text, code)
"\e[#{CODES[code]}m#{text}\e[0m"
end
end
PrismChecker.colorizer = MyColorizer