【问题标题】:Capybara Ambiguity ResolutionCapybara 歧义解析
【发布时间】:2012-10-19 09:49:48
【问题描述】:

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

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

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

【问题讨论】:

  • 能否请您也发布一些代码?
  • 您不应该为页面上的两个元素分配相同的 id。如果您将拥有相同的链接,则不要为元素分配 id,而是使用类。

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


【解决方案1】:

我的解决办法是

first(:link, link).click

而不是

click_link(link)

【讨论】:

  • 这在Capybara Upgrade Guide 中有详细说明,如果您遇到此问题,您可能会发现它很有用。
  • 从 Capybara 2.0 开始,除非您绝对必须这样做,否则不要这样做。请参阅下面@Andrey 的回答以及上面链接的升级指南中对歧义匹配的解释。
  • 具体来说,Capybara 2.0 具有智能等待逻辑,可确保规格在不同处理速度的机器上始终如一地通过或失败,同时只等待最短的必要时间。如上所述使用first,除非您绝对知道自己在做什么,否则可能会导致规范通过,但在 CI 构建或同事的机器上失败。
  • 如需良好的讨论,请参阅:robots.thoughtbot.com/…
【解决方案2】:

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

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

这里最赞成的答案建议使用firstall 而不是find 但是:

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

所以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 只查找完全匹配,即不查找“密码确认”

【讨论】:

  • 这应该是最佳答案。始终尝试使用能够利用 Capybara 中内置等待功能的选择器。
  • 谢谢。我尝试使用 :first 但意识到它只适用于 jQuery。我要找的是 :first-child
【解决方案3】:

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

click_link(link_name, match: :first)

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

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

【讨论】:

    【解决方案4】:

    新答案:

    你可以试试

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

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


    老答案:(我认为“#tag1”表示该元素有一个“tag1”的id

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

    find('#tag1').click
    

    否则你可以这样做

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

    点击第二个。

    【讨论】:

    • 第一个解决方案可能有效,但现在的问题是它可能被误认为是 css id --------- Failure/Error: find('#tag1') .click # or all('#tag1')[0].click Capybara::ElementNotFound: Unable to find css "#tag1"
    • find('#tag1') 表示您只想找到一个id为tag1的元素。引发异常,因为页面上有几个 id 为 tag1 的元素
    • 你可以做all(:xpath, '//a[text()="#tag1"]').first.click
    【解决方案5】:

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

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

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

    最好的办法是使用within

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

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

    【讨论】:

      【解决方案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 会很高兴地忽略歧义并只返回第一个匹配的选择器。这也不是很好,因为它使您的规范不那么明确,我想这就是为什么不再是默认行为。我将省略细节,因为它们已经在上面讨论过。

      更多资源:

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

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

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

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

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

          类似When a user clicks on "text" link

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

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

          【讨论】:

            【解决方案9】:

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

            解决方案 1

            first("#tag1").click
            

            解决方案 2

            Cucumber features/filename.feature --guess
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-06-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-05-04
              相关资源
              最近更新 更多