Playwright: 10 buenas prácticas que separan una suite pro de una frágil
Playwright se convirtió en el estándar de facto para pruebas e2e modernas. Pero una suite Playwright puede ser tan frágil como una de Selenium 2010 si se usa mal. Estas son las prácticas que aplicamos en proyectos reales — incluida la suite de 58 tests de este mismo sitio.
1. Prefiere locators accesibles
Los selectores CSS acoplan el test al DOM. Los locators por rol acoplan el test a lo que el usuario ve:
// ❌ Frágil: se rompe si cambia la clase
await page.click('.btn-submit-primary');
// ✅ Robusto: se rompe solo si cambia la experiencia
await page.getByRole('button', { name: 'Enviar' }).click();
Bonus: si getByRole no encuentra tu botón, probablemente tu HTML tampoco es accesible. El test te está haciendo una auditoría a11y gratis.
2. Nunca uses waitForTimeout
Cada waitForTimeout(3000) es una confesión de que no sabes qué estás esperando. Playwright ya espera automáticamente a que los elementos sean accionables. Si necesitas esperar algo más, espera esa condición:
// ❌ Lento cuando pasa, frágil cuando no
await page.waitForTimeout(3000);
// ✅ Espera exactamente lo que importa
await expect(page.getByText('Reserva confirmada')).toBeVisible();
await page.waitForResponse((r) => r.url().includes('/api/bookings') && r.ok());
3. Usa web-first assertions
expect(locator) reintenta hasta el timeout; expect(value) evalúa una sola vez:
// ❌ Race condition: lee el texto una vez
expect(await page.locator('.total').textContent()).toBe('$18.500');
// ✅ Reintenta hasta que el total se actualice
await expect(page.locator('.total')).toHaveText('$18.500');
4. Fixtures para el setup, no beforeEach gigantes
Un beforeEach que loguea, crea datos y navega es código que todos los tests pagan aunque no lo usen. Las fixtures son composables y solo corren cuando se piden:
export const test = base.extend<{ adminPage: Page }>({
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 use(page);
},
});
5. Paraleliza por archivo, aísla por test
Playwright corre archivos en paralelo por defecto. Aprovéchalo: cada test debe poder correr solo, en cualquier orden. Si un test depende de datos que dejó otro, tienes una bomba de tiempo, no una suite.
6. Trace viewer antes que screenshots
trace: 'on-first-retry' te da DOM completo, network, consola y video de cada fallo. Es la diferencia entre "falló en CI, no sé por qué" y verlo en 30 segundos:
// playwright.config.ts
use: { trace: 'on-first-retry' }
7. Proyectos para viewports, no tests duplicados
¿Móvil y desktop? No copies el test — decláralo una vez y corre en dos proyectos con devices['iPhone 14'] y devices['Desktop Chrome'].
8. Un test = un comportamiento
Los tests kilométricos que verifican todo el flujo en un solo test() fallan por cualquier cosa y no dicen qué se rompió. Divide por comportamiento de usuario, no por página.
9. Datos de prueba deterministas
new Date() y Math.random() en tests son flakiness con delay. Fija fechas con page.clock y datos con factories.
10. La suite verde es un requisito de merge, no una aspiración
Una suite que "casi siempre pasa" entrena al equipo a ignorarla. Cuarentena inmediata para tests flaky, y cero merges con rojo.
¿Quieres dominar esto en serio? Nuestro curso gratuito de Playwright cubre fixtures, trace viewer y paralelismo con ejercicios reales.