Introduction
In this article, we’ll cover five tips for writing effective Capybara tests, and how to use them with RSpec and Minitest.
What is Capybara?
Capybara is an acceptance test framework for web applications. It’s a common choice for end-to-end, acceptance, or integration testing in Rails applications.
It allows developers to simulate a user on a web page and make assertions based on the content and environment of the page. It also provides an API to interact with the web page.
By default, it will run in headless mode using Rack::Test, but it can also use a number of other drivers, such as PhantomJS, to test pages with JavaScript.
Integration and Acceptance Testing
The terminology for integration and acceptance testing is quite broad within the Rails community. Most of the time Capybara is used in integration tests, e.g.feature specs in RSpec, to test all workflows in your web application from user perspective. The goal is to test whether all components of your application work together.
RSpec and Minitest
RSpec and Minitest are the two most commonly used test frameworks for Rails applications. Capybara works with both of them, but the syntax can differ slightly. We won’t go into details about RSpec and Minitest in this tutorial, however, if you’d like to know more about them you can check out our Getting Started with RSpec and Getting Started with Minitest tutorials.
Five Tips for Writing Capybara Tests
1. More Robust Element Search with Scopng
If you have a large page, scoping your Capybara page assertions can be considered a good practice. It can solve unexpected problems with duplicate content on the page.
For example, if you’re working on a component which has a sign up button, when there’s already a sign up button further up the page, you might end up never testing the second one unless you scope your page lookup query.
<div class="header-cta">
<a href="/signup" class="btn btn-signup">Sign up</a>
</div>
...
<div class="article-cta">
<a href="/signup" class="btn btn-signup">Sign up</a>
</div>
A way around this is to use within
to scope the lookup.
within('.article-cta') do
click 'Sign up'
end
The within
method also supports using XPath rather than element names, IDs, or classes.
within(:xpath, '//div[@class="article-cta"]') do
click 'Sign up'
end
If you’re performing only one action inside the block you pass into within
, the same can be accomplished using find
.
find('div#article-cta').click 'Sign up'
It’s worth mentioning that Capybara also has specific methods for scoping tables within_table
and fieldsets within_fieldset
. It’s also possible to use all the other matches supported by Capybara with within
, such as XPath, but it defaults to the CSS matcher.
2. Testing Pages that Run JavaScript
There are two ways of executing JavaScript using Capybara. Both require the use of a driver that can run JavaScript. One easy way of accomplishing this is to use PhantomJS via the Poltergeist gem.
First, install the gem by adding it to your gemfile.
gem 'poltergeist'
Then, load it in your test setup file (test_helper.rb
or spec_helper.rb
).
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
Once you’ve configured Capybara’s JavaScript driver, you can execute JavaScript. In this example, we use jQuery to change the text on the sign up button to register.
page.execute_script("$('a.btn-signup').text('Register');")
It’s also possible to evaluate JavaScript and store the return in a variable in Ruby. Here, we store the value in button_text
.
button_text = page.evaluate_script("$('a.btn-signup').text();")
These can have a variety of uses. However, it’s more common to just want Capybara to navigate a web page with JavaScript support.
With both RSpec and Minitest, this is not enabled by default, and we have to tell them to use Capybara with the JavaScript driver for indvidual tests that need it.
In RSpec, we add a js: true
as a second argument for describe
, scenario
, context
, and feature
.
scenario 'Doing something on a page with JavaScript', js: true do
# Anything you do in there will run JavaScript on pages.
end
The same without JavaScript support.
scenario 'Doing something on a page with JavaScript' do
# This test will not run any JavaScript on pages or otherwise.
end
In Minitest, we have to literally tell Capybara to switch drivers, and then switch back.
def test_with_javascript
Capybara.current_driver = :poltergeist
# Anything you do in there will run JavaScript on pages.
Capybara.use_default_driver
end
These could also be performed in Minitest’s setup
and teardown
methods, if every test in this file requires JavaScript.
def setup
Capybara.current_driver = :poltergeist
end
def test_with_javascript
# Anything you do in there will run JavaScript on pages.
end
def teardown
Capybara.use_default_driver
end
If you always need JavaScript in your tests, you could specify Poltergeist to be the default driver in Capybara.
Capybara.default_driver = :poltergeist
This also adds some safety for websites that use some JavaScript but not on all pages. It prevents tests failing because JavaScript is changing the state of the page but not being run in the test suite. There’s a trade-off here, because using drivers other than Rack::Test is significantly slower and can make your tests run for a long time if you have a lot of them.
3. Waiting for JavaScript-driven Elements
Now that you know all about running JavaScript in Capybara, you will eventually run into a problem where you execute some JavaScript on your page, but your assertions fail because Capybara runs them before the page has been updated.
Thankfully, the Capybara developers have provided us with tools to get around this. Capybara’s find
method waits by default for an element to appear for a certain amount of time. It also accepts count expectations which tell Capybara to wait for the expectations to match when using all
. There are four different count expectations. The first one matches on exact count.
all 'a.btn-signup', count: 1
Next two methods allow us to specify a minimum or maximum number of elements that need to be matched.
all 'a.btn-signup', maximum: 1
all 'a.btn-signup', minimum: 1
The final count expectation lets us provide a range and the number of matches must fall within this range.
all 'a.btn-signup', between: 1..1
Each of them has different uses, all
otherwise works the same as it always does. The default maximum time Capybara waits for the expectation to be fulfilled can be configured in your test setup file. The time is measured in seconds and the default value is two.
Capybara.default_max_wait_time = 5 # Seconds
This wait time can also be changed for individual find method calls. It can be helpful if your have a slow JavaScript call in your test.
all 'a.btn-signup', count: 1, wait: 5
4. Saving Pages for Debugging
Capybara has two different ways to help with debugging. Although something like pry is useful if you want to see the state of your variables, sometimes we want to know what is going on with the HTML on our web page.
For those situations, we can use save_and_open_page
or save_and_open_screenshot
. They are called in the same way with both RSpec and Minitest.
scenario 'debugging a failing test' do
visit root_path
save_and_open_page
# Rest of your test
end
This will save the HTML of the web page in a temporary file and open it in your browser. Styles will be missing, but it’s enough to see what state your web page is in and what’s going on to spot the problems. save_and_open_screenshot
works in exactly the same way, except that it opens an image file of your web page.
5. Use Different Element Matching Strategies
By default, Capybara matches on partial matches. Both of these click method calls match on the sign up button we created earlier.
click 'Sign'
click 'Sign up'
It’s possible to change this behavior by passing in exact: true
. This click method call does not match on the signup button.
click 'Sign', exact: true
It’s also possible to change this to be the default behavior in your test setup.
Capybara.exact = true
It’s also possible to configure how Capybara handles matching. There are four built in methods. The first one is called :first
and it makes find
always return the first match and ignore the rest.
Capybara.match = :first
The second one is :one
, which makes Capybara raise an error when there is more than one match. Using this setting can prevent many tests from breaking without your knowledge, because of more than one of the same elements on a page.
Capybara.match = :one
The third one is :prefer_exact
, which makes Capybara return an exact match if one exists, otherwise it will return a non-exact element.
Capybara.match = :prefer_exact
The final and default is :smart
. It always raises an error if there is more than one match, similar to :one
. However, for matching, the behavior changes depending on if Capybara.exact
is false
. When exact is false
, it tries to find an exact match, but if none is found it tries to find partial matches.
Capybara.match = :smart
Conclusion
In this article, we have covered five useful Capybara testing methods in detail.
If you found this article useful, you might want to check out other articles on testing in the Semaphore Community. We’d also like to hear your comments and questions, so feel free to leave them in the section below.
P.S. Would you like to learn how to build sustainable Rails apps and ship more often? We’ve recently published an ebook covering just that — “Rails Testing Handbook”. Learn more and download a free copy.