水豚歧义解决


97

如何解决水豚的歧义?由于某些原因,我需要页面中具有相同值的链接,但由于收到错误,因此无法创建测试

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

我之所以无法避免,是因为设计。我正在尝试使用页面右侧的tweets / tags和页面左侧的tags重新创建Twitter页面。因此,不可避免的是,相同的链接页面会显示在同一页面上。


还可以张贴一些代码吗?
Heena Hussain

8
您不应该为页面上的两个元素分配相同的ID。如果您具有相同的链接,则不要为这些元素分配ID,请改用类。
克里斯·萨尔茨伯格

Answers:


147

我的解决方案是

first(:link, link).click

代替

click_link(link)

6
如果有此问题,《水豚升级指南》中对此进行了详细说明。
里奇

1
从Capybara 2.0开始,除非绝对必要,否则请不要这样做。请参阅下面的@Andrey答案以及上面链接的升级指南中的歧义匹配的说明。
2014年

4
具体来说,Capybara 2.0具有智能的等待逻辑,可确保规格在不同处理速度的机器上始终通过或失败,同时仅等待最短的必要时间。first除非您完全知道自己在做什么,否则按照上面的建议进行使用很可能会导致规范通过,但在CI构建或同事的计算机上失败。
吉姆

1
对于一个良好的讨论,请参阅:robots.thoughtbot.com/...
吉姆

74

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

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

此处最受支持的答案建议使用firstall代替,find但:

  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 告诉水豚只找到完全匹配的内容,即找不到“密码确认”


7
这应该是最佳答案。始终尝试使用选择器,该选择器将利用Capybara中的内置等待功能。
tgf

谢谢。我尝试使用:first,但意识到这仅适用于jQuery。我一直在寻找:first-child
Overload119


24

新答案:

您可以尝试类似

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

可能有一种方法可以更好地利用可用的Capybara语法- all("a[text='#tag1']").first.click大致类似,但我无法立即想到正确的语法,也找不到合适的文档。这就是说这是一个有点怪现状有开始,有两个<a>具有相同的标签idclass和文本。他们是否有可能是不同div的子级,因为您可以随后find within对DOM进行适当的细分。(这将有助于您了解一些HTML源代码)。


老答案:(我以为'#tag1'表示该元素具有一个 id “ tag1”)

您想单击哪个链接?如果是第一个(或没关系),则可以

find('#tag1').click

否则你可以做

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

单击第二个。


第一个解决方案可能是可行的,但现在的问题是它可能会误认为一个CSS ID ---------失败/错误:find('#tag1')。click#或all('# tag1')[0]
.click Capybara

find('#tag1')表示您只想找到一个ID为的元素tag1tag1由于页面上有多个具有id的元素而引发了异常
Andrei Botalov

你可以做的all(:xpath, '//a[text()="#tag1"]').first.click
香川修平

9

您可以使用以下方法确保找到第一个match

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

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

更好的选择是使用within

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

这样可以确保在找到所需元素的同时,还可以利用Capybara的自动等待和自动重试功能(如果使用则会丢失find('.selector').click),并使意图更加清晰。


7

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

对于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。

如Andrei所述,您应该改用

find('selector', match: :first)

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

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

更多资源:


5

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

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

2

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

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

如果您使用的是黄瓜,也可以遵循此步骤

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

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

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

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


0

避免黄瓜中含糊不清。

解决方案1

first("#tag1").click

解决方案2

Cucumber features/filename.feature --guess
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.