Capybara Ambiguity Resolution

How to resolve ambiguity in Capybara? For some reason, I need links with the same values ​​on the page, but I cannot create a test because I get an error

Failure/Error: click_link("#tag1") Capybara::Ambiguous: Ambiguous match, found 2 elements matching link "#tag1" 

The reason I cannot avoid this is because of the design. I am trying to recreate a Twitter page with tweets / tags to the right and tags to the left of the page. Therefore, it is inevitable that an identical link page will appear on one page.

+81
ruby-on-rails-3 rspec capybara
Oct 30
source share
8 answers

My decision

 first(:link, link).click 

instead

 click_link(link) 
+133
Nov 14 '12 at 21:08
source share

This behavior of Capybara is intentional, and I believe that this should not be fixed, as suggested in most other answers.

Versions of Capybara prior to 2.0 returned the first element instead of raising an exception, but later, those supporting Capybara decided that this was a bad idea and it was better to raise it. It was decided that in many situations, returning the first element results in not returning the element that the developer wanted to return.

The most recommended answer here is to use first or all instead of find , but:

  • all and first do not wait until an element with such a locator appears on the page, although find waiting
  • all(...).first and first will not protect you from a situation where in the future another page with such a locator may appear on the page, and as a result you may find the wrong element

So, he is recommended to choose another, less ambiguous locator : for example, select an element by id, class or other css / xpath locator so that only one element matches it.




As a note, here are some locators that I usually find useful in resolving ambiguity:

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

    This is more useful than first('ul > li') as it will wait until li appears on the page.

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

    This is better than first(:link, 'Create Account').click , because it will wait until at least one Create Account link appears on the page. However, I believe that it is better to choose a unique locator that does not 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

+64
Oct. 15 '13 at 8:12
source share

The above solution works fine, but for the curious you can also use the following syntax.

 click_link(link_name, match: :first) 

Here you can find more information:

http://taimoorchangaizpucitian.wordpress.com/2013/09/06/capybara-click-link-different-cases-and-solutions/

+23
Mar 20 '14 at 19:22
source share

NEW RESPONSE:

You can try something like

 all('a').select {|elt| elt.text == "#tag1" }.first.click 

Perhaps there is a way to do this to make better use of the available Capybara syntax - something like the all("a[text='#tag1']").first.click , but I can't think of the correct syntax, and I don't I can find the appropriate documentation. To say that this is a bit strange situation, with two <a> tags with the same id , class and text. Is it likely that they are children of different divs, since you could make your find within corresponding DOM segment. (This will help to see a bit of your HTML source.)




OLD ANSWER: (where I thought β€œ# tag1” means the element had id β€œtag1”)

Which link do you want to click? If this is the first (or it does not matter), you can do

 find('#tag1').click 

Otherwise you can do

 all('#tag1')[1].click 

to click the second.

+21
Oct 30 '12 at 5:52
source share

You can be sure to find the first one using match :

 find('.selector', match: :first).click 

But, importantly, you probably don't want to do this , as this will lead to fragile tests that ignore the smell of code with repeating code, which in turn leads to false positives that continue to work when they should have failed because you deleted one matching item, but the test happily found another.

It is best to use within :

 within('#sidebar') do find('.selector).click end 

This ensures that you find the item that you expect to find, but use the Capybara auto-wait and auto-retry capabilities (which you lose if you use find('.selector').click ), and this greatly simplifies the intent.

+7
Nov 05 '15 at 23:17
source share

To add knowledge to an existing body:

For tests, JS Capybara must support the synchronization of two threads (one for RSpec, one for Rails) and the second process (browser). It does this by waiting (up to the set maximum timeout) in most matching and node-binding methods.

Capybara also has methods that don't wait, most notably Node#all . Using them is like telling your specifications that you want them to fail intermittently.

The accepted answer offers page.first('selector') . This is undesirable, at least for JS specifications, because Node#first uses Node#all .

However, Node#first will wait if you configure Capybara like this:

 # 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 Andrew mentioned, you should use instead

 find('selector', match: :first) 

or change your selector. Any of these will work well regardless of configuration or driver.

To complicate matters even further, in older versions of Capybara (or with the configuration option enabled) #find happily ignores the ambiguity and simply returns the first matching selector. This is also not great, as it makes your specifications less explicit, which, I think, is no longer the default behavior. I do not take into account the specifics, because they have already been discussed above.

Additional resources:

+6
Mar 03 '16 at 19:52
source share

Thanks to this post , you can fix it with the match option:

 Capybara.configure do |config| config.match = :prefer_exact end 
+5
Oct. 12 '13 at 18:22
source share

To avoid an ambiguous mistake in cucumbers.

Solution 1

 first("#tag1").click 

Decision 2

 Cucumber features/filename.feature --guess 
0
Nov 25 '15 at 6:23
source share



All Articles