Jak rozwiązać dwuznaczność w Kapibara? Z jakiegoś powodu potrzebuję linków z tymi samymi wartościami na stronie, ale nie mogę utworzyć testu, ponieważ pojawia się błąd
Failure/Error: click_link("#tag1")
Capybara::Ambiguous:
Ambiguous match, found 2 elements matching link "#tag1"
Powodem, dla którego nie mogę tego uniknąć, jest projekt. Próbuję odtworzyć stronę twittera z tweetami / tagami po prawej stronie i tagami po lewej stronie. Dlatego nieuniknione będzie, że identyczna strona z linkami pojawi się na tej samej stronie.
ruby-on-rails-3
rspec
capybara
neilmarion
źródło
źródło
Odpowiedzi:
Moje rozwiązanie to
zamiast
źródło
first
zgodnie z powyższą sugestią, chyba że absolutnie wiesz, co robisz, prawdopodobnie spowoduje, że specyfikacje będą dla ciebie dobre, ale zawiodą w kompilacji CI lub na komputerze kolegi.Takie zachowanie Kapibary jest celowe i uważam, że nie powinno się tego naprawiać, jak sugeruje większość innych odpowiedzi.
Wersje Kapibary przed 2.0 zwróciły pierwszy element zamiast zgłaszać wyjątek, ale później opiekunowie Kapibary uznali, że to zły pomysł i lepiej go podnieść. Uznano, że w wielu sytuacjach zwrócenie pierwszego elementu prowadzi do zwrócenia nie tego elementu, który deweloper chciał zwrócić.
Najbardziej pozytywną odpowiedzią tutaj jest użycie
first
luball
zamiastfind
ale:all
ifirst
nie czekaj, aż element z takim lokalizatorem pojawi się na stronie, chociażfind
czekaall(...).first
ifirst
nie uchroni Cię przed sytuacją, że w przyszłości na stronie może pojawić się kolejny element z takim lokalizatorem iw rezultacie możesz znaleźć nieprawidłowy elementDlatego zaleca się wybranie innego, mniej niejednoznacznego lokalizatora : na przykład wybierz element według identyfikatora, klasy lub innego lokalizatora css / xpath, aby pasował do niego tylko jeden element.
Uwaga: niektóre lokalizatory, które zwykle uważam za przydatne przy rozwiązywaniu niejednoznaczności:
find('ul > li:first-child')
Jest to bardziej przydatne niż
first('ul > li')
to, że będzie czekało do pierwszegoli
pojawi się na stronie.click_link('Create Account', match: :first)
To jest lepsze niż
first(:link, 'Create Account').click
będzie czekać, aż na stronie pojawi się co najmniej jeden link Utwórz konto. Uważam jednak, że lepiej jest wybrać unikalny lokalizator, który nie pojawia się na stronie dwukrotnie.fill_in('Password', with: 'secret', exact: true)
exact: true
mówi Kapibarze, aby znalazł tylko dokładne dopasowania, tj. nie znalazł „Potwierdzenia hasła”źródło
Powyższe rozwiązanie działa świetnie, ale dla ciekawskich możesz również użyć następującej składni.
Więcej informacji znajdziesz tutaj:
http://taimoorchangaizpucitian.wordpress.com/2013/09/06/capybara-click-link-different-cases-and-solutions/
źródło
NOWA ODPOWIEDŹ:
Możesz spróbować czegoś takiego
Może istnieć sposób, aby to zrobić, który lepiej wykorzystuje dostępną składnię Kapibary - coś podobnego,
all("a[text='#tag1']").first.click
ale nie mogę wymyślić prawidłowej składni od ręki i nie mogę znaleźć odpowiedniej dokumentacji. Który powiedział, że jest to trochę dziwnej sytuacji, aby rozpocząć, posiadające dwa<a>
tagi z tego samegoid
,class
i tekstu. Czy jest jakaś szansa, że są dziećmi różnych elementów div, skoro możesz wtedy utworzyćfind
within
odpowiedni segment DOM. (Przydałoby się zobaczyć trochę twojego źródła HTML).STARE ODPOWIEDŹ: (gdzie pomyślałem, że „# tag1” oznacza, że element ma
id
„tag1”)Który z linków chcesz kliknąć? Jeśli to pierwsza (lub nie ma to znaczenia), możesz to zrobić
W przeciwnym razie możesz to zrobić
kliknij drugi.
źródło
find('#tag1')
oznacza, że chcesz znaleźć tylko jeden element o identyfikatorzetag1
. Jest wyjątek gdyż istnieje kilka elementów z identyfikatoremtag1
na stronieall(:xpath, '//a[text()="#tag1"]').first.click
.Możesz upewnić się, że znajdziesz pierwszy, używając
match
:Ale co ważne, prawdopodobnie nie chcesz tego robić , ponieważ doprowadzi to do kruchych testów , które ignorują zapach kodu duplikatów danych wyjściowych, co z kolei prowadzi do fałszywych alarmów, które działają, gdy powinny się nie udać, ponieważ usunąłeś jeden pasujący element, ale test szczęśliwie odnalazł drugi.
Lepiej jest użyć
within
:Gwarantuje to, że znajdujesz element, którego spodziewasz się znaleźć, jednocześnie wykorzystując możliwości automatycznego czekania i automatycznego ponawiania kapibary (które tracisz, jeśli używasz
find('.selector').click
), i sprawia, że jest znacznie jaśniejszy, jaki jest zamiar.źródło
Aby dodać do istniejącej wiedzy tutaj:
W przypadku testów JS Capybara musi utrzymywać synchronizację dwóch wątków (jednego dla RSpec, jednego dla Railsów) i drugiego procesu (przeglądarki). Odbywa się to poprzez czekanie (do skonfigurowanego maksymalnego czasu oczekiwania) w większości dopasowań i metod wyszukiwania węzłów.
Kapibara ma również metody, które przede wszystkim nie czekają
Node#all
. Korzystanie z nich jest jak mówienie specyfikacjom, że chcesz, aby okresowo zawodziły.Przyjęta odpowiedź sugeruje
page.first('selector')
. Jest to niepożądane, przynajmniej w przypadku specyfikacji JS, ponieważNode#first
używaNode#all
.Powiedział, że
Node#first
będzie czekać, jeśli skonfigurować Capybara tak:Ta opcja została dodana w Kapibara 2.5.0 i domyślnie jest fałszywa.
Jak wspomniał Andrei, powinieneś zamiast tego użyć
lub zmień selektor. Albo będzie działać dobrze niezależnie od konfiguracji lub sterownika.
Aby jeszcze bardziej skomplikować sprawę, w starszych wersjach Kapibary (lub z włączoną opcją konfiguracji)
#find
z radością zignoruje niejednoznaczność i zwróci pierwszy pasujący selektor. To też nie jest świetne, ponieważ sprawia, że twoje specyfikacje są mniej wyraźne, co, jak sądzę, jest powodem, dla którego nie jest już domyślnym zachowaniem. Pominę szczegóły, ponieważ zostały już omówione powyżej.Więcej zasobów:
źródło
W związku z tym postem możesz to naprawić za pomocą opcji „dopasuj”:
źródło
Biorąc pod uwagę wszystkie powyższe opcje, możesz spróbować również tego
Jeśli używasz ogórka, również możesz to zrobić
Możesz przekazać tekst jako parametr z kroków scenariusza, który może być ogólnym krokiem do ponownego użycia
Coś jak
When a user clicks on "text" link
I w definicji kroku
When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|
W ten sposób możesz ponownie użyć tego samego kroku, minimalizując linie kodu i łatwo byłoby napisać nowe scenariusze dotyczące ogórków
źródło
Aby uniknąć niejednoznacznych błędów w ogórku.
Rozwiązanie 1
Rozwiązanie 2
źródło