https://d226lax1qjow5r.cloudfront.net/blog/blogposts/how-to-build-a-visual-regression-test-system-using-playwright/visual-regression-test.png

Cómo crear un sistema visual de pruebas de regresión con Playwright

Publicado el August 23, 2022

Tiempo de lectura: 10 minutos

Supongamos que Joe está trabajando en el componente reutilizable "botón", realiza un cambio y se asegura de que el botón funciona como se esperaba. ¿Sabe Joe que los demás componentes quedan bien con este cambio? Joe tendría que saber qué componentes dependen del botón y revisarlos todos -asegurándose visualmente de que los botones se ven bien- en varios escenarios. ¿Y qué pasa con los distintos navegadores? La matriz de pruebas no para de crecer...

Ayudemos a Joe a aprovechar mejor su tiempo automatizando el proceso de pruebas visuales.

Como Joe, tienes una aplicación en marcha, o has creado tus componentes y quieres compartirlos con el mundo. En cada cambio, tú (o alguien de tu equipo) pasáis horas revisando la interfaz de usuario para aseguraros de que es perfecta en píxeles. Incluso así, a veces hay cosas que se escapan a la atención humana. Admitámoslo: las máquinas son mejores que nosotros a la hora de hacer coincidir píxeles. También cuestan menos y pueden hacerlo una y otra vez.

Aquí empieza nuestra historia. Tenemos un montón de componentes de interfaz de usuario. También tenemos documentación para nuestros componentes en la que podemos "jugar" con ellos (es decir, probarlos manualmente).

La documentación se crea a partir de archivos readme dentro de la carpeta de cada componente. He aquí un ejemplo.

Observe que los ejemplos son simples fragmentos de HTML:

<vwc-icon type="heart-solid" connotation="accent"></vwc-icon>
<vwc-icon type="heart-solid" connotation="announcement"></vwc-icon>
<vwc-icon type="heart-solid" connotation="cta"></vwc-icon>
<vwc-icon type="heart-solid" connotation="success"></vwc-icon>
<vwc-icon type="heart-solid" connotation="alert"></vwc-icon>
<vwc-icon type="heart-solid" connotation="info"></vwc-icon>

Volveremos a ello más tarde.

Así que... tenemos unos 50 componentes y una forma de verlos visualmente mientras los desarrollamos y probamos manualmente. ¡Estupendo! Quieres ser capaz de ver los componentes antes de enviarlos :).

Algunos de estos componentes están formados por múltiples componentes atómicos. Eso significa que cambiar el componente "botón" podría afectar a múltiples componentes complejos que utilizan el botón.

¿Cómo configurar un dramaturgo en un proyecto?

El primer paso es correr:

npm i -D playwright

Esto va a hacer un montón de cosas:

  1. Añade playwright a nuestro archivo package.json.

  2. Instale playwright localmente.

  3. Instale playwrightlos navegadores localmente.

También tendremos que crear un archivo playwright.config archivo. Para esta introducción (y porque es lo que hago personalmente), usaremos la versión typescript pero puedes usar javascript normal. Crearemos un archivo playwright.config.ts con el siguiente contenido:

import type {
  PlaywrightTestConfig
} from '@playwright/test';

const config: PlaywrightTestConfig = {
  testMatch: 'src/**/*.test.ts',
  projects: [{
      name: 'Chrome Stable',
      use: {
        browserName: 'chromium',
        channel: 'chrome',
      },
    },
    {
      name: 'Desktop Safari',
      use: {
        browserName: 'webkit',
      }
    },
    {
      name: 'Desktop Firefox',
      use: {
        browserName: 'firefox',
      }
    },
  ]
};

export default config;

La configuración anterior hace lo siguiente:

  1. testMatch: Indica a playwright dónde obtener los archivos de prueba. Tenga en cuenta que la ruta es relativa a la ubicación del archivo. En el repositorio vivid, el archivo de configuración se encuentra en la carpeta components, y todos los componentes están dentro de la carpeta src carpeta. Hacemos la convención de llamar a los archivos de prueba *.test.ts - por lo que cada archivo que siga este patrón se incluirá en la ejecución de la prueba.

  2. projects: Indica a playwright en qué configuraciones debe ejecutar las pruebas. En este caso, tenemos 3 configuraciones para cada uno de los principales navegadores que queremos probar.

Hay mucho más en la playwright configuración. Puede leer más aquí.

Ahora que playwright "sabe" dónde obtener los archivos y en qué navegadores ejecutar nuestras pruebas, podemos empezar a configurar las pruebas propiamente dichas.

Cómo escribir ensayos de dramaturgia

