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?
If you think XPath is too ugly, why don’t you use the default CSS selectors?
It would be even uglier with CSS selectors, wouldn’t it?
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 …
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.
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.
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/