Роздільна здатність Капібари


97

Як вирішити двозначність у Капібарі? Чомусь мені потрібні посилання на однакові значення на сторінці, але я не можу створити тест, оскільки я отримую помилку

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

Причина, чому я не можу цього уникнути, - це через дизайн. Я намагаюся відтворити сторінку щебетання з твітами / тегами праворуч та тегами зліва на сторінці. Тому неминуче, що однакова сторінка посилань відображатиметься на одній сторінці.


Чи можете ви також опублікувати якийсь код?
Хена Хуссей

8
Не слід призначати один і той же ідентифікатор двом елементам на сторінці. Якщо у вас будуть однакові посилання, то не призначайте ідентифікаторам елементів, використовуйте натомість клас.
Кріс Зальцберг

Відповіді:


147

Моє рішення таке

first(:link, link).click

замість

click_link(link)

6
Це детально описано в Посібнику з оновлення Capybara, який може бути корисним у вас, якщо виникли проблеми.
Річі

1
Як і у Capybara 2.0, не робіть цього, якщо вам абсолютно не доведеться. Дивіться відповідь @ Андрея нижче та пояснення неоднозначних збігів у посібнику з оновлення, пов’язаному вище.
Джим

4
Зокрема, Capybara 2.0 має інтелектуальну логіку очікування, щоб забезпечити послідовне проходження або збій технічних характеристик на машинах різної швидкості обробки, очікуючи лише мінімально необхідний час. Використання, firstяк було запропоновано вище, якщо ви абсолютно не знаєте, що ви робите, швидше за все, це призведе до специфікацій, які проходять для вас, але виходять з ладу у складі CI або на машині колеги.
Джим

1
Для гарної дискусії дивіться: robots.thoughtbot.com/…
jim

74

Така поведінка Капібари є навмисною, і я вважаю, що її не слід фіксувати, як пропонується в більшості інших відповідей.

Версії Capybara до версії 2.0 повернули перший елемент замість того, щоб збільшити виняток, але пізніше технічні працівники Capybara вирішили, що це погана ідея і краще підняти її. Було вирішено, що в багатьох ситуаціях повернення першого елемента призводить до повернення не того елемента, який розробник хотів повернути.

Тут найкраще відповідь рекомендую використовувати firstабо allзамість цього 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
Це має бути головна відповідь. Завжди намагайтеся використовувати селектор, який використовуватиме вбудовані можливості очікування в Капібарі.
tgf

Дякую. Я спробував використати: спершу, але зрозумів, що працює лише у jQuery. Що я шукав, це: перша дитина
Перевантажений119


24

НОВИЙ ВІДПОВІДЬ:

Можна спробувати щось на кшталт

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

Можливо, це зробити так, щоб краще використовувати наявний синтаксис Capybara - щось уздовж, all("a[text='#tag1']").first.clickале я не можу придумати правильний синтаксис і не можу знайти відповідну документацію. Це говорить , що це трохи дивною ситуація , щоб почати с, маючи дві <a>мітки з однаковим id, classі текстом. Чи є ймовірність, що вони є дітьми різних дивів, оскільки ви потім зможете зробити find withinвідповідний сегмент DOM. (Це допоможе побачити трохи вашого джерела HTML).


СТАРИЙ ВІДПОВІДЬ: (де я думав, що "# tag1" означав, що елемент має id"tag1")

На яке з посилань ви хочете натиснути? Якщо це перше (або це не має значення), ви можете зробити

find('#tag1').click

Інакше можна зробити

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

натиснути другий.


Це рішення на першому може бути спрацьовим, але проблема полягає в тому, що це може бути помилково для css id --------- Failure / Error: find ('# tag1'). Клацніть # або all ('# tag1 ') [0] .click Capybara :: ElementNotFound: Не вдається знайти css "# tag1"
neilmarion

find('#tag1')означає, що ви хочете знайти лише один елемент з id tag1. Виняток виникає , оскільки є кілька елементів з ідентифікатором tag1на сторінці
Андрію Боталов

Можна зробити all(:xpath, '//a[text()="#tag1"]').first.click.
Shuhei Kagawa

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 .

Однак це Node#first буде чекати, якщо ви налаштуєте Capybara так:

# rails_helper.rb
Capybara.wait_on_first_by_default = true

Ця опція була додана в Capybara 2.5.0 і за замовчуванням є false.

Як згадував Андрій, замість цього вам слід скористатися

find('selector', match: :first)

або змінити перемикач. Або буде добре працювати незалежно від конфігурації чи драйвера.

Щоб ще більше ускладнити речі, у старих версіях Capybara (або з увімкненою опцією config) #findрадісно ігноруватиметься неоднозначність і просто поверне перший вибір відповідника. Це теж не чудово, оскільки це робить ваші характеристики менш явними, що, як я думаю, тому більше не поведінка за замовчуванням. Я залишу без уваги специфіку, оскільки про них вже йшлося вище.

Більше ресурсів:



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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.