水豚歧义解决方案

nei*_*ion 94 rspec capybara ruby-on-rails-3

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

Failure/Error: click_link("#tag1")
     Capybara::Ambiguous:
       Ambiguous match, found 2 elements matching link "#tag1"
Run Code Online (Sandbox Code Playgroud)

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

小智 143

我的解决方案是

first(:link, link).click
Run Code Online (Sandbox Code Playgroud)

代替

click_link(link)
Run Code Online (Sandbox Code Playgroud)

  • 这在[Capybara升级指南](http://techblog.fundinggates.com/blog/2012/08/capybara-2-0-upgrade-guide/)中有详细说明,如果遇到此问题,您可能会觉得有用. (6认同)
  • 具体而言,Capybara 2.0具有智能等待逻辑,可确保规格在不同处理速度的机器上一致通过或失败,同时仅等待最短的必要时间.使用上面建议的"first",除非你完全知道自己在做什么,否则很可能会导致规格传递给你但在CI版本或同事的机器上失败. (4认同)

And*_*lov 72

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

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 告诉Capybara只找到完全匹配,即找不到"密码确认"

  • 这应该是最好的答案.总是尝试使用能够利用Capybara内置等待功能的选择器. (7认同)

小智 25

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

click_link(link_name, match: :first)
Run Code Online (Sandbox Code Playgroud)

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

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


Ami*_*pta 23

新答案:

你可以尝试类似的东西

all('a').select {|elt| elt.text == "#tag1" }.first.click
Run Code Online (Sandbox Code Playgroud)

可能有一种方法可以更好地利用可用的Capybara语法 - 这是all("a[text='#tag1']").first.click我的想法,但我无法想到正确的语法,我找不到合适的文档.这就是说这是一个有点怪现状有开始,有两个<a>具有相同的标签id,class和文本.有没有机会他们是不同div的孩子,因为你可以做你find within的DOM的适当部分.(看看你的HTML源代码会有所帮助).


老答案:(我认为'#tag1'意味着该元素具有id"tag1")

您想要点击哪个链接?如果它是第一个(或无关紧要),你可以做到

find('#tag1').click
Run Code Online (Sandbox Code Playgroud)

否则你可以做到

all('#tag1')[1].click
Run Code Online (Sandbox Code Playgroud)

单击第二个.


TAL*_*ama 9

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

find('.selector', match: :first).click
Run Code Online (Sandbox Code Playgroud)

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

更好的选择是使用within:

within('#sidebar') do
  find('.selector).click
end
Run Code Online (Sandbox Code Playgroud)

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


joh*_*cip 6

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

对于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
Run Code Online (Sandbox Code Playgroud)

此选项已在Capybara 2.5.0添加,默认为false.

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

find('selector', match: :first)
Run Code Online (Sandbox Code Playgroud)

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

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

更多资源:


Sky*_*dan 5

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

Capybara.configure do |config|
  config.match = :prefer_exact
end
Run Code Online (Sandbox Code Playgroud)


小智 5

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

find("a", text: text, match: :prefer_exact).click
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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