The Basics of Capybara and Improving Your Tests

Capybara is a web-based automation framework used for creating functional tests that simulate how users would interact with your application. Today, there are many alternatives for web-based automation tools, such as Selenium, Watir, Capybara, etc. All of these tools have the same purpose, but there are slight differences that make each of them more or less suitable.

The main characteristic that developers are aiming for is the ability to have tests that are modular, easy to write, and easy to maintain. This is especially true in Agile/TDD environments where writing tests is second nature. These tests are expected to give good and fast feedback on code quality. As time goes by, the number of tests grows and it can be a real nightmare to maintain the tests, especially when the tests are not modular and simple enough.

In this tutorial, I will describe some of Capybara key features and explain why it could be your tool of choice for developing web-based automated tests.

Anatomy of Capybara

Just like Watir, Capybara is a library/gem built to be used on top of an underlying web-based driver. It offers a user-friendly DSL (Domain Specific Language) which is used to describe actions that are executed by the underlying web driver.

When the page is loaded using the DSL (and underlying web driver), Capybara will try to locate the relevant element in the DOM (Document Object Model) and execute the action, such as click button, link, etc.

Here are some of the web drivers supported by Capybara:

rack::test:
By default, it works with rack::test driver. This driver is considerably faster than other drivers, but it lacks JavaScript support and it cannot access HTTP resources outside of the application for which the tests are made (Rails app, Sinatra app).

selenium-webdriver:
Capybara supports selenium-webdriver, which is mostly used in web-based automation frameworks. It supports JavaScript, can access HTTP resources outside of application, and can also be setup for testing in headless mode which is especially useful for CI scenarios. To setup usage of this driver you need to add:Capybara.default_driver = :selenium

capybara-webkit:
For true headless testing with JavaScript support, we can use the capybara-webkit driver (gem). It uses QtWebKit and it is significantly faster than selenium as it does not load the entire browser. To setup usage of this driver, you need to add:Capybara.default_driver = :webkit

Installation

Capybara supports Ruby versions >= 1.9 and can be used in two modes: UI and headless.

Advanced DSL

For scenarios where basic the DSL cannot help, we use xpath and CSS selectors (CSS selectors will be used by default). This is very common, since modern web applications usually have a lot of JavaScript code that generates HTML elements with attributes that have generated values (like random ids, etc.)

To find a specific element and click on it we can use:

find('xpath/css').click

To get text from specific element use:

find('xpath/css').text

To use xpath selectors, simply change the following configuration value:

Capybara.default_selector = :xpath

The selector type can be specified, if necessary:

find(:xpath, 'actual_xpath')

Matchers

When trying to find an element either using the DSL or xpath/CSS selectors, it is common to have two or more matches which will cause Capybara to fail with an Ambiguous match error. Also, Capybara can perform partial matches, leading to unexpected results. Because of that, it is important to be aware of matching strategies supported by Capybara:

:one – raises an error when more than one match found

:first – simply picks the first match – Don’t use this!

:prefer_exact – finds all matching elements, but will return only an exact match discarding other matches

:smart – depends on the value for Capybara.exact. If set to true, it will behave like :one. Otherwise, it will first search for exact matches. If multiple matches are found, an ambiguous exception is raised. If none are found, it will search for inexact matches and again raise an ambiguous exception when multiple inexact matches are found.

Scoping

To avoid unexpected behaviour (like ambiguous matches), it is a good practice to scope part of the DOM in which some action will be performed:

within(:xpath, 'actual_xpath') do
fill_in 'Name', :with => 'John'
end

Capybara will first search the DOM with the xpath selector. Then, inside this part of the DOM, it will do an additional search for the text field.

Custom Selectors

It is also possible to define your own custom selectors. This can be very useful when we want to make modular selectors that can be reused in many scripts. Here is an example:

Here are some best practices around Capybara DSL usage, custom selectors, and scoping and matching:

Scope part of the DOM on which action is performed.

Inside this scope prefer Capybara DSL when possible.

If it’s not possible, use relative xpath/CSS selectors within the scope.

xpath/CSS selectors should be as shallow as possible inside the scope.

JavaScript and Handling Asynchronous Calls

Capybara has an internal mechanism for handling asynchronous loading of parts of the page. A good example is clicking on some part of the page causing JavaScript to be executed that creates a new element on the page.

click_link('foo')
click_link('bar')

Here, let’s say clicking on the link ‘foo’ triggers an asynchronous process, like an Ajax request, that adds the link ‘bar’. It’s likely that second statement will fail since the link does not exist yet.

However, Capybara has a mechanism for handling these situations where it retries finding the element for a brief period of time before throwing an error. The default time is 2 seconds and can be changed with property:

Capybara.default_wait_time

There is no silver bullet for handling these cases. On one hand, it is a good practice to hide the complexity of the wait mechanism from the creator of the test script so the syntax is cleaner and easier to understand. On the other hand, sometimes it is better to have more control over the wait mechanism and handle it yourself.

In any case, this wait issue is often the cause of unexpected failures in the script. This is likely a good indicator that the implementation has either performance or other issues.

Capybara also supports execution of JavaScript via:

page.execute_script()

where JavaScript code can be passed like this:

page.execute_script("$('#area button.primary').click()")

Using Capybara with RSpec

Capybara naturally fits with RSpec and no special integration is needed.

describe "Create place scenario" do
context "Go to home page" do
it "opens homepage" do
visit(get_homepage)
end
end
context "Click on create object link" do
it "opens create new object form" do
find(:homepage_navigation_create_object).click
end
end
end

So far, we have seen how we can use the DSL to interact with page elements, but there is one caveat: With every new context, a new Capybara session will be instantiated which is probably not what we want.Capybara::Session.new :driver helps manage this issue. For example:

The session represents a single user interaction with the application, which is precisely what we want.

Good practice: Define a method in spec_helper.rb which will return the session object and use this session object when calling all Capybara methods inside RSpec context (session.visit, session.find, etc.)

There is another method, which I think is more useful for debugging: the pry and pry-debugger gems.

These gems will let you inspect script execution in an actual debugger, allowing you to set breakpoints, go next, step into, continue, etc. To use them, do the following:

gem install pry-debugger (automatically installs pry as well)

require 'pry' in your test script or spec helper

add binding.pry to the script to set a breakpoint and then, execute the test script. Execution will paus on the breakpoint. At this point, use next (to go to next line), step (to step into definition), continue (to continue execution) to debug as necessary.

Wrapping Up

I’ve briefly run through the basic of Capybara and how to customize it to meet your needs. Testing is a way of like in the Ruby community, and Capybara is one of the tools of choice. Test well, my friends.

IT professional with 4+ years experience in software automation and testing who is currently employed in AtlantBH. Always willing to learn new technologies and improve my skills as well as improve existing software solutions on which I'm working