ReefPointsBlog

Fixing Capybara 2.0 and Labels

Brian Cardarella

I love Capybara, it makes integration testing a breeze. However, one of the decisions made for Capybara 2.0 confuses an annoys me. In Capybara 1.x you could do the following:

1
2
fill_in 'Password', :with => '123456'
fill_in 'Password confirmation', :with => '123456'

And everything worked. In Capybara 2.0 this does not work. Capybara will notice two labels that contain 'Password' and complain about an ambiguous locator. The suggested work around is to attach meta data to the input element and use that for the selector. There are two reasons why I don't like this. First, I am doing Ember development now and I have no control of the ID, it is generated by the framework. Second, I believe that the integration test should be recreating the steps (as much as possible) as if a user were actually using the app. Something like:

1
2
fill_in '[data-name="password"]', :with => '123456'
fill_in '[data-name="password_confirmation"]', :with => '123456'

Doesn't sit right with me. Users are looking at the text, not the selectors. I get that apps have the ability to show different languages but that doesn't conern me, I don't need to test if the Rails i18n works or not. I just care about asserting the happy and sad paths in my app.

So, to fix this problem simply add the following code into your test_helper.rb

1
2
3
4
5
6
7
8
9
module XPath::HTML
  protected

  def locate_field(xpath, locator)
    locate_field = xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:placeholder).equals(locator) | attr(:id).equals(anywhere(:label)[string.n.equals(locator)].attr(:for))]
    locate_field += descendant(:label)[string.n.contains(locator)].descendant(xpath)
    locate_field[~attr(:disabled)]
  end
end

And you should be all set!