Ideas para configurar, organizar y consolidar sus pruebas de Cypress #frontend@twiliosendgrid
Publicado: 2020-12-15Hemos escrito muchas pruebas de Cypress en diferentes aplicaciones web y equipos frontend aquí en Twilio SendGrid. A medida que nuestras pruebas escalaron a muchas funciones y páginas, nos topamos con algunas opciones de configuración útiles y desarrollamos formas de mantener mejor nuestra creciente cantidad de archivos, selectores para los elementos de nuestra página y valores de tiempo de espera en el camino.
Nuestro objetivo es mostrarle estos consejos e ideas para configurar, organizar y consolidar sus propios elementos relacionados con Cypress, así que siéntase libre de hacer con ellos lo que funcione mejor para usted y su equipo.
Esta publicación de blog asume que tiene algún conocimiento práctico de las pruebas de Cypress y está buscando ideas para mantener y mejorar sus pruebas de Cypress. Sin embargo, si tiene curiosidad por obtener más información sobre funciones, aserciones y patrones comunes que pueden resultarle útiles para escribir pruebas de Cypress en entornos separados, puede consultar esta publicación de blog de descripción general de mil pies.
De lo contrario, continuemos y primero lo guiaremos a través de algunos consejos de configuración para Cypress.
Configurando su cypress.json
El archivo cypress.json
es donde puede establecer toda la configuración para sus pruebas de Cypress , como los tiempos de espera base para sus comandos de Cypress, las variables de entorno y otras propiedades.
Aquí hay algunos consejos para que pruebes en tu configuración:
- Ajuste los tiempos de espera de los comandos base, para que no tenga que agregar siempre
{ timeout: timeoutInMs }
a lo largo de sus comandos de Cypress. Juega con los números hasta que encuentres el equilibrio adecuado para configuraciones como "defaultCommandTimeout", "requestTimeout" y "responseTimeout". - Si su prueba involucra iframes, lo más probable es que necesite establecer "chromeWebSecurity" en falso para poder acceder a iframes de origen cruzado en su aplicación.
- Intente bloquear los scripts de marketing, análisis y registro de terceros cuando ejecute sus pruebas de Cypress para aumentar la velocidad y no desencadenar eventos no deseados. Puede configurar fácilmente una lista de denegación con su propiedad "blacklistHosts" (o propiedad "blockHosts" en Cypress 5.0.0), tomando una serie de cadenas globales que coinciden con las rutas de host de terceros.
- Ajuste las dimensiones predeterminadas de la ventana gráfica con "viewportWidth" y "viewportHeight" para cuando abra la GUI de Cypress para que las cosas sean más fáciles de ver.
- Si hay problemas de memoria en Docker para pruebas pesadas o si desea ayudar a que las cosas sean más eficientes, puede intentar modificar "numTestsKeptInMemory" a un número más pequeño que el predeterminado y establecer "videoUploadOnPasses" en falso para concentrarse en cargar videos para pruebas fallidas. corre solo.
En otra nota, después de ajustar su configuración de Cypress, también puede agregar TypeScript y escribir mejor sus pruebas de Cypress como lo hicimos en esta publicación de blog. Esto es especialmente beneficioso para la finalización automática, las advertencias de tipo o los errores al llamar y encadenar funciones (como cy.task('someTaskPluginFunction)
, Cypress.env('someEnvVariable')
o cy.customCommand()
).
También puede experimentar con la configuración de un archivo cypress.json
base y la carga de un archivo de configuración de Cypress independiente para cada entorno de prueba, como un staging.json
cuando ejecuta diferentes secuencias de comandos de Cypress en su package.json
. La documentación de Cypress hace un gran trabajo al guiarlo a través de este enfoque.
Organización de su carpeta Cypress
Cypress ya configura una carpeta cypress
de nivel superior con una estructura guiada cuando inicia Cypress por primera vez en su base de código. Esto incluye otras carpetas, como integration
para sus archivos de especificaciones, fixtures
para archivos de datos JSON, plugins
para sus cy.task(...)
y otra configuración, y una carpeta de support
para sus comandos y tipos personalizados.
Una buena regla general a seguir al organizar dentro de las carpetas de Cypress, los componentes de React o cualquier código en general es colocar las cosas juntas que es probable que cambien juntas. En este caso, dado que estamos tratando con aplicaciones web en el navegador, un enfoque que escala bien es agrupar cosas en una carpeta por página o característica general.
Creamos una carpeta de pages
separada dividida por nombre de función como "SenderAuthentication" para contener todos los objetos de la página debajo de la ruta /settings/sender_auth
como "DomainAuthentication" (/settings/sender_auth/domains/**/*) y "LinkBranding" (/configuración/sender_auth/enlaces/**/*). En nuestra carpeta de plugins
, también hicimos lo mismo con la organización de todos los archivos de complementos cy.task(...)
para una función o página determinada en la misma carpeta para la autenticación del remitente, y seguimos el mismo enfoque para nuestra carpeta de integration
para los archivos de especificaciones. Hemos escalado nuestras pruebas a cientos de archivos de especificaciones, objetos de página, complementos y otros archivos en una de nuestras bases de código, y es fácil y conveniente navegar por ellos.
Aquí hay un resumen de cómo organizamos la carpeta de cypress
.
Otra cosa a tener en cuenta al organizar su carpeta de integration
(donde se encuentran todas sus especificaciones) es potencialmente dividir los archivos de especificaciones según la prioridad de la prueba. Por ejemplo, puede tener todas las pruebas de mayor prioridad y valor en una carpeta "P1" y las pruebas de segunda prioridad en una carpeta "P2" para que pueda ejecutar fácilmente todas las pruebas "P1" configurando la opción --spec
como --spec 'cypress/integration/P1/**/*'
.
Cree una jerarquía de carpetas de especificaciones que funcione para usted para que pueda agrupar fácilmente las especificaciones no solo por página o característica como --spec 'cypress/integration/SomePage/**/*'
, sino también por otros criterios como prioridad, producto , o entorno.
Consolidación de selectores de elementos
Cuando desarrollamos los componentes React de nuestra página, generalmente agregamos algún nivel de pruebas unitarias y de integración con Jest y Enzyme. Hacia el final del desarrollo de funciones, agregamos otra capa de pruebas Cypress E2E para asegurarnos de que todo funcione con el backend. Tanto las pruebas unitarias para nuestros componentes React como los objetos de página para nuestras pruebas Cypress E2E requieren selectores para los componentes/elementos DOM en la página con la que deseamos interactuar y afirmar.
Cuando actualizamos esas páginas y componentes, existe la posibilidad de que se produzcan desviaciones y errores al tener que sincronizar varios lugares desde los selectores de prueba unitaria hasta el objeto de la página de Cypress y el código del componente real. Si confiáramos únicamente en los nombres de clase relacionados con los estilos, sería una molestia recordar actualizar todos los lugares que pueden fallar. En su lugar, agregamos "data-hook", "data-testid" o cualquier otro atributo "data-*" con el nombre constante a componentes y elementos específicos en la página que deseamos recordar y escribimos un selector para ello en nuestras capas de prueba.
Podemos agregar atributos de "gancho de datos" a muchos de nuestros elementos, pero aún necesitábamos una forma de agruparlos en un solo lugar para actualizarlos y reutilizarlos en otros archivos. Descubrimos una forma escrita de administrar todos estos selectores de "gancho de datos" para distribuirlos en nuestros componentes React y utilizarlos en nuestras pruebas unitarias y selectores de objetos de página para una mayor reutilización y un mantenimiento más fácil en los objetos exportados.
Para la carpeta de nivel superior de cada página, crearíamos un archivo hooks.ts
que administra y exporta un objeto con un nombre de clave legible para el elemento y el selector CSS de cadena real para el elemento como valor. Los llamaremos "Selectores de lectura" ya que necesitamos leer y usar el formulario de selección de CSS para un elemento en llamadas como wrapper.find(“[data-hook='selector']”)
de Enzyme en nuestras pruebas unitarias o Cypress's cy.get(“[data-hook='selector']”)
en nuestros objetos de página. Estas llamadas se verían más limpias como wrapper.find(Selectors.someElement)
o cy.get(Selectors.someElement)
.
El siguiente fragmento de código cubre más de por qué y cómo estamos usando estos selectores de lectura en la práctica.
De manera similar, también exportamos objetos con un nombre de clave legible para el elemento y con un objeto como { “data-hook”: “selector” }
como valor. Los llamaremos "Selectores de escritura", ya que necesitamos escribir o distribuir estos objetos en un componente de React como accesorios para agregar con éxito el atributo "gancho de datos" a los elementos subyacentes. El objetivo sería hacer algo como esto. y el elemento DOM real debajo, suponiendo que los accesorios se transmitan correctamente a los elementos JSX, también tendrá el conjunto de atributos "data-hook=".
El siguiente fragmento de código cubre más de por qué y cómo estamos usando estos selectores de escritura en la práctica.
Podemos crear objetos para consolidar nuestros selectores de lectura y escribir selectores para actualizar menos lugares, pero ¿qué pasaría si tuviéramos que escribir muchos selectores para algunas de nuestras páginas más complejas? Esto puede ser más propenso a errores para construirlo usted mismo, así que vamos a crear funciones para generar fácilmente estos selectores de lectura y escribir selectores para exportar eventualmente a una página determinada.
Para la función de generador de selector de lectura, recorreremos las propiedades de un objeto de entrada y formaremos la cadena de selector CSS [data-hook=”selector”]
para cada nombre de elemento. Si el valor correspondiente de una clave es null
, asumiremos que el nombre del elemento en el objeto de entrada será el mismo que el valor del "enganche de datos" como { someElement: null } => { someElement: '[data-hook=”someElement”] }
. De lo contrario, si el valor correspondiente de una clave no es nulo, podemos optar por anular ese valor de "gancho de datos" como { someElement: “newSelector” } => { someElement: '[data-hook=”newSelector”]' }
.
Para la función de generador de selector de escritura, recorreremos las propiedades de un objeto de entrada y formaremos los objetos { “data-hook”: “selector” }
para cada nombre de elemento. Si el valor correspondiente de una clave es null
, asumiremos que el nombre del elemento en el objeto de entrada será el mismo que el valor del "enganche de datos" como { someElement: null } => { someElement: { “data-hook”: “someElement” } }
. De lo contrario, si el valor correspondiente de una clave no es null
, podemos optar por anular ese valor de "enlace de datos" como { someElement: “newSelector” } => { someElement: { “data-hook”: “newSelector” }' }
.
Usando esas dos funciones de generador, construimos nuestro selector de lectura y objetos de selector de escritura para una página y los exportamos para reutilizarlos en nuestras pruebas unitarias y objetos de página de Cypress. Otra ventaja es que estos objetos se escriben de tal manera que no podemos hacer accidentalmente Selectors.unknownElement
o WriteSelectors.unknownElement
en nuestros archivos TypeScript. Antes de exportar nuestros selectores de lectura, también permitimos agregar asignaciones de elementos adicionales y selectores de CSS para componentes de terceros sobre los que no tenemos control. En algunos casos, no podemos agregar un atributo de "gancho de datos" a ciertos elementos, por lo que aún debemos seleccionar por otros atributos, ID y clases y agregar más propiedades al objeto selector de lectura como se muestra a continuación.
Este patrón nos ayuda a mantenernos organizados con todos nuestros selectores para una página y para cuando necesitamos actualizar cosas. Recomendamos investigar formas de administrar todos estos selectores en algún tipo de objeto o por cualquier otro medio para minimizar la cantidad de archivos que necesita para tocar cambios futuros.
Consolidación de tiempos de espera
Una cosa que notamos después de escribir muchas pruebas de Cypress fue que nuestros selectores para ciertos elementos expiraban y tomaban más tiempo que los valores de tiempo de espera predeterminados establecidos en nuestro cypress.json
, como "defaultCommandTimeout" y "responseTimeout". Regresaríamos y ajustaríamos ciertas páginas que necesitaban tiempos de espera más largos, pero luego, con el tiempo, la cantidad de valores de tiempo de espera arbitrarios creció y mantenerlo se volvió más difícil para cambios a gran escala.
Como resultado, consolidamos nuestros tiempos de espera en un objeto que comienza por encima de nuestro "defaultCommandTimeout", que está en algún lugar en el rango de cinco a diez segundos para cubrir la mayoría de los tiempos de espera generales para nuestros selectores como cy.get(...)
o cy.contains(...)
. Más allá de ese tiempo de espera predeterminado, escalaríamos a "corto", "medio", "largo", "xlargo" y "xxlargo" dentro de un objeto de tiempo de espera que podemos importar en cualquier lugar de nuestros archivos para usar en nuestros comandos de Cypress, como cy.get(“someElement”, { timeout: timeouts.short })
o cy.task('pluginName', {}, { timeout: timeouts.xlong })
. Después de reemplazar nuestros tiempos de espera con estos valores de nuestro objeto importado, tenemos un lugar para actualizar para escalar hacia arriba o hacia abajo el tiempo que toma para ciertos tiempos de espera.
A continuación se muestra un ejemplo de cómo puede comenzar fácilmente con esto.
Terminando
A medida que crezcan sus conjuntos de pruebas de Cypress, es posible que se encuentre con algunos de los mismos problemas que tuvimos cuando descubrimos la mejor manera de escalar y mantener sus pruebas de Cypress. Puede optar por organizar sus archivos según la página, función o alguna otra convención de agrupación, para que siempre sepa dónde buscar archivos de prueba existentes y dónde agregar nuevos a medida que más desarrolladores contribuyan a su base de código.
A medida que cambia la interfaz de usuario, puede usar algún tipo de objeto escrito (como los selectores de lectura y escritura) para mantener sus selectores de atributos de "datos" en los elementos clave de cada página que le gustaría afirmar o interactuar dentro de sus pruebas unitarias y Cypress. pruebas Si comienza a aplicar demasiados valores arbitrarios para cosas como valores de tiempo de espera para sus comandos de Cypress, puede ser el momento de configurar un objeto lleno de valores escalados para que pueda actualizar esos valores en un solo lugar.
A medida que las cosas cambian en la interfaz de usuario de la interfaz de usuario, la API de backend y las pruebas de Cypress, siempre debe pensar en formas de mantener estas pruebas con mayor facilidad. Menos lugares para actualizar y cometer errores y menos ambigüedad en cuanto a dónde colocar las cosas marcan una gran diferencia, ya que muchos desarrolladores agregan nuevas páginas, funciones e (inevitablemente) pruebas de Cypress en el futuro.
¿Interesado en más publicaciones sobre Cypress? Echa un vistazo a los siguientes artículos:
- Qué considerar al escribir pruebas E2E
- Descripción general de 1,000 pies de escritura de pruebas de ciprés
- TypeScript Todas las cosas en sus pruebas de Cypress
- Manejo de flujos de correo electrónico en Cypress Tests
- Integración de pruebas Cypress con Docker, Buildkite y CICD