Escribimos pruebas de integración para la interfaz y aceleramos lanzamientos

¡Hola! Mi nombre es Vova, soy frontend en Tinkoff. Nuestro equipo es responsable de dos productos para personas jurídicas. Puedo decir en cifras sobre el tamaño del producto: una regresión completa de cada producto por dos probadores toma de tres días (sin la influencia de factores externos).



Los términos son significativos y piden luchar contra ellos. Hay varias formas de pelear, las principales:



  • Cortar la aplicación en productos más pequeños con sus propios ciclos de lanzamiento.

  • Cobertura del producto con pruebas de acuerdo con la pirámide de prueba.



El último párrafo fue el tema de mi artículo.



imagen



Pirámide de prueba



Como sabemos, hay tres niveles en la pirámide de pruebas: pruebas unitarias, pruebas de integración y pruebas e2e. Creo que muchos están familiarizados con las unidades, así como con e2e, por lo que me detendré en las pruebas de integración con más detalle.



Como parte de las pruebas de integración, verificamos el funcionamiento de toda la aplicación a través de la interacción con la interfaz de usuario, sin embargo, la principal diferencia con respecto a las pruebas e2e es que no hacemos solicitudes reales de respaldo. Esto se hace para verificar solo la interacción de todos los sistemas en el frente, a fin de reducir el número de pruebas e2e en el futuro.



Usamos Cypress para escribir pruebas de integración . En este artículo no lo compararé con otros marcos, solo diré por qué resultó estar con nosotros:



  1. Documentación muy detallada.

  2. Fácil depuración de pruebas (Cypress ha creado una GUI especial para esto con pasos de viaje en el tiempo en la prueba).



Estos puntos fueron importantes para nuestro equipo, ya que no teníamos experiencia en escribir pruebas de integración y se necesitaba un comienzo muy simple. En este artículo, quiero hablar sobre el camino que hemos recorrido, sobre los baches que hemos completado y compartir recetas para la implementación.



El comienzo del camino



Al principio, utilicé Angular Workspace con una aplicación para organizar el código. Después de instalar el paquete Cypress, apareció una carpeta de cypress con configuración y pruebas en la raíz de la aplicación, nos detuvimos en esta opción. Al intentar preparar el script en package.json necesario para ejecutar la aplicación y ejecutar pruebas encima, encontramos los siguientes problemas:



  1. En index.html, se cosieron algunos scripts que no eran necesarios en las pruebas de integración.

  2. Para ejecutar las pruebas de integración, debe asegurarse de que el servidor con la aplicación se esté ejecutando.



El problema con index.html se resolvió mediante una configuración de compilación separada, llamémosla sypress, en la que especificamos un index.html personalizado. ¿Cómo implementar esto? Encontramos la configuración de su aplicación en angular.json, abrimos la sección de compilación, agregamos una configuración separada para Cypress allí y no olvidemos especificar esta configuración para el modo de servicio.



Configuración de ejemplo para compilación:



"build": {
 ...
 "configurations": {
   … //  
   "cypress": {
     "aot": true,
     "index": "projects/main-app-integrations/src/fixtures/index.html",
     "fileReplacements": [
       {
         "replace": "projects/main-app/src/environments/environment.ts",
         "with": "projects/main-app/src/environments/environment.prod.ts"
       }
     ]
   }
 }
}


Servir integración:



"serve": {
 ...
 "configurations": {
   … //  
   "cypress": {
     "browserTarget": "main-app:build:cypress"
   }
 }
}


Desde el principio: para la configuración de cipreses, especificamos el conjunto aot y reemplazamos los archivos con el entorno; esto es necesario para crear un conjunto similar a un producto durante la prueba.



Entonces, descubrimos el index.html, queda por abrir las aplicaciones, esperar a que finalice la compilación y ejecutar pruebas encima. Para hacer esto, use la biblioteca start-server-and-test y escriba scripts basados ​​en ella:



 "main-app:cy:run": "cypress run",
 "main-app:cy:open": "cypress open",
 "main-app:integrations": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:run",
 "main-app:integrations:open": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:open"


