Lección 2 de 7 · 2 min
Fixtures: el corazón de una suite mantenible
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.
Ejercicio
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?