3
votes

Capybara is not finding my checkbox's label, and I know I'm referencing it correctly by it's label. What am I doing wrong, or is this a bug in Capybara?

According to http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Actions:check, "The check box can be found via name, id or label text."

Here's the section of my request spec I ran:

describe "with valid information" do   

it_should_behave_like "all item pages"

before { valid_create_item }

it "should create an item" do
    expect { click_button submit }.to change(Item, :count).by(1)
end

describe "after saving the item" do
    before { click_button submit }

    it { should have_link('Sign out') }
    it { should have_selector('h1', text: "Items") }
    it { should have_title("Items") }
    it { should have_success_message }
end      

describe "and options selected" do

    before do
        puts page.html
        check('Option 1')
        click_button "Save changes"
    end

    it { should have_title("Items") }
    it { should have_success_message }
    it { should have_link('Sign out', href: signout_path) }
    specify { expect(item.reload.options.find_by_name("Option 1")).to eq true }
end

And here's a gist of the resulting test, including the html generated for the page: https://gist.github.com/anonymous/11270055

According to the test results, Capybara couldn't find the checkbox labelled "Option 1", when it's clearly in the generated html.

As a sidenote, I've also noticed I can fill in a form by it's label with Capybara only when I let the rails FormHelper display a field's default label text.

For example: The FormHelper label text for a field called "email_address" appears as "Email address", and fill_in "Email address", with: "[email protected]" works. If I don't use the FormHelper to generate the label, and instead make the label "Email", fill_in "Email", with: "[email protected]" doesn't work, because Capybara can't find the field labeled "Email". It seems this behavior is consistent with all form elements (or at least with text fields and checkboxes -- the only ones I've tested so far).

3

3 Answers

1
votes

The html for Option 1 looks like:

<label class="control-label" for="__Option:0x000001061be660_">Option 1</label>
<div class="controls">
  <input id="options_" name="options[]" type="checkbox" value="1" />
</div>

The problem is that the label and checkbox are not properly associated with each other:

  • The label has a for attribute of "__Option:0x000001061be660_"
  • The checkbox has an id attribute of "options_"

As a result, Capybara finds the label "Option 1", but not one that is associated to a checkbox.

The page needs to be updated so that the label for attribute and checkbox id attribute match. For example:

<label class="control-label" for="__Option:0x000001061be660_">Option 1</label>
<div class="controls">
  <input id="__Option:0x000001061be660_" name="options[]" type="checkbox" value="1" />
</div>
2
votes

You can also make Capybara find the checkbox by using check(\[locator\], options) method by passing the value of the checkbox as an option.

The code is page.check(name, option: value), where name is the name of the checkbox and value - its value.

This also should work for id instead of name.

1
votes

Have you tried doing it directly based off of the input instead?

<div class="control-group offset2"> 
<label class="control-label" for="__Option:0x000001061be660_">Option 1</label>
<div class="controls">
<input id="options_" name="options[]" type="checkbox" value="1" />
/div>
</div>

If you ever want to debug further, you could add a binding.pry before it checks it, and then just run a few capybara node checking commands to see if it can actually detect the element or not.

If you don't want to use pry, you could just do a

puts all('.control-label', text: 'Option 1').size

and if it returns 1, then it's detected, and thus you could just do a

find('.control-label', text: 'Option 1').set(true)

The check method for Capybara is this:

File 'lib/capybara/node/actions.rb', line 78
def check(locator, options={})
  find(:checkbox, locator, options).set(true)
end

If it doesn't work to do it based off the label like that, you could try doing it based off of the input.