Como puede ver, hay dos tipos de scripts: abrir y ejecutar. El modo abierto abre la GUI de Cypress, donde puede cambiar entre pruebas y usar el viaje en el tiempo. El modo de ejecución es solo una ejecución de prueba y el resultado final de esa ejecución, ideal para ejecutar en CI.



Con base en los resultados del trabajo realizado, pudimos obtener un marco inicial para escribir la primera prueba y ejecutarla en CI.



Monorepository



El enfoque descrito tiene un problema muy notable: si el repositorio tiene dos o más aplicaciones, entonces el enfoque con una carpeta no es viable. Y así sucedió con nosotros. Pero sucedió de una manera bastante interesante. En el momento de la introducción de Cypress, nos estábamos mudando a NX, y este atractivo fuera de la caja permite trabajar con Cypress. ¿Cuál es el principio del trabajo en él?



  1. Tiene una aplicación como main-app, al lado se crea la aplicación main-app-e2e.

  2. Cambia el nombre de main-app-e2e a main-app-integrations: eres increíble.



Ahora puede ejecutar pruebas de integración con un comando: ng e2e main-app-integrations. NX elevará automáticamente la aplicación principal, esperará una respuesta y ejecutará las pruebas.



Desafortunadamente, aquellos que actualmente usan Angular Workspace se han mantenido al margen, pero está bien, también tengo una receta para usted. Utilizaremos la estructura de archivos como en NX:



  1. Cree la carpeta main-app-integrations junto a su aplicación.

  2. Cree una carpeta src en él y agregue el contenido de la carpeta cypress.

  3. No olvide mover cypress.json (inicialmente aparecerá en la raíz) a la carpeta main-app-integrations.

  4. Corregimos cypress.json, indicando las rutas a nuevas carpetas con pruebas, complementos y comandos auxiliares (parámetros de IntegrationFolder, pluginsFile y supportFile).

  5. Cypress puede trabajar con pruebas en cualquier carpeta, el parámetro del

    proyecto se usa para especificar la carpeta , por lo que cambiamos el comando de cypress run / open a cypress run / open -–project ./projects/main-app-integrations/src .



La solución para Angular Workspace es muy similar a la solución para NX, excepto que creamos la carpeta a mano y no es uno de los proyectos en su mono-repositorio. Alternativamente, puede usar directamente el generador de NX para Cypress ( un ejemplo de un repositorio en NX con Cypress, allí puede ver el uso final del generador de nx-cypress - atención a angular.json y el proyecto

cart-e2e y products-e2e).



Regresión visual



Después de las primeras cinco pruebas, comenzamos a pensar en las pruebas de captura de pantalla, porque, de hecho, existen todas las posibilidades para esto. Diré de antemano que la palabra "prueba de captura de pantalla" causa mucho dolor dentro del equipo, ya que el camino para obtener pruebas estables no fue el más fácil. A continuación, describiré los principales problemas que encontramos y su solución.



La biblioteca de instantáneas de imagen de ciprés se tomó como una solución . La implementación no tomó mucho tiempo, y después de 20 minutos recibimos la primera captura de pantalla de nuestra aplicación con un tamaño de 1000 × 600 px. Hubo mucha alegría porque la integración y el uso eran demasiado fáciles, y los beneficios podrían ser enormes.



Después de generar cinco capturas de pantalla de referencia, lanzamos una prueba en CI, como resultado, la construcción se vino abajo. Resultó que las capturas de pantalla creadas con los comandos abrir y ejecutar son diferentes. La solución fue bastante simple: tomar capturas de pantalla solo en modo CI, para esto eliminamos tomar capturas de pantalla en modo local, por ejemplo:



Cypress.Commands.overwrite(
   'matchImageSnapshot',
   (originalFn, subject, fileName, options) => {
       if (Cypress.env('ci')) {
           return originalFn(subject, fileName, options);
       }

       return subject;
   },
);


En esta solución estamos viendo el parámetro env en Cypress, se puede configurar de diferentes maneras.



