Resolución de ambigüedad de carpincho

97

¿Cómo resuelvo la ambigüedad en Capybara? Por alguna razón, necesito enlaces con los mismos valores en una página, pero no puedo crear una prueba porque obtengo el error

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

La razón por la que no puedo evitar esto es por el diseño. Estoy intentando recrear la página de Twitter con tweets / etiquetas a la derecha y las etiquetas a la izquierda de la página. Por lo tanto, será inevitable que aparezcan páginas de enlaces idénticas en la misma página.

neilmarion
fuente
¿Puedes publicar algún código también?
Heena Hussain
8
No deberías asignar la misma identificación a dos elementos en la página. Si tiene enlaces idénticos, no asigne una identificación a los elementos, use una clase en su lugar.
Chris Salzberg

Respuestas:

147

Mi solucion es

first(:link, link).click

en vez de

click_link(link)
e-zinc
fuente
6
Esto se detalla en la Guía de actualización de Capybara que puede resultarle útil si tuviera este problema.
Ritchie
1
A partir de Capybara 2.0, no haga esto a menos que sea absolutamente necesario. Vea la respuesta de @ Andrey a continuación y la explicación de Coincidencias ambiguas en la guía de actualización vinculada anteriormente.
Jim
4
Específicamente, Capybara 2.0 tiene una lógica de espera inteligente para garantizar que las especificaciones pasen o fallen de manera consistente en máquinas de diferentes velocidades de procesamiento mientras solo se espera el tiempo mínimo necesario. Usar firstcomo se sugirió anteriormente, a menos que sepa absolutamente lo que está haciendo, es probable que dé como resultado especificaciones que se le pasan pero fallan en una compilación de CI o en la máquina de un colega.
jim
1
Para una buena discusión, consulte: robots.thoughtbot.com/…
jim
74

Tal comportamiento de Capybara es intencional y creo que no debería arreglarse como se sugiere en la mayoría de las otras respuestas.

Las versiones de Capybara anteriores a 2.0 devolvieron el primer elemento en lugar de generar una excepción, pero los mantenedores posteriores de Capybara decidieron que es una mala idea y es mejor aumentarla. Se decidió que en muchas situaciones devolver el primer elemento conduce a devolver no el elemento que el desarrollador quería que se devolviera.

La respuesta más votada aquí recomienda usar firsto en alllugar de findpero:

  1. ally firstno espere hasta que el elemento con dicho localizador aparezca en la página, aunque findespera
  2. all(...).firsty firstno lo protegerá de una situación en la que en el futuro pueda aparecer otro elemento con dicho localizador en la página y, como resultado, puede encontrar un elemento incorrecto

Por lo tanto, se recomienda elegir otro localizador menos ambiguo : por ejemplo, seleccione el elemento por id, clase u otro localizador css / xpath para que solo un elemento coincida con él.


Como nota, aquí hay algunos localizadores que generalmente considero útiles a la hora de resolver ambigüedades:

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

    Es más útil que first('ul > li')esperar hasta lique aparezca primero en la página.

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

    Es mejor que first(:link, 'Create Account').clickesperar hasta que aparezca al menos un enlace Crear cuenta en la página. Sin embargo, creo que es mejor elegir un localizador único que no aparezca dos veces en la página.

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

    exact: true le dice a Capybara que busque solo coincidencias exactas, es decir, que no encuentre "Confirmación de contraseña"

Andrei Botalov
fuente
7
Esta debería ser la mejor respuesta. Intente siempre usar un selector que haga uso de las capacidades de espera integradas en Capybara.
tgf
Gracias. Intenté usar: primero, pero me di cuenta de que solo funciona en jQuery. Lo que estaba buscando es: primer hijo
Overload119
24

NUEVA RESPUESTA:

Puedes probar algo como

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

Puede haber una forma de hacer esto que haga un mejor uso de la sintaxis de Capybara disponible, algo parecido a, all("a[text='#tag1']").first.clickpero no puedo pensar en la sintaxis correcta y no puedo encontrar la documentación adecuada. Dicho esto, es un poco de una situación extraña para empezar, que tiene dos <a>etiquetas con el mismo id, classy texto. ¿Existe alguna posibilidad de que sean hijos de diferentes divs, ya que luego podría hacer su find withinsegmento apropiado del DOM? (Sería útil ver un poco de su fuente HTML).


