Как устранить двусмысленность в Capybara? По какой-то причине мне нужны ссылки с одинаковыми значениями на странице, но я не могу создать тест, так как получаю сообщение об ошибке
Failure/Error: click_link("#tag1")
Capybara::Ambiguous:
Ambiguous match, found 2 elements matching link "#tag1"
Причина, по которой я не могу этого избежать, заключается в дизайне. Я пытаюсь воссоздать страницу Twitter с твитами / тегами справа и тегами слева от страницы. Следовательно, страницы с одинаковыми ссылками неизбежно будут отображаться на одной странице.
ruby-on-rails-3
rspec
capybara
Neilmarion
источник
источник
Ответы:
Мое решение
вместо того
источник
first
как предложено выше, если вы абсолютно не знаете, что делаете, может привести к спецификациям, которые подходят для вас, но терпят неудачу в сборке CI или на машине коллеги.Такое поведение Capybara является преднамеренным, и я считаю, что его не следует исправлять, как предлагается в большинстве других ответов.
Версии Capybara до 2.0 возвращали первый элемент вместо того, чтобы вызывать исключение, но более поздние разработчики Capybara решили, что это плохая идея и лучше поднять ее. Было решено, что во многих ситуациях возврат первого элемента приводит к возврату не того элемента, который разработчик хотел вернуть.
Самый популярный ответ здесь рекомендует использовать
first
илиall
вместоfind
но:all
иfirst
не ждите пока элемент с таким локатором появится на странице, хотяfind
и ждетall(...).first
иfirst
не защитит вас от ситуации, что в будущем на странице может появиться другой элемент с таким локатором, и в результате вы можете найти неверный элементПоэтому рекомендуется выбрать другой, менее неоднозначный локатор : например, выбрать элемент по идентификатору, классу или другому локатору 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 искать только точные совпадения, т.е. не находить «Подтверждение пароля»источник
Вышеупомянутое решение отлично работает, но для тех, кому интересно, вы также можете использовать следующий синтаксис.
Вы можете найти дополнительную информацию здесь:
http://taimoorchangaizpucitian.wordpress.com/2013/09/06/capybara-click-link-different-cases-and-solutions/
источник
НОВЫЙ ОТВЕТ:
Вы можете попробовать что-то вроде
Может быть способ сделать это, который лучше использует доступный синтаксис Capybara - что-то вроде строк,
all("a[text='#tag1']").first.click
но я не могу придумать правильный синтаксис, и я не могу найти соответствующую документацию. Это говорит , что это немного странной ситуация , чтобы начать с, имея две<a>
метки с одинаковымid
,class
и текстом. Есть ли вероятность, что они являются дочерними элементами разных div, поскольку тогда вы можетеfind
within
создать соответствующий сегмент DOM. (Было бы полезно увидеть немного вашего источника HTML).СТАРЫЙ ОТВЕТ: (где я подумал, что «# tag1» означает, что элемент имеет значение
id
«tag1»)На какую из ссылок вы хотите перейти? Если это первое (или это не имеет значения), вы можете сделать
В противном случае вы можете сделать
чтобы щелкнуть второй.
источник
find('#tag1')
означает, что вы хотите найти только один элемент с идентификаторомtag1
. Исключение возникает из-за того, чтоtag1
на странице есть несколько элементов с idall(:xpath, '//a[text()="#tag1"]').first.click
.Вы можете убедиться, что найдете первый, используя
match
:Но, что важно, вы, вероятно, не захотите этого делать , так как это приведет к нестабильным тестам , игнорирующим запах кода дублированного вывода, что, в свою очередь, приводит к ложным срабатываниям, которые продолжают работать, когда они должны были выйти из строя, потому что вы удалили одно совпадение. элемент, но тест успешно нашел другой.
Лучше использовать
within
:Это гарантирует, что вы найдете элемент, который ожидаете найти, при этом по-прежнему используя возможности автоматического ожидания и автоповтора Capybara (которые вы теряете, если используете
find('.selector').click
), и делает гораздо более ясным, какова цель.источник
Чтобы добавить к существующим знаниям здесь:
Для тестов JS Capybara должен поддерживать синхронизацию двух потоков (один для RSpec, один для Rails) и второй процесс (браузер). Это достигается путем ожидания (до настроенного максимального времени ожидания) в большинстве сопоставлений и методов поиска узлов.
У Capybara также есть методы, которые в первую очередь не ждут
Node#all
. Их использование - это все равно что говорить вашим спецификациям, что вы хотели бы, чтобы они периодически выходили из строя.Принятый ответ подсказывает
page.first('selector')
. Это нежелательно, по крайней мере, для спецификаций JS, потому чтоNode#first
используетNode#all
.Тем не менее,
Node#first
будет ждать, если вы настроите Capybara так:Эта опция была добавлена в Capybara 2.5.0 и по умолчанию является ложной.
Как упоминал Андрей, вместо этого вы должны использовать
или измените свой селектор. Любой из них будет работать независимо от конфигурации или драйвера.
Чтобы еще больше усложнить ситуацию, в старых версиях Capybara (или с включенной опцией конфигурации),
#find
будет с радостью игнорировать двусмысленность и просто вернуть первый соответствующий селектор. Это тоже не очень хорошо, так как делает ваши спецификации менее явными, и я думаю, поэтому больше не поведение по умолчанию. Я опущу детали, потому что они уже обсуждались выше.Дополнительные ресурсы:
источник
Из-за этого поста вы можете исправить это с помощью опции "сопоставить":
источник
Учитывая все вышеперечисленные варианты, вы тоже можете попробовать это
Если вы используете огурец, вы тоже можете это сделать.
Вы можете передать текст в качестве параметра из шагов сценария, который может быть общим шагом для повторного использования
Что-то вроде
When a user clicks on "text" link
И в определении шага
When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|
Таким образом, вы можете повторно использовать один и тот же шаг, сведя к минимуму строки кода, и вам будет легко написать новые сценарии огурца.
источник
Чтобы избежать неоднозначной ошибки в огурце.
Решение 1
Решение 2
источник