Fuentes



Localmente, las pruebas comenzaron a pasar al reiniciar, intentamos ejecutarlas nuevamente en CI. El resultado se puede ver a continuación:







es bastante fácil notar la diferencia en las fuentes en la captura de pantalla de diferencias. La captura de pantalla de referencia se generó en macOS, y en CI, los agentes instalaron Linux.



Mala decisión



Recogimos una de las fuentes estándar (como si fuera Ubuntu Font), que daba una diferencia mínima por píxel, y aplicamos esta fuente para bloques de texto (hecho en

index.html, que estaba destinado solo para pruebas de ciprés). Luego aumentamos la diferencia general al 0.05% y la diferencia por píxel al 20%. Vivimos con tales parámetros durante una semana, hasta el primer caso en que fue necesario cambiar el texto en el componente. Como resultado, la compilación permaneció verde, aunque no actualizamos la captura de pantalla. La solución actual ha demostrado ser inútil.



Solución correcta



El problema original estaba en diferentes entornos, la solución en principio se sugiere a sí misma: Docker. Ya hay imágenes acopladas ya preparadas para Cypress . Hay diferentes variaciones de las imágenes, nos interesa incluirlas, ya que Cypress ya está incluido en la imagen y no descargará ni desempaquetará el binario de Cypress cada vez (la GUI de Cypress se ejecuta a través de un archivo binario , y la descarga y desempaquetado lleva más tiempo que la descarga imagen del acoplador).

En base a la imagen de acoplador incluida, creamos nuestro propio contenedor de acoplador, para esto hemos realizado una prueba de integración. Archivo de Dockerfile con contenido similar:



FROM cypress:included:4.3.0
COPY package.json /app/
COPY package-lock.json app/
WORKDIR /app
RUN npm ci
COPY / /app/
ENTRYPOINT []


Me gustaría señalar la puesta a cero de ENTRYPOINT, esto se debe al hecho de que está configurado de forma predeterminada en la imagen de ciprés / incluido y apunta al comando de ejecución de ciprés, que nos impide usar otros comandos. También dividimos nuestro dockerfile en capas para que cada vez que reiniciemos las pruebas, no volvamos a ejecutar npm ci.



Agregue el archivo .dockerignore (si no existe) a la raíz del repositorio y, en él, asegúrese de especificar node-modules / y * / node-modules /.



Para ejecutar nuestras pruebas en Docker, escribiremos un script bash integracion-tests.sh con el siguiente contenido:



docker build -t integrations -f integration-tests.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations


Breve descripción: Creamos nuestras pruebas de integración. Dockerfile docker container y apuntamos el volumen a la carpeta de pruebas para que podamos obtener las capturas de pantalla generadas por Docker.



Fuentes de nuevo



Después de resolver el problema descrito en el capítulo anterior, hubo una pausa en las compilaciones, pero aproximadamente un día después encontramos el siguiente problema (capturas de pantalla izquierda y derecha de un componente tomadas en diferentes momentos):







Creo que el más atento notó que no hay suficiente título en la ventana emergente ... La razón es muy simple: la fuente no logró cargarse, porque no estaba conectada a través de activos, sino que estaba en el CDN.



Mala decisión



Descargue fuentes de CDN, colóquelas en activos para la configuración de cipreses, y en nuestro

index.html personalizado para pruebas de integración las conectamos. Con esta decisión, vivimos un tiempo decente hasta que cambiamos la fuente corporativa. No había ningún deseo de hacer la misma historia por segunda vez.



Solución correcta



Se decidió comenzar a precargar todas las fuentes necesarias para la prueba en

index.html para la configuración de ciprés, se parecía a esto:



<link	
      rel="preload"
      href="...."	
      as="font"	
      type="font/woff2"	
      crossorigin="anonymous"
/>


El número de bloqueos de prueba debido a fuentes que no tuvieron tiempo de cargar disminuyó a un mínimo, pero no a cero: aún así, a veces la fuente no tuvo tiempo de cargar. La solución de KitchenSink de Cypress en sí vino al rescate: waitForResource.

