Ir al contenido

Tutorial: Una prueba de punta a punta con Playwright

Al terminar este tutorial vas a ser capaz de:

  • Instalar Playwright en tu proyecto Angular sin mezclarlo con las pruebas de Vitest.
  • Escribir un recorrido de usuario con localizadores por rol y por etiqueta, y aserciones que esperan solas.
  • Preparar datos propios en cada corrida para que la prueba no dependa de lo que dejó la anterior.
  • Comprobar que la prueba falla cuando el back no permite el origen del front, y que Vitest no se entera.
  • Reconocer los errores más frecuentes de Playwright y qué dicen.

Prerrequisitos: los tutoriales de rutas y menú, detalle por ruta y asociación, porque el recorrido usa esas pantallas. Qué es una prueba de punta a punta y en qué se diferencia de las de Vitest está en la teoría de Pruebas de punta a punta con Playwright: este tutorial la aplica y no la repite.


Tienes el front funcionando, con el menú, el detalle por ruta y el componente de asociación, y tu back corriendo. La prueba va a hacer, en un navegador real, lo que haría una persona:

  1. Abrir la aplicación y entrar a “Nuevo artista”.
  2. Llenar el formulario y guardar. La aplicación vuelve a la lista.
  3. Abrir el artista nuevo en su propia página.
  4. Entrar a “Agregar un álbum”, elegir uno y asociarlo.
  5. Ver el álbum en el detalle del artista.

Es un recorrido que cruza todas las capas: el menú, el formulario, el POST de creación, el detalle por ruta, el selector con los álbumes del back y el POST de asociación. Por eso vale una prueba de punta a punta: un fallo en cualquier capa lo rompe.


En la carpeta de tu proyecto Angular:

Ventana de terminal
pnpm add -D @playwright/test
npx playwright install chromium

El primer comando agrega Playwright como dependencia de desarrollo. El segundo descarga el navegador que va a controlar (Chromium); solo hace falta una vez por computador.

Agrega a tu .gitignore las carpetas que Playwright genera, para no subirlas al repositorio:

.gitignore
/test-results
/playwright-report

Crea playwright.config.ts en la raíz del proyecto:

playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
// Un solo navegador y una prueba a la vez: las pruebas escriben en el back y comparten sus datos.
workers: 1,
reporter: 'list',
use: {
// El front debe estar corriendo (ng serve) y el back también.
baseURL: 'http://localhost:4200',
trace: 'retain-on-failure',
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
  • testDir: './e2e' pone las pruebas en una carpeta propia, fuera de src. Así ng test (Vitest) no las encuentra y no intenta correrlas: son dos herramientas con dos comandos distintos. Se comprobó: con la carpeta e2e y este archivo presentes, las pruebas de Vitest siguen pasando igual.
  • baseURL es la dirección del front. Con ella, page.goto('/') abre http://localhost:4200/.
  • workers: 1 corre una prueba a la vez. Las pruebas de punta a punta escriben en el back, y dos corriendo juntas pueden pisarse.
  • trace: 'retain-on-failure' guarda una traza de cada prueba que falla: una grabación paso a paso con lo que la página mostraba, que se abre más adelante.

Crea e2e/asociar-album.spec.ts:

asociar-album.spec.ts
import { expect, test } from '@playwright/test';
test('crear un artista, agregarle un álbum y verlo en su detalle', async ({ page }) => {
// Un nombre distinto en cada corrida: el back guarda lo que se crea, y la prueba no
// debe depender de lo que dejó la anterior.
const nombre = `Artista de prueba ${Date.now()}`;
await page.goto('/');
await page.getByRole('link', { name: 'Nuevo artista' }).click();
await page.getByLabel('Nombre').fill(nombre);
await page.getByLabel('URL de la foto').fill('https://example.com/foto.jpg');
await page.getByLabel('País de origen').fill('Colombia');
await page.getByLabel('Fecha de nacimiento').fill('1990-05-14');
await page.getByLabel('Biografía').fill('Artista creado por una prueba de punta a punta.');
await page.getByRole('button', { name: 'Crear artista' }).click();
// Al guardar, la aplicación vuelve a la lista, donde aparece el artista nuevo.
await expect(page).toHaveURL('/artistas');
await page.getByRole('button', { name: nombre }).click();
await page.getByRole('link', { name: 'Abrir en su propia página' }).click();
await expect(page).toHaveURL(/\/artistas\/\d+$/);
await page.getByRole('link', { name: 'Agregar un álbum' }).click();
await page.getByLabel('Álbum').selectOption({ index: 1 });
await page.getByRole('button', { name: 'Asociar álbum' }).click();
// Al asociar, vuelve al detalle del artista, que ahora muestra un álbum.
await expect(page).toHaveURL(/\/artistas\/\d+$/);
await expect(page.getByRole('heading', { name: `Álbumes de ${nombre}` })).toBeVisible();
await expect(page.locator('app-album-list a.card')).toHaveCount(1);
});

Cómo leerla:

  • page es la pestaña del navegador que Playwright abre para esta prueba. Cada prueba recibe una nueva y no comparte con las demás el almacenamiento del navegador.
  • Los localizadores buscan lo que ve el usuario. getByRole('link', { name: 'Nuevo artista' }) busca un enlace por su nombre, getByLabel('Nombre') un campo por su etiqueta. Por eso la prueba no depende de las clases de Bootstrap ni de la estructura del HTML.
  • No hay esperas. Cada acción (click, fill) y cada expect esperan hasta que el elemento exista y esté listo, o hasta que se acabe el tiempo. No hace falta escribir pausas fijas.
  • Date.now() hace el nombre único. Sin eso, la segunda corrida buscaría un artista que ya existe, y el botón de su nombre aparecería dos veces.
  • selectOption({ index: 1 }) elige la primera opción real: la opción 0 es “Elige un álbum”. Lo importante es que la prueba no depende de un álbum concreto.
  • toHaveURL comprueba la URL con una expresión regular (/\/artistas\/\d+$/) porque el id del artista nuevo cambia en cada corrida.
  • La última línea es la prueba de verdad. Las anteriores solo llevan hasta ahí. Comprobar que el detalle muestra un álbum es lo que demuestra que la asociación se creó en el back y que el front la volvió a pedir.

Con el back y el front andando (ng serve), en otra terminal:

Ventana de terminal
npx playwright test

Debes ver algo así:

ok 1 [chromium] › e2e\asociar-album.spec.ts:3:5 › crear un artista, agregarle un álbum y verlo en su detalle (3.9s)
1 passed (15.0s)

Córrela una segunda vez: también debe pasar. Se comprobó que dos corridas seguidas pasan, porque cada una crea un artista distinto.


Vas a comprobar el argumento principal de la teoría. En tu back, cambia temporalmente el origen permitido en CorsConfig:

.allowedOrigins("http://localhost:4201") // antes: "http://localhost:4200"

Reinicia el back y corre las dos herramientas:

  1. ng test --watch=false: todas las pruebas de Vitest siguen en verde. Ninguna hace una petición real, así que ninguna ve que el navegador bloquearía la respuesta.
  2. npx playwright test: la prueba falla.
1) [chromium] › e2e\asociar-album.spec.ts:3:5 › crear un artista, agregarle un álbum y verlo en su detalle
Error: expect(page).toHaveURL(expected) failed
Expected: "http://localhost:4200/artistas"
Received: "http://localhost:4200/artistas/nuevo"
Timeout: 5000ms

