page_match
RSpec 2 matcher class for building custom Capybara matchers for use in acceptance-level tests.
Install
page_match is distributed as a Ruby gem:
sudo gem install page_match
Using page_match
With the rise in popularity of RSpec + Capybara as an acceptance (aka. request-level) test replacement for Cucumber, using custom matchers can go a long way to clean up your examples, as well as improve your test readability.
Consider the following HTML, which is rendered as the home page of a standard Rack-based web app:
<html>
<head><title>Home Page</title></head>
<body>
<div id="#logout-link">
<a href="/logout">Logout Joe User</a>
</div>
<h1 id="#main-header">Welcome Joe User</h1>
</body>
</html>
One fairly common acceptance test would be to verify the text rendered for the logout link and the main header to make sure they include the correct user's name. In vanilla RSpec that might look like this:
describe "Visiting the home page", :type => :request do
before(:each) do
@user = User.login(:name => "Joe User")
end
context "The home page" do
before(:each) do
visit(root_path)
end
it "should have the correct logout link" do
within("#logout-link") do
page.should have_content("Logout #{@user.name}")
end
end
it "should have the correct main header" do
within("#main-header") do
page.should have_content("Welcome #{@user.name}")
end
end
end
end
Running these examples with the --format documentation option turned on, gives us:
Visiting the home page
The home page
should have the correct logout link
should have the correct main header
On the surface, this example code and the documentation output is just fine. However the code is both hard to refactor and extremely verbose, with the HTML structure traversal code directly in the examples. In addition, the output lines that describe the examples are very generic.
Using page_match we can create helper methods that wrap the matcher logic for our examples, giving us methods that we can reuse and letting us construct much better example descriptions:
module MatchHelpers
def have_logout_link_for(name)
PageMatch.match do |pm|
pm.have %(a logout link for "#{name}")
pm.page { within ("#logout-link") { has_content?("Logout #{name}") } }
end
end
def have_main_header_for(name)
PageMatch.match do |pm|
pm.have %(a main header for "#{name}")
pm.page { within ("#main-header") { has_content?("Welcome #{name}") } }
end
end
end
With these helper methods in place, we can now rewrite the acceptance test:
describe "Visiting the home page", :type => :request do
before(:each) do
@user = User.login(:name => "Joe User")
end
subject { page }
context "The home page" do
before(:each) do
visit(root_path)
end
it { should have_logout_link_for(@user.name) }
it { should have_main_header_for(@user.name) }
end
end
When we run this example file, with the --format documentation option, we get:
Visiting the home page
The home page
should have a logout link for "Joe User"
should have a main header for "Joe User"
The resulting test file is now easier to read, contains examples that make use of custom, domain specific matchers, that when output provide us with context-specific descriptions for those examples.
Included Helper Methods
When you install page_match you will also get a set of included helper methods, for the most common types of rendered page inspections. These include:
have_link
it { should have_link(<Link Label>) }
have_button
it { should have_button(<Button Label>) }
have_flash_notice
it { should have_flash_notice(<Flash Text>) }
have_form_error
it { should have_form_error(<Error Text>) }
have_text_field
it { should have_text_field(:form_name, :field_name) }
have_text_area
it { should have_text_area(:form_name, :field_name) }
have_check_box
it { should have_check_box(:form_name, :field_name) }
have_select_field
it { should have_select_field(:form_name, :field_name) }