En nuestro caso, dado que la precarga de fuentes ya estaba habilitada, simplemente redefinimos el comando de visita en Cypress, como resultado, no solo navega a la página, sino que también espera a que se carguen las fuentes especificadas. También me gustaría agregar que waitForResource resuelve el problema no solo de las fuentes, sino también de cualquier estadística cargada, por ejemplo, imágenes (debido a ellas, las capturas de pantalla también se rompieron y waitForResource ayudó mucho). Después de aplicar esta solución, no hubo problemas con las fuentes y las estadísticas de carga.



Animaciones



Nuestro dolor de cabeza está relacionado con las animaciones, que se mantienen hasta el día de hoy. En algún momento, las capturas de pantalla del elemento comenzarán a aparecer o se tomó una captura de pantalla antes de que comience la animación. Tales capturas de pantalla son inestables, y cada vez que se comparan con la referencia, habrá diferencias. Entonces, ¿qué camino tomamos al resolver el problema de la animación?



Primera solución



Lo más simple que se nos ocurrió en la etapa inicial: antes de crear una captura de pantalla, detenga el navegador por un tiempo determinado para que las animaciones puedan completarse. Caminamos a lo largo de una cadena de 100 ms, 200 ms, 500 ms y finalmente 1000 ms. Mirando hacia atrás, entiendo que esta decisión fue inicialmente terrible, pero solo quería advertirte contra la misma decisión. ¿Por qué horrible? Los tiempos de animación son diferentes, los agentes en CI también pueden aburrirse a veces, por lo que cualquier tiempo de espera para la estabilización de la página de vez en cuando era diferente.



Segunda solución



Incluso con una espera de 1 segundo, la página no siempre logró estabilizarse. Después de un poco de investigación, encontramos una herramienta de Angular - Testability. El principio se basa en el seguimiento de la estabilidad de ZoneJS:



Cypress.Commands.add('waitStableState', () => {
   return cy.window().then(window => {
       const [testability]: [Testability] = window.getAllAngularTestabilities();

       return new Cypress.Promise(resolve => {
           testability.whenStable(() => {
               resolve();
           }, 3000);
       });
   });
});


Por lo tanto, al crear capturas de pantalla, llamamos a dos comandos: cy.wait (1000) y cy.waitStableState ().



Desde entonces, no ha habido una sola captura de pantalla aleatoria, pero cuentemos cuánto tiempo pasó en el navegador inactivo. Supongamos que tiene 5 capturas de pantalla en su prueba, para cada una hay un tiempo de espera estable de 1 segundo y algo de tiempo aleatorio, digamos 1.5 segundos en promedio (no medí el valor promedio en realidad, así que lo saqué de mi cabeza de acuerdo con mis propios sentimientos) . Como resultado, pasamos 12.5 segundos adicionales para crear capturas de pantalla en la prueba. Imagine que ya ha escrito 20 scripts de prueba, donde cada prueba tiene al menos 5 capturas de pantalla. Obtenemos que el pago excesivo para la estabilidad es de ~ 4 minutos con 20 pruebas disponibles. 



Pero incluso este no es el mayor problema. Como se discutió anteriormente, cuando se ejecutan pruebas localmente, las capturas de pantalla no se persiguen, pero en CI se persiguen, y debido a las expectativas, se activaron devoluciones de llamada en el código, por ejemplo, en Tiempo de rebote, para cada captura de pantalla, que ya creó aleatorización en las pruebas, porque en CI y localmente pasado de diferentes maneras.



Solución actual



Comencemos con animaciones angulares. Nuestro marco favorito durante la animación en el elemento DOM cuelga la clase ng-animating. Esta fue la clave de nuestra solución, porque ahora debemos asegurarnos de que no haya una clase de animación en el elemento ahora. Como resultado, resultó en tal función:



export function waitAnimation(element: Chainable<JQuery>): Chainable<JQuery> {
   return element.should('be.visible').should('not.have.class', 'ng-animating');
}


