Intive-FDV Blog

La eterna espera de estabilidad con Selenium

Durante años he perseguido incansablemente la estabilidad en los tests de Selenium. Aunque muchas veces aceptamos que la naturaleza cambiante del desarrollo web afecta directamente nuestros tests, la experiencia me indica que, en general, la inestabilidad no se debe a una propiedad intrínseca de las páginas web, o de ajax, de ReactJS, de Ember, o de cuanto framework estemos pensando en este momento.Son muchas más las veces en que el responsable es nuestro propio código.

En este artículo vamos a ver algunos errores comunes, junto con una solución robusta y que utilice las herramientas que ya nos provee Selenium.

Los errores más comunes con Selenium

Aunque a veces a todos nos cueste admitirlo, no nacimos sabiendo. En mis comienzos, en el mundo de la automatización era normal encontrar código como este:

Ese código tiene un problema muy grave, que en un principio no me parecía tal: estamos esperando a que transcurra una determinada cantidad de tiempo.

En este planteo no importa si la cantidad de tiempo está hardcodeada, referenciada desde algún archivo prolijamente, o exportada en una variable para el cómodo uso de otros mortales. Pausar el código es una práctica que debemos evitar.

En un contexto de ejecución de muchos tests (permutaciones), esto puede hacer demorar los tests mucho tiempo. Hay estrategias mucho más efectivas y recomendadas para estos casos, como esperar a eventos y cambios en la página, en lugar de esperar un determinado tiempo.

En lo que resta del artículo analizaremos estas estrategias y plantearemos con código algunas prácticas recomendadas, y otras totalmente desaconsejadas.

Por qué evitar las pausas

Comencemos por analizar el código de ejemplo. Hay tres motivos fundamentales por los que una pausa no es aconsejable:

  • Si consideramos que la página puede tardar menos tiempo en terminar de cargar para mostrar el elemento, utilizar pausas en el código tiene resultados negativos ya que aumenta el tiempo de ejecución del conjunto de pruebas.
  • Si consideramos que en ocasiones la página puede tardar más tiempo en cargar, agregando una pausa estamos reduciendo la estabilidad.
  • Si insistimos en usar pausas en el código, y empezamos a permitir excepciones, entonces seguro intentaremos evitar los dos primeros problemas. Esto conlleva a que el código rápidamente empiece a tener pausas de distintas longitudes, reduciendo significativamente la mantenibilidad, y eventualmente (aunque intentemos evitarlo) la estabilidad.

Para poner estos problemas en perspectiva, busqué todas las referencias a métodos dedicados a esperar elementos o eventos en una página de un framework, que escribí para un cliente que apenas tiene automatizados 10 test cases y que, para correr este pequeño conjunto de pruebas demora menos de 2:30 minutos (incluyendo el tiempo de setup y descarga de dependencias).

El resultado fue que para esos diez casos de prueba esperamos en distintos escenarios un total de 40 veces por los siguientes eventos:

  • Que cambiara de página
  • Que terminara de cambiar la página
  • Que dejara de cargar elementos por AJAX
  • Que un elemento HTML desapareciera
  • Que un elemento HTML apareciera

Si hubiésemos usado pausas, y en cada elemento estuviésemos agregando apenas 5 segundos extra a la espera normal para mantener los tests estables ante fluctuaciones de velocidad, entonces nos quedaría un tiempo de ejecución de ¡6 minutos! Este ejemplo exagerado lo hicimos con pausas pequeñas. Imaginen el tiempo de ejecución si agregáramos pausas innecesarias de 10 segundos o más. Ahora ¡imaginen qué pasaría si usáramos pausas en un conjunto de 100 pruebas o más!

Ahora bien, señor juez, hemos presentado un caso robusto contra el uso de pausas en el código, e incluso hemos atacado la idea de permitir excepciones en algunos casos. ¿Qué debemos hacer?

La solución es básicamente un resumen del listado de eventos que detallé unos párrafos más arriba: nunca esperar por tiempo, siempre esperar eventos (cambios en los elementos de la página).

Esta sugerencia casi siempre termina en otro problema, aunque no tan grave,  que es el de no usar las herramientas que nos provee Selenium.

Cuando no usamos las herramientas de Selenium

Veamos algunos casos:

  1. Cambiar el Wait implícito de todo el proyecto

Es muy común ver cómo, en algunos casos, se opta por soluciones que afectan todo el desarrollo, cuando quizás sólo queríamos tener una pausa larga y otra corta para la espera de elementos. Establecer una larga espera implícita tiene el efecto secundario de agregar muchísimo tiempo de ejecución a todo el framework, para casos en los que puede haber errores reales.

Pongamos un ejemplo:

Si estamos haciendo click en un elemento que apenas cambia un texto en la página, y que normalmente es casi instantáneo, agregar una espera implícita global de 30 segundos puede causar que el tiempo de ejecución se vea incrementado por varios minutos si este código es utilizado desde varios casos de prueba, cuando normalmente con la espera corta hubiésemos detectado un error en mucho menos tiempo, y hubiésemos agilizado los tiempos de desarrollo.

        2. Reimplementar funcionalidad existente

En este caso podemos ver la intención de utilizar una de las herramientas más útiles de Selenium: WebDriverWait. Sin embargo, el programador termina reinventando la rueda, ya que este mismo caso ya está resuelto, y siempre es mejor utilizar código probado por miles de personas:

Nótese que el código no sólo nos brinda la seguridad de estar usando código probado por muchos desarrolladores, sino que es más corto, y más fácil de mantener.

               3. Reimplementar funcionalidad existente V2

El caso es muy parecido al caso del problema 2 aunque un poco más oscuro, y no todo el mundo conoce que existe una forma sencilla de manejarlo.

Lo que intentaron resolver artesanalmente fue el problema de un elemento HTML que cambia mientras lo estamos usando y se vuelve Stale, por lo que no podemos hacer click en él, y obtenemos una excepción en su lugar.

El término Stale se usa para representar estas instancias de WebElement que ya hayamos obtenido, pero que por algún cambio en la página, se volvieron viejas y obsoletas.

Es un problema no tan habitual con desarrollos que se construyen sobre Selenium y hacen Lazy Loading, como Selene. Pero puede ser un problema recurrente cuando usamos Selenium sin ninguna capa de abstracción.

Esto se puede solucionar de la misma forma que solucionamos el problema 2, agregando las excepciones que queremos ignorar:

Una vez más, el código es más fácil de leer y mantener que otras alternativas, además de que ofrece la flexibilidad de las ExpectedCondition, por lo que hasta ¡podríamos implementar una a nuestra medida! Aquí un ejemplo de cómo podríamos implementar nuestra propia expected condition:

¿Cómo lograr la estabilidad en Selenium?

La biblioteca de Selenium ya incluye en su código y documentación todas las herramientas que necesitamos. Muchas veces está en nosotros el esfuerzo para que nuestro conjunto de pruebas corra de manera estable y predecible.

Como referencia, a los ejemplos los tomé de algunas respuestas a esta pregunta de StarkOverflow que, en mi experiencia, son incorrectas.

Espero que les sea de utilidad. ¡Los invito a contactarme por sugerencias o correcciones!

Emilio Moretti

Emilio Moretti es desarrollador de software en intive-FDV desde 2017. Ingeniero en Informática graduado de la Universidad Nacional del Noroeste de la Provincia de Buenos Aires (UNNOBA), Emilio actualmente cursa la Especialización en Sistemas Embebidos en la Universidad de Buenos Aires (UBA). Entusiasta del código libre, aporta y colabora en los proyectos abiertos de distinta índole que usa para sus hobbies, que van desde el desarrollo de juegos hasta las impresoras 3D.

3 comentarios

  • Emilio, no tengo experiencia en Selenium como para corroborar tus aportes o discutir con ellos. Lo que quería preguntarte es si, como se ve en el código tomado de StackOverflow, estás trabajando con Selenium en Python o sólo lo tomaste de ejemplo. Te pregunto porque quiero especializarme en automation con Python pero veo que no tiene tanta demanda laboral como con Java o C#. Gracias de antemano.

    • Hola Gustavo, estuve trabajando en proyectos anteriores con Python, porque directamente no existía un framework previo y el lenguaje junto con Selene me permitieron tener una base robusta en muy poco tiempo. Personalmente si tengo que armar frameworks de testeo para un proyecto particular sigo eligiendo Python, pero son muchos más los casos donde ya empezaron con Java y hay que darle soporte también. Es sólo una cuestión de gustos. Si profesionalmente te tengo que recomendar que aprendas algo te recomiendo que aprendas a usar WebDriver en Java, y entiendas como Selenide hace una capa de abstracción, para realmente comprender como funciona todo. Después vas a poder pasar a cualquier lenguaje y no te va a dar ningún tipo de dificultad. De hecho trabajé muchos años con Java + Selenide, y creo que hay muchos más proyectos que en el pasado se iniciaron en Java comparado con los que hay en otros, por lo que seguramente encuentres más oportunidades laborales por este camino. Yo después abandoné el mundo Java y seguí con Python y otros lenguajes, pero esa ya es una cuestión aparte, muy personal y que no tiene que ver con el mundo de Testing ni de QA Automation.
      Creo que una vez que entiendas lo que hay en el medio y deje de verse como una caja negra que hace funcionar el navegador web, el lenguaje en el que lo uses se convierte en algo anecdótico.

      Saludos!

      • Gracias por la info, Emilio. Entiendo que el lenguaje es lo de menos cuando entendés la lógica del funcionamiento, pero también es cierto que, como me confirmás, hay más demanda de automation con Java que con Python, lo cual para empezar es relevante. Estoy haciendo el curso de Nahual con Facu Jr. y Guille Morzan, pero en C#. El tema es que no uso Windows y no pienso seguir con ese lenguaje. No conocía Selenide. Ahora estoy viendo la documentación oficial. Gracias por responder. Abrazos.