El mensaje dice qué pasó: la aplicación debía volver a la lista al guardar, y se quedó en el formulario, porque el navegador bloqueó la respuesta del POST y el front mostró su error. Playwright esperó los cinco segundos por defecto antes de rendirse.

Postman tampoco habría encontrado el error: no es un navegador y no manda la cabecera Origin. Se comprobó con la configuración rota: una petición sin Origin, como la de Postman, sigue respondiendo 200, y la misma petición con Origin: http://localhost:4200 responde 403.

Restaura el origen a http://localhost:4200 y reinicia el back antes de seguir.

Al fallar, Playwright guardó una traza en test-results, dentro de la carpeta de la prueba que falló (trace.zip). Se abre con:

Ventana de terminal
npx playwright show-trace test-results/<carpeta-de-la-prueba>/trace.zip

La traza muestra, paso a paso, qué hizo la prueba, qué mostraba la página en cada momento y qué peticiones salieron. Es la forma de ver por qué falló sin tener que repetir el recorrido a mano.


Cada mensaje de esta lista se provocó a propósito y se leyó tal como aparece:

  • page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:4200/. El front no está corriendo. Arranca ng serve y el back antes de correr la prueba.
  • strict mode violation: getByRole('link', { name: 'artista' }) resolved to 2 elements. El localizador coincide con más de un elemento: aquí, con “Artistas” y con “Nuevo artista”, porque el nombre se compara como un fragmento sin distinguir mayúsculas. Haz el nombre más específico ({ name: 'Nuevo artista' }) o agrega exact: true.
  • element is not enabled, o la acción agota el tiempo esperando. El botón está deshabilitado: el formulario es inválido, o falta elegir una opción. Playwright hace clic como una persona y no puede pulsar un botón deshabilitado.
  • expect(page).toHaveURL(...) failed ... Timeout: 5000ms. La URL esperada no llegó. Mira el Received del mensaje y abre la traza: casi siempre la aplicación se quedó en otra pantalla porque una petición falló.
  • La prueba pasa una vez y falla la siguiente. Depende de datos que dejó la corrida anterior. Crea datos propios y únicos en cada corrida, como el nombre con Date.now().
  • pnpm test no corre las pruebas de Playwright. Es lo esperado: pnpm test corre Vitest. Las de punta a punta se corren con npx playwright test.

Se puede pedir a la IA el esqueleto de un recorrido: los pasos y los localizadores, a partir de la lista de pasos que escribas.

Lo que debes revisar tú:

  • Que los localizadores sean por rol, etiqueta o texto, y no selectores CSS frágiles.
  • Que no haya pausas fijas (waitForTimeout): las aserciones ya esperan.
  • Que la prueba cree sus propios datos y no dependa de los que ya existen.
  • Que el último paso compruebe el resultado de verdad (lo que ve el usuario al final), y no solo que se llegó a una pantalla.
  • Que el recorrido no repita casos que ya cubren las pruebas de Vitest.

Al final de este tutorial tienes:

  • Playwright instalado, con su configuración y sus pruebas separadas de las de Vitest.
  • Un recorrido de punta a punta que crea un artista, le asocia un álbum y comprueba el resultado.
  • La comprobación de que esta prueba detecta un error de CORS que las pruebas de Vitest no ven.

Con esto termina la serie de tutoriales del front del Release 8.