10
votes

A W3C-validated HTML 5 web page contains this working, simple button inside a login form.

<input data-disable-with="Signing in, please wait&amp;hellip;"
       name="commit" type="submit" value="Sign in" />

I'm writing a largely pointless test :-) in a Rails 3.2.17 application that's just to get the hang of Capybara and I've already got completely stuck Googling, reading documentation and reading source code to the test framework, with no joy - attempting to find this button by its name (i.e. "commit") fails.

click_button("commit")
find_button("commit")

Both result in Capybara::ElementNotFound: Unable to find button "commit". If I use the visible button text of Sign in then the element is found, i.e. these:

click_button("Sign in")
find_button("Sign in")

...both work fine, so it would appear that the XML parser isn't having any trouble finding the element.

Documentation for click_button says that the locator works on "id, text or value", with "text" being meaningless for an input element like this (the visible text is taken from the value attribute), but relevant perhaps for button elements. So, we might expect that to fail, though if we view the code via the documentation, find that it calls down to find in the same way as find_button. Yet find_button is documented differently; it says it locates by "id, name or value". So sadly, we know from this that the documentation is broken because it says two different things for what turns out to be an identical call at the back end.

Either way, the element isn't found by name, and that means the lower level find call isn't searching name attributes as far as I can see. This means Capybara (2.2.1, on Nokogiri 1.6.1) is rather broken in that respect. How come nobody has noticed? I've Googled for ages and it doesn't seem to come up. I seem to be rather missing the point :-)

Why don't I just search for the English text in the button, you might ask? Because of internationalisation. This old, Rails 1 -> 2 -> 3 upgraded app has some I18n parts and other static text parts. I don't want to be forced to put I18n into any view that Capybara tests, just so I can have the test use I18n.t() to ensure a match despite different languages or locale file updates. Likewise, it would clearly be very stupid in 2014 to write hard-coded English strings into my tests.

That's why we have names and IDs and such... The unique (in theory!) identifiers that are machine-read, not human-read.

I could hack up something that CSS-selected by "type=submit" but seriously, why isn't Capybara searching the name attribute when its documentation says it does, and why does the documentation disagree on what attributes are searched on two methods that call down to exactly the same back-end implementation with exactly the same parameters?

TIA :)

6
Capybara::ElementNotFound: Unable to find button "commit" You don't have a button, you have an input.sevenseacat
That's what "button" means in this context. The underlying find call takes various values in the first parameter with lots of different meanings. For example I can pass :xpath or :fillable_field. See lib/capybara/selector.rb for how these are added; :button passes to XPath::HTML.button and rdoc.info/github/jnicklas/xpath/XPath/HTML says that this matches "a submit, image, or button element". Given this, if you're responsible for the question downvote I'd appreciate it if you'd revoke that.Andrew Hodgkinson
If you rewrite the question to cut out half of the conversation to focus just on the issue, I'd be happy to.sevenseacat
You're basically saying "tl;dr" (and you're still wrong about what those calls do, see my other comment). If you don't want to read a question and answer intelligently, please don't pollute StackOverflow with meaningless commentary and downvotes. Let's keep things civil and as technical and accurate as possible. Thanks.Andrew Hodgkinson

6 Answers

17
votes

It turns out the docs are misleading for both calls, as neither look at the attributes listed. It's also clearly very confusing what exactly a "button" means, since a couple of people herein seemed to think it literally only meant an HTML button element but that's not the case.

If you view the source for the documentation of, say, click_button:

https://github.com/jnicklas/capybara/blob/a94dfbc4d07dcfe53bbea334f7f47f584737a0c0/lib/capybara/node/actions.rb#L36

...you will see that this just calls (as I've mentioned elsewhere) to find with a type of :button, which in turn passes through to Capybara's Query engine which, in turn, ends up just using the standard internal selection mechanism to find things. It's quite elegant; in the same way that an external client can add their own custom selectors to making finding things more convenient:

http://rubydoc.info/github/jnicklas/capybara/master/Capybara#add_selector-class_method

...so Capybara adds its own selectors internally, including, importantly, :button:

https://github.com/jnicklas/capybara/blob/a94dfbc4d07dcfe53bbea334f7f47f584737a0c0/lib/capybara/selector.rb#L133

It's not done by any special case magic, just some predefined custom selectors. Thus, if you've been wondering what custom selectors are available from the get-go in Capybara, that's the file to read (it's probably buried in the docs too but I've not found the list myself yet).

Here, we see that the button code is actually calling XPath::HTML.button, which is a different chunk of code in a different repository, with this documentation:

http://rdoc.info/github/jnicklas/xpath/XPath/HTML#button-instance_method

...which is at the time of writing slightly out of date with respect to the code, since the code shows quite a lot more stuff being recognised, including input types of reset and button (i.e. <input type="button"...> rather than <button...>...</button>, though the latter is also included of course).

https://github.com/jnicklas/xpath/blob/59badfa50d645ac64c70fc6a0c2f7fe826999a1f/lib/xpath/html.rb#L22

We can also see in this code that the finder method really only finds by id, value and title - i.e. not by "text" and not by name either.

So assuming XPath is behaving as intended, though it's not clear from docs, we can see that Capybara isn't documenting itself correctly but probably ought to make the link down to XPath APIs for more information, to avoid the current duplication of information and the problems this can cause for both maintainers and API clients.

In the mean time, I've filed this issue:

https://github.com/jnicklas/capybara/issues/1267

11
votes

You can also use css selectors which are default capybara locators. People say they are faster.

find('[name=commit]').click

Capybara do not look at name attribute in it's finders :(

6
votes

You can use xpath selector if you want

find(:xpath, "//input[contains(@name, 'commit')]").click()
2
votes

If anyone wants it is possible to add (quite easily) find by name selector. In order to do so:

Add following code to test/test_helper.rb (for minitest)

Capybara.add_selector(:name) do
  xpath { |name| XPath.descendant[XPath.attr(:name).contains(name)] }
end

Use it

Now in your tests you can use following selector:

find(:name, 'part_of_the_name_attribute')

It will find every element which name attribute contains searched value.

Example

find(:name, 'user')

This will find elements (element could be of any type):

<select name='user_name'>
<input name='name_of_user'>
<textarea name='some_user_info'>
0
votes

You can use this selector to find a button on a page with RSpec and Capybara:

expect(page).to have_selector(:link_or_button, "Button text")
0
votes

Check your gem depencies. RSpec 3 or higher works with gem 'rspec-rails', '~> 3.7.1' then capybara version must be gem 'capybara', '~>2.18.0' and poltergeist should be gem 'poltergeist', '~>1.17.0'.