Capybara::Poltergeist::ObsoleteNode 当 angular 更新使用 ng-repeat 呈现的 table 行

Capybara::Poltergeist::ObsoleteNode when angular updates a table row rendered with ng-repeat

我正在测试 angular 水豚、黄瓜和闹鬼的实时更新。

我有以下失败的步骤定义:

Then(/^I should see the following inventory:$/) do |table|
  rows = find(".inventory table").all('tr')
  page_table = rows.map { |r| r.all('th,td').map { |c| c.text.strip } }
  table.dup.diff!(page_table)
end

错误:

The element you are trying to interact with is either not part of the DOM, or is not currently visible on the page (perhaps display: none is set). It's possible the element has been replaced by another element and you meant to interact with the new element. If so you need to do a new 'find' in order to get a reference to the new element. (Capybara::Poltergeist::ObsoleteNode)

但是,如果我用预期块(重试)包装断言(实际上只是 #find),则测试通过。

w/预期块:

Then(/^I should see the following inventory:$/) do |table|
  sleeping(0.1).seconds.between_tries.failing_after(20).tries do
    rows = find(".inventory table").all('tr')
    page_table = rows.map { |r| r.all('th,td').map { |c| c.text.strip } }
    table.dup.diff!(page_table)
  end
end

我讨厌这个解决方案,因为 cucumber/capybara 应该 已经有一个重试机制。因此,如果重试超时为 5 秒,那么您实际上可能会重试 5 秒 * 20 次重试 + 额外的 2 秒。现在,我可以在查找操作上添加 wait: 0,但这些解决方案看起来都像是 hack。

我正在使用 poltergeist 1.9.8,但已尝试升级到 2.1,但仍然没有成功。有解决办法吗?

Capybaras 重试机制内置于它的匹配器中,您在此测试中没有使用它。您还使用了 #all,它的缺点是元素 returns 不能自动重新加载,因此仅当元素不会更改或已经更改时才需要使用。 #all 也有效地具有您使用它的方式的 0 等待时间,因为空元素数组(无匹配项)是有效响应,因此没有等待行为。如果在测试中可见行数发生变化,那么您可以使用 count 选项强制 #all 等待并实现类似

Then(/^I should see the following inventory:$/) do |table|
  rows = find(".inventory table").all('tr', count: table.raw.size)
  page_table = rows.map { |r| r.all('th,td').map { |c| c.text.strip } }
  table.dup.diff!(page_table)
end

这将使 #all 等待页面上出现预期的行数,这应该意味着行已完成更改并调用 all('th,td') 查找文本变得安全。

如果行数不会改变(只有行数),一个选项是将所有内容连接在一起并检查 table 的文本 - 它不会是 100 % 正在测试 table 完全匹配,但在您控制数据的测试环境中它可能已经足够好了。这是未经测试的,但按照以下内容应该可以做到这一点

Then(/^I should see the following inventory:$/) do |table|
  expect(find(".inventory table")).to have_content(table.raw.flatten.join)
end

另一种尝试方法是利用 Capybara::Node::synchronize 重试 - 类似于

Then(/^I should see the following inventory:$/) do |table|
  inv_table = find(".inventory table")
  inv_table.synchronize do
    page_table = inv_table.all('tr').map { |r| r.all('th,td').map { |c| c.text.strip } }
    table.dup.diff!(page_table)
  end
end

#synchronize 应该允许 Capybara 最多重试块 Capybara.default_max_wait_time 直到它通过——默认情况下它只会重试 driver.invalid_elements 和 Capybara::ElementNotFound - 如果您还希望它重试 diff 返回的错误! (最多 max_wait_time 秒)你必须传递选项才能像 inv_table.synchronize max_wait_time, errors: page.driver.invalid_element_errors + [Capybara::ElementNotFound, WhateverErrorDiffRaisesOnFailure] do ...

一样同步