¿Cómo hacer que Capybara compruebe la visibilidad después de que se haya ejecutado un JS?

82

Después de cargar una página, tengo un código que se ejecuta, oculta y muestra varios elementos basados ​​en datos devueltos por un xhr.

Mi prueba de integración se parece a esto:

it "should not show the blah" do
    page.find('#blah').visible?.should be_true
end 

Cuando voy manualmente a la página en el contexto en que se ejecuta esta prueba, #blah no es visible como esperaba. Sospecho que Capybara está mirando el estado inicial de la página (invisible en este caso), evaluando el estado del DOM y fallando la prueba antes de que se ejecute JS.

Sí, configuré el :js => truebloque de descripción que contiene :)

¡Cualquier idea será muy apreciada! Espero no tener que poner un retraso intencional aquí, que se siente inestable y ralentizará las cosas.

Kevin Davis
fuente
1
¿Qué es el conductor? ¿Cuál es el HTML esperado una vez finalizado el XHR?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

132

Creo que la finddeclaración aquí es la que tiene la espera implícita, por lo que Capybara esperará hasta que el elemento esté en la página, pero no esperará a que se haga visible.

Aquí, querrá que Capybara espere a que aparezca el elemento visible, lo que debería ser posible al especificar la visibleopción:

expect(page).to have_selector('#blah', visible: true)

No lo he probado, pero la ignore_hidden_elementsopción de configuración también podría ser útil aquí, si quisiera findesperar siempre los elementos visibles.

Jon M
fuente
6
en caso de que alguien inicialmente pierda la nota de @ Kevin (como yo), debe tener js:trueen el bloque contenedor para que esto funcione.
Chris Beck
3
Esto es asombroso. Me ayudó a llegar a: expect(page).to have_selector('#flash-message', visible: false, text: "Signed in Successfully")- gracias por publicar esta respuesta
Chuck Bergeron
La opción visible no tuvo ningún efecto para mí al probar la visibilidad de un elemento deslizante de jquery. Tuve que usar esperar (find ('# blah'). Visible?). To be_falsey.
trueinViso
Quiero algo como is_visible? luego haz eso, haz otra cosa
user1735921
37

Esta es otra forma de hacerlo que funciona perfectamente bien para mí:

find(:css, "#some_element").should be_visible

Especialmente para hallazgos más complejos, como

find(:css, "#comment_stream_list li[data-id='#{@id3}']").should_not be_visible

lo que afirmaría que un elemento se ha ocultado.

Björn Grossmann
fuente
1
¿ should_not be_visibleNo me parece bien que Capybara se ejecute wait_until { base.visible? }cuando el elemento # está visible? siendo invocado.
Phương Nguyễn
1
@ phng-nguyn, #wait_until se ha eliminado de las versiones recientes de Capybara.
solidcell
3
En realidad, esta es una variante mejor que la "page.should have_selector ...", porque en caso de que aparezca un texto defectuoso, se diría que el elemento es realmente invisible de forma explícita, mientras que la variante mencionada solo evoca el error 'no hubo coincidencias' , eso es un poco frustrante.
installero
Esta no es la misma que la solución aceptada. Para este código, Capybara solo esperará hasta que el elemento esté en el DOM y luego verificará la visibilidad de inmediato. Si espera que cambie la visibilidad, esto puede agregar una condición de carrera a las especificaciones.
johncip
1
¿Alguien sabe si las .should_not be_visiblecubiertas display: noneestablecidas por jQuery? No parece funcionar para mí.
Antrikshy
20

Si desea verificar que un elemento está en la página pero no está visible, visible: falseno funcionará como podría esperar. Me dejó perplejo un poco.

He aquí cómo hacerlo:

# assert element is present, regardless of visibility
page.should have_css('#some_element', :visible => false)
# assert visible element is not present
page.should have_no_css('#some_element', :visible => true)
Zubin
fuente
1
No se puede usar should_notcon carpincho, debido a ajax
Marc-André Lafortune
@ Marc-AndréLafortune, para probar ajax con capibara usa Capybara.javascript_driver.
Zubin
1
con javascript_drive, have_cssesperará toda la duración del tiempo de espera antes de darse por vencido, porque espera encontrar el elemento. Por ejemplo, tienes que usar en should have_no_contentlugar de should_not have_content.
Marc-André Lafortune
1
expect(page).not_to have_selector("#some_element", visible: true)funcionó bien para mí javascript_driver, sin esperar toda la duración del tiempo de espera.
Jason Swett
1
En realidad, desde mi comentario, esto se ha cambiado y puede (y debe) usar should_notahora, ¡lo siento!
Marc-André Lafortune
9

Utilizando:

 Ruby:     ruby 1.9.3dev (2011-09-23 revision 33323) [i686-linux]
 Rails:    3.2.9
 Capybara: 2.0.3

Tengo una aplicación Rails en la que hay un enlace que, cuando se hace clic, debe enviar una solicitud de publicación AJAX y devolver una respuesta JS.

Código de enlace:

 link_to("Send Notification", notification_path(user_id: user_id), remote: true, method: :post)

La respuesta JS (archivo .js.haml) debe alternar el siguiente div oculto en la página donde existe el enlace:

 #notification_status(style='display:none')

Contenido del archivo js.haml:

:plain
  var notificationStatusContainer = $('#notification_status');
  notificationStatusContainer.val("#{@notification_status_msg}");
  notificationStatusContainer.show();

Estaba probando mi escenario de enviar una notificación y mostrar el mensaje de estado de la notificación al usuario que usa Cucumber ( gema de cucumber-rails con soporte de Capybara incorporado )

Estaba tratando de probar que el elemento que tiene id: notification_status era visible en una respuesta exitosa en mi definición de paso. Para esto, intenté las siguientes declaraciones:

page.find('#notification_status').should be_visible
page.should have_selector('#notification_status', visible: true)
page.should have_css('#notification_status', visible: true)
page.find('#notification_status', visible: true)
page.find(:css, 'div#notification_status', visible: true)

Ninguno de los anteriores funcionó para mí y falló mi paso.De los 5 fragmentos enumerados anteriormente, los últimos 4 fallaron con el siguiente error:

'expected to find css "#notification_status" but there were no matches. Also found "", which matched the selector but not all filters. (Capybara::ExpectationNotMet)'

lo cual fue extraño porque la siguiente declaración pasaba correctamente:

page.has_selector?('#notification_status')

Y de hecho inspeccioné la fuente de la página usando

  print page.html

que apareció

<div style='' id='notification_status'></div>

lo que se esperaba.

Finalmente encontré este enlace capibara afirmar atributos de un elemento que mostraba cómo inspeccionar el atributo de un elemento de manera cruda.

También encontré en la documentación de Capybara para visible? método ( http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Element#visible%3F-instance_method ) siguiente información:

 Not all drivers support CSS, so the result may be inaccurate.

Por lo tanto, llegué a la conclusión de que al probar la visibilidad de un elemento, ¿no confía en los resultados de la visibilidad de Capybara? método cuando se usa un selector de CSS y se usa la solución sugerida en el enlace capibara afirmar atributos de un elemento

Se me ocurrió lo siguiente:

 module CustomMatchers
   def should_be_visible(css_selector)
    find(css_selector)['style'].should_not include('display:none', 'display: none')
   end
 end

 World(CustomMatchers)

Uso:

should_be_visible('#notification_status')
Jignesh Gohel
fuente
8

Lo que significa visible no es obvio

La falla puede provenir de un malentendido de lo que se considera visible o no, ya que no es obvio, no es portátil para el controlador y está poco documentado. Algunas pruebas:

HTML:

<div id="visible-empty"                                                                   ></div>
<div id="visible-empty-background"      style="width:10px; height:10px; background:black;"></div>
<div id="visible-empty-background-same" style="width:10px; height:10px; background:white;"></div>
<div id="visible-visibility-hidden"     style="visibility:hidden;"                        >a</div>
<div id="visible-display-none"          style="display:none;"                             >a</div>

Lo único que Rack test considera invisible es en línea display: none(no CSS interno, ya que no hace selectores):

!all('#visible-empty',                 visible: true).empty? or raise
!all('#visible-empty-background',      visible: true).empty? or raise
!all('#visible-empty-background-same', visible: true).empty? or raise
!all('#visible-visibiility-hidden',    visible: true).empty? or raise
 all('#visible-display-none',          visible: true).empty? or raise

Poltergeist tiene un comportamiento similar, pero puede lidiar con la style.displaymanipulación interna de CSS y Js :

Capybara.current_driver = :poltergeist
!all('#visible-empty',                 visible: true).empty? or raise
!all('#visible-empty-background',      visible: true).empty? or raise
!all('#visible-empty-background-same', visible: true).empty? or raise
!all('#visible-visibiility-hidden',    visible: true).empty? or raise
 all('#visible-display-none',          visible: true).empty? or raise

El selenio se comporta de manera bastante diferente: si considera que un elemento vacío es invisible y visibility-hiddenademás display: none:

Capybara.current_driver = :selenium
 all('#visible-empty',                 visible: true).empty? or raise
!all('#visible-empty-background',      visible: true).empty? or raise
!all('#visible-empty-background-same', visible: true).empty? or raise
 all('#visible-visibiility-hidden',    visible: true).empty? or raise
 all('#visible-display-none',          visible: true).empty? or raise

Otro problema común es el valor predeterminado de visible:

  • lo que solía ser false(ve ambos elementos visibles e invisibles),
  • actualmente es true
  • está controlado por la Capybara.ignore_hidden_elementsopción.

Referencia .

Prueba completa ejecutable en mi GitHub .

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1
Gran respuesta: gracias por el esfuerzo que puso en esto. Probar la visibilidad de los elementos en una suite usando múltiples controladores no es trivial.
Andy Triggs
1
"solía ser falso (ve elementos visibles e invisibles), actualmente es verdadero y está controlado por la opción Capybara.ignore_hidden_elements". Consulte esta publicación de blog para obtener más información: elabs.se/blog/60-introducing-capybara-2-1
rizidoro
5

Es posible que desee ver esta publicación , que brinda un método de muestra para esperar hasta que se completen todas las solicitudes de ajax:

def wait_for_ajax(timeout = Capybara.default_wait_time)
  page.wait_until(timeout) do
    page.evaluate_script 'jQuery.active == 0'
  end
end
sentido pequeño
fuente
5
Esta solución no es compatible en el futuro, ya que wait_untilse eliminó de Capybara 2 .
parhamr
1
UsoTimeout.timeout
Ian Vaughan
Mejor usoSelenium::WebDriver::Wait
Nakilon
si usa selenio solamente
Nickolay Kondratenko
2

La respuesta aceptada está un poco desactualizada ahora, ya que 'debería' es una sintaxis obsoleta. En estos días, sería mejor que hicieras algo comoexpect(page).not_to have_css('#blah', visible: :hidden)

Alex Foxleigh
fuente
1

Las otras respuestas aquí son la mejor manera de "esperar" el elemento. Sin embargo, he descubierto que esto no funcionó para el sitio en el que estoy trabajando. Básicamente, el elemento que necesitaba hacer clic era visible antes de que la función detrás de eso estuviera completamente cargada. Esto es en fracciones de segundo, pero descubrí que mi prueba se ejecutó tan rápido en ocasiones que hizo clic en el botón y no sucedió nada. Me las arreglé para solucionarlo haciendo esta expresión booleana improvisada:

if page.has_selector?('<css-that-appears-after-click>')
  puts ('<Some-message-you-want-printed-in-the-output>')
else
  find('<css-for-the-button-to-click-again>', :match == :first).trigger('click')
end

Básicamente, usa el tiempo de espera predeterminado del capibara para buscar algo que debería aparecer, si no está allí, volverá a intentar hacer clic.

Nuevamente diré que el should have_selectormétodo debe probarse primero, pero si simplemente no funciona, intente esto

No hacer
fuente