5 tips for more effective capybara tests

5 Tips for More Effective Capybara Tests

Capybara is an acceptance testing framework for web applications. Here are five useful tips for writing better tests using Capybara.

Cut your Rails test suite down to a few minutes with one-click automatic parallelization.

Automate parallelizing tests

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. Semaphore is working on a book "The Ultimate Guide to BDD with Rails". Sign up to receive a FREE copy.

51cee7009c304faca416b4475bd42446
Heidar Bernhardsson

Heidar is a London-based software engineer from Iceland. He's the founder of PressPanda. For more information about Heidar, visit his website.

on this tutorial so far.
User deleted author {{comment.createdAt}}

Edited on {{comment.updatedAt}}

Cancel

Sign In You must be logged in to comment.