Разрешение Двусмысленности Капибары
Как разрешить двусмысленность в капибаре? По какой-то причине мне нужны ссылки с одинаковыми значениями на странице, но я не могу создать тест, так как я получаю ошибку
Failure/Error: click_link("#tag1")
Capybara::Ambiguous:
Ambiguous match, found 2 elements matching link "#tag1"
причина, почему я не могу избежать это из-за дизайна. Я пытаюсь воссоздать страницу twitter с твитами / тегами справа и тегами слева от страницы. Поэтому это будет неизбежно, что одинаковые ссылки на одной странице.
8 ответов:
такое поведение капибары является преднамеренным, и я считаю, что оно не должно быть исправлено, как это предлагается в большинстве других ответов.
версии 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
говорит капибара, чтобы найти только точные совпадения, т. е. не найти "подтверждение пароля"
вышеуказанное решение отлично работает, но для тех, кто любопытен, вы также можете использовать следующий синтаксис.
click_link(link_name, match: :first)
вы можете найти дополнительную информацию здесь:
ОТВЕТ:
вы можете попробовать что-то вроде
all('a').select {|elt| elt.text == "#tag1" }.first.click
может быть способ сделать это, который лучше использует доступный синтаксис капибары - что-то вроде
all("a[text='#tag1']").first.click
но я не могу придумать правильный синтаксис из рук, и я не могу найти соответствующую документацию. Тем не менее, это немного странная ситуация для начала, имея два<a>
теги с тем жеid
,class
, и текст. Есть ли шанс, что они дети разные дивы, так как вы могли бы тогда сделать свойfind
within
соответствующий сегмент DOM. (Это поможет увидеть немного вашего HTML-код).
старый ответ: (где я думал, что '#tag1 ' означает, что элемент имел
id
из "tag1")по какой из ссылок вы хотите перейти? Если это первый (или это не имеет значения), вы можете сделать
find('#tag1').click
в противном случае вы можете сделать
all('#tag1')[1].click
нажать на второй.
вы можете убедиться, что вы найдете первый с помощью
match
:find('.selector', match: :first).click
но главное, вы наверное не хотите этого делать, так как это приведет к ломкими тесты которые игнорируют запах повторяющегося кода, что в свою очередь приводит к ложных срабатываний которые продолжают работать, когда они должны были потерпеть неудачу, потому что вы удалили один соответствующий элемент, но тест счастливо нашел другой.
лучше всего использовать
within
:within('#sidebar') do find('.selector).click end
Это гарантирует, что вы находите элемент, который вы ожидаете найти, все еще используя возможности автоматического ожидания и автоматического повтора Capybara (которые вы теряете, если используете
find('.selector').click
), и это делает его гораздо яснее, чем умысел.
чтобы добавить к существующему объему знаний здесь:
для тестов JS Capybara должна синхронизировать два потока (один для RSpec, один для Rails) и второй процесс (браузер). Это делается путем ожидания (до настроенного максимального времени ожидания) в большинстве сопоставителей и методов поиска узлов.
капибара также имеет методы, которые не ждут, в первую очередь
Node#all
. Использование их похоже на то, что вы говорите своим спецификациям, что вы хотите, чтобы они периодически терпели неудачу.принят ответ предполагает
page.first('selector')
. Это нежелательно, по крайней мере для спецификаций JS, потому чтоNode#first
используетNode#all
.что сказал:
Node#first
будет подождите, если вы настроите капибару так:# rails_helper.rb Capybara.wait_on_first_by_default = true
этот вариант был добавлено в Capybara 2.5.0 и по умолчанию имеет значение false.
как упоминал Андрей, вы должны вместо этого использовать
find('selector', match: :first)
или измените свой селектор. Либо будет работать хорошо, независимо от конфигурации или водитель.
чтобы еще больше усложнить ситуацию, в старых версиях Capybara (или с включенной опцией config),
#find
С радостью проигнорирует двусмысленность и просто вернет первый соответствующий селектор. Это тоже не очень хорошо, так как это делает ваши спецификации менее явными, и я полагаю, что это больше не поведение по умолчанию. Я опущу детали, потому что они уже обсуждались выше.больше ресурсы:
из-за этот пост, вы можете исправить это с помощью опции "match":
Capybara.configure do |config| config.match = :prefer_exact end