101

如何解决 Capybara 中的歧义?出于某种原因,我需要页面中具有相同值的链接,但由于出现错误,我无法创建测试

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

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

4

9 回答 9

149

我的解决方案是

first(:link, link).click

代替

click_link(link)
于 2012-11-14T21:08:15.653 回答
77

Capybara 的这种行为是故意的,我认为不应像大多数其他答案中所建议的那样对其进行修复。

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

此处最受好评的答案建议使用firstorall代替findbut:

  1. all并且first不要等到带有此类定位器的元素会出现在页面上,尽管find确实会等待
  2. all(...).first并且first不会保护您免受将来带有此类定位器的另一个元素可能出现在页面上的情况,因此您可能会发现不正确的元素

所以建议选择另一个不那么模棱两可的定位器:例如,通过 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 只查找完全匹配,即不查找“密码确认”

于 2013-10-15T08:12:18.483 回答
28

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

click_link(link_name, match: :first)

您可以在这里找到更多信息:

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

于 2014-03-20T19:22:23.410 回答
24

新答案:

你可以尝试类似的东西

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

可能有一种方法可以更好地利用可用的 Capybara 语法——类似的东西,all("a[text='#tag1']").first.click但我想不出正确的语法,也找不到合适的文档。也就是说,一开始情况有点奇怪,有两个<a>标签具有相同idclass, 和 text 。有没有可能他们是不同 div 的孩子,因为你可以做你find within适当的 DOM 部分。(这将有助于查看您的一些 HTML 源代码)。


旧答案:(我认为“#tag1”表示该元素具有id“tag1”)

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

find('#tag1').click

否则你可以做

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

单击第二个。

于 2012-10-30T05:52:04.473 回答
11

您可以确保使用以下命令找到第一个match

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

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

更好的选择是使用within

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

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

于 2015-11-05T23:17:26.847 回答
10

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

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

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

接受的答案表明page.first('selector')。这是不可取的,至少对于 JS 规范而言,因为Node#first使用Node#all.

也就是说,如果您像这样配置 Capybara ,Node#first 将等待:

# rails_helper.rb
Capybara.wait_on_first_by_default = true

此选项是在 Capybara 2.5.0 中添加的,默认为 false。

正如安德烈提到的,你应该改用

find('selector', match: :first)

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

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

更多资源:

于 2016-03-03T19:52:58.173 回答
5

由于这篇文章,您可以通过“匹配”选项修复它:

Capybara.configure do |config|
  config.match = :prefer_exact
end
于 2013-10-12T18:22:05.637 回答
3

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

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

如果你用的是黄瓜,你也可以按照这个

您可以从场景步骤中将文本作为参数传递,这可以是通用步骤以再次重用

就像是When a user clicks on "text" link

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

这样,您可以通过最小化代码行来重用相同的步骤,并且可以轻松编写新的黄瓜场景

于 2019-09-14T02:46:20.867 回答
0

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

解决方案 1

first("#tag1").click

解决方案 2

Cucumber features/filename.feature --guess
于 2015-11-25T06:23:14.923 回答