Improving your tests with Capybara custom selectors

Here at PlataformaTec we like to use Capybara for acceptance tests. Recently we have discovered the custom selectors feature in Capybara and we would like to share with you how that feature helped us to improve our tests.

Sometimes we need to implement features that involves showing some ordered items to the user, like a ranking feature. The HTML for a feature like that could be:

<ol id="overall-ranking">
  <% @top_users.each do |user| %>
    <li><%= user.name %></li>
  <% end %>
</ol>

The acceptance tests for this ranking could be written as follows:

scenario "The user can see an overall ranking" do
  Factory(:user, :name => "Hugo",  :score => 5000)
  Factory(:user, :name => "Ozaki", :score => 3000)
  Factory(:user, :name => "João",  :score => 4000)

  visit overall_ranking_path

  within("#overall-ranking") do
    find(:xpath, './/li[1]').text.should match("Hugo")
    find(:xpath, './/li[2]').text.should match("João")
    find(:xpath, './/li[3]').text.should match("Ozaki")
  end
end

Generally, I don’t like to see those XPath selectors inside my acceptance tests. And sometimes it can get really ugly! So, in order to improve our tests, we can create a custom selector with Capybara as follows:

# spec/spec_helper.rb

RSpec.configure do |config|

  Capybara.add_selector(:li) do
    xpath { |num| ".//li[#{num}]" }
  end

end

After that, we can refactor our test as shown below:

scenario "The user can see an overall ranking" do
  Factory(:user, :name => "Hugo",  :score => 5000)
  Factory(:user, :name => "Ozaki", :score => 3000)
  Factory(:user, :name => "João",  :score => 4000)

  visit overall_ranking_path

  within("#overall-ranking") do
    find(:li, 1).text.should match("Hugo")
    find(:li, 2).text.should match("João")
    find(:li, 3).text.should match("Ozaki")
  end
end

If you wanna know more about Capybara’s custom selectors, check its README.

And you? Any tips about using Capybara or improving your acceptance/integration tests?

6 responses to “Improving your tests with Capybara custom selectors”

  1. Anonymous says:

    If you think XPath is too ugly, why don’t you use the default CSS selectors?

  2. josevalim says:

    It would be even uglier with CSS selectors, wouldn’t it?

  3. Anonymous says:

    I think you can do something like this:

    find(“li”).text.should …

    In the case of multiple elements, maybe this is possible:

    find(“li”)[0].text.should …

  4. josevalim says:

    The first one would not give the same result. You are merging all li texts into one and it happens that asserting the order is important (it is an ordered ranking after all). The second one does not work on capybara afaik, the [] method is used to retrieve attributes of a given element.

  5. Actually guys, the first example:

    find(“li”).text.should

    Would return the first “li” in the page. The second one:

    find(“li”)[0].text.should

    Would probably raise an error, as José pointed, the [] method is used to retrieve arguments. What should work is something like:

    lis = all(“li”)
    lis[0].text.should…
    lis[1].text.should…
    lis[2].text.should…

    The all method will return all “li” elements as an array in this case.
    But I think that using the selectors is pretty cool and makes the syntax clearer.

  6. Anonymous says:

    This post really me helped out when trying to set my user agent for mobile view specs with Capybara.
    http://aflatter.de/2010/06/testing-headers-and-ssl-with-cucumber-and-capybara/