Aprender / Hoja de referencia
Hoja de referencia de Playwright
Las APIs de Playwright Test que usas a diario, en una sola hoja. Los locators aparecen en el orden que recomienda Playwright: rol, etiqueta y texto antes que los test ids, y CSS o XPath al final. Todas las assertions de esta hoja son web-first: reintentan hasta pasar o agotar el timeout, así que no necesitas esperas manuales.
Revisada con Playwright Test 1.5x, TypeScript
Ejecutar y depurar
| Tarea | Código |
|---|---|
| Proyecto nuevo | npm init playwright@latest |
| Ejecutar todos los tests | npx playwright test |
| Un archivo, una línea | npx playwright test tests/login.spec.ts:12 |
| Por título o tag | npx playwright test --grep @smoke |
| Un solo proyecto de navegador | npx playwright test --project=chromium |
| Ver el navegador | npx playwright test --headed |
| Ir paso a paso con el inspector | npx playwright test --debug |
| Modo UI (watch, viaje en el tiempo) | npx playwright test --ui |
| Solo los fallos de la última ejecución | npx playwright test --last-failed |
| Cazar un test flaky | npx playwright test my.spec.ts --repeat-each=20 |
| Repartir (shard) entre máquinas de CI | npx playwright test --shard=1/4 |
| Abrir el informe HTML | npx playwright show-report |
| Abrir un trace | npx playwright show-trace trace.zip |
| Grabar un script | npx playwright codegen https://example.com |
Locators (los preferidos primero)
| Tarea | Código |
|---|---|
| Por rol y nombre accesible | page.getByRole('button', { name: 'Sign in' }) |
| Campo de formulario por su etiqueta | page.getByLabel('Email') |
| Por placeholder | page.getByPlaceholder('Search') |
| Por texto (exacto) | page.getByText('Welcome back', { exact: true }) |
| Por test id (data-testid) | page.getByTestId('checkout-submit') |
| CSS | page.locator('#orders tbody tr') |
| XPath | page.locator('xpath=//table[@id="orders"]//tr') |
| Filtrar por texto | page.getByRole('row').filter({ hasText: 'Mei Lin' }) |
| Filtrar por elemento hijo | page.getByRole('listitem').filter({ has: page.getByRole('button', { name: 'Buy' }) }) |
| Encadenar dentro de un resultado | row.getByRole('button', { name: 'Cancel' }) |
| Elegir uno de varios | .first() .last() .nth(2) |
| Dentro de un iframe | page.frameLocator('#payment').getByLabel('Card number') |
Los locators son lazy: no se busca nada hasta que actúas o haces una assertion, así que defínelos una vez y reutilízalos.
Acciones
| Tarea | Código |
|---|---|
| Navegar (relativo a baseURL) | await page.goto('/login') |
| Clic | await locator.click() |
| Reemplazar el valor de un input | await locator.fill('mei@example.com') |
| Escribir tecla a tecla | await locator.pressSequentially('abc', { delay: 50 }) |
| Pulsar una tecla | await locator.press('Enter') |
| Checkbox o radio | await locator.check() / await locator.uncheck() |
| Seleccionar una opción | await page.getByLabel('Country').selectOption('UK') |
| Subir un archivo | await locator.setInputFiles('fixtures/invoice.pdf') |
| Pasar el ratón por encima | await locator.hover() |
| Arrastrar y soltar | await source.dragTo(target) |
| Aceptar el siguiente diálogo | page.once('dialog', (d) => d.accept()) |
Web-first assertions
| Tarea | Código |
|---|---|
| Visible / oculto | await expect(locator).toBeVisible() / .toBeHidden() |
| Texto exacto | await expect(locator).toHaveText('Order placed') |
| El texto contiene | await expect(locator).toContainText('placed') |
| Valor de un input | await expect(input).toHaveValue('42') |
| Número de coincidencias | await expect(page.getByRole('row')).toHaveCount(5) |
| Habilitado / deshabilitado / marcado | toBeEnabled() toBeDisabled() toBeChecked() |
| Atributo | await expect(menu).toHaveAttribute('aria-expanded', 'true') |
| URL y título | await expect(page).toHaveURL(/dashboard/) / .toHaveTitle('Orders') |
| Negar | await expect(spinner).not.toBeVisible() |
| Seguir tras un fallo | await expect.soft(total).toHaveText('£40.00') |
| Reintentar cualquier comprobación asíncrona | await expect.poll(async () => (await api.get(url)).status()).toBe(200) |
| Comparación visual | await expect(page).toHaveScreenshot('home.png') |
Red y API
| Tarea | Código |
|---|---|
| Simular (mock) una respuesta | await page.route('**/api/orders', (r) => r.fulfill({ json: [] })) |
| Hacer fallar una petición | await page.route('**/api/orders', (r) => r.abort()) |
| Modificar una respuesta real | await page.route('**/api/me', async (r) => { const res = await r.fetch(); const json = await res.json(); await r.fulfill({ response: res, json: { ...json, plan: 'pro' } }); }) |
| Esperar una respuesta | const res = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Save' }).click();
expect((await res).ok()).toBeTruthy(); |
| Test de API | test('list users', async ({ request }) => {
const res = await request.get('/api/users');
await expect(res).toBeOK();
expect(await res.json()).toHaveLength(3);
}); |
Estructura, fixtures y hooks
| Tarea | Código |
|---|---|
| Agrupar tests | test.describe('checkout', () => { /* tests */ }) |
| Ejecutar antes de cada test | test.beforeEach(async ({ page }) => { await page.goto('/'); }) |
| Etiquetar un test | test('pays by card', { tag: '@smoke' }, async ({ page }) => { /* ... */ }) |
| Saltar según una condición | test.skip(browserName === 'webkit', 'Not supported on WebKit') |
| En paralelo dentro de un archivo | test.describe.configure({ mode: 'parallel' }) |
| Fixture personalizada | export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
}); |
| Reutilizar una sesión iniciada | test.use({ storageState: 'playwright/.auth/user.json' }) |
| Nombrar un paso en el informe | await test.step('add to cart', async () => { /* ... */ }) |
playwright.config.ts
| Ajuste | Ejemplo |
|---|---|
| URL base para page.goto | use: { baseURL: 'http://localhost:3000' } |
| Evidencias en caso de fallo | use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' } |
| Reintentos solo en CI | retries: process.env.CI ? 2 : 0 |
| Paralelismo | fullyParallel: true, workers: process.env.CI ? 2 : undefined |
| Fallar si se hace commit de test.only | forbidOnly: !!process.env.CI |
| Arrancar antes la app | webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI } |
| Navegadores | projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }] |
| Timeouts por defecto | timeout: 30_000, expect: { timeout: 5_000 } |