Capybara Ambiguity Resolution

97

Wie löse ich Mehrdeutigkeiten in Capybara auf? Aus irgendeinem Grund benötige ich Links mit denselben Werten auf einer Seite, kann jedoch keinen Test erstellen, da der Fehler angezeigt wird

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

Der Grund, warum ich dies nicht vermeiden kann, ist das Design. Ich versuche, die Twitter-Seite mit Tweets / Tags rechts und den Tags links auf der Seite neu zu erstellen. Daher ist es unvermeidlich, dass identische Links auf derselben Seite angezeigt werden.

neilmarion
quelle
Kannst du bitte auch einen Code posten?
Heena Hussain
8
Sie sollten nicht zwei Elementen auf der Seite dieselbe ID zuweisen. Wenn Sie identische Links haben, weisen Sie den Elementen keine ID zu, sondern verwenden Sie stattdessen eine Klasse.
Chris Salzberg

Antworten:

147

Meine Lösung ist

first(:link, link).click

anstatt

click_link(link)
E-Zink
quelle
6
Dies wird im Capybara-Upgrade-Handbuch beschrieben, das Sie möglicherweise nützlich finden, wenn Sie dieses Problem hatten.
Ritchie
1
Ab Capybara 2.0 tun Sie dies nur, wenn Sie es unbedingt müssen. Siehe die Antwort von @ Andrey unten und die Erklärung der mehrdeutigen Übereinstimmungen im oben verlinkten Upgrade-Handbuch.
Jim
4
Insbesondere verfügt Capybara 2.0 über eine intelligente Wartelogik, um sicherzustellen, dass Spezifikationen auf Computern mit unterschiedlichen Verarbeitungsgeschwindigkeiten konsistent bestanden oder nicht bestanden werden, während nur die minimal erforderliche Zeit gewartet wird. Die Verwendung firstwie oben vorgeschlagen führt, sofern Sie nicht genau wissen, was Sie tun, wahrscheinlich zu Spezifikationen, die für Sie gelten, aber in einem CI-Build oder auf dem Computer eines Kollegen fehlschlagen.
Jim
1
Für eine gute Diskussion siehe: robots.thoughtbot.com/…
Jim
74

Ein solches Verhalten von Capybara ist beabsichtigt und ich glaube, es sollte nicht behoben werden, wie in den meisten anderen Antworten vorgeschlagen.

Versionen von Capybara vor 2.0 gaben das erste Element zurück, anstatt eine Ausnahme auszulösen, aber spätere Betreuer von Capybara entschieden, dass es eine schlechte Idee ist und es besser ist, sie auszulösen. Es wurde entschieden, dass in vielen Situationen die Rückgabe des ersten Elements dazu führt, dass nicht das Element zurückgegeben wird, das der Entwickler zurückgeben wollte.

Die am besten bewertete Antwort hier empfiehlt die Verwendung von firstoder allanstelle von findaber:

  1. allund firstwarten Sie nicht, bis ein Element mit einem solchen Locator auf der Seite angezeigt findwird
  2. all(...).firstund firstschützt Sie nicht vor der Situation, dass in Zukunft ein anderes Element mit einem solchen Locator auf der Seite angezeigt wird und Sie möglicherweise ein falsches Element finden

So es eine andere wählen adviced ist, weniger zweideutig Locator : zum Beispiel wählt Element von ID, Klasse oder anderen css / XPath - Locator , so dass nur ein Element es übereinstimmen.


Als Hinweis hier einige Locators, die ich normalerweise als nützlich erachte, um Mehrdeutigkeiten aufzulösen:

  • find('ul > li:first-child')

    Es ist nützlicher als first('ul > li')zu warten, bis das erste liMal auf der Seite erscheint.

  • click_link('Create Account', match: :first)

    Es ist besser als first(:link, 'Create Account').clickzu warten, bis mindestens ein Link zum Erstellen eines Kontos auf der Seite angezeigt wird. Ich glaube jedoch, dass es besser ist, einen eindeutigen Locator zu wählen, der nicht zweimal auf der Seite erscheint.

  • fill_in('Password', with: 'secret', exact: true)

    exact: true fordert Capybara auf, nur genaue Übereinstimmungen zu finden, dh keine "Passwortbestätigung" zu finden

Andrei Botalov
quelle
7
Dies sollte die beste Antwort sein. Versuchen Sie immer, einen Selektor zu verwenden, der die in Capybara integrierten Wartefunktionen nutzt.
tgf
Vielen Dank. Ich habe versucht zu verwenden: zuerst aber festgestellt, dass das nur in jQuery funktioniert. Was ich gesucht habe ist: erstes Kind
Überladung119
24

NEUE ANTWORT:

Sie können so etwas versuchen

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

Möglicherweise gibt es eine Möglichkeit, die verfügbare Capybara-Syntax besser zu nutzen - etwas in der Art von, all("a[text='#tag1']").first.clickaber ich kann mir die richtige Syntax nicht sofort vorstellen und finde keine geeignete Dokumentation. Das heißt, es ist zunächst eine etwas seltsame Situation, zwei <a>Tags mit demselben zu idhaben.class und Text. Gibt es eine Chance, dass sie Kinder verschiedener Divs sind, da Sie dann find withindas entsprechende Segment des DOM erstellen können? (Es wäre hilfreich, ein bisschen von Ihrer HTML-Quelle zu sehen).


ALTE ANTWORT: (wo ich dachte, '# tag1' bedeutete, dass das Element ein id"tag1" hatte)

Auf welchen der Links möchten Sie klicken? Wenn es das erste ist (oder es keine Rolle spielt), können Sie es tun

find('#tag1').click

Sonst kannst du machen

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

um auf den zweiten zu klicken.

Amit Kumar Gupta
quelle
Diese Lösung für die erste könnte funktionieren, aber das Problem ist jetzt, dass sie möglicherweise mit einer CSS-ID verwechselt wird --------- Fehler / Fehler: find ('# tag1'). Klicken Sie auf # oder alle ('# tag1 ') [0] .click Capybara :: ElementNotFound: CSS "# tag1" konnte nicht gefunden werden
neilmarion
find('#tag1')bedeutet, dass Sie nur ein Element mit der ID finden möchten tag1. Ausnahme wird tag1
ausgelöst,
Das kannst du machen all(:xpath, '//a[text()="#tag1"]').first.click.
Shuhei Kagawa
9

Sie können sicherstellen, dass Sie den ersten finden, indem Sie match:

find('.selector', match: :first).click

Aber wichtig ist, dass Sie dies wahrscheinlich nicht möchten , da dies zu spröden Tests führt , bei denen der Geruch von Code mit doppelter Ausgabe ignoriert wird, was wiederum zu Fehlalarmen führt, die weiter funktionieren, wenn sie hätten fehlschlagen sollen, weil Sie eine Übereinstimmung entfernt haben Element, aber der Test fand glücklich den anderen.

Die bessere Wette ist zu verwenden within:

within('#sidebar') do
  find('.selector).click
end

Dies stellt sicher, dass Sie das Element finden, das Sie erwarten, und nutzt gleichzeitig die automatischen Warte- und Wiederholungsfunktionen von Capybara (die Sie verlieren, wenn Sie es verwenden find('.selector').click), und macht deutlich, was die Absicht ist.

TALlama
quelle
7

Um das vorhandene Wissen hier zu erweitern:

Für JS-Tests muss Capybara zwei Threads (einen für RSpec, einen für Rails) und einen zweiten Prozess (den Browser) synchron halten. Dies geschieht durch Warten (bis zur konfigurierten maximalen Wartezeit) in den meisten Matchern und Knotenfindungsmethoden.

Capybara hat auch Methoden, die in erster Linie nicht warten Node#all. Wenn Sie sie verwenden, teilen Sie Ihren Spezifikationen mit, dass sie zeitweise fehlschlagen sollen.

Die akzeptierte Antwort schlägt vor page.first('selector'). Dies ist zumindest für JS-Spezifikationen wegen der Verwendung unerwünschtNode#firstNode#all .

Das heißt, Node#first ich werde warten, wenn Sie Capybara so konfigurieren:

# rails_helper.rb
Capybara.wait_on_first_by_default = true

Diese Option wurde in Capybara 2.5.0 hinzugefügt und ist standardmäßig falsch.

Wie Andrei erwähnt, sollten Sie stattdessen verwenden

find('selector', match: :first)

oder ändern Sie Ihren Selektor. Entweder funktioniert gut, unabhängig von Konfiguration oder Treiber.

Um die Sache noch weiter zu verkomplizieren, werden in alten Versionen von Capybara (oder mit aktivierter Konfigurationsoption) #findMehrdeutigkeiten gerne ignoriert und nur der erste passende Selektor zurückgegeben. Dies ist auch nicht besonders gut, da dadurch Ihre Spezifikationen weniger explizit werden. Ich kann mir vorstellen, dass dies nicht mehr das Standardverhalten ist. Ich werde die Einzelheiten weglassen, da sie oben bereits besprochen wurden.

Mehr Ressourcen:

Johncip
quelle
5

Aufgrund dieses Beitrags können Sie ihn über die Option "Übereinstimmung" beheben:

Capybara.configure do |config|
  config.match = :prefer_exact
end
Skydan
quelle
2

Wenn Sie alle oben genannten Optionen in Betracht ziehen, können Sie dies auch versuchen

find("a", text: text, match: :prefer_exact).click

Wenn Sie Gurke verwenden, können Sie dies auch befolgen

Sie können den Text als Parameter aus den Szenarioschritten übergeben, die ein allgemeiner Schritt zur erneuten Verwendung sein können

Etwas wie When a user clicks on "text" link

Und in Schritt Definition When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|

Auf diese Weise können Sie denselben Schritt wiederverwenden, indem Sie die Codezeilen minimieren, und es wäre einfach, neue Gurkenszenarien zu schreiben

Kiran Reddy
quelle
0

Um mehrdeutige Gurkenfehler zu vermeiden.

Lösung 1

first("#tag1").click

Lösung 2

Cucumber features/filename.feature --guess
Aravin
quelle