Para crear una prueba, necesitamos crear un archivo que será encontrado por la aplicación testMatches. Por eso, en nuestro repositorio, creamos un archivo ui.test.ts para cada componente. He aquí un ejemplo de archivo de prueba:

test('should show the component', async ({
  page
}: {
  page: Page
}) => {
  const template = `...`;

  await loadComponents({
    page,
    components,
  });
  await loadTemplate({
    page,
    template,
  });

  const testWrapper = await page.locator('#wrapper');
  await page.locator('#modal');

  await page.waitForLoadState('networkidle');

  await page.evaluate(() => {
    const modal = (document.getElementById('modal') as Dialog);
    modal.showModal();
    return modal;
  });

  expect(await testWrapper?.screenshot()).toMatchSnapshot(
    './snapshots/dialog.png'
  );
});

Para ver el archivo completo, haga clic aquí.

Vamos a desglosarlo.

Bloque de prueba para dramaturgos

Lo primero que observamos después de realizar todas las importaciones es que tenemos un bloque test bloque. La función enviada al bloque test acepta un objeto de prueba. Una de las propiedades del objeto es la propiedad page es la propiedad Este page es la representación page en playwright. La utilizamos para manipular y observar la página probada.

Preparación de la página de prueba

En este archivo, tenemos una prueba que genera una plantilla (la variable template ). A continuación, carga los componentes necesarios y la plantilla a la página de prueba utilizando funciones de utilidad que hemos construido (más sobre esto más adelante).

Esperar a que se cargue el HTML

Las siguientes líneas utilizan el método page.locator para obtener los elementos que buscamos. Otro "efecto secundario" de usar el localizador es que resuelve cuando nuestros elementos son visibles en la página. Esto asegura que cuando ejecutamos las pruebas, nuestros elementos ya están en el DOM.

A la espera de la obtención de recursos

Otra espera a continuación es esperar hasta que todo el tráfico de red esté inactivo. Esto es importante para nosotros porque a veces queremos esperar hasta que los iconos o las imágenes se carguen desde un CDN.

Activar elementos DOM API programática

Finalmente, está el evaluate método del page objeto. Este método evalúa el código JavaScript dentro de la página. En este caso, este es el código evaluado:

await page.evaluate(() => {
  const modal = (document.getElementById('modal') as Dialog);
  modal.showModal();
  return modal;
});

En este caso, obtenemos el elemento modal y evocamos su método showModal método. De esta forma, podemos probar la API programática de los elementos DOM.

Generar y comparar instantáneas

La línea final es la "magia" de la regresión visual en el dramaturgo:

expect(await testWrapper?.screenshot()).toMatchSnapshot(
  './snapshots/dialog.png'
);

El locator objeto resuelto testWrapper tiene un método screenshot método. Cuando invocamos este método y utilizamos la toMatchSnapshot expectativa, hace una de las siguientes cosas:

  1. Si existe una instantánea en la ruta dada a toMatchSnapshotse compara la instantánea tomada con la que existe en el sistema de archivos. Si no coinciden, la prueba falla.

  2. Si no hay ninguna instantánea en la ruta, genera una nueva.

¿Cómo cargar la página de prueba durante una prueba?

En la última sección, vimos un ejemplo de cómo se pueden escribir pruebas. Usamos un objeto page para manipular nuestra página de prueba, pero esto plantea algunas preguntas: ¿qué es esta misteriosa página? ¿De dónde viene el contenido HTML/CSS/JS?

En esencia, el objeto page tiene un método goto. Este método acepta una URL y la carga en el archivo page. Esto está bien si tienes una aplicación funcionando. En este caso, todo lo que necesitas hacer es servir tu aplicación. Esto puede hacerse usando el servidor de desarrollo de tu bundler, usando algún servidor de HTML estático o incluso probando tu entorno de QA, dev o producción.

Sus pruebas suelen comenzar con un goto y tendrían un aspecto similar a este:

test('should show text hello world', async ({page}) => {
	await page.goto(myAppUrl);
	const headerText = await manipulateApp(page);
	expect(headerText).toEqual('Hello World');
});

Nosotros goto la aplicación o la página de prueba, la manipulamos de alguna manera y luego esperamos que exista alguna característica o que sea igual a algo.

¿Cómo servimos componentes individuales para comparar instantáneas?

En vivid, como en otras bibliotecas de componentes de interfaz de usuario, no hay una aplicación real que probar. Tenemos nuestra documentación, pero probarla supondría probar también otras cosas que no están relacionadas con nuestros componentes. De hecho, la mayoría de los datos visuales de la documentación no están relacionados con los componentes.