No parece nada complicado, pero fue esto lo que formó la base de nuestras decisiones. A lo que quiero prestar atención en este enfoque: al hacer una captura de pantalla, debe comprender qué animación del elemento puede hacer que su captura de pantalla sea inestable, y antes de crear una captura de pantalla, agregue una afirmación que verifique que el elemento no esté animado. Pero las animaciones también pueden estar en CSS. Como dice el propio Cypress, cualquier afirmación sobre un elemento está esperando que la animación termine en él, más información aquí y aquí . Es decir, la esencia del enfoque es la siguiente: tenemos un elemento animado, agreguemos una afirmación: should ('be.visible') / should ('not.be.visible')- y Cypress esperará a que la animación termine en el elemento (quizás, por cierto, la solución con ng-animating no es necesaria y solo las comprobaciones de Cypress son suficientes, pero por ahora estamos usando la utilidad waitAnimation).



Como se indica en la documentación misma, Cypress verifica la posición de un elemento en la página, pero no todas las animaciones son sobre cambios de posición, también hay animaciones fadeIn / fadeOut. En estos casos, el principio de la solución es el mismo: verificamos que el elemento sea visible / no visible en la página. 



Al pasar de cy.wait (1000) + cy.waitStableState () a waitAnimation y Cypress Assertion, tuvimos que pasar ~ 2 horas para estabilizar las capturas de pantalla antiguas, pero como resultado obtuvimos + 20-30 segundos en lugar de +4 minutos para el tiempo de ejecución de la prueba ... En este momento, nos estamos acercando cuidadosamente a la revisión de capturas de pantalla: verificamos que no se ejecutaron durante la animación de elementos DOM y agregamos verificaciones en la prueba para esperar la animación. Por ejemplo, a menudo agregamos una visualización de "esqueleto" en la página hasta que los datos se hayan cargado. En consecuencia, el requisito llega inmediatamente a la revisión de que al crear capturas de pantalla, un esqueleto no debe estar presente en el DOM, ya que hay una animación desvanecida en él. 



Solo hay un problema con este enfoque: no siempre es posible prever todo al crear una captura de pantalla, y aún puede caer en CI. Solo hay una forma de lidiar con esto: vaya y edite inmediatamente la creación de dicha captura de pantalla, no puede posponerla, de lo contrario se acumulará como una bola de nieve y al final simplemente apagará las pruebas de integración.



Tamaño de la captura de pantalla



Es posible que haya notado una característica interesante: la resolución predeterminada de las capturas de pantalla es de 1000 × 600 px. Desafortunadamente, hay un problema con el tamaño de la ventana del navegador cuando se ejecuta en Docker: incluso si cambia el tamaño de la ventana gráfica a través de Cypress, no ayudará. Encontramos una solución para el navegador Chrome (para Electron, no fue posible encontrar rápidamente una solución que funcionara, pero no comenzamos la solución propuesta en este número ). Primero, debe cambiar el navegador para ejecutar pruebas en Chrome:



  1. No para NX lo hacemos usando el argumento --browser chrome cuando iniciamos el comando cypress open / run y para el comando run especificamos el parámetro --headless.

  2. Para NX, en la configuración del proyecto en angular.json con pruebas, especificamos el parámetro browser: chrome, y para la configuración que se ejecutará en CI, especificamos headless: true.



Ahora hacemos los cambios en los complementos y obtenemos capturas de pantalla con un tamaño de 1440 × 900 px:



module.exports = (on, config) => {
   on('before:browser:launch', (browser, launchOptions) => {
       if (browser.name === 'chrome' && browser.isHeadless) {
           launchOptions.args.push('--disable-dev-shm-usage');
           launchOptions.args.push('--window-size=1440,1200');

           return launchOptions;
       }

       return launchOptions;
   });
};


fechas



Aquí todo es simple: si la fecha asociada con la actual se muestra en algún lugar, la captura de pantalla tomada hoy caerá mañana. Fixim es simple:



cy.clock(new Date(2025, 11, 22, 0).getTime(), ['Date']);


