Testing with Rails and Capybara: Methods that Wait, Methods that Won’t

The Problem

You’ve started doing automated testing of your key front-end features using a real browser, e.g. Capybara + Selenium & Firefox.

Testing is going well, but you are often slowed down by intermittent failures that are hard or impossible to reproduce consistently.

You’re testing a remote (AJAX) form and looking to check that the page updates as it should, and you sometimes get errors like:

Selenium::WebDriver::Error::StaleElementReferenceError:
   Element not found in the cache - perhaps the page has changed since it was looked up

Or the test simply fails, unable to find the updated content on the page.

The Solution

While there could be all manner of things wrong with the test, or the test setup, one of the first things to check is, are you using any Capybara methods which don’t wait?

The list of methods which wait, and those that won’t, is far from intuitive. Let’s take a look (reference):

Methods that Wait

  • find(selector), find_field, find_link, etc.
  • within(selector) (scoping)
  • has_selector?, has_no_selector? & assertions
  • form & link actions
    • click_link, click_button
    • fill_in
    • check, uncheck, select, choose, etc.

Methods that Won’t

  • visit(path)
  • current_path
  • all(selector)
  • first(selector) (counter-intuitively)
  • execute_script, evaluate_script
  • simple accessors: text, value, title, etc.

So the absolute first step in diagnosing a possible race condition, assuming your setup is known to be sound, is to check that you haven’t accidentally used one of these Capybara methods that won’t wait for page content update.

Common Issue: Resolving Ambiguous Selectors

A common case where this might arise is where you look for a specific element:

page.find("form.edit_task").find("input").click

However, you have multiple matches and you get the following error:

Capybara::Ambiguous: Ambiguous match, found 2 elements matching css "form.edit_task"

For the test in question let’s say that is isn’t important which edit_task form we work with, so to resolve the ambiguity we change the matcher to:

page.first("form.edit_task").find("input").click

We have now resolved the ambiguity by using first instead of find but have now subtly introduced a race condition, due to first being on the list of Capybara matchers that won’t wait.

A Better Solution

The simplest way to resolve this is to use a more specific selector, e.g.

page.find("#overdue-tasks tr.task_#{task.id} form input").click

Not only will this make your application easier to test it will also make it more straightforward to access elements via jQuery.

If for some reason you can’t change the HTML, then you might try XPath instead, where you can use subscripting on the selector to get the nth element, in this case the first table row:

page.find(:xpath, "(//tr[@class='task_#{task.id}'])[1]").find("form input").click

To avoid needing to write XPath however, and to make testing easier overall, I would instead recommend that you keep your HTML DOM clear, well organised and correct (e.g. ensure that all element ids used are unique).

Beyond this, there are unfortunately many other reasons that your tests might be failing, intermittently or consistently. For some more discussion of this you can take a look at this post on setting up a robust integration testing suite with RSpec, Capybara and Selenium.