24 Nov 2021 · Software Engineering

    5 Tips for More Effective Capybara Tests

    9 min read
    Contents

    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.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    mm
    Writen by:
    I'm a London based Ruby consultant. Visit my website for more information.