Ahora los temporizadores. No nos molestamos y usamos la opción de apagón al crear capturas de pantalla, por ejemplo:



cy.matchImageSnapshot('salary_signing-several-payments', {
   blackout: ['.timer'],
});


Pruebas escamosas



Usando las recomendaciones anteriores, puede lograr la máxima estabilidad de la prueba, pero no el 100%, porque las pruebas se ven afectadas no solo por su código, sino también por el entorno en el que se ejecutan.



Como resultado, un cierto porcentaje de pruebas ocasionalmente caerá, por ejemplo, debido al rendimiento del agente en CI. En primer lugar, estabilizamos la prueba desde nuestro lado: agregamos la afirmación necesaria antes de tomar capturas de pantalla, pero durante el período de reparación de dichas pruebas, puede usar el reintento de las pruebas descartadas utilizando cypress-plugin-retries.



Bombeamos CI



En los capítulos anteriores, aprendimos a ejecutar pruebas con un comando y aprendimos a trabajar con las pruebas de captura de pantalla. Ahora podemos mirar hacia la optimización de CI. Nuestra compilación definitivamente se ejecutará:



  1. Npm ci comando.
  2. Elevar la aplicación en modo aot.
  3. Ejecución de pruebas de integración.


Echemos un vistazo al primer y segundo punto y comprendamos que se realizan pasos similares en su otra compilación en el CI: compilación con el ensamblaje de la aplicación.

La principal diferencia no es ejecutar ng serve, sino ng build. Por lo tanto, si podemos obtener la aplicación ya compilada en la compilación con pruebas de integración y elevar el servidor con ella, entonces podemos reducir el tiempo de ejecución de la compilación con pruebas.



¿Por qué lo necesitamos? Solo tenemos una gran aplicación y ejecución

npm ci + npm empezar en modo aot en el agente en CI tomó ~ 15 minutos, lo que en principio requirió mucho esfuerzo por parte del agente, y además se realizaron pruebas de integración. Supongamos que ya ha escrito más de 20 pruebas y en la prueba 19 el navegador en el que se ejecutan las pruebas se bloquea debido a la gran carga del agente. Como sabe, reiniciar la compilación nuevamente está esperando que se instalen las dependencias y se inicie la aplicación.



Además, solo hablaré sobre scripts en el lado de la aplicación. Deberá resolver el problema de transferir artefactos entre tareas a CI usted mismo, por lo que tenemos en cuenta que una nueva compilación con pruebas de integración tendrá acceso a la aplicación ensamblada desde la tarea para la compilación de su aplicación.



Servidor con estática



Necesitamos un reemplazo para ng serve para elevar el servidor con nuestra aplicación. Hay muchas opciones, comenzaré con nuestro primer servidor angular-http . No hay nada complicado en su configuración: instalamos la dependencia, indicamos en qué carpeta están ubicadas nuestras estadísticas, indicamos en qué puerto elevar la aplicación y nos alegramos.



Esta solución fue suficiente para nosotros por hasta 20 minutos, y luego nos dimos cuenta de que queríamos enviar algunas solicitudes al circuito de prueba. El proxy de conexión para angular-http-server falló. La solución final fue actualizar el servidor a Express . Para resolver el problema, utilizamos express y express-http-proxy. Distribuiremos nuestras estadísticas usando

express.static, como resultado obtendremos un script similar a este:



const express = require('express');
const appStaticPathFolder = './dist';
const appBaseHref = './my/app';
const port = 4200;
const app = express();

app.use((req, res, next) => {
   const accept = req
       .accepts()
       .join()
       .replace('*/*', '');

   if (accept.includes('text/html')) {
       req.url = baseHref;
   }

   next();
});
app.use(appBaseHref, express.static(appStaticPathFolder));
app.listen(port);


El punto interesante aquí es que antes de escuchar la ruta en la baseHref de la aplicación, también procesamos todas las solicitudes y buscamos una solicitud para index.html. Esto se hace para los casos en que las pruebas van a una página de aplicación cuya ruta es diferente de baseHref. Si no hace este truco, cuando vaya a cualquier página de su aplicación, excepto a la página principal, recibirá un error 404. Ahora agreguemos una pizca de representación:



