rails
Simplify your Capybara selectors
How to use a Ruby-friendly syntax alternative to esoteric CSS and xpath expressions. Plus, a trick for making Capybara automatically aware of data-testid
attributes.
Attribute selectors in browser tests
When writing browser tests in Rails with capybara, sometimes you’ll need to find an element based on an HTML attribute. A common use-case is when an element is explicitly marked with data-testid
to make it easier to find in automated tests.
Consider this markup:
<div data-testid="bio-card" class="max-w-sm rounded overflow-hidden shadow-lg">
<div class="px-6 py-4">
<p class="text-gray-700 text-base">
Matt Brictson is a software engineer in San Francisco.
</p>
</div>
</div>
data-testid
.
Let’s say we want to write an assertion about the text contained in that card. We can use the data-testid
to find it.
expect(find('[data-testid="bio-card"]')).to have_text(/software engineer/)
find
method can take a CSS selector. In this case we’re using the square-bracketed attribute selector syntax.
This works, but the syntax is not ideal: it requires a mixture of nested single- and double-quotes, plus square brackets that must be placed just so, and it is not particularly easy to read.
Thankfully Capybara’s find
method has a better way to write this, which I’ll explain next.
The :element
strategy
The find
method in Capybara is a lot more capable than it appears at first glance. By default, it accepts a CSS expression, but other strategies are available:
# css expression (implicit)
find('[data-testid="bio-card"]')
# css expression (explicit)
find(:css, '[data-testid="bio-card"]')
# xpath expression
find(:xpath, "//*[@data-testid='bio-card']")
:css
and :xpath
being the most familiar.
The :element
strategy is one that works particular well to find HTML attributes like data-testid
. Here’s how to use it:
expect(find(:element, "data-testid": "bio-card")).to have_text(/software engineer/)
:element
selector takes an arbitrary hash of attribute names and values. Values can be strings or regular expressions.
This looks more like idiomatic Ruby. It’s easier to read and write than CSS and xpath expressions, which always feel a bit out of place within Ruby code. The syntax is also familiar because it is similar to Rails tag helpers.
tag.div("data-testid": "bio-card")
# => '<div data-testid="bio-card"></div>'
find(:element, "data-testid": "bio-card")
# => Capybara::Node::Element
Of course, the :element
selector strategy is not limited to data-testid
.
# Password field should be empty
expect(page).to have_selector(:element, "input", type: "password", value: "")
:element
selector also accepts the name of the HTML element to search for, and can take multiple attribute matchers.
Capybara’s test_id
magic
The :element
strategy is a great way to find elements. But for clicking and interacting with elements, Capybara has one more more trick up its sleeve: the test_id
configuration option.
Capybara.configure do |config|
config.test_id = "data-testid"
end
test_id
option is nil
by default. When specified, Capybara will automatically search for HTML elements by that attribute name when performing click
, fill_in
, select
, and other actions.
The test_id
option doesn’t affect the default behavior of find
, but it does simplify interactions with buttons, links, and form fields.
# Entering text into a form field identified by data-testid="username"
find(:element, "data-testid": "username").fill_in(with: "matt")
# Can be simplified to this
fill_in "username", with: "matt"
<label>
, or arial-label
value). In rare cases where data-testid
is the only option, the implicit selector comes in handy.
This might be too much magic for some, but is a powerful option, especially if your frontend code relies heavily on data-testid
attributes.
Resources
The Capybara configuration and best practices explained in this blog post are included in nextgen, my interactive Rails application generator. Check it out when starting your next project.
Additional references:
- Making your UI tests resilient to change (Kent C. Dodds explains
data-testid
) - Capybara’s configuration options (API docs)
- Capybara’s over 20 selector strategies (API docs)