Lección 2 de 7 · 2 min
Fixtures: el corazón de una suite mantenible
Al terminar sabrás
12 min- Reemplazar un beforeEach gigante por fixtures declaradas y bajo demanda
- Componer fixtures con teardown garantizado incluso si el test explota
- Autenticar una vez con storageState y reutilizarlo en toda la suite
Construyes: Dos fixtures composables con teardown que reemplazan un beforeEach acoplado.
El problema con beforeEach
// ❌ Todos los tests pagan todo el setup, lo usen o no
test.beforeEach(async ({ page }) => {
await loginComoAdmin(page);
await crearDatosDeAgenda(page);
await page.goto('/dashboard');
});
Con 200 tests, ese beforeEach es minutos de suite desperdiciados y un acoplamiento invisible: nadie sabe qué tests realmente necesitan qué.
Fixtures: setup declarado y bajo demanda
// fixtures.ts
import { test as base, type Page } from '@playwright/test';
type Fixtures = {
adminPage: Page;
agendaConDatos: { servicioId: string };
};
export const test = base.extend<Fixtures>({
adminPage: async ({ page }, use) => {
await page.goto('/admin/login');
await page.getByLabel('Contraseña').fill(process.env.ADMIN_PASS!);
await page.getByRole('button', { name: 'Entrar' }).click();
await expect(page.getByRole('heading', { name: 'Panel' })).toBeVisible();
await use(page);
},
agendaConDatos: async ({ request }, use) => {
const res = await request.post('/api/test/seed', { data: { escenario: 'agenda' } });
const { servicioId } = await res.json();
await use({ servicioId });
await request.delete(`/api/test/seed/${servicioId}`); // teardown garantizado
},
});
// El test declara lo que necesita — y solo eso corre
test('admin ve las reservas del día', async ({ adminPage, agendaConDatos }) => {
await adminPage.goto('/admin/reservas');
await expect(adminPage.getByRole('row')).toHaveCount(3);
});
Ventajas concretas: composición (fixtures que usan fixtures), teardown garantizado incluso si el test explota, y tipos — el autocompletado te dice qué contexto hay disponible.
Autenticación pro: storageState
Loguearse en cada test es lento incluso por API. El patrón oficial: un setup project se loguea una vez y guarda el estado; todos los tests lo reutilizan:
// playwright.config.ts
projects: [
{ name: 'setup', testMatch: /auth\.setup\.ts/ },
{
name: 'chromium',
use: { storageState: 'playwright/.auth/admin.json' },
dependencies: ['setup'],
},
]
// auth.setup.ts
setup('autenticar', async ({ page }) => {
await page.goto('/admin/login');
await page.getByLabel('Contraseña').fill(process.env.ADMIN_PASS!);
await page.getByRole('button', { name: 'Entrar' }).click();
await page.context().storageState({ path: 'playwright/.auth/admin.json' });
});
Cada worker arranca ya autenticado: cero login repetido, cero estado compartido entre tests.
Comprueba lo aprendido en el quiz y refactoriza tu setup en el reto.
Compruébalo
0/3Responde sin mirar atrás. Verás la explicación al instante.
1. ¿Cuál es el problema central de un beforeEach gigante?
2. ¿Para qué sirve storageState?
3. ¿Qué ventaja da una fixture sobre un beforeEach para preparar datos?
Reto: de beforeEach a fixtures
Convierte un beforeEach real (tuyo o inventado) en dos fixtures composables con teardown.
Luego mide: ¿cuántos de tus tests realmente usaban todo lo que el beforeEach preparaba?