const proxy = require('express-http-proxy');

app.use(
   '/common',
   proxy('https://qa-stand.ru', {
       proxyReqPathResolver: req => '/common' + req.url,
   }),
);


Echemos un vistazo más de cerca a lo que está sucediendo. Hay constantes:



  1. appStaticForlderPath es la carpeta donde se encuentran las estadísticas de su aplicación. 
  2. appBaseHref: su aplicación puede tener una baseHref; de lo contrario, puede especificar '/'.


Proxy todas las solicitudes que comienzan con / common, y cuando prox mantenemos la misma ruta que la solicitud, usando la configuración proxyReqPathResolver. Si no lo usa, todas las solicitudes simplemente irán a https://qa-stand.ru.



Personalización index.html



Necesitábamos resolver el problema con index.html personalizado, que usábamos cuando ng servíamos aplicaciones en modo Cypress. Escribamos un script simple en node.js. Teníamos index.modern.html como parámetros iniciales, necesitábamos convertirlo en index.html y eliminar los scripts innecesarios desde allí:



const fs = require('fs');
const appStaticPathFolder = './dist';

fs.copyFileSync(appStaticPathFolder + '/index.modern.html', appStaticPathFolder + '/index.html');

fs.readFile(appStaticPathFolder + '/index.html', 'utf-8', (err, data) => {
   const newValue = data
       .replace(
           '<script type="text/javascript" src="/auth.js"></script>',
           '',
       )
       .replace(
           '<script type="text/javascript" src="/analytics.js"></script>',
           '',
       );

   fs.writeFileSync(appStaticPathFolder + '/index.html', newValue, 'utf-8');
});


Guiones



Realmente no quería volver a hacer npm ci de todas las dependencias para ejecutar pruebas en CI (después de todo, esto ya se hizo en la tarea con la compilación de la aplicación), por lo que la idea parecía crear una carpeta separada para todos estos scripts con nuestro propio package.json. Asignemos un nombre a la carpeta, por ejemplo, integración-tests-scripts y coloquemos tres archivos allí: server.js, create-index.js, package.json. Los dos primeros archivos se describieron anteriormente, ahora analicemos el contenido de package.json:



{
 "name": "cypress-tests",
 "version": "0.0.0",
 "private": true,
 "scripts": {
   "create-index": "node ./create-index.js",
   "main-app:serve": "node ./server.js",
   "main-app:cy:run": "cypress run --project ./projects/main-app-integrations ",
   "main-app:integrations": "npm run create-index && start-server-and-test main-app:serve http://localhost:4200/my/app/ main-app:cy:run"
 },
 "devDependencies": {
   "@cypress/webpack-preprocessor": "4.1.0",
   "@types/express": "4.17.2",
   "@types/mocha": "5.2.7",
   "@types/node": "8.9.5",
   "cypress": "4.1.0",
   "cypress-image-snapshot": "3.1.1",
   "express": "4.17.1",
   "express-http-proxy": "^1.6.0",
   "start-server-and-test": "1.10.8",
   "ts-loader": "6.2.1",
   "typescript": "3.8.3",
   "webpack": "4.41.6"
 }
}


En package.json, solo hay dependencias necesarias para ejecutar pruebas de integración ( con soporte de mecanografía y pruebas de captura de pantalla) y scripts para iniciar el servidor, crear index.html y el conocido del capítulo sobre el lanzamiento de pruebas de integración en Angular Workspace start-server-and-test ...



Corriendo



Envolvemos la ejecución de las pruebas de integración en un nuevo Dockerfile - Integration -tests-ci.Dockerfile :



FROM cypress/included:4.3.0
COPY integration-tests-scripts /app/
WORKDIR /app
RUN npm ci
COPY projects/main-app-integrations /app/projects/main-app-integrations
COPY dist /app/dist
COPY tsconfig.json /app/
ENTRYPOINT []