Lo mejor sería crear una página de prueba dedicada a cada componente. Para ello, servimos todo nuestro proyecto utilizando http-server. Nuestro comando de regresión visual se parece a esto: npm run build & npx http-server -s & npx playwright test.

Eso significa que construimos nuestros componentes, servimos el repositorio y por lo tanto nuestra construcción y luego iniciamos las pruebas. También tenemos un archivo HTML vacío que se sirve, y lo usamos en el comando page.goto así:

await page.goto('[http://127.0.0.1:8080/scripts/visual-tests/index.html](http://127.0.0.1:8080/scripts/visual-tests/index.html)');

Este archivo es un archivo HTML vacío. Podríamos crear un archivo HTML manual para cada prueba, pero ¿qué gracia tiene eso? Veamos cómo empezamos a automatizar la generación de nuestra página de prueba desde nuestro código.

¿Cómo Inyectar Archivos JS y Estilos en nuestra página con Playwright?

Ahora tenemos una página HTML para goto. Sigue siendo inútil porque necesitamos poder inyectar el código de nuestros componentes.

Para ello, podemos recurrir a la función de utilidad que vimos en el ejemplo del archivo de prueba - loadComponents.

loadComponents es una función que acepta una matriz con los nombres de los componentes y utiliza el método addScriptTag para añadir los componentes:

export async function loadComponents({
    page,
    components,
    styleUrls = defaultStyles,
}: {
    page: Page,
    components: string[],
    styleUrls ? : string[]
}) {
    await page.goto('http://127.0.0.1:8080/scripts/visual-tests/index.html');

    (async function() {
        for (const component of components) {
            await page.addScriptTag({
                url: `http://127.0.0.1:8080/dist/libs/components/${component}/index.js`,
                type: 'module',
            });
        }
    })();

    const styleTags$ = styleUrls.map(url => page.addStyleTag({
        url
    }));
    await Promise.all(styleTags$);
}

La magia ocurre dentro del bucle for async en medio de la función: recorre cada componente del array y utiliza addScriptTag para cargarlo en nuestra página de demostración.

También lo hace con las etiquetas de estilo de una fila inferior. La función se resuelve cuando todas las etiquetas de estilo están en el DOM.

De esta forma, en cada prueba, tomamos la misma página en blanco y cargamos los componentes que queremos probar. Por ejemplo, si queremos probar elementos de formulario, establecemos un array con text-field, text-area, select, button y otros elementos de formulario. La función loadComponents los inyectará en la página de prueba por nosotros.

Nuestro tráfico de red se va a ver así en caso de que tuviéramos nuestro array configurado de la siguiente manera: [icon, button, focus, dialog, text-field,layout]:

The JavaScript files loaded in the test page as shown in the network tab of chrome dev toolsA list of JS files from the network tab

Ten en cuenta que también tenemos plantillas cargadas:

The CSS files loaded in the test page as shown in the network tab of chrome dev toolsA list of css files from the network tab

Whoopy - ¡JS y CSS están cargados!

¿Cómo mostrar los componentes en la página HTML?

Ahora tenemos una página HTML con los scripts y estilos necesarios inyectados en ella. Nuestra tarea pendiente es inyectar el HTML que realmente utilizará nuestros componentes.

Digamos que queremos probar un botón. Vamos a crear una cadena que se parece a esto:

<vwc-button label=”Click Me”></vwc-button>

Podemos añadir más sabores al botón como la connotación o la apariencia o incluso elegir un componente diferente (es decir, si quisiéramos probar el diálogo, crear un fragmento de un botón sería bastante inútil).

Ahora queremos que este HTML se muestre en nuestra página de prueba. Para ello, vamos a utilizar un truco con playwright's page.addScriptTag. En la función de utilidad loadTemplatevamos a aceptar la página y la cadena de plantilla. Entonces añadiremos un script a la página que inserte la plantilla HTML en la página:

export async function loadTemplate({
    page,
    template
}: {
    page: Page,
    template: string
}) {
    const wrappedTemplate = `<div id="wrapper">${template}</div>`;
    await page.addScriptTag({
        content: `
            document.body.innerHTML = \`${wrappedTemplate}\`;
        `,
    });
}

La función primero envuelve nuestra plantilla con una cadena div y luego añade un script a la página que reemplaza el innerHTML del cuerpo con nuestra cadena HTML.

Así que si tenemos una cadena HTML como esta:

<vwc-dialog id="modal"
		icon="info" 
		headline="Headline"
		text="This is the content that I want to show, and I will show it!!!"
>				
</vwc-dialog>

Obtendremos el siguiente resultado en el navegador:

