Blog

Mar

Introducing Capybara 2.1


With the release of Capybara 2.0, we made a few changes to how Capybara acts in certain situations, which were designed to reduce unexpected and unintuitive behaviour. We got a lot of feedback that the new behaviour was too restrictive and that upgrading existing test suites from Capybara 1.x was too difficult.

Our goal with Capybara 2.1 has been to correct these problem. To provide more forgiving defaults, and more configurability for those who need it. To provide a smoother upgrade path for those on Capybara 1.x who failed to upgrade their apps due to too many breaking changes.

We focused on these key problems:

  • Matching exactly or allowing substrings
  • Behaviour when multiple elements match a query
  • Visibility
  • Asserting against the page title
  • Finding disabled elements

Aside from this, Capybara 2.1 contains many tweaks and new features.

Since we're following semver, we promise to maintain backward compatibility in all minor releases. Capybara 2.1 makes a compromise in maintaining backwards compatibilit but changing a few defaults. In order to have Capybara 2.1.0 behave identically to 2.0.x, set these options:

Capybara.configure do |config|
  config.match = :one
  config.exact_options = true
  config.ignore_hidden_elements = true
  config.visible_text_only = true
end

We have also enabled a smoother upgrade path for Capybara 1.x users. There are still changes which break compatibility between 1.x and 2.x, even with this configuration enabled, but most of them should be fairly easy to deal with. Try this:

Capybara.configure do |config|
  config.match = :prefer_exact
  config.ignore_hidden_elements = false
end

Matching exactly or allowing substrings

Capybara has always been very lenient about what it matches. Sometimes that's not what you want. To that end find, as well as all action methods like click_link and fill_in now accept an option called :exact which works together with the is expression inside the XPath gem. Without going too much into the internals, it allows you to specify whether for example click_link will allow you to specify a substring, or will need to match the entire link text exactly.

We have also added a global config option, Capybara.exact, which controls the default value of this property. Just as in Capybara 2.0.x however, this option defaults to false. That is, by default, matches are not exact, and substrings are allowed.

Behaviour when multiple elements match a query

When using metods such as click_link, and fill_in, what happens when more than one element matches? Under Capybara 1.0, we tried to find an element that matched exactly, and failing that, or if there were multiple such elements, we would simply return the first one and move on.

This has the obvious problem that sometimes, the element you end up interacting with isn't the one you expected at all.

We tried to solve this problem in Capybara 2.0 by being stricter and raising an exception in such a case instead. This was the biggest change in terms of compatibility between 1.x and 2.0 and has been very frustrating for many users.

In Capybara 2.1 we are making this behaviour configurable, and allowing users to pick which strategy they prefer, including reverting to the 1.x behaviour. We also changed the default behaviour to a slightly more lenient strategy.

We've added the match option, which takes four possible arguments: :one, :first, :prefer_exact, and :smart. Let's go through what they do:

:one is the current behaviour in Capybara 2.0.x. When two elements are found which both match the selector, a Capybara::Ambiguous error is raised.

:first is a looser behaviour which, when confronted with two elements which match the selector, simply grabs the first one and uses that one.

:prefer_exact is the behaviour present in Capybara 1.x. If multiple matches are found, some of which are exact, and some of which are not, then the first eaxctly matching element is returned.

:smart is the new default. The behaviour of :smart depends on the value of :exact. If :exact is true, the behaviour is identical to :one, that is, Capybara will perform a search for an exactly matching element, and if there is more than one, it will raise an error.

If :exact is false, things get more interesting. In that case, Capybara will first perform a search for elements which match the selector exactly, if there is exactly one, that element is returned. If there is more than one, a Capybara::Ambiguous error is raised. If no element matches, a new search is performed, allowing inexact matches. Again, if more than one element matches that search, Capybara::Ambiguous is raised.

This solves the much discussed Password confirmation problem, if there is a field with the exact label Password and another with Password Confirmation, and someone does this:

fill_in "Password", :with => "Capybara"

Then Capybara will pick the Password field and fill that in. If, however, the label of the password field would have been Password * (to indicate that it is required). Then an ambiguous error would have been raised, since both Password * and Password Confirmation are inexact matches for Password.

The idea is to strike a compromise between strictness and user friendliness. Those that prefer strictness can set:

Capybara.exact = true

Exactness of options

With Capybara 2.0 we changed the behaviour so that in the following case…

select "1", :from => "Number of people"

The option needed to match "1" exactly. So that if "10" was an option, it would be possible to differentiate between them. Options now use the same smart matching by default, as outlined above. To revert to the old behaviour of always requiring an exact match, the config options exact_options can be set to true.

Visibility

We have had an option which makes Capybara ignore all hidden elements for a long time: Capybara.ignore_hidden_elements. This option has always been false by default. This has confused people for a long time, on occasion even myself. We've made no further change to this behaviour other than the fact that this option now defaults to true. To revert to the old behaviour, simply set:

Capybara.ignore_hidden_elements = false

Visibility of text

In Capybara 1.x, the behaviour of text in the presence of invisible (display: none), DOM elements was undefined. While RackTest offers some rudimentary support for visibility, it was ignored for text, and even text in, for example, script tags was returned. Selenium ignored hidden text and other drivers did what they wanted.

In Capybara 2.0.x, we specified that text should only return text visible to the user, never hidden text, even for RackTest.

In Capybara 2.1.0, the visibility of text depends on ignore_hidden_elements. Setting ignore_hidden_elements to false means that even invisible text is returned.

We also make it possible to retrieve override this default by passing :all or :visible to the text method:

find("#thing").text           # depends on Capybara.ignore_hidden_elements
find("#thing").text(:all)     # all text
find("#thing").text(:visible) # only visible text

Since that is a departure from the Capybara 2.0.x API, we offer a special option for backward compatibility, which will make text always return only the text which is visible, even if ignore_hidden_elements is false.

Capybara.visible_text_only = true

Asserting against the page title

A lot of people migrating to Capybara 2.0 had problems with code like this:

page.should have_css("title", :text => "Whatever")

They found that title has no text, and thus this never matches

This is not however, a bug in Capybara. The above code is wrong, and should not work. To understand why, it's important to realize that the page title is quite distinct from the title element. The title element is invisible by default, and thus has no text which is visible to the user. Asking for its text very correctly returns nothing.

Don't believe me? Try pasting the following CSS into any web page:

head, head title { display: block }

You can now see the title element on the page. And making it visible this way will make the Selenium driver return the text inside that element, just as it should.

Instead of fixing Capybara to work with broken code, we are introducing a new API, which provides a nicer way of querying the page title:

page.title # => "The title"
page.has_title?("The title") # => true
page.should have_title("The title")

The has_title? and have_title matchers both have the same waiting behaviour as all other matchers in Capybara.

Finding disabled elements

Since Capybara 2.0, methods which interact with form fields and buttons, such as fill_in and click_button, do not allow interaction with disabled form elements and buttons. In addition, the matcher has_field? and the finder method find_field no longer match on disabled fields. It is still possible to locate disabled fields and buttons through other means, such as via find or has_selector?.

Capybara 2.1 makes it possible to override this behaviour by passing :disabled => true to any of these methods, which will find only disabled elements.

More

There are more new features in Capybara 2.1 which did not fit in this blog post. Please refer to the History file for a complete list.

blog comments powered by Disqus