La conclusión es simple: copie y expanda la carpeta de integración-pruebas-scripts a la raíz de la aplicación y copie todo lo que se necesita para ejecutar las pruebas (esta suite puede ser diferente para usted). Las principales diferencias con respecto al archivo anterior son que no copiamos toda la aplicación dentro del contenedor acoplable, solo una optimización mínima del tiempo de ejecución de la prueba en CI.



Cree un archivo de integración- tests- ci.sh con el siguiente contenido:



docker build -t integrations -f integration-tests-ci.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations


Cuando se ejecuta el comando con pruebas, package.json de la carpeta de integración-pruebas-scripts se convertirá en la raíz y la aplicación principal: se lanzará el comando de integraciones. En consecuencia, dado que esta carpeta se expandirá a la raíz, las rutas a la carpeta con las estadísticas de su aplicación deben especificarse con la idea de que todo se iniciará desde la raíz, y no desde la carpeta de integración-pruebas-scripts.



También quiero hacer un pequeño comentario: llamé al script final de bash para ejecutar pruebas de integración, ya que evolucionó de manera diferente. Esto no es necesario, se hizo solo por la conveniencia de leer este artículo. Siempre debe tener un archivo restante, por ejemplo integración-tests.sh, que ya está desarrollando. Si tiene varias aplicaciones en el repositorio y sus métodos de preparación difieren, puede usar las variables en basho diferentes archivos para cada aplicación, según sus necesidades.



Salir



Había mucha información, creo que ahora vale la pena resumir sobre la base de lo que se escribió anteriormente.

Preparación de herramientas para escritura local y ejecución de pruebas con una pizca de pruebas de captura de pantalla:



  1. Agregar dependencia para Cypress.
  2. Prepare la carpeta con pruebas:

    1. Aplicación única angular: deje todo en la carpeta de cipreses.
    2. Espacio de trabajo angular: cree una carpeta de integraciones de nombres de aplicaciones junto a la aplicación con la que se ejecutarán las pruebas y mueva todo desde la carpeta de cipreses a ella.
    3. NX: cambie el nombre del proyecto de appname-e2e a appname-integrations.


  3. cypress- — build- Cypress, aot, index.html, environment prod- serve- Cypress ( , - prod-, ).
  4. :

    1. Angular Single Application — serve- cypress- , start-server-and-test.
    2. Angular Workspace — Angular Single Application, cypress run/open.
    3. NX — ng e2e.


  5. -:

    1. cypress-image-snapshot.
    2. CI.
    3. En las pruebas, no tomamos capturas de pantalla al azar. Si la captura de pantalla está precedida por una animación, asegúrese de esperarla; por ejemplo, agregue Cypress Assertion al elemento animado.
    4. La fecha se humedece a través de cy.clock o usamos la opción de apagón al tomar una captura de pantalla.
    5. Esperamos cualquier carga estática en tiempo de ejecución a través del comando personalizado cy.waitForResource (imágenes, fuentes, etc.).


  6. Envuélvelo todo en Docker:

    1. Preparando el Dockerfile.
    2. Creamos un archivo bash.




 Realizamos pruebas sobre la aplicación ensamblada:



  1. En CI, aprendemos a lanzar artefactos de la aplicación ensamblada entre compilaciones (depende de usted).
  2. Preparación de la carpeta de integración-pruebas-scripts:

    1. Un script para elevar el servidor de su aplicación.
    2. El script para cambiar su index.html (si está satisfecho con el index.html original, puede omitirlo).
    3. Agregue a la carpeta package.json con los scripts y dependencias necesarios.
    4. Preparando un nuevo Dockerfile.
    5. Creamos un archivo bash.


Enlaces útiles



  1. Angular Workspace + Cypress + CI — Angular Workspace CI , ( typescript).

  2. Cypresstrade-offs.

  3. Start-server-and-test — , .

  4. Cypress-image-snapshot — -.

  5. Cypress recipes — Cypress, .

  6. Flaky Tests — , Google.

  7. Github Action — Cypress GitHub Action, README , — wait-on. docker.




All Articles