Playwright es un framework moderno de automatización de pruebas de extremo a extremo (E2E) desarrollado por Microsoft. Ofrece un soporte nativo y multiplataforma para los principales motores de renderizado del mercado (Chromium, WebKit y Firefox), permitiendo además la ejecución en segundo plano (modo headless) por defecto para optimizar los tiempos de integración continua.
Para evitar las interferencias del autocompletado nativo o sugerencias en línea mientras se escribe código en editores como Visual Studio Code, se puede deshabilitar temporalmente la herramienta ejecutando la combinación de teclas:
Control + Shift + P -> Escribir "Disable Inline Suggestions"
La interfaz de línea de comandos (CLI) de Playwright permite inicializar proyectos, ejecutar pruebas bajo diferentes modalidades y consultar reportes detallados tras cada ejecución.
npm init playwright@latest
npx playwright test
npx playwright test hooks.spec.ts
npx playwright test hooks.spec.ts --ui
npx playwright test visual.spec.ts --update-snapshots
npx playwright show-report
Para diseñar arquitecturas de pruebas robustas, es indispensable comprender la diferencia entre los componentes estructurales de Playwright:
Navegador (Browser)
├─ Contexto 1 (Usuario A - Sesión A)
│ └─ Página (Pestaña 1)
├─ Contexto 2 (Usuario B - Sesión B)
│ └─ Página (Pestaña 1)
└─ Contexto 3 (Usuario C - Sesión C)
└─ Página (Pestaña 1)
Debido a que las interacciones con el navegador ocurren de manera asíncrona a través de la red, la palabra clave
await debe anteceder a cada operación que interactúe con la página. Esto asegura que Playwright
espere a que la acción termine (como dar un clic o escribir texto) antes de continuar a la siguiente línea de
instrucción.
const { chromium } = require('playwright');
(async () => {
// Lanzamiento del navegador en modo visible (headless: false)
const browser = await chromium.launch({ headless: false });
// Creación de un contexto aislado
const googleContext = await browser.newContext();
const googlePage = await googleContext.newPage();
// Navegación y operaciones en la página
await googlePage.goto('https://www.google.com/');
await googlePage.waitForTimeout(2000);
console.log('Título de la página:', await googlePage.title());
// Cierre ordenado de los recursos
await googlePage.close();
await googleContext.close();
await browser.close();
})();
A pesar de que Playwright recomienda el uso de selectores basados en accesibilidad, también ofrece compatibilidad total con selectores CSS estándar para casos específicos.
| Tipo de Selector CSS | Criterio de Búsqueda | Ejemplo de Sintaxis |
|---|---|---|
| .clase | Busca elementos asociados a una clase específica. | page.locator('.new-todo') |
| #id | Busca un elemento único a través de su identificador. | page.locator('#APjFqb') |
| tag | Busca elementos por su etiqueta HTML. | page.locator('a') |
| [atributo] | Busca elementos que posean un atributo específico. | page.locator('[data-test]') |
| [atributo="valor"] | Filtra elementos cuyo atributo coincida exactamente con el valor. | page.locator('[data-test="Cappuccino"]') |
Nota sobre la resolución de ambigüedades: Si un selector CSS coincide con múltiples elementos idénticos en
el DOM, se pueden emplear los métodos .first() o .last() para interactuar
explícitamente con el primer o el último elemento de la lista devuelta.
Los métodos getBy* representan las mejores prácticas de automatización, ya que emulan la forma en
que un usuario real o una tecnología de asistencia interactúan con la interfaz gráfica, volviendo los tests
inmunes a cambios menores en la estructura HTML interna.
| Método Localizador | Elementos Típicos Destino | Ejemplo de Aplicación Profesional |
|---|---|---|
getByRole() |
Botones, enlaces, encabezados u otros roles accesibles. | await page.getByRole('button', { name: 'Login' }).click(); |
getByText() |
Cualquier elemento que contenga un texto visible en pantalla. | await page.getByText('Logged in as Simon').toBeVisible(); |
getByLabel() |
Campos de entrada asociados directamente a una etiqueta <label>. | await page.getByLabel('Email').fill('user@test.com'); |
getByPlaceholder() |
Inputs que poseen texto de sugerencia interno o placeholder. | await page.getByPlaceholder('Password').fill('1234'); |
getByAltText() |
Imágenes que contienen texto alternativo (atributo alt). | await page.getByAltText('Logo Principal').click(); |
getByTitle() |
Elementos con información emergente definida en el atributo title. | await page.getByTitle('Cerrar Ventana').click(); |
getByTestId() |
Elementos marcados con atributos específicos de testing (por defecto data-testid). | await page.getByTestId('submit-btn').click(); |
locator() |
Estrategia comodín para selectores CSS complejos o XPath. | await page.locator('#main-panel .item-active').click(); |
Playwright utiliza aserciones de tipo Web-First, lo que significa que el framework esperará automáticamente de forma interna a que se cumpla la condición solicitada (por ejemplo, que el elemento sea visible) antes de lanzar un error por timeout.
| Acción / Verificación Deseada | Implementación en Playwright (Sintaxis Correcta) |
|---|---|
| Hacer clic en un elemento | await page.locator('#btn-id').click(); |
| Escribir o rellenar texto | await page.locator('.input-class').fill('Texto ejemplo'); |
| Presionar una tecla del teclado | await page.press('#input-id', 'Enter'); |
| Seleccionar una opción en un menú desplegable | await page.locator('#days').selectOption('10'); |
| Validar visibilidad afirmativa | await expect(page.locator('.alerta')).toBeVisible(); |
| Validar invisibilidad o ausencia | await expect(page.locator('.modal')).not.toBeVisible(); |
| Verificar el contenido de texto exacto | await expect(page.locator('#title')).toHaveText('Bienvenido'); |
| Verificar el valor actual de un input | await expect(page.locator('#email')).toHaveValue('user@test.com'); |
| Validar la URL actual del navegador | await expect(page).toHaveURL('https://ejemplo.com/dashboard'); |
| Validar el título de la pestaña actual | await expect(page).toHaveTitle('Página de Inicio'); |
| Imprimir la URL actual en consola | console.log(await page.url()); |
Controlar los tiempos de carga y las respuestas asíncronas es fundamental para evitar la inestabilidad (flakiness) en los entornos distribuidos.
waitForURL o waitForSelector.Cuando una acción (como dar un clic) desencadena un cambio drástico o una redirección compleja, es una excelente
práctica envolver ambas operaciones en un bloque Promise.all para mitigar condiciones de carrera:
await Promise.all([
page.waitForURL('https://www.facebook.com/'),
page.click('button[name="login"]')
]);
Playwright puede interceptar eventos nativos lanzados por la aplicación, tales como la apertura de pop-ups o redirecciones de marcos internos (iframes):
// Capturar la apertura de una nueva pestaña (Pop-up)
page.on('popup', async popup => {
console.log('Título de la nueva pestaña:', await popup.title());
});
// Registrar eventos de navegación interna dentro de un iframe
page.on('framenavigated', frame => {
console.log('Frame redirigido a:', frame.url());
});
El Test Runner automatiza la organización, ejecución y reporte de tus pruebas. Los hooks permiten preparar y limpiar el entorno antes y después de cada test.
beforeAll: Se ejecuta una única vez antes de iniciar cualquier prueba del archivo (ideal para
levantar bases de datos o configuraciones globales).beforeEach: Se ejecuta antes de cada test individual (ideal para navegar a una URL común).afterEach: Se ejecuta al terminar cada test de manera individual.afterAll: Se ejecuta una única vez cuando todas las pruebas del archivo han concluido.test.only: Ejecuta exclusivamente esa prueba, ignorando las demás del archivo.test.skip: Omite la prueba por completo durante la ejecución.test.fixme: Declara que la prueba está rota o desactualizada y no debe ejecutarse hasta que sea
reparada.test.describe.serial: Agrupa pruebas que deben ejecutarse obligatoriamente en estricto orden
secuencial debido a que dependen del estado del test anterior.
import { test, expect } from '@playwright/test';
test.describe('Suite de Pruebas de Tareas de Usuario', () => {
test.beforeAll(async () => {
console.log('Iniciando el entorno general de pruebas...');
});
test.beforeEach(async ({ page }) => {
console.log('Navegando a la aplicación antes del test...');
await page.goto('https://demo.playwright.dev/todomvc/');
});
test('Validar título del sitio', async ({ page }) => {
await expect(page).toHaveTitle('React • TodoMVC');
});
test('Ingresar texto en la lista y confirmar con Enter', async ({ page }) => {
await page.fill('.new-todo', 'comprar pan');
await page.press('.new-todo', 'Enter');
});
test.afterAll(async () => {
console.log('Limpiando y cerrando el entorno de pruebas...');
});
});
Para asegurar la escalabilidad y mantenibilidad del software de pruebas, se recomienda aplicar el patrón de diseño Page Object Model (POM). Consiste en encapsular la lógica de interacción de cada página web dentro de una clase de JavaScript/TypeScript independiente, evitando la duplicación de Selectores y Código de infraestructura.
mi-proyecto-playwright/
├─ pages/
│ ├─ LoginPage.ts
│ └─ UserProfilePage.ts
└─ tests/
└─ login.spec.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
private page: Page;
private usernameInput: Locator;
private passwordInput: Locator;
private loginButton: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.locator('#user-name');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator('#login-button');
}
async navigate() {
await this.page.goto('https://www.saucedemo.com/');
}
async login(username: string, password: string) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
async getErrorMessage() {
return await this.page.textContent('[data-test="error"]');
}
}
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
test('Debería denegar el acceso ante credenciales inválidas', async ({ page }) => {
const login = new LoginPage(page);
await login.navigate();
await login.login('usuario_invalido', 'clave_incorrecta');
const error = await login.getErrorMessage();
expect(error).toContain('Epic sadface');
});
Playwright cuenta con la capacidad nativa de interceptar peticiones de red salientes (HTTP/API) y simular sus respuestas (mockear). Esto permite aislar por completo las pruebas del Frontend de la disponibilidad o inconsistencia de los servidores de Backend o APIs de terceros.
import { test, expect } from '@playwright/test';
test('Debería simular la respuesta de una API externa mediante un GET mockeado', async ({ page }) => {
const mockedTodo = {
userId: 1,
id: 1,
title: 'Tarea simulada por Playwright para estudiantes',
completed: false
};
// Interceptación de la ruta específica de la API antes de que el navegador la llame
await page.route('https://jsonplaceholder.typicode.com/todos/1', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockedTodo)
});
});
// Ejecución de la acción que detona la petición de red
const response = await page.goto('https://jsonplaceholder.typicode.com/todos/1');
expect(response).not.toBeNull();
expect(response?.status()).toBe(200);
const data = await response!.json();
expect(data).toEqual(mockedTodo);
});
El testing visual permite detectar regresiones estéticas o de maquetación (como desalineación de elementos CSS o cambios inesperados de color) comparando la pantalla actual de la aplicación contra una captura patrón o de referencia (snapshot).
import { test, expect } from '@playwright/test';
test('Validación visual completa de la landing page', async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
// Realiza una captura y comprueba de forma automatizada que coincida con el patrón guardado
await expect(page).toHaveScreenshot('pagina-inicial.png');
});