Guía Completa de Playwright: Apuntes Profesionales de Automatización

Índice General

  1. Introducción y Configuración Inicial
  2. Comandos Esenciales de la CLI
  3. Conceptos Clave de la Arquitectura
  4. Interacción con Elementos y Selectores Básicos
  5. Localizadores Avanzados (Locators)
  6. Acciones y Aserciones Frecuentes
  7. Manejo de Eventos y Sincronización
  8. Hooks y Anotaciones del Test Runner
  9. Patrones de Diseño y Buenas Prácticas (POM)
  10. Interceptación y Simulación de APIs (Mocking)
  11. Testing Visual y Capturas de Pantalla

1. Introducción y Configuración Inicial

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.

Consejo de Productividad en el IDE

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"

2. Comandos Esenciales de la CLI

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.

3. Conceptos Clave de la Arquitectura

Para diseñar arquitecturas de pruebas robustas, es indispensable comprender la diferencia entre los componentes estructurales de Playwright:

Jerarquía de Componentes

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)

Uso Crítico de Asincronismo (async / await)

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.

Ejemplo Práctico de Gestión de Contextos

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();
})();

4. Interacción con Elementos y Selectores Básicos

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.

5. Localizadores Avanzados (Locators)

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();

6. Acciones y Aserciones Frecuentes

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());

7. Manejo de Eventos y Sincronización

Controlar los tiempos de carga y las respuestas asíncronas es fundamental para evitar la inestabilidad (flakiness) en los entornos distribuidos.

Estrategias de Esperas

Ejemplo de Sincronización Avanzada con Promesas

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"]')
]);

Escucha de Eventos del Navegador

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());
});

8. Hooks y Anotaciones del Test Runner

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.

Ciclo de Vida de los Hooks

Anotaciones Especiales para el Control de Flujo

Código de Ejemplo Estructurado

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...');
  });
});

9. Patrones de Diseño y Buenas Prácticas (POM)

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.

Estructura Profesional de un Proyecto

mi-proyecto-playwright/
 ├─ pages/
 │   ├─ LoginPage.ts
 │   └─ UserProfilePage.ts
 └─ tests/
     └─ login.spec.ts

1. Definición de la Clase (pages/LoginPage.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"]');
  }
}

2. Consumo en el Archivo de Pruebas (tests/login.spec.ts)

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');
});

10. Interceptación y Simulación de APIs (Mocking)

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);
});

11. Testing Visual y Capturas de Pantalla

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');
});