Разрешение неоднозначности Capybara

Как разрешить двусмысленность в Capybara? По какой-то причине мне нужны ссылки с одинаковыми значениями на странице, но я не могу создать тест, так как получаю ошибку

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

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

Ответ 1

Мое решение

first(:link, link).click

вместо

click_link(link)

Ответ 2

Такое поведение Капибары преднамеренно, и я считаю, что это не должно быть исправлено, как было предложено в большинстве других ответов.

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

Наиболее рекомендуемый ответ здесь рекомендуется использовать first или all вместо find, но:

  • all и first не ждите, пока элемент с таким локатором не появится на странице, хотя find ждет
  • all(...).first и first не защитят вас от ситуации, когда в будущем на странице может появиться другой элемент с таким локатором, и в результате вы можете найти неправильный элемент

Итак, ему рекомендуется выбрать другой, менее двусмысленный локатор: например, выбрать элемент по id, классу или другому локатору 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 найти только точные совпадения, т.е. не найти "Подтверждение пароля"

Ответ 4

НОВЫЙ ОТВЕТ:

Вы можете попробовать что-то вроде

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

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


OLD ANSWER: (где я думал, что "# tag1" означает, что у элемента был id "tag1" )

Какую из ссылок вы хотите нажать? Если это первый (или это не имеет значения), вы можете сделать

find('#tag1').click

В противном случае вы можете сделать

all('#tag1')[1].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) и второго процесса (браузера). Он делает это, ожидая (до установленного максимального времени ожидания) в большинстве методов сопоставления и node -finding.

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 и по умолчанию является ложным.

Как упомянул Андрей, вместо этого вы должны использовать

find('selector', match: :first)

или измените свой селектор. Любой из них будет хорошо работать независимо от конфигурации или драйвера.

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

Дополнительные ресурсы:

Ответ 7

Благодаря этот пост, вы можете исправить его с помощью опции "match":

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

Ответ 8

Чтобы избежать неоднозначной ошибки в огурцах.

Решение 1

first("#tag1").click

Решение 2

Cucumber features/filename.feature --guess