Capybara Ambiguity Resolution

55,510

Solution 1

My solution is

first(:link, link).click

instead of

click_link(link)

Solution 2

Such behavior of Capybara is intentional and I believe it shouldn't be fixed as suggested in most of other answers.

Versions of Capybara before 2.0 returned the first element instead of raising exception but later maintainers of Capybara decided that it's a bad idea and it's better to raise it. It was decided that in many situations returning first element leads to returning not the element that the developer wanted to be returned.

The most upvoted answer here recommend to use first or all instead of find but:

  1. all and first don't wait till element with such locator will appear on the page though find does wait
  2. all(...).first and first won't protect you from situation that in future another element with such locator may appear on the page and as the result you may find incorrect element

So it's adviced to choose another, less ambiguous locator: for example select element by id, class or other css/xpath locator so that only one element will match it.


As a note here are some locators that I usually consider useful when resolving ambiguity:

  • find('ul > li:first-child')

    It's more useful than first('ul > li') as it will wait till first li will appear on the page.

  • click_link('Create Account', match: :first)

    It's better than first(:link, 'Create Account').click as it will wait till at least one Create Account link will appear on the page. However I believe it's better to choose unique locator that doesn't appear on the page twice.

  • fill_in('Password', with: 'secret', exact: true)

    exact: true tells Capybara to find only exact matches, i.e. not find "Password Confirmation"

Solution 3

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/

Solution 4

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.

Solution 5

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.

Share:
55,510

Related videos on Youtube

neilmarion
Author by

neilmarion

Updated on July 24, 2022

Comments

  • neilmarion
    neilmarion almost 2 years

    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/Error: click_link("#tag1")
         Capybara::Ambiguous:
           Ambiguous match, found 2 elements matching link "#tag1"
    

    The reason why I can't avoid this is because of the design. I'm trying to recreate the twitter page with tweets/tags on the right and the tags on the left of the page. Therefore it will be inevitable that identical links page shows up on the same page.

    • Heena Hussain
      Heena Hussain over 11 years
      Can you please post some code also?
    • Chris Salzberg
      Chris Salzberg over 11 years
      You shouldn't be assigning the same id to two elements on the page. If you will have identical links, then don't assign an id to the elements, use a class instead.
  • neilmarion
    neilmarion over 11 years
    That solution on the first one might be work but the problem now is that it's maybe mistaken for a css id --------- Failure/Error: find('#tag1').click # or all('#tag1')[0].click Capybara::ElementNotFound: Unable to find css "#tag1"
  • Andrei Botalov
    Andrei Botalov about 11 years
    find('#tag1') means that you want to find only one element with id tag1. Exception is raised as there are several elements with id tag1 on the page
  • Ritchie
    Ritchie almost 11 years
    This is detailed in the Capybara Upgrade Guide you may find useful if you had this problem.
  • Shuhei Kagawa
    Shuhei Kagawa almost 11 years
    You can do all(:xpath, '//a[text()="#tag1"]').first.click.
  • tgf
    tgf almost 10 years
    This should be the top answer. Always try to use a selector that will make use of the built-in waiting capabilities in Capybara.
  • jim
    jim over 9 years
    As of Capybara 2.0 don't do this unless you absolutely have to. See @Andrey's answer below and the explanation of Ambiguous Matches in the upgrade guide linked above.
  • jim
    jim over 9 years
    Specifically, Capybara 2.0 has intelligent waiting logic to ensure specs pass or fail consistently across machines of differing processing speeds while only waiting the minimum necessary time. Using first as suggested above, unless you absolutely know what you're doing, is likely to result in specs that pass for you but fail in a CI build or on a colleague's machine.
  • jim
    jim over 9 years
    For a good discussion see: robots.thoughtbot.com/…
  • Overload119
    Overload119 almost 9 years
    Thanks. I tried to use :first but realized that only works in jQuery. What I was looking for is :first-child