Pruebas de regresión visual con Cypress: un enfoque pragmático
Publicado: 2022-03-11Cada vez que se lanza una nueva versión de nuestra biblioteca de componentes, Picasso, actualizamos todas nuestras aplicaciones frontales para aprovechar al máximo las nuevas funciones y alinear nuestros diseños en todas las partes de nuestro sitio.
El mes pasado, lanzamos una actualización de Picasso en el Portal de talentos de Toptal, la plataforma que utilizan nuestros talentos para encontrar trabajos e interactuar con los clientes. Sabiendo que el lanzamiento vendría con cambios de diseño importantes y, en un esfuerzo por minimizar los problemas inesperados, tenía sentido usar técnicas de prueba de regresión visual para ayudarnos a encontrar problemas antes del lanzamiento.
Las pruebas de regresión visual no son un concepto nuevo; muchos otros proyectos en Toptal ya lo usan, incluido el propio Picasso.
Se pueden usar herramientas como Percy, Happo y Chromatic para ayudar a los equipos a crear una canalización de regresión visual saludable, y consideramos agregarlas al principio. Finalmente, decidimos que el proceso de configuración llevaría demasiado tiempo y podría descarrilar nuestro cronograma. Ya teníamos una fecha establecida para congelar el código para comenzar la migración, y con solo unos pocos días para la fecha límite, no tuvimos más remedio que ser creativos.
Pruebas de regresión visual a través de pruebas de interfaz de usuario
Si bien no teníamos pruebas de regresión visual en el proyecto, teníamos una buena cobertura de las pruebas de integración de la interfaz de usuario con Cypress. Aunque la herramienta no se usa principalmente para eso, Cypress tiene una página en su documentación dedicada a las pruebas visuales y otra que enumera todos los complementos disponibles para ayudar a configurar Cypress para las pruebas visuales.
De Cypress a las capturas de pantalla
Después de revisar la documentación disponible, decidimos probar cypress-snapshot-plugin. Solo tomó unos minutos configurarlo y, una vez que lo hicimos, rápidamente nos dimos cuenta de que no estábamos buscando una salida de regresión visual tradicional.
La mayoría de las herramientas de regresión visual ayudan a identificar cambios no deseados mediante la comparación de instantáneas y la detección de diferencias de píxeles entre una línea base conocida y aceptada y la versión modificada de una página o un componente. Si la diferencia de píxeles es mayor que un umbral de tolerancia establecido, la página o el componente se marcan para que se examinen manualmente. En esta versión, sin embargo, sabíamos que íbamos a tener varios cambios pequeños en la mayoría de los componentes de la interfaz de usuario, por lo que establecer un umbral no era aplicable. Incluso si un componente determinado fuera 100 % diferente, aún podría ser correcto en el contexto de la nueva versión. Del mismo modo, una desviación tan pequeña como unos pocos píxeles podría significar que un componente no es apto para la producción en ese momento.
En ese momento, quedaron claras dos cosas contrastantes: notar las diferencias de píxeles no iba a ayudar a identificar problemas, y tener una comparación de los componentes lado a lado era precisamente lo que necesitábamos. Dejamos a un lado el complemento de instantáneas y nos dispusimos a crear una colección de imágenes con nuestros componentes antes y después de que se aplicara la actualización de Picasso. De esa manera, podríamos escanear rápidamente todos los cambios para determinar si las nuevas versiones aún se ajustaban a las necesidades del sitio y los estándares de la biblioteca.
El nuevo plan era tomar una captura de pantalla de un componente, almacenarlo localmente, tomar una nueva captura de pantalla del mismo componente en la rama con la versión actualizada de Picasso y luego fusionarlos en una sola imagen. En última instancia, este nuevo enfoque no fue muy diferente de lo que empezamos, pero nos dio más flexibilidad durante la fase de implementación, ya que ya no necesitábamos importar el complemento y usar sus nuevos comandos.
Aprovechamiento de API para imágenes de comparación
Con un objetivo claro en mente, era hora de ver cómo Cypress podía ayudarnos a obtener las capturas de pantalla que necesitábamos. Como se mencionó, teníamos una buena cantidad de pruebas de interfaz de usuario que cubrían la mayoría del Portal de talentos, por lo que en un esfuerzo por recopilar tantos componentes críticos como fuera posible, decidimos tomar capturas de pantalla de elementos individuales después de cada interacción.
Un enfoque alternativo habría sido tomar capturas de pantalla de toda la página en momentos clave durante la prueba, pero decidimos que esas imágenes serían demasiado difíciles de comparar. Además, tales comparaciones podrían ser más propensas a errores humanos, como pasar por alto que un pie de página ha cambiado.
Una tercera opción habría sido pasar por cada caso de prueba individual para decidir qué capturar, pero eso habría llevado mucho más tiempo, por lo que ceñirse a todos los elementos utilizados en las páginas parecía un compromiso práctico.
Recurrimos a la API de Cypress para generar las imágenes. El cy.screenshot() puede, listo para usar, crear imágenes individuales de componentes, y la API After Screenshot nos permite cambiar el nombre de los archivos, cambiar los directorios y distinguir las ejecuciones de regresión visual de las estándar. Al combinar los dos, creamos ejecuciones que no afectaron nuestras pruebas funcionales y nos permitieron almacenar imágenes en sus carpetas correspondientes.
Primero, ampliamos el archivo index.js en nuestro directorio de complementos para admitir los dos nuevos tipos de ejecución (línea base y comparación). Luego, establecemos la ruta para nuestras imágenes según el tipo de ejecución:

// plugins/index.js const fs = require('fs') const path = require('path') module.exports = (on, config) => { // Adding these values to your config object allows you to access them in your tests. config.env.baseline = process.env.BASELINE || false config.env.comparison = process.env.COMPARISON || false on('after:screenshot', details => { // We only want to modify the behavior of baseline and comparison runs. if (config.env.baseline || config.env.comparison) { // We keep track of the file name and number to make sure they are saved in the proper order and in their relevant folders. // An alternative would have been to look up the folder for the latest image, but this was the simpler approach. let lastScreenshotFile = '' let lastScreenshotNumber = 0 // We append the proper suffix number to the image, create the folder, and move the file. const createDirAndRename = filePath => { if (lastScreenshotFile === filePath) { lastScreenshotNumber++ } else { lastScreenshotNumber = 0 } lastScreenshotFile = filePath const newPath = filePath.replace( '.png', ` #${lastScreenshotNumber}.png` ) return new Promise((resolve, reject) => { fs.mkdir(path.dirname(newPath), { recursive: true }, mkdirErr => { if (mkdirErr) { return reject(mkdirErr) } fs.rename(details.path, newPath, renameErr => { if (renameErr) { return reject(renameErr) } resolve({ path: newPath }) }) }) }) } const screenshotPath = `visualComparison/${config.env.baseline ? 'baseline' : 'comparison'}` return createDirAndRename(details.path .replace('cypress/integration', screenshotPath) .replace('All Specs', screenshotPath) ) } }) return config } Luego, invocamos cada una de las ejecuciones agregando la variable de entorno correspondiente a la llamada de Cypress en el package.json del proyecto:
"scripts": { "cypress:baseline": "BASELINE=true yarn cypress:open", "cypress:comparison": "COMPARISON=true yarn cypress:open" }Una vez que ejecutamos nuestros nuevos comandos, pudimos ver que todas las capturas de pantalla tomadas durante la ejecución se movieron a las carpetas correspondientes.
A continuación, intentamos sobrescribir cy.get() , el comando principal de Cypress para devolver elementos DOM y tomar una captura de pantalla de cualquier elemento llamado junto con su implementación predeterminada. Desafortunadamente, cy.get() es un comando difícil de cambiar, ya que llamar al comando original en su propia definición conduce a un bucle infinito. El enfoque sugerido para evitar esta limitación es crear un comando personalizado separado y luego hacer que ese nuevo comando tome una captura de pantalla después de encontrar el elemento:
Cypress.Commands.add("getAndScreenshot", (selector, options) => { // Note: You might need to tweak the command when getting multiple elements. return cy.get(selector).screenshot() }); it("get overwrite", () => { cy.visit("https://example.cypress.io/commands/actions"); cy.getAndScreenshot(".action-email") }) Sin embargo, nuestras llamadas para interactuar con los elementos de la página ya estaban incluidas en una función interna getElement() . Entonces, todo lo que teníamos que hacer era asegurarnos de que se tomó una captura de pantalla cuando se llamó al envoltorio.
Resultados obtenidos a través de pruebas de regresión visual
Una vez que teníamos las capturas de pantalla, lo único que quedaba por hacer era fusionarlas. Para eso, creamos un script de nodo simple usando Canvas. ¡Al final, el script nos permitió generar 618 imágenes de comparación! Algunas de las diferencias fueron fáciles de detectar al abrir el Portal de talentos, pero algunos de los problemas no fueron tan obvios.
Agregar valor a las pruebas de interfaz de usuario
En primer lugar, las pruebas de regresión visual añadidas demostraron ser útiles y descubrieron algunos problemas que podríamos haber pasado por alto sin ellas. Aunque esperábamos diferencias en nuestros componentes, saber lo que realmente cambió ayudó a reducir los casos problemáticos. Entonces, si su proyecto tiene una interfaz pero aún no está realizando estas pruebas, ¡hágalo!
La segunda lección aquí, y quizás la más importante, es que se nos recordó una vez más que lo perfecto es enemigo de lo bueno. Si hubiéramos descartado la posibilidad de ejecutar pruebas de regresión visual para esta versión porque no había una configuración previa, es posible que nos hayamos perdido algunos errores durante la migración. En cambio, acordamos un plan que, aunque no era ideal, fue rápido de ejecutar, trabajamos para lograrlo y valió la pena.
Para obtener más detalles sobre la implementación de una canalización de regresión visual robusta en su proyecto, consulte la página de pruebas visuales de Cypress, seleccione la herramienta que mejor se adapte a sus necesidades y vea los videos tutoriales.
