水豚歧义解决方案

时间:2012-10-30 04:08:04

标签: ruby-on-rails-3 rspec capybara

如何解决Capybara的歧义?出于某种原因,我需要在页面中使用相同值的链接,但由于我收到错误

,因此无法创建测试
Failure/Error: click_link("#tag1")
     Capybara::Ambiguous:
       Ambiguous match, found 2 elements matching link "#tag1"

我无法避免这种情况的原因是因为设计。我正在尝试使用右侧的推文/标签和页面左侧的标签重新创建推特页面。因此,相同的链接页面显示在同一页面上是不可避免的。

9 个答案:

答案 0 :(得分:138)

我的解决方案是

first(:link, link).click

而不是

click_link(link)

答案 1 :(得分:71)

Capybara的这种行为是故意的,我相信它不应该像大多数其他答案所建议的那样修复。

2.0之前的Capybara版本返回了第一个元素而不是引发异常,但后来Capybara的维护者认为这是一个坏主意,最好提高它。决定在许多情况下返回第一个元素导致返回不是开发人员想要返回的元素。

此处最受欢迎的答案建议使用firstall代替find,但是:

  1. allfirst不要等到具有此类定位器的元素出现在页面上,find会等待
  2. all(...).firstfirst不会保护您免受以后在页面上显示此类定位器的其他元素的情况,因此您可能会发现错误的元素
  3. 所以it's adviced to choose another, less ambiguous locator:例如,按id,class或其他css / xpath定位器选择元素,这样只有一个元素匹配它。


    作为一个注释,这里有一些我通常认为在解决歧义时有用的定位器:

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

      它比first('ul > li')更有用,因为它会等到第一个li出现在页面上。

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

      它优于first(:link, 'Create Account').click,因为它会等到页面上出现至少一个创建帐户链接。但是我认为最好选择两次没有出现在页面上的唯一定位器。

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

      exact: true告诉Capybara只找到完全匹配,即找不到“密码确认”

答案 2 :(得分:25)

上述解决方案效果很好,但对于那些好奇的人,您也可以使用以下语法。

click_link(link_name, match: :first)

您可以在此处找到更多信息:

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

答案 3 :(得分:23)

新答案:

您可以尝试类似

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

可能有一种方法可以更好地利用可用的Capybara语法 - 类似all("a[text='#tag1']").first.click的内容,但我无法想到正确的语法,我可以&# 39;找到合适的文件。这说明开始时有点奇怪的情况,有两个<a>标签具有相同的idclass和文本。他们是否有可能成为不同div的孩子,因为您可以在find within处理DOM的相应部分。 (看看你的HTML源代码会有所帮助。)


老答案:(我认为&#39;#tag1&#39;表示该元素的id为&#34; tag1&#34;)

您想要点击哪些链接?如果它是第一个(或者它没关系),你可以做

find('#tag1').click

否则你可以做到

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

点击第二个。

答案 4 :(得分:9)

您可以使用match确保找到第一个:

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

但重要的是,你可能不想这样做,因为它会导致忽略重复输出代码气味的脆弱测试,这反过来导致误报,当它们应该失败时继续工作,因为你删除了一个匹配的元素,但测试愉快地找到了另一个。

更好的选择是使用within

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

这可确保您找到您希望找到的元素,同时仍然利用Capybara的自动等待和自动重试功能(如果您使用find('.selector').click,则会丢失),并且意图是什么更清楚。

答案 5 :(得分:6)

在此处添加现有知识体系:

对于JS测试,Capybara必须保持两个线程(一个用于RSpec,一个用于Rails)和第二个进程(浏览器)同步。它通过在大多数匹配器和节点查找方法中等待(直到配置的最大等待时间)来完成此操作。

Capybara也有不等待的方法,主要是Node#all。使用它们就像告诉你的规格,你喜欢它们间歇性地失败。

接受的答案显示page.first('selector')。至少对JS规范来说,这是不可取的,因为Node#first uses Node#all

那就是说,Node#first 等待,如果你这样配置Capybara:

# rails_helper.rb
Capybara.wait_on_first_by_default = true

此选项为added in Capybara 2.5.0,默认为false。

正如安德烈所说,你应该改用

find('selector', match: :first)

或更改您的选择器。无论配置或驱动程序如何,都可以正常工作。

为了进一步复杂化,在旧版本的Capybara中(或启用了配置选项),#find将很乐意忽略歧义并返回第一个匹配选择器。这也不是很好,因为它使你的规范不那么明确,我想这就是为什么不再是默认行为。我已经省略了细节,因为它们已经在上面讨论过了。

更多资源:

答案 6 :(得分:5)

由于this post,您可以通过“匹配”选项修复它:

Capybara.configure do |config|
  config.match = :prefer_exact
end

答案 7 :(得分:2)

考虑到以上所有选项,您也可以尝试

find("a", text: text, match: :prefer_exact).click

如果您正在使用黄瓜,也可以按照此操作

您可以在场景步骤中将文本作为参数传递,该步骤可以是通用步骤以再次使用

类似When a user clicks on "text" link

在步骤定义When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|

这样,您可以通过最小化代码行来重复使用同一步骤,并且很容易编写新的黄瓜方案

答案 8 :(得分:0)

避免黄瓜出现模棱两可的错误。

解决方案1 ​​

first("#tag1").click

解决方案2

Cucumber features/filename.feature --guess