ANTIGUA RESPUESTA: (donde pensé que '# etiqueta1' significaba que el elemento tenía una id"etiqueta1")

¿En cuál de los enlaces quieres hacer clic? Si es el primero (o no importa), puede hacer

find('#tag1').click

De lo contrario, puedes hacer

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

para hacer clic en el segundo.

Amit Kumar Gupta
fuente
Esa solución en la primera podría funcionar, pero el problema ahora es que tal vez se confunda con un ID de CSS --------- Fallo / Error: busque ('# tag1'). Haga clic en # o en todo ('# tag1 ') [0] .haga clic en Capybara :: ElementNotFound: No se puede encontrar css "# tag1"
neilmarion
find('#tag1')significa que desea encontrar solo un elemento con id tag1. Se plantea una excepción ya que hay varios elementos con identificación tag1en la página
Andrei Botalov
Puede hacer all(:xpath, '//a[text()="#tag1"]').first.click.
Shuhei Kagawa
9

Puede asegurarse de encontrar el primero utilizando match:

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

Pero lo que es más importante, probablemente no desee hacer esto , ya que conducirá a pruebas frágiles que ignoran el olor del código de salida duplicado, lo que a su vez conduce a falsos positivos que siguen funcionando cuando deberían haber fallado, porque eliminó una coincidencia elemento, pero la prueba felizmente encontró el otro.

La mejor opción es usar within:

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

Esto asegura que está encontrando el elemento que espera encontrar, mientras sigue aprovechando las capacidades de espera automática y reintento automático de Capybara (que pierde si usa find('.selector').click), y deja mucho más claro cuál es la intención.

TALlama
fuente
7

Para agregar al cuerpo de conocimiento existente aquí:

Para las pruebas de JS, Capybara tiene que mantener dos hilos (uno para RSpec, otro para Rails) y un segundo proceso (el navegador) sincronizados. Lo hace esperando (hasta el tiempo de espera máximo configurado) en la mayoría de los métodos de búsqueda de nodos y comparadores.

Carpincho también tiene métodos que no esperan, principalmente Node#all. Usarlos es como decirle a sus especificaciones que le gustaría que fallaran de forma intermitente.

La respuesta aceptada sugiere page.first('selector'). Esto no es deseable, al menos para las especificaciones de JS, porque Node#firstusesNode#all .

Dicho esto, Node#first se espera si configura capibara, así:

# rails_helper.rb
Capybara.wait_on_first_by_default = true

Esta opción se agregó en Capybara 2.5.0 y es falsa por defecto.

Como mencionó Andrei, deberías usar

find('selector', match: :first)

o cambia tu selector. Cualquiera funcionará bien independientemente de la configuración o el controlador.

Para complicar aún más las cosas, en versiones antiguas de Capybara (o con una opción de configuración habilitada), #findfelizmente ignorará la ambigüedad y simplemente devolverá el primer selector coincidente. Esto tampoco es genial, ya que hace que sus especificaciones sean menos explícitas, lo que imagino que es la razón por la que ya no es el comportamiento predeterminado. Dejaré de lado los detalles porque ya se han discutido anteriormente.

Más recursos:

johncip
fuente
5

Debido a esta publicación , puede solucionarlo a través de la opción "coincidir":

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

Teniendo en cuenta todas las opciones anteriores, también puede probar esto

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

Si está usando pepino, puede seguir esto también

Puede pasar el texto como parámetro de los pasos del escenario, que puede ser un paso genérico para reutilizarlo nuevamente.

Algo como When a user clicks on "text" link

Y en paso definición When(/^(?:user) clicks on "([^"]*)" (?:link)$/) do |text|

De esta manera, puede reutilizar el mismo paso minimizando las líneas de código y sería fácil escribir nuevos escenarios de pepino.

Kiran Reddy
fuente
0

Para evitar errores ambiguos en pepino.

Solución 1

first("#tag1").click

Solucion 2

Cucumber features/filename.feature --guess
Aravin
fuente