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?

Tags: ,

This entry was posted on Wednesday, February 9th, 2011 at 2:31 pm and is filed under English. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

  • Anonymous

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

  • http://blog.plataformatec.com.br/ josevalim

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

  • Anonymous

    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 …

  • http://blog.plataformatec.com.br/ josevalim

    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.

  • http://carlosantoniodasilva.wordpress.com Carlos Antonio

    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.

  • Anonymous

    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/