A web browser with a modal dialog open. To the side the chrome dev tools are open showing the HTML we expectedThe test page and the devtools

Mostrando nuestro modal. A la derecha del modal, puedes ver el código HTML que inyectamos en la página.

Generación y comparación de capturas de pantalla en Playwright

Ahora que nuestra página de prueba está lista, podemos tomar nuestra instantánea. Ya pasamos por eso en la visión general, pero aquí está un recordatorio:

const testWrapper = await page.locator('#wrapper');
await page.locator('#modal');

await page.waitForLoadState('networkidle');

await page.evaluate(() => {
  const modal = (document.getElementById('modal') as Dialog);
  modal.showModal();
  return modal;
});

expect(await testWrapper?.screenshot()).toMatchSnapshot(
  './snapshots/dialog.png'
);

Paso 1: Esperar a que la página esté lista

Esperamos a que el wrapper y el modal estén en la página usando el método page.locator y al evento networkidle para asegurarnos de que todos nuestros activos están cargados.

Paso 2: Manipular la página

Una vez que la página está lista, evaluamos el script que utiliza la showModal en el elemento modal.

Paso 3: Haga una captura de pantalla y valídela

Ahora, si todo funciona correctamente, llegamos a nuestra fila expect que toma la instantánea y la compara con una instantánea anterior.

El método testWrapper.screenshot forma parte de la playwright.locator API. Toma una captura de pantalla del elemento y nos permite utilizar la toMatchSnapshot API de expectativas. En este caso, la comparamos con una ruta de archivo (la ruta match path), que es donde se guarda la captura de pantalla.

En caso de que sea la primera vez que ejecutamos la prueba, se genera una captura de pantalla en el archivo match path. Todos los cambios que hagamos se compararán con el resultado original, asegurándonos de que cambiar el aspecto de nuestro componente no pasará la prueba y no enviaremos cambios visuales no deseados.

Paso 4: Actualizar las capturas de pantalla

En este caso, sí queremos cambiar el aspecto, playwright tiene una update-snapshots que actualiza la instantánea. Para ello, tenemos una update tarea en nuestro repositorio, pero en playwright, funciona así:

npx playwright test --update-snapshots

Resumen

Ahora tenemos un sistema de regresión visual completamente funcional para los componentes de interfaz de usuario. Crear este tipo de pruebas para componentes de interfaz de usuario es un poco más complicado que probar una aplicación.

Para probar una aplicación, basta con cargarla a través de un servidor externo y, a continuación, utilizar goto a la dirección. Cuando se prueban componentes, se necesita ese mismo servidor, pero esta vez apuntará a una página vacía que necesitamos construir durante la propia prueba.

Las dos funciones de utilidad loadComponents y loadTemplate del dramaturgo addScriptTag y addStyleTagson la salsa secreta que nos permite probar cada componente de forma aislada con sólo crear nuestros fragmentos HTML.

Utilizamos un código muy similar para crear una prueba para cada componente de nuestra biblioteca o página/relato de usuario de nuestra aplicación. De esta forma, creamos una instantánea para cada componente/página/historia de usuario y nos aseguramos de que el usuario obtendrá el mismo resultado para el mismo procedimiento. Todo ocurre automáticamente en los navegadores que hemos establecido en nuestra configuración (chrome, firefox y Safari). Mejor calidad, menos tiempo.

Ya disponemos de un mecanismo local automatizado de pruebas de regresión visual. La razón para crearlo es ser capaz de ejecutar estas pruebas localmente, por lo que el desarrollador será capaz de obtener retroalimentación rápida y corregir los errores antes incluso de empujar los cambios de uno.

Pero podemos mejorar mucho más:

  • ¿Cómo automatizamos la generación de pruebas?

  • ¿Cómo nos protegemos de la flacidez (la inestabilidad es un problema conocido en las pruebas e2e)?

  • ¿Cómo integramos y optimizamos nuestras pruebas en las acciones de GitHub?

  • ¿Y el modo de desarrollo y depuración de las pruebas visuales?

Repasaré estos temas en el próximo artículo. Mientras tanto, puedes echar un vistazo a nuestro repositorio Vividnuestra configuración de playwright y documentación de pruebas de interfaz de usuario para verlo en acción.

Compartir:

https://a.storyblok.com/f/270183/400x400/7bf76cb05c/yonatankra.png
Yonatan KraArquitecto de software de Vonage

Yonatan ha estado involucrado en algunos proyectos impresionantes en la academia y la industria - desde C / C ++ a través de Matlab a PHP y javascript. Fue director de tecnología en Webiks y arquitecto de software en WalkMe. Actualmente es arquitecto de software en Vonage e instructor de egghead.