Capybara Ambiguity Resolution


97

Come risolvo l'ambiguità in Capybara? Per qualche motivo ho bisogno di collegamenti con gli stessi valori in una pagina, ma non posso creare un test poiché ricevo l'errore

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

Il motivo per cui non posso evitarlo è a causa del design. Sto cercando di ricreare la pagina Twitter con tweet / tag a destra e i tag a sinistra della pagina. Pertanto sarà inevitabile che la pagina dei link identici compaia sulla stessa pagina.


Puoi anche inserire del codice?
Heena Hussain

8
Non dovresti assegnare lo stesso ID a due elementi nella pagina. Se avrai collegamenti identici, non assegnare un ID agli elementi, usa invece una classe.
Chris Salzberg

Risposte:


147

La mia soluzione è

first(:link, link).click

invece di

click_link(link)

6
Questo è dettagliato nella Guida all'aggiornamento di Capybara che potresti trovare utile se hai avuto questo problema.
Ritchie

1
A partire da Capybara 2.0 non farlo a meno che non sia assolutamente necessario. Vedi la risposta di @ Andrey di seguito e la spiegazione delle corrispondenze ambigue nella guida all'aggiornamento collegata sopra.
jim

4
In particolare, Capybara 2.0 ha una logica di attesa intelligente per garantire che le specifiche passino o falliscano in modo coerente su macchine con velocità di elaborazione diverse mentre attendono solo il tempo necessario minimo. Utilizzando firstcome suggerito sopra, a meno che tu non sappia assolutamente cosa stai facendo, è probabile che si traduca in specifiche che passano per te ma che falliscono in una build CI o sulla macchina di un collega.
jim

1
Per una buona discussione, vedere: robots
Thoughtbot.com/…

74

Tale comportamento di Capybara è intenzionale e credo che non dovrebbe essere corretto come suggerito nella maggior parte delle altre risposte.

Le versioni di Capybara precedenti alla 2.0 restituivano il primo elemento invece di sollevare eccezioni, ma in seguito i manutentori di Capybara hanno deciso che è una cattiva idea ed è meglio aumentarla. È stato deciso che in molte situazioni la restituzione del primo elemento porta a non restituire l'elemento che lo sviluppatore desiderava essere restituito.

La risposta più votata qui consiglia di usare firsto allinvece di findma:

  1. alle firstnon aspettare che l'elemento con tale localizzatore appaia sulla pagina anche findse aspetta
  2. all(...).firste firstnon ti proteggerà dalla situazione in cui in futuro un altro elemento con tale localizzatore potrebbe apparire sulla pagina e di conseguenza potresti trovare un elemento errato

Quindi è consigliabile scegliere un altro localizzatore meno ambiguo : ad esempio selezionare l'elemento per id, classe o altro localizzatore css / xpath in modo che solo un elemento lo corrisponda.


Come nota, qui ci sono alcuni localizzatori che di solito considero utili per risolvere l'ambiguità:

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

    È più utile di first('ul > li')come aspetterà fino a quando il primo liapparirà sulla pagina.

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

    È meglio di first(:link, 'Create Account').clickcome aspetterà fino a quando almeno un collegamento Crea account apparirà sulla pagina. Tuttavia credo che sia meglio scegliere un localizzatore unico che non appare due volte sulla pagina.

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

    exact: true dice a Capybara di trovare solo corrispondenze esatte, ovvero di non trovare "Conferma password"


7
Questa dovrebbe essere la risposta migliore. Cerca sempre di utilizzare un selettore che utilizzi le funzionalità di attesa integrate in Capybara.
TGF

Grazie. Ho provato a usare: prima ma ho capito che funziona solo in jQuery. Quello che stavo cercando è: primo figlio
Sovraccarico119


24

NUOVA RISPOSTA:

Puoi provare qualcosa di simile

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

Potrebbe esserci un modo per farlo che faccia un uso migliore della sintassi Capybara disponibile - qualcosa del genere all("a[text='#tag1']").first.clickma non riesco a pensare alla sintassi corretta e non riesco a trovare la documentazione appropriata. Detto questo è un po 'una situazione strana per cominciare, avere due <a>tag con lo stesso id, classe il testo. C'è qualche possibilità che siano figli di div diversi, dal momento che potresti quindi fare il tuo find withinsegmento appropriato del DOM. (Sarebbe utile vedere un po 'del tuo codice HTML).


VECCHIA RISPOSTA: (dove pensavo che "# tag1" significasse che l'elemento avesse un id"tag1")

Su quale link vuoi fare clic? Se è il primo (o non importa), puoi farlo

find('#tag1').click

Altrimenti puoi farlo

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

per fare clic sul secondo.


Quella soluzione sulla prima potrebbe funzionare, ma il problema ora è che forse è scambiata per un ID CSS --------- Fallimento / Errore: trova ('# tag1'). Fai clic su # o su tutti ('# tag1 ') [0] .click Capybara :: ElementNotFound: Impossibile trovare css "# tag1"
neilmarion

find('#tag1')significa che vuoi trovare solo un elemento con id tag1. Viene sollevata tag1
un'eccezione

Puoi farlo all(:xpath, '//a[text()="#tag1"]').first.click.
Shuhei Kagawa

9

Puoi assicurarti di trovare il primo utilizzando match:

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

Ma soprattutto, probabilmente non vuoi farlo , poiché porterà a test fragili che ignorano l'odore del codice di output duplicato, il che a sua volta porta a falsi positivi che continuano a funzionare quando avrebbero dovuto fallire, perché ne hai rimosso uno corrispondente elemento ma il test ha trovato felicemente l'altro.

La soluzione migliore è usare within:

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

Ciò ti assicura di trovare l'elemento che ti aspetti di trovare, sfruttando al contempo le funzionalità di attesa e ripetizione automatica di Capybara (che perdi se usi find('.selector').click), e rende molto più chiaro quale sia l'intento.


7

Per aggiungere al corpo di conoscenza esistente qui:

Per i test JS, Capybara deve mantenere sincronizzati due thread (uno per RSpec, uno per Rails) e un secondo processo (il browser). Lo fa attendendo (fino al tempo di attesa massimo configurato) nella maggior parte dei matcher e dei metodi di ricerca dei nodi.

Capybara ha anche metodi che non aspettano, principalmente Node#all. Usarli è come dire alle tue specifiche che vorresti che fallissero in modo intermittente.

La risposta accettata suggerisce page.first('selector'). Questo è indesiderabile, almeno per le specifiche JS, perché Node#firstutilizzaNode#all .

Detto questo, Node#first vi aspetta se si configura Capybara in questo modo:

# rails_helper.rb
Capybara.wait_on_first_by_default = true

Questa opzione è stata aggiunta in Capybara 2.5.0 ed è falsa per impostazione predefinita.

Come ha detto Andrei, dovresti invece usare

find('selector', match: :first)

o cambia il tuo selettore. Entrambi funzioneranno bene indipendentemente dalla configurazione o dal driver.

Per complicare ulteriormente le cose, nelle vecchie versioni di Capybara (o con un'opzione di configurazione abilitata), #findignorerà felicemente l'ambiguità e restituirà semplicemente il primo selettore corrispondente. Anche questo non è eccezionale, poiché rende le tue specifiche meno esplicite, il che immagino sia il motivo per cui non è più il comportamento predefinito. Tralascio le specifiche perché sono già state discusse sopra.

Più risorse:


5

A causa di questo post , puoi correggerlo tramite l'opzione "match":

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

2

Considerando tutte le opzioni di cui sopra, puoi provare anche questo

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

Se stai usando il cetriolo, puoi seguire anche questo

È possibile passare il testo come parametro dai passaggi dello scenario che possono essere passaggi generici da riutilizzare nuovamente

Qualcosa di simile a When a user clicks on "text" link

E nella definizione del passaggio When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|

In questo modo, puoi riutilizzare lo stesso passaggio riducendo al minimo le righe di codice e sarebbe facile scrivere nuovi scenari di cetriolo


0

Per evitare errori ambigui nel cetriolo.

Soluzione 1

first("#tag1").click

Soluzione 2

Cucumber features/filename.feature --guess
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.