Capybara::Poltergeist::ObsoleteNode, когда angular обновляет строку таблицы, отображаемую с помощью ng-repeat

Я тестирую угловые живые обновления с водосвинкой, огурцом и полтергейстом.

У меня есть следующее определение шага, которое терпит неудачу:

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

Ошибка:

Элемент, с которым вы пытаетесь взаимодействовать, либо не является частью DOM, либо в данный момент не отображается на странице (возможно, display: none установлен). Возможно, элемент был заменен другим элементом, и вы хотели взаимодействовать с новым элементом. Если это так, вам нужно выполнить новый поиск, чтобы получить ссылку на новый элемент. (Капибара::Полтергейст::ObsoleteNode)

Однако, если я оборачиваю утверждение (на самом деле просто #find) блоком ожидания (повторные попытки), тест проходит.

с блоком ожидания:

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

Я НЕНАВИЖУ это решение, потому что огурец/капибара должны уже иметь механизм повторных попыток. Таким образом, если этот тайм-аут повторной попытки составляет 5 секунд, вы потенциально действительно повторяете попытку в течение 5 секунд * 20 повторных попыток + дополнительные 2 секунды. Теперь я могу добавить wait: 0 к действию поиска, но все эти решения кажутся хаками.

Я использую полтергейст 1.9.8, но попытался обновиться до 2.1, и до сих пор нет кубиков. Есть ли решение для этого?


person daino3    schedule 09.06.2016    source источник
comment
Какую версию phantom.js вы используете?   -  person bkunzi01    schedule 09.06.2016


Ответы (1)


Механизм повторных попыток Capybaras встроен в его сопоставители, которые вы не используете в этом тесте. Вы также используете #all, недостатком которого является то, что возвращаемые им элементы не перезагружаются автоматически, и поэтому его необходимо использовать только тогда, когда элементы не собираются изменяться или уже изменились. #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') для поиска текста становится безопасным.

Вариант, если количество строк не изменится (только их содержимое), состоит в том, чтобы просто объединить все содержимое вместе и проверить текст таблицы - это не будет на 100% проверять точное совпадение таблицы, но в тестовой среде, где вы контролируете данные, этого, вероятно, достаточно. Это не проверено, но что-то вроде следующего должно сделать это

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. Если вы также хотите, чтобы он повторил попытку при ошибке, возвращенной разница! (до max_wait_time секунд) вам нужно будет передать параметры для синхронизации, например inv_table.synchronize max_wait_time, errors: page.driver.invalid_element_errors + [Capybara::ElementNotFound, WhateverErrorDiffRaisesOnFailure] do ...

person Thomas Walpole    schedule 09.06.2016
comment
Привет том. Спасибо за ваш ответ, но они не работают. Я должен уточнить, что мы изменяем отдельную строку в существующей таблице. Так что count: table.raw.size == find(".inventory table").all('tr').size всегда верно. Ошибка возникает при попытке извлечь текст из узлов заголовка и данных (rows.map { |r| r.all('th,td').map { |c| c.text.strip } }). - person daino3; 09.06.2016
comment
Я использую решение № 2, потому что diff! дает гораздо более чистый вывод и более снисходителен к столбцам не по порядку / отсутствующим (важно для полей идентификаторов, которые невозможно протестировать) - person daino3; 09.06.2016
comment
@ daino3 Тогда вы, вероятно, застряли, используя свои собственные повторные попытки, поскольку вы используете методы, которые отключают капибару, встроенную в ожидание / повторную попытку, и не используете какие-либо сопоставители капибар - МОЖЕТ быть возможно использовать #synchronize, чтобы получить то, что вы хотите, передавая ошибки вы хотите повторить попытку, я обновлю ответ через несколько минут, если это так - person Thomas Walpole; 09.06.2016
comment
Спасибо, Том! Я очень ценю вашу помощь (и свалку знаний)! - person daino3; 09.06.2016
comment
@daino3 Я добавил некоторые подробности о #synchronize - на самом деле не тестировал его, но он должен указать вам на возможное решение - в основном он должен делать то же самое, что и ваш код tries, но с использованием функций капибары. - person Thomas Walpole; 10.06.2016