Un viaje de prueba E2E Parte 2: WebdriverIO a Cypress
Publicado: 2019-11-21Nota: Esta es una publicación de #frontend@twiliosendgrid. Para otras publicaciones de ingeniería, diríjase al blog técnico.
En todas nuestras aplicaciones de interfaz de usuario, teníamos, y todavía tenemos, el siguiente objetivo: proporcionar una forma de escribir pruebas de automatización E2E (extremo a extremo) consistentes, depurables, mantenibles y valiosas para nuestras aplicaciones de interfaz de usuario e integrarlas con CICD (integración continua). y despliegue continuo).
Para llegar al estado que tenemos hoy en el que posiblemente se activen cientos de pruebas E2E o se ejecuten según un cronograma en todos nuestros equipos frontend en SendGrid, tuvimos que investigar y experimentar con muchas soluciones potenciales en el camino hasta lograr ese objetivo principal. meta.
Intentamos implementar nuestra propia solución Ruby Selenium personalizada desarrollada por ingenieros de prueba dedicados llamada SiteTestUI, también conocida como STUI, en la que todos los equipos podían contribuir a un repositorio y ejecutar pruebas de automatización entre navegadores. Tristemente, sucumbió a pruebas lentas y poco fiables, falsas alarmas, falta de contexto entre repositorios e idiomas, demasiadas manos en una canasta, dolorosas experiencias de depuración y más tiempo dedicado al mantenimiento que a proporcionar valor.
Luego experimentamos con otra biblioteca prometedora en WebdriverIO para escribir pruebas en JavaScript ubicadas en los repositorios de aplicaciones de cada equipo. Si bien esto resolvió algunos problemas, fortaleció más la propiedad del equipo y nos permitió integrar pruebas con Buildkite, nuestro proveedor de CICD, todavía teníamos pruebas escamosas que eran dolorosas para depurar y difíciles de escribir, además de lidiar con todos los errores y peculiaridades demasiado similares de Selenium. .
Queríamos evitar otro STUI 2.0 y comenzamos a explorar otras opciones. Si desea leer más sobre nuestras lecciones aprendidas y estrategias descubiertas en el camino al migrar de STUI a WebdriverIO, consulte la parte 1 de la serie de publicaciones del blog.
En esta segunda parte final de la serie de publicaciones de blog, cubriremos nuestro viaje de STUI y WebdriverIO a Cypress y cómo pasamos por migraciones similares para configurar la infraestructura general, escribir pruebas E2E organizadas, integrarnos con nuestras canalizaciones de Buildkite y escalar a otros equipos frontend en la organización.
TLDR: Adoptamos Cypress sobre STUI y WebdriverIO y logramos todos nuestros objetivos de escribir valiosas pruebas E2E para integrar con CICD. Gran parte de nuestro trabajo y las lecciones aprendidas de WebdriverIO y STUI se trasladaron muy bien a la forma en que usamos e integramos las pruebas de Cypress en la actualidad.
Tabla de contenido
Explorando y aterrizando en Cypress
Cambiar de STUI y WebdriverIO a Cypress
Paso 1: Instalación de dependencias para Cypress
Paso 2: Configuraciones y scripts del entorno
Primera pasada de configuraciones y scripts de entorno
Evolución de las configuraciones y secuencias de comandos del entorno
Paso 3: Implementación de pruebas E2E localmente
Paso 4: Dockerización de las pruebas
Paso 5: Integración con CICD
Paso 6: Comparación de Cypress con WebdriverIO/STUI
Paso 7: escalar a otros equipos frontend
Lo que esperamos con Cypress
Adoptando Cypress en el futuro
Explorando y aterrizando en Cypress
Cuando buscamos alternativas a WebdriverIO, vimos otros envoltorios de Selenium como Protractor y Nightwatch con un conjunto de características similar a WebdriverIO, pero sentimos que lo más probable es que nos encontremos con configuraciones largas, pruebas inestables y tediosas tareas de depuración y mantenimiento en el futuro.
Afortunadamente, nos topamos con un nuevo marco de prueba E2E llamado Cypress, que mostró configuraciones rápidas, pruebas rápidas y depurables ejecutadas en el navegador, creación de apéndices de solicitud de capa de red y, lo más importante, no usar Selenium debajo del capó.
Nos maravillamos con las increíbles funciones como las grabaciones de video, la interfaz gráfica de usuario de Cypress, el servicio de tablero de pago y la paralelización para probar. Estábamos dispuestos a ceder en la compatibilidad entre navegadores a favor de pruebas valiosas que se aprobaran de forma consistente en Chrome con un repertorio de herramientas a nuestra disposición para implementar, depurar y mantener las pruebas para nuestros desarrolladores y QA.
También apreciamos los marcos de prueba, las bibliotecas de aserciones y todas las demás herramientas elegidas para que tengamos un enfoque más estandarizado para las pruebas en todos nuestros equipos de interfaz. A continuación, proporcionamos una captura de pantalla de las diferencias entre una solución como WebdriverIO y Cypress y, si desea ver más diferencias entre las soluciones Cypress y Selenium, puede consultar su documentación sobre cómo funciona.
Con eso en mente, nos comprometimos a probar Cypress como otra solución para escribir pruebas E2E rápidas, consistentes y depurables para integrarse eventualmente con Buildkite durante CICD. También establecimos deliberadamente otro objetivo de comparar Cypress con las soluciones anteriores basadas en Selenium para determinar en última instancia, mediante puntos de datos, si debemos seguir buscando o construir nuestras suites de prueba con Cypress en el futuro. Planeamos convertir las pruebas de WebdriverIO existentes y cualquier otra prueba de alta prioridad que todavía esté en STUI a las pruebas de Cypress y comparar las experiencias de nuestros desarrolladores, la velocidad, la estabilidad, los tiempos de ejecución de las pruebas y el mantenimiento de las pruebas.
Cambiar de STUI y WebdriverIO a Cypress
Al cambiar de STUI y WebdriverIO a Cypress, lo abordamos sistemáticamente a través de la misma estrategia de alto nivel que usamos cuando intentamos nuestra migración de STUI a WebdriverIO en nuestros repositorios de aplicaciones frontend. Para obtener más detalles sobre cómo logramos estos pasos para WebdriverIO, consulte la parte 1 de la serie de publicaciones del blog. Los pasos generales para la transición a Cypress incluyeron lo siguiente:
- Instalación y configuración de dependencias para conectarse con Cypress
- Establecimiento de configuraciones de entorno y comandos de scripts
- Implementación de pruebas E2E que pasan localmente contra diferentes entornos
- Dockerizando las pruebas
- Integración de pruebas Dockerizadas con Buildkite, nuestro proveedor de CICD
Para lograr nuestros objetivos secundarios, también agregamos pasos adicionales para comparar Cypress con las soluciones anteriores de Selenium y eventualmente escalar Cypress en todos los equipos front-end de la organización:
6. Comparación de Cypress en términos de experiencias de desarrollador, velocidad y estabilidad de las pruebas con WebdriverIO y STUI
7. Escalar a otros equipos frontend
Paso 1: Instalación de dependencias para Cypress
Para ponernos en marcha rápidamente con Cypress, todo lo que teníamos que hacer era `npm install cypress` en nuestros proyectos e iniciar Cypress por primera vez para que se dispusiera automáticamente con un archivo de configuración `cypress.json` y una carpeta cypress
. con accesorios de inicio, pruebas y otros archivos de configuración para comandos y complementos. Apreciamos cómo Cypress vino incluido con Mocha como corredor de pruebas, Chai para afirmaciones y Chai-jQuery y Sinon-Chai para aún más afirmaciones para usar y encadenar. Ya no tuvimos que dedicar un tiempo considerable a investigar qué corredor de pruebas, reportero, aserciones y bibliotecas de servicios instalar y usar en comparación con cuando comenzamos con WebdriverIO o STUI. Inmediatamente ejecutamos algunas de las pruebas generadas con su Cypress GUI y exploramos las muchas funciones de depuración a nuestra disposición, como la depuración de viajes en el tiempo, el patio de juegos selector, videos grabados, capturas de pantalla, registros de comandos, herramientas de desarrollo de navegador, etc.
También lo configuramos más tarde con Eslint y TypeScript para verificar el tipo estático adicional y las reglas de formato que se deben seguir al confirmar el nuevo código de prueba de Cypress. Inicialmente tuvimos algunos contratiempos con la compatibilidad con TypeScript y algunos archivos debían ser archivos JavaScript como los que se centran en los archivos de complementos, pero en su mayor parte pudimos verificar el tipo de la mayoría de nuestros archivos para nuestras pruebas, objetos de página y comandos.
Aquí hay una estructura de carpetas de ejemplo que siguió uno de nuestros equipos frontend para incorporar objetos de página, complementos y comandos:
Paso 2: Configuraciones y scripts del entorno
Después de instalar y configurar rápidamente Cypress para que se ejecutara localmente, necesitábamos una forma de que nuestras pruebas de Cypress se ejecutaran con diferentes configuraciones para cada entorno y queríamos admitir los mismos casos de uso que nuestros comandos WebdriverIO nos permitían hacer. Aquí hay una lista que ilustra la mayoría de los casos de uso de cómo queríamos ejecutar estas pruebas y por qué deseábamos apoyarlas:
- Contra un servidor de desarrollo de Webpack que se ejecuta en localhost (es decir, http://localhost:8000) y ese servidor de desarrollo apuntaría a una API de entorno determinada (es decir, https://testing.api.com o https://staging.api. com) como prueba o puesta en escena.
¿Por qué? A veces, necesitamos realizar cambios en nuestra aplicación web local, como agregar selectores más específicos para que nuestras pruebas interactúen con los elementos de una manera más robusta o estábamos en progreso de desarrollar una nueva función y necesitábamos ajustar y validar las pruebas de automatización existentes. pasaría localmente contra nuestros nuevos cambios de código. Cada vez que el código de la aplicación cambiaba y aún no impulsábamos el entorno implementado, usamos este comando para ejecutar nuestras pruebas en nuestra aplicación web local. - Contra una aplicación implementada para un determinado entorno (es decir, https://testing.app.com o https://staging.app.com) como testing o staging
¿Por qué? Otras veces, el código de la aplicación no cambia, pero es posible que tengamos que modificar nuestro código de prueba para corregir algunas fallas o nos sentimos lo suficientemente seguros como para agregar o eliminar pruebas por completo sin realizar ningún cambio en la interfaz. Usamos mucho este comando para actualizar o depurar pruebas localmente contra la aplicación implementada para simular más de cerca cómo se ejecutan nuestras pruebas en CICD. - Ejecución en un contenedor de Docker contra una aplicación implementada para un determinado entorno, como prueba o puesta en escena
¿Por qué? Esto está destinado a CICD para que podamos activar las pruebas E2E para que se ejecuten en un contenedor de Docker, por ejemplo, en la aplicación implementada de prueba y asegurarnos de que pasen antes de implementar el código en producción o en ejecuciones de prueba programadas en una canalización dedicada. Al configurar estos comandos inicialmente, realizamos muchas pruebas y errores para hacer girar los contenedores Docker con diferentes valores de variables de entorno y probar para ver las pruebas adecuadas ejecutadas con éxito antes de conectarlo con nuestro proveedor de CICD, Buildkite.
Primera pasada de configuraciones y scripts de entorno
Cuando experimentamos por primera vez con la configuración de Cypress, lo hicimos en el repositorio que cubre https://app.sendgrid.com, una aplicación web que incluye páginas de características como Autenticación del remitente, Actividad de correo electrónico y Validación de correo electrónico, e inevitablemente compartimos nuestros descubrimientos y aprendizajes con los equipos detrás de nuestra aplicación web Marketing Campaigns, que abarca el dominio https://mc.sendgrid.com. Deseábamos ejecutar nuestras pruebas E2E en nuestro entorno de prueba y utilizamos la interfaz de línea de comandos de Cypress y opciones como --config
o --env
para lograr nuestros casos de uso.
Para ejecutar las pruebas de Cypress en la aplicación web localmente, por ejemplo, http://127.0.0.1:8000
o en la URL de la aplicación de prueba implementada, ajustamos el indicador de configuración de baseUrl
en el comando y agregamos variables de entorno adicionales como testEnv
para ayudar. cargue ciertos accesorios o datos de prueba específicos del entorno en nuestras pruebas de Cypress. Por ejemplo, las claves de API utilizadas, los usuarios creados y otros recursos pueden ser diferentes entre entornos. Utilizamos testEnv
para alternar esos accesorios o agregar una lógica condicional especial si algunas funciones no eran compatibles con un entorno o si la configuración de la prueba difería y accederíamos al entorno a través de una llamada como Cypress.env(“testEnv”)
en nuestras especificaciones.
Luego organizamos nuestros comandos cypress:open:*
para representar la apertura de la GUI de Cypress para que podamos seleccionar nuestras pruebas para ejecutarlas a través de la IU cuando desarrollamos localmente y cypress:run:*
para indicar la ejecución de pruebas en modo sin cabeza, que fue más personalizado para ejecutarse en un contenedor Docker durante CICD. Lo que vino después open
o run
sería el entorno, por lo que nuestros comandos se leerían fácilmente como npm run cypress:open:localhost:staging
para abrir la GUI y ejecutar pruebas en un servidor de desarrollo de Webpack local que apunte a las API de preparación o npm run cypress:run:staging
para ejecutar pruebas en modo autónomo contra la API y la aplicación de ensayo implementadas. Los scripts de Cypress de package.json
quedaron así:
Evolución de las configuraciones y secuencias de comandos del entorno
En otro proyecto, desarrollamos nuestros comandos y configuraciones de Cypress para aprovechar alguna lógica de nodo en el archivo cypress/plugins/index.js
para tener un archivo base cypress.json
y archivos de configuración separados que se leerían en función de una variable de entorno llamada configFile
para cargar un archivo de configuración específico. Los archivos de configuración cargados luego se fusionarían con el archivo base para eventualmente apuntar a un servidor de pruebas o de back-end simulado.
En caso de que se pregunte más sobre el servidor backend simulado, desarrollamos un servidor Express con puntos finales backend que simplemente devuelven respuestas variables de datos JSON estáticos y códigos de estado (es decir, 200, 4XX, 5XX) según los parámetros de consulta que se pasan en las solicitudes. Esto desbloqueó la interfaz para continuar desarrollando los flujos de la página con llamadas de red reales al servidor backend simulado con respuestas que emulan cómo se verá la API real cuando esté disponible en el futuro. También podríamos simular fácilmente diferentes niveles de éxito y respuestas de error para nuestros diferentes estados de interfaz de usuario que, de lo contrario, serían difíciles de reproducir en producción, y dado que estaríamos haciendo llamadas de red deterministas, nuestras pruebas de Cypress serían menos inestables al disparar desde la misma red. solicitudes y respuestas cada vez.
Teníamos un archivo base cypress.json
que incluía propiedades compartidas para tiempos de espera generales, ID de proyecto para conectarse con Cypress Dashboard Service, del que hablaremos más adelante, y otras configuraciones, como se muestra a continuación:
Creamos una carpeta de config
en la carpeta cypress
para contener cada uno de nuestros archivos de configuración, como localhostMock.json
para ejecutar nuestro servidor de desarrollo de Webpack local contra un servidor de API simulado local o staging.json
para ejecutar contra la API y la aplicación de prueba implementadas. Estos archivos de configuración que se diferenciarán y fusionarán con la configuración base se verían así:
Los archivos de configuración de CICD tenían un archivo JSON aún más simple, ya que necesitábamos configurar las variables de entorno de forma dinámica para tener en cuenta la URL base del frontend del servicio de Docker variable y los hosts de la API del servidor simulado que analizaremos más adelante.
En nuestro archivo cypress/plugins/index.js
, agregamos lógica para leer una variable de entorno llamada configFile
establecida desde un comando de Cypress que eventualmente leería el archivo correspondiente en la carpeta de config
y lo fusionaría con la base cypress.json
, como se muestra a continuación:
Para escribir comandos de Cypress sensatos con variables de entorno configuradas para nuestros casos de uso, aprovechamos un Makefile
que se parecía a lo siguiente:
Con esos comandos claramente dispuestos en un Makefile, podríamos hacer rápidamente cosas como make cypress_open_staging
o make cypress_run_staging
en nuestros scripts `package.json` npm.
Antes, solíamos colocar algunos comandos en una línea larga que sería difícil de editar sin errores. Afortunadamente, Makefile ayudó a distribuir las cosas mucho mejor con la interpolación legible de las variables de entorno en los comandos de Cypress en varias líneas. Podríamos configurar o exportar rápidamente variables de entorno como configFile
para qué archivo de configuración de entorno cargar, BASE_URL
para visitar nuestras páginas, API_HOST
para diferentes entornos de back-end o SPECS
para determinar qué pruebas ejecutar antes de iniciar cualquiera de los comandos de Makefile.
También usamos comandos de Makefile para otros scripts npm largos y comandos de Docker, así como para construir nuestros activos de Webpack, instalar dependencias o ejecutar comandos simultáneamente con otros. Finalmente, traduciríamos algunos comandos de Makefile a la sección de secuencias de comandos de package.json
, aunque esto no era necesario si alguien solo quería usar Makefile, y se vería así:
Omitimos a propósito muchos de los comandos de Cypress CICD, ya que no eran comandos que se usarían en el desarrollo diario y, como resultado, mantuvimos el package.json
más optimizado. Lo que es más importante, pudimos ver de un vistazo de inmediato todos los comandos de Cypress relacionados con el servidor simulado y el servidor de desarrollo de Webpack local en comparación con los entornos de prueba y cuáles están "abriendo" la GUI en lugar de "ejecutarse" en modo autónomo.
Paso 3: Implementación de pruebas E2E localmente
Cuando comenzamos a implementar pruebas E2E con Cypress, hicimos referencia a nuestras pruebas existentes de WebdriverIO y STUI para convertir y agregar pruebas más nuevas para otras características de alta prioridad, que van desde simples controles de salud hasta complicados flujos felices. Traducir los objetos de página existentes y los archivos de prueba de WebdriverIO o STUI a objetos de página y especificaciones equivalentes en Cypress resultó ser muy sencillo. De hecho, resultó en un código mucho más limpio que antes, con elementos de espera menos explícitos y una mejor encadenabilidad de aserciones y otros comandos de Cypress.
Por ejemplo, los pasos generales de las pruebas siguieron siendo los mismos desde la perspectiva del usuario final, por lo que el trabajo de conversión involucró la asignación de la API de WebdriverIO o STUI a la API de Cypress de las siguientes maneras:
- Esencialmente, aparecieron muchos comandos y funcionaron de manera similar al punto en el que casi reemplazábamos
$
obrowser
concy
oCypress
, es decir, visitando una página a través$(“.button”).click()
acy.get(“.button”).click()
,browser.url()
acy.visit()
, o$(“.input”).setValue()
acy.get(“.input”).type()
- El uso
$
o$$
normalmente se convierte ency.get(...)
ocy.contains(...)
es decir$$(“.multiple-elements-selector”)
o$(“.single-element-selector”)
convertido ency.get(“.any-element-selector”)
,cy.contains(“text”)
ocy.contains(“.any-selector”)
- Eliminación de llamadas extrañas
$(“.selector”).waitForVisible(timeoutInMs)
,$(“.selector”).waitUntil(...)
o$(“.selector”).waitForExist()
a favor de dejar Cypress por defecto maneje los reintentos y la recuperación de los elementos una y otra vez concy.get('.selector')
ycy.contains(textInElement)
. Si necesitáramos un tiempo de espera más largo que el predeterminado,cy.get('.selector', { timeout: longTimeoutInMs })
por completo y luego, después de recuperar el elemento, encadenaríamos el siguiente comando de acción para hacer algo con el elemento, es decir,cy.get(“.selector”).click()
. - Comandos personalizados con navegador.
addCommand('customCommand, () => {})` turned into `Cypress.Commands.add('customCommand', () => {})
y haciendo `cy.customCommand()` - Hacer solicitudes de red para configurar o desmantelar a través de la API usando una biblioteca llamada
node-fetch
y envolviéndola enbrowser.call(() => return fetch(...))
y/obrowser.waitUntil(...)
condujo a realizar solicitudes HTTP en un servidor Cypress Node a travéscy.request(endpoint)
o un complemento personalizado que definimos e hicimos llamadas comocy.task(taskToHitAPIOrService)
. - Antes, cuando teníamos que esperar a que una solicitud de red importante terminara posiblemente sin cambios notables en la interfaz de usuario, a veces teníamos que recurrir al uso de
browser.pause(timeoutInMs)
, pero con Cypress mejoramos eso con la funcionalidad de creación de apéndices de red y pudimos escuchar y espere a que finalice la solicitud específica concy.server()
,cy.route(“method”, “/endpoint/we/are/waiting/for).as(“endpoint”)`, and `cy.wait(“@endpoint”)
antes de iniciar la acción que desencadenaría la solicitud.
Después de traducir gran parte de la sintaxis y los comandos de WebdriverIO a los comandos de Cypress, trajimos el mismo concepto de tener un objeto de página base para la funcionalidad compartida común y objetos de página extendidos para cada página que necesitábamos para las pruebas. Este es un ejemplo de un objeto de página base con una funcionalidad open()
común para compartir en todas las páginas.
Un objeto de página extendida agregaría captadores para selectores de elementos, implementaría la funcionalidad open()
con su ruta de página y proporcionaría cualquier funcionalidad auxiliar como se muestra a continuación.
Nuestros objetos de página extendida reales también utilizaron un mapa de objetos simple para mantener todos nuestros selectores de elementos CSS en un solo lugar que conectaríamos en nuestros componentes React como atributos de datos, referencia en pruebas unitarias y uso como nuestros selectores en objetos de página Cypress. Además, nuestras clases de objetos de página a veces variaban al aprovechar el constructor de clases si, por ejemplo, un objeto de página se reutilizaba para un montón de páginas de apariencia y funcionamiento similares, como nuestras páginas de Supresión, y pasábamos argumentos para cambiar la ruta o propiedades específicas.
Como nota al margen, los equipos no necesitaban usar objetos de página, pero apreciamos la consistencia del patrón para mantener la funcionalidad de la página y las referencias del selector de elementos DOM junto con una estructura de objeto de clase estándar para compartir una funcionalidad común en todas las páginas. Otros equipos prefirieron crear muchos archivos diferentes con pocas funciones de utilidad y sin el uso de las clases de ES6, pero lo importante que se debía eliminar era proporcionar una forma organizada y predecible de encapsular todo y escribir pruebas para mejorar la eficiencia y el mantenimiento del desarrollador.
Nos adherimos a la misma estrategia de prueba general utilizada con nuestras pruebas WebdriverIO anteriores al intentar configurar la prueba tanto como sea posible a través de la API. En especial, queríamos evitar construir nuestro estado de configuración a través de la interfaz de usuario para no presentar descamación y pérdida de tiempo para las partes que no pretendíamos probar. La mayoría de las pruebas involucraron esta estrategia:
- Configuración o eliminación a través de la API : si necesitáramos probar la creación de una entidad a través de la interfaz de usuario, nos aseguraríamos de eliminar primero la entidad a través de la API. Independientemente de cómo la ejecución de la prueba anterior terminó con éxito o fracaso, la prueba debía configurarse o eliminarse correctamente a través de la API para garantizar que la prueba se comporte de manera consistente y comience con las condiciones adecuadas.
- Iniciar sesión en un usuario de prueba dedicado a través de la API : creamos usuarios de prueba dedicados por página o incluso por prueba de automatización para que nuestras pruebas estén aisladas y no pisoteen los recursos de los demás cuando se ejecutan en paralelo. Hicimos la misma solicitud que nuestra página de inicio de sesión a través de la API y almacenamos la cookie antes de que comience la prueba para que podamos visitar la página autenticada directamente y comenzar los pasos de prueba reales.
- Automatización de los pasos desde la perspectiva del usuario final : después de iniciar sesión en el usuario a través de la API, visitamos la página directamente y automatizamos los pasos que haría un usuario final para finalizar un flujo de funciones y verificar que el usuario vea e interactúe con las cosas correctas. por el camino.
Para restablecer la prueba a su estado original esperado, iniciaríamos sesión en un usuario de prueba dedicado a través de la API con un comando global cy.login
, estableceríamos una cookie para mantener la sesión del usuario, realizaríamos las llamadas a la API necesarias para devolver el usuario al estado de inicio deseado a través de cy.request(“endpoint”)
o cy.task(“pluginAction”)
, y visite la página autenticada que buscábamos probar directamente. Luego, automatizaríamos los pasos para lograr un flujo de funciones de usuario como se muestra en el diseño de prueba a continuación.
¿Recuerda los comandos personalizados de los que hablamos para iniciar sesión, cy.login()
, y cerrar sesión, cy.logout()
? Los implementamos fácilmente en Cypress de esta manera, por lo que todas nuestras pruebas iniciarían sesión con un usuario a través de la API de la misma manera.
Además, queríamos automatizar y verificar ciertos flujos complejos relacionados con el correo electrónico que antes no podíamos hacer bien con WebdriverIO o STUI. Algunos ejemplos incluyeron exportar actividad de correo electrónico a un CSV, pasar por el flujo Enviar a un compañero de trabajo para la autenticación del remitente o exportar los resultados de la validación de correo electrónico a un CSV. Cypress evita que uno acceda a múltiples superdominios en una sola prueba, por lo que navegar a un cliente de correo electrónico a través de una interfaz de usuario que no poseemos era inestable y no era una opción.
En su lugar, desarrollamos complementos de Cypress a través de sus cy.task(“pluginAction”)
para usar algunas bibliotecas dentro del servidor Cypress Node para conectarse a un cliente/bandeja de entrada IMAP de correo electrónico de prueba como SquirrelMail para verificar si hay correos electrónicos coincidentes en una bandeja de entrada después de solicitar una acción. en la interfaz de usuario y por seguir los enlaces de redirección de esos correos electrónicos a nuestro dominio de la aplicación web para verificar que ciertas páginas de descarga aparecieran y completar efectivamente un flujo completo de clientes. Implementamos complementos que esperarían a que los correos electrónicos llegaran a la bandeja de entrada de SquirrelMail dadas ciertas líneas de asunto, eliminar correos electrónicos, enviar correos electrónicos, activar eventos de correo electrónico, sondear servicios de backend y realizar configuraciones y desmontajes mucho más útiles a través de la API para que nuestras pruebas los usen.
Para brindar más información sobre lo que realmente probamos con Cypress, cubrimos una gran cantidad de casos de alto valor como estos:
- Comprobaciones de estado para todas nuestras páginas , también conocidas como recorridos por la aplicación: queríamos asegurarnos de que las páginas cargadas con algún contenido causaran, a veces, ciertos servicios de back-end o el alojamiento de front-end estaría inactivo. También recomendamos hacer estas pruebas primero para desarrollar la memoria muscular mental de construir objetos de página con selectores y funciones auxiliares y para obtener pruebas rápidas y funcionales que se ejecuten en un entorno.
- Operaciones CRUD en una página : restableceríamos las pruebas en consecuencia a través de la API siempre y luego probaríamos específicamente la creación, lectura, actualización o eliminación en la interfaz de usuario. Por ejemplo, si probamos la posibilidad de crear una autenticación de dominio a través de la interfaz de usuario, independientemente de cómo terminó la última ejecución de prueba, necesitábamos asegurarnos de que el dominio que íbamos a crear a través de la interfaz de usuario se eliminó primero a través de la API antes de continuar. con los pasos automatizados de la interfaz de usuario para crear el dominio y evitar colisiones. Si probamos poder eliminar una supresión a través de la interfaz de usuario, primero nos aseguramos de crear la supresión a través de la API y luego continuamos con los pasos.
- Prueba de filtros de búsqueda en una página : probamos la configuración de un conjunto de filtros de búsqueda avanzados con Actividad de correo electrónico y visitamos la página con parámetros de consulta para asegurarnos de que los filtros se completaron automáticamente. También agregamos datos a través de la API para la validación de correo electrónico y, una vez más, activamos diferentes filtros de búsqueda y validamos que la tabla coincidiera con los filtros de búsqueda en esa página.
- Diferentes accesos de usuarios : en Twilio SendGrid, tenemos cuentas principales que pueden tener compañeros de equipo con diferentes ámbitos o permisos de acceso o subusuarios debajo de ellos que también tienen diversos grados de acceso y se comportan de manera similar a una cuenta principal. Los compañeros de equipo con acceso de solo lectura versus acceso de administrador para ciertas páginas y subusuarios verían o no verían ciertas cosas en una página y eso facilitó la automatización del inicio de sesión en esos tipos de usuarios y verificar lo que ven o no ven en las pruebas de Cypress.
- Diferentes paquetes de usuario : nuestros usuarios también pueden variar en los tipos de paquetes gratuitos a pagos, como Essentials, Pro y Premier, y esos paquetes también pueden ver o no ciertas cosas en una página. Iniciaríamos sesión en usuarios con diferentes paquetes y verificaríamos rápidamente las funciones, copias o páginas a las que los usuarios tenían acceso en las pruebas de Cypress.
Paso 4: Dockerización de las pruebas
Al ejecutar cada paso de canalización de Buildkite en una nueva máquina de AWS en la nube, no podíamos simplemente llamar a npm run cypress:run:staging
porque esas máquinas no tienen Nodo, navegadores, nuestro código de aplicación o cualquier otra dependencia para ejecutar Cypress. pruebas Cuando configuramos WebdriverIO anteriormente, necesitábamos ensamblar tres servicios separados en un archivo Docker Compose para que los servicios de código de aplicación, Chrome y Selenium adecuados operaran juntos para ejecutar las pruebas.
Con Cypress, fue mucho más sencillo ya que solo necesitábamos la imagen Docker base de Cypress, cypress/base
, para configurar el entorno en un Dockerfile
y solo un servicio en un archivo docker-compose.yml
con nuestro código de aplicación para ejecutar Cypress. pruebas Revisaremos una forma de hacerlo, ya que hay otras imágenes de Cypress Docker para usar y otras formas de configurar las pruebas de Cypress en Docker. Le animamos a consultar la documentación de Cypress para obtener información alternativa.
Para abrir un servicio con toda nuestra aplicación y el código de prueba necesario para ejecutar las pruebas de Cypress, creamos un Dockerfile
llamado Dockerfile.cypress
e instalamos todos los node_modules
y copiamos el código en el directorio de trabajo de la imagen en un entorno Node. Esto sería utilizado por nuestro servicio Dockerfile
cypress
la siguiente manera:
Con este Dockerfile.cypress
, podemos integrar Cypress para ejecutar especificaciones seleccionadas en una determinada API de entorno y aplicación implementada a través de un servicio Docker Compose llamado cypress
. Todo lo que tuvimos que hacer fue interpolar algunas variables de entorno como SPECS
y BASE_URL
para ejecutar pruebas de Cypress seleccionadas en una determinada URL base a través del npm run cypress:run:cicd:staging
que se ve así, ”cypress:run:cicd:staging”: “cypress run --record --key --config baseUrl=$BASE_URL --env testEnv=staging”
.
Estas variables de entorno se establecerían a través de los archivos de configuración/configuración de la canalización de Buildkite o se exportarían dinámicamente al activar las pruebas de Cypress para que se ejecuten desde nuestra canalización de implementación. Un ejemplo de archivo docker-compose.cypress.yml
se parecía a esto:
También hay un par de otras cosas para observar. Por ejemplo, puede ver la variable de entorno VERSION
que nos permite hacer referencia a una imagen de Docker etiquetada específica. Más adelante demostraremos cómo etiquetamos una imagen de Docker y luego desplegamos la misma imagen de Docker para que esa compilación se ejecute con el código correcto para las pruebas de Cypress.
Además, también notará que se pasa BUILDKITE_BUILD_ID
, que viene gratis junto con otras variables de entorno de Buildkite para cada compilación que iniciamos, y el indicador ci-build-id
. Esto habilita la función de paralelización de Cypress y cuando configuramos una cierta cantidad de máquinas asignadas para las pruebas de Cypress, automáticamente sabrá cómo hacer girar esas máquinas y separar nuestras pruebas para ejecutarlas en todos esos nodos de máquinas para optimizar y acelerar nuestra prueba. tiempos de ejecución.
Finalmente, también aprovechamos el montaje de volumen y la función de artefactos de Buildkite. Subimos los videos y las capturas de pantalla para que sean accesibles directamente a través de la pestaña "Artefactos" de la interfaz de usuario de Buildkite en caso de que nos quedemos sin nuestras grabaciones de prueba asignadas pagadas para el mes o de alguna manera no podamos acceder al Servicio de tablero. Cada vez que uno ejecuta el comando "ejecutar" de Cypress en modo sin cabeza, hay una salida en las carpetas cypress/videos
y cypress/screenshots
para que uno las revise localmente y simplemente montamos esas carpetas y las subimos a Buildkite para nosotros como un mecanismo de seguridad.
Paso 5: Integración con CICD
Una vez que logramos que las pruebas de Cypress se ejecutaran con éxito en un contenedor Docker en diferentes entornos, comenzamos a integrarnos con Buildkite, nuestro proveedor de CICD. Buildkite proporcionó formas de ejecutar pasos en un archivo .yml
en nuestras máquinas de AWS con secuencias de comandos de Bash y variables de entorno configuradas en el código o a través de la configuración de canalización de Buildkite del repositorio en la interfaz de usuario web. Buildkite también nos permitió activar esta canalización de prueba desde nuestra canalización de implementación principal con variables de entorno exportadas y reutilizaríamos estos pasos de prueba para otras canalizaciones de prueba aisladas que se ejecutarían según un cronograma para que nuestros controles de calidad las monitorearan y observaran.
En un alto nivel, nuestras canalizaciones de prueba de Buildkite para Cypress y también nuestras canalizaciones anteriores de WebdriverIO compartieron los siguientes pasos similares:
- Configure las imágenes de Docker . Cree, etiquete e inserte las imágenes de Docker requeridas para las pruebas en el registro para que podamos bajarlas en un paso posterior.
- Ejecute las pruebas en función de las configuraciones de las variables de entorno . Extraiga las imágenes de Docker etiquetadas para la compilación específica y ejecute los comandos adecuados en un entorno implementado para ejecutar conjuntos de pruebas seleccionados a partir de las variables de entorno establecidas.
Este es un ejemplo de un archivo pipeline.cypress.yml
que muestra cómo configurar las imágenes de Docker en el paso "Crear imagen de Cypress Docker" y ejecutar las pruebas en el paso "Ejecutar pruebas de Cypress":
Una cosa a tener en cuenta es el primer paso, "Crear imagen de Cypress Docker", y cómo configura la imagen de Docker para la prueba. Utilizó el comando de build
Docker Compose para compilar el servicio cypress
con todo el código de prueba de la aplicación y lo etiquetó con la variable de entorno latest
y ${VERSION}
para que finalmente podamos desplegar esa misma imagen con la etiqueta adecuada para esta compilación en un paso futuro. Cada paso puede ejecutarse en una máquina diferente en la nube de AWS en algún lugar, por lo que las etiquetas identifican de forma única la imagen para la ejecución específica de Buildkite. Después de etiquetar la imagen, subimos la última imagen etiquetada con la versión a nuestro registro privado de Docker para reutilizarla.
En el paso "Ejecutar pruebas de Cypress", bajamos la imagen que construimos, etiquetamos y empujamos en el primer paso e iniciamos el servicio Cypress para ejecutar las pruebas. En función de variables de entorno como SPECS
y BASE_URL
, ejecutaríamos archivos de prueba específicos en un determinado entorno de aplicación implementado para esta compilación específica de Buildkite. Estas variables de entorno se establecerían a través de la configuración de canalización de Buildkite o se activarían dinámicamente desde un script de Bash que analizaría un campo de selección de Buildkite para determinar qué conjuntos de pruebas ejecutar y en qué entorno.
Cuando seleccionamos qué pruebas ejecutar durante nuestra canalización de implementación de Buildkite CICD y activamos una canalización de pruebas activadas dedicada con ciertas variables de entorno exportadas, seguimos los pasos en el archivo pipeline.cypress.yml
para que esto suceda. Un ejemplo de activación de las pruebas después de implementar un código nuevo en un entorno de rama de características desde la canalización de implementación se ve así:
Las pruebas desencadenadas se ejecutarían en una canalización separada y, después de seguir el enlace "Compilación n.º 639", nos llevaría a los pasos de compilación para la ejecución de la prueba desencadenada, como se muestra a continuación:
Reutilizando el mismo archivo pipeline.cypress.yml
para nuestras canalizaciones Cypress Buildkite dedicadas que se ejecutan según un cronograma, tenemos compilaciones como la que ejecuta nuestras pruebas E2E "P1", de máxima prioridad, como se muestra en la foto a continuación:
Todo lo que tenemos que hacer es establecer las variables de entorno adecuadas para cosas como qué especificaciones ejecutar y qué entorno de back-end alcanzar en la configuración de la canalización de Buildkite. Luego, podemos configurar una compilación programada de Cron, que también está en la configuración de canalización, para que se inicie cada cierta cantidad de horas y estamos listos para comenzar. We would then create many other separate pipelines for specific feature pages as needed to run on a schedule in a similar way and we would only vary the Cron schedule and environment variables while once again uploading the same `pipeline.cypress.yml` file to execute.
In each of those “Run Cypress tests” steps, we can see the console output with a link to the recorded test run in the paid Dashboard Service, the central place to manage your team's test recordings, billing, and other Cypress stats. Following the Dashboard Service link would take us to a results view for developers and QAs to take a look at the console output, screenshots, video recordings, and other metadata if required such as this:
Step 6: Comparing Cypress vs. WebdriverIO/STUI
After diving into our own custom Ruby Selenium solution in STUI, WebdriverIO, and finally Cypress tests, we recorded our tradeoffs between Cypress and Selenium wrapper solutions.
ventajas
- It's not another Selenium wrapper – Our previous solutions came with a lot of Selenium quirks, bugs, and crashes to work around and resolve, whereas Cypress arrived without the same baggage and troubles to deal with in allowing us full access to the browser.
- More resilient selectors – We no longer had to explicitly wait for everything like in WebdriverIO with all the
$(.selector).waitForVisible()
calls and now rely oncy.get(...)
and cy.contains(...)
commands with their default timeout. It will automatically keep on retrying to retrieve the DOM elements and if the test demanded a longer timeout, it is also configurable per command. With less worrying about the waiting logic, our tests became way more readable and easier to chain. - Vastly improved developer experience – Cypress provides a large toolkit with better and more extensive documentation for assertions, commands, and setup. We loved the options of using the Cypress GUI, running in headless mode, executing in the command-line, and chaining more intuitive Cypress commands.
- Significantly better developer efficiency and debugging – When running the Cypress GUI, one has access to all of the browser console to see some helpful output, time travel debug and pause at certain commands in the command log to see before and after screenshots, inspect the DOM with the selector playground, and discern right away at which command the test failed. In WebdriverIO or STUI we struggled with observing the tests run over and over in a browser and then the console errors would not point us toward and would sometimes even lead us astray from where the test really failed in the code. When we opted to run the Cypress tests in headless mode, we got console errors, screenshots, and video recordings. With WebdriverIO we only had some screenshots and confusing console errors. These benefits resulted in us cranking out E2E tests much faster and with less overall time spent wondering why things went wrong. We recorded it took less developers and often around 2 to 3 times less days to write the same level of complicated tests with Cypress than with WebdriverIO or STUI.
- Network stubbing and mocking – With WebdriverIO or STUI, there was no such thing as network stubbing or mocking in comparison to Cypress. Now we can have endpoints return certain values or we can wait for certain endpoints to finish through
cy.server()
andcy.route()
. - Less time to set up locally – With WebdriverIO or STUI, there was a lot of time spent up front researching which reporters, test runners, assertions, and services to use, but with Cypress, it came bundled with everything and started working after just doing an
npm install cypress.
- Less time to set up with Docker – There are a bunch of ways to set up WebdriverIO with Selenium, browser, and application images that took us considerably more time and frustration to figure out in comparison to Cypress's Docker images to use right out of the gate.
- Parallelization with various CICD providers – We were able to configure our Buildkite pipelines to spin up a certain number of AWS machines to run our Cypress tests in parallel to dramatically speed up the overall test run time and uncover any flakiness in tests using the same resources. The Dashboard Service would also recommend to us the optimal number of machines to spin up in parallel for the best test run times.
- Paid Dashboard Service – When we run our Cypress tests in a Docker container in a Buildkite pipeline during CICD, our tests are recorded and stored for us to look at within the past month through a paid Dashboard Service. We have a parent organization for billing and separate projects for each frontend application to check out console output, screenshots, and recordings of all of our test runs.
- Tests are way more consistent and maintainable – Tests passed way more consistently with Cypress in comparison to WebdriverIO and STUI where the tests kept on failing so much to the point where they were often ignored. Cypress tests failing more often signaled actual issues and bugs to look into or suggested better ways to refactor our tests to be less flaky. With WebdriverIO and STUI, we wasted a lot more time in maintaining those tests to be somewhat useful, whereas with Cypress, we would every now and then adjust the tests in response to changes in the backend services or minor changes in the UI.
- Tests are faster – Builds passed way more consistently and overall test run times would be around 2 to 3 times faster when run serially without parallelization. We used to have overall test runs that would take hours with STUI and around 40 minutes with WebdriverIO, but now with way more tests and with the help of parallelization across many machine nodes, we can run over 200 tests in under 5 minutes .
- Room to grow with added features in the future – With a steady open-source presence and dedicated Cypress team working towards releasing way more features and improvements to the Cypress infrastructure, we viewed Cypress as a safer bet to invest in rather than STUI, which would require us to engineer and solve a lot of the headaches ourselves, and WebdriverIO, which appeared to feel more stagnant in new features added but with the same baggage as other Selenium wrappers.
Contras
- Lack of cross-browser support – As of this writing, we can only run our tests against Chrome. With WebdriverIO, we could run tests against Chrome, Firefox, Safari, and Opera. STUI also provided some cross-browser testing, though in a much limited form since we created a custom in-house solution
- Cannot integrate with some third-party services – With WebdriverIO, we had the option to integrate with services like BrowserStack and Sauce Labs for cross-browser and device testing. However, with Cypress there are no such third-party integrations but there are some plugins with services like Applitools for visual regression testing available. STUI, on the other hand, also had some small integrations with TestRail , but as a compromise, we log out the TestRail links in our Cypress tests so we can refer back to them if we needed to.
- Requires workarounds to test with iframes – There are some issues around handling iframes with Cypress. We ended up creating a global Cypress command to wrap how to deal with retrieving an iframe's contents as there is no specific API to deal with iframes like how WebdriverIO does.
To summarize our STUI, WebdriverIO, and Cypress comparison, we analyzed the overall developer experience (related to tools, writing tests, debugging, API, documentation, etc.), test run times, test passing rates, and maintenance as displayed in this table:
Following our analysis of the pros and cons of Cypress versus our previous solutions, it was pretty clear Cypress would be our best bet to accomplish our goal of writing fast, valuable, maintainable, and debuggable E2E tests we could integrate with CICD.
Though Cypress lacked features such as cross-browser testing and other integrations with third-party services that we could have had with STUI or WebdriverIO, we most importantly need tests that work more often than not and with the right tools to confidently fix broken ones. If we ever needed cross-browser testing or other integrations we could always still circle back and use our knowledge from our trials and experiences with WebdriverIO and STUI to still run a subset of tests with those frameworks.
We finally presented our findings to the rest of the frontend organization, engineering management, architects, and product. Upon demoing the Cypress test tools and showcasing our results between WebdriverIO/STUI and Cypress, we eventually received approval to standardize and adopt Cypress as our E2E testing library of choice for our frontend teams.
Step 7: Scaling to Other Frontend Teams
After successfully proving that using Cypress was the way to go for our use cases, we then focused on scaling it across all of our frontend teams' repos. We shared lessons learned and patterns of how to get up and running, how to write consistent, maintainable Cypress tests, and of how to hook those tests up during CICD or in scheduled Cypress Buildkite pipelines.
To promote greater visibility of test runs and gain access to a private monthly history of recordings, we established our own organization to be under one billing method to pay for the Dashboard Service with a certain recorded test run limit and maximum number of users in the organization to suit our needs.
Once we set up an umbrella organization, we invited developers and QAs from different frontend teams and each team would install Cypress, open up the Cypress GUI, and inspect the “Runs” and “Settings” tab to get the “Project ID” to place in their `cypress.json` configuration and “Record Key” to provide in their command options to start recording tests to the Dashboard Service. Finally, upon successfully setting up the project and recording tests to the Dashboard Service for the first time, logging into the Dashboard Service would show that team's repo under the “Projects” tab like this:
When we clicked a project like “mako”, we then had access to all of the test runs for that team's repo with quick access to console output, screenshots, and video recordings per test run upon clicking each row as shown below:
For more insights into our integration, we set up many separate dedicated test pipelines to run specific, crucial page tests on a schedule like say every couple hours to once per day. We also added functionality in our main Buildkite CICD deploy pipeline to select and trigger some tests against our feature branch environment and staging environment.
Como era de esperar, esto superó rápidamente nuestras ejecuciones de prueba registradas asignadas para el mes, especialmente porque hay varios equipos que contribuyen y activan las pruebas de varias maneras. Por experiencia, recomendamos tener en cuenta cuántas pruebas se ejecutan en un programa, con qué frecuencia se ejecutan esas pruebas y qué pruebas se ejecutan durante CICD. Puede haber algunas ejecuciones de prueba redundantes y otras áreas para ser más frugal, como volver a marcar la frecuencia de las ejecuciones de prueba programadas o posiblemente deshacerse de algunas por completo para activar las pruebas solo durante CICD.
La misma regla de ser frugal se aplica a la adición de usuarios, ya que enfatizamos brindar acceso solo a los desarrolladores y QA en los equipos de front-end que utilizarán el Dashboard Service en gran medida en lugar de la alta gerencia y otras personas fuera de esos equipos para llenar esos lugares limitados.
Lo que esperamos con Cypress
Como mencionamos antes, Cypress demostró una gran promesa y potencial de crecimiento en la comunidad de código abierto y con su equipo dedicado a cargo de brindar funciones más útiles para que las usemos con nuestras pruebas E2E. Muchas de las desventajas que destacamos se están abordando actualmente y esperamos cosas como:
- Compatibilidad entre navegadores : esta es importante porque gran parte de nuestro rechazo a la adopción de Cypress provino de su uso de solo Chrome en comparación con las soluciones basadas en Selenium, que admitían navegadores como Firefox, Chrome y Safari. Afortunadamente, las pruebas más confiables, mantenibles y depurables ganaron para nuestra organización y esperamos mejorar nuestros conjuntos de pruebas con más pruebas entre navegadores en el futuro cuando el equipo de Cypress lance dicha compatibilidad entre navegadores.
- Reescritura de la capa de red : esto también es muy importante, ya que tendemos a usar la API Fetch en gran medida en nuestras áreas React más nuevas y en las áreas de aplicaciones Backbone / Marionette más antiguas, todavía usamos jQuery AJAX y llamadas normales basadas en XHR. Podemos bloquear fácilmente o escuchar solicitudes en las áreas XHR, pero tuvimos que hacer algunas soluciones alternativas con la obtención de polirrelleno para lograr el mismo efecto. Se supone que la reescritura de la capa de red ayudará a aliviar esos dolores.
- Mejoras incrementales en el Servicio de tablero : ya vimos algunos cambios nuevos en la interfaz de usuario en el Servicio de tablero recientemente y esperamos seguir viéndolo crecer con más visualizaciones de estadísticas y desgloses de datos útiles. También usamos mucho la función de paralelización y revisamos con frecuencia nuestras grabaciones de prueba fallidas en el Servicio de tablero, por lo que sería agradable ver cualquier mejora iterativa en el diseño y/o las funciones.
Adoptando Cypress en el futuro
Para nuestra organización, valoramos la eficiencia del desarrollador, la depuración y la estabilidad de las pruebas de Cypress cuando se ejecutan en el navegador Chrome. Logramos pruebas consistentes y valiosas con menos mantenimiento en el futuro y con muchas herramientas para desarrollar nuevas pruebas y corregir las existentes.
En general, la documentación, la API y las herramientas a nuestra disposición superaron con creces cualquier inconveniente. Después de probar la interfaz gráfica de usuario de Cypress y el servicio de panel de pago, definitivamente no queríamos volver a WebdriverIO ni a nuestra solución Ruby Selenium personalizada.
Conectamos nuestras pruebas con Buildkite y logramos nuestro objetivo de proporcionar una forma de escribir pruebas de automatización E2E consistentes, depurables, mantenibles y valiosas para que nuestras aplicaciones frontend se integren con CICD . Probamos con evidencia al resto de los equipos frontend, a los superiores de ingeniería y a los propietarios de productos los beneficios de adoptar Cypress y eliminar WebdriverIO y STUI.
Cientos de pruebas más tarde en equipos frontend en Twilio SendGrid y detectamos muchos errores en nuestro entorno de prueba y pudimos corregir rápidamente cualquier prueba irregular de nuestro lado con mucha más confianza que nunca. Los desarrolladores y los QA ya no temen la idea de escribir pruebas E2E, pero ahora esperan escribirlas para cada función nueva que lanzamos o para cada función anterior que podría necesitar más cobertura.