How do I resolve ambiguity in Capybara? For some reason I need links with the same values in a page but I can\'t create a test since I get the error
Failure/
The above solution works great but for those curious you can also use the following syntax.
click_link(link_name, match: :first)
You can find more information here:
http://taimoorchangaizpucitian.wordpress.com/2013/09/06/capybara-click-link-different-cases-and-solutions/
To add to the existing body of knowledge here:
For JS tests, Capybara has to keep two threads (one for RSpec, one for Rails) and a second process (the browser) in sync. It does this by waiting (up to the configured maximum wait time) in most matchers and node-finding methods.
Capybara also has methods that don't wait, primarily Node#all
. Using them is like telling your specs that you'd like them to fail intermittently.
The accepted answer suggests page.first('selector')
. This is undesirable, at least for JS specs, because Node#first uses Node#all.
That said, Node#first
will wait if you configure Capybara like so:
# rails_helper.rb
Capybara.wait_on_first_by_default = true
This option was added in Capybara 2.5.0 and is false by default.
As Andrei mentioned, you should instead use
find('selector', match: :first)
or change your selector. Either will work well regardless of config or driver.
To further complicate things, in old versions of Capybara (or with a config option enabled), #find
will happily ignore ambiguity and just return the first matching selector. This isn't great either, as it makes your specs less explicit, which I imagine is why no longer the default behavior. I'll leave out the specifics because they've been discussed above already.
More resources:
You can ensure that you find the first one using match
:
find('.selector', match: :first).click
But importantly, you probably do not want to do this, as it will lead to brittle tests that are ignoring the duplicate-output code smell, which in turn leads to false positives that keep working when they should have failed, because you removed one matching element but the test happily found the other one.
The better bet is to use within
:
within('#sidebar') do
find('.selector).click
end
This ensures that you're finding the element you expect to find, while still leveraging the auto-wait and auto-retry capabilities of Capybara (which you lose if you use find('.selector').click
), and it makes it much clearer what the intent is.
As considering all the above options, you can try this too
find("a", text: text, match: :prefer_exact).click
If you are using cucumber, you can follow this too
You can pass the text as a parameter from the scenario steps which can be generic step to reuse again
Something like When a user clicks on "text" link
And in step definition When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|
This way, you can reuse the same step by minimizing the lines of code and would be easy to write new cucumber scenarios
To avoid ambiguous error in cucumber.
Solution 1
first("#tag1").click
Solution 2
Cucumber features/filename.feature --guess
NEW ANSWER:
You can try something like
all('a').select {|elt| elt.text == "#tag1" }.first.click
There may be a way to do this which makes better use of the available Capybara syntax -- something along the lines of all("a[text='#tag1']").first.click
but I can't think of the correct syntax off hand and I can't find the appropriate documentation. That said it's a bit of a strange situation to begin with, having two <a>
tags with the same id
, class
, and text. Is there any chance they are children of different divs, since you could then do your find
within
the appropriate segment of the DOM. (It would help to see a bit of your HTML source).
OLD ANSWER: (where I thought '#tag1' meant the element had an id
of "tag1")
Which of the links do you want to click on? If it's the first (or it doesn't matter), you can do
find('#tag1').click
Otherwise you can do
all('#tag1')[1].click
to click the second one.