Playwright: El Fin de la Era de los Tests Inestables
En mis seis años desarrollando software, he visto morir más proyectos de automatización por falta de confianza que por falta de talento. Herramientas tradicionales como Selenium, e incluso versiones tempranas de Cypress, suelen sufrir de un mal endémico: las ejecuciones inestables (flaky tests).
El culpable casi siempre es el timing. Intentar interactuar con un botón que el DOM ya reporta como existente, pero que el motor de renderizado del navegador aún no ha terminado de pintar o habilitar. Aquí es donde Playwright, el framework de Microsoft, cambia las reglas del juego con una premisa clara: resiliencia por defecto.
¿Por qué Playwright es diferente?
A diferencia de sus predecesores, Playwright no es solo un "wrapper" de comandos. Su arquitectura se basa en:
- Auto-waiting: Olvídate de los
sleep()o esperas manuales. Playwright espera a que los elementos sean accionables antes de ejecutar cualquier acción. - Browser Contexts: Permite aislar pruebas de forma ultra rápida, simulando múltiples sesiones independientes en una sola instancia del navegador.
- Multi-motor nativo: Soporte real para Chromium (Chrome, Edge), WebKit (Safari) y Firefox, incluyendo emulación de dispositivos móviles sin configuraciones redundantes.
Manos al código: Setup Inicial
La barrera de entrada es mínima. Para inicializar un proyecto desde cero, solo necesitamos ejecutar:
Bash
npm init playwright@latest
Este comando no solo instala las dependencias necesarias, sino que genera una estructura profesional:
playwright.config.ts: El cerebro de la configuración.tests/: Carpeta dedicada para nuestros archivos de especificación.- Reportes y flujos de CI/CD pre-configurados.
Configuración Estratégica
En el archivo playwright.config.ts, podemos definir el comportamiento global. Un consejo de experiencia: aunque Playwright brilla ejecutando pruebas en paralelo para optimizar el tiempo, durante la fase de desarrollo es útil limitar los workers para depurar con precisión.
TypeScript
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true, // ¡Potencia máxima en CI!
timeout: 30000,
expect: { timeout: 5000 },
reporter: 'html',
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry', // Vital para depurar errores
video: 'on',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});Caso de Estudio: Automatizando una TODO List
Para demostrar su potencia, utilizaremos una aplicación React sencilla. Lo interesante aquí no es solo la interacción, sino cómo Playwright utiliza los data-testid para crear pruebas desacopladas del estilo CSS o la estructura HTML.
// src/App.tsx
import { useState } from 'react'
interface Todo {
id: number
text: string
completed: boolean
}
function App() {
const [todos, setTodos] = useState<Todo[]>([])
const [inputValue, setInputValue] = useState('')
const addTodo = (e: React.FormEvent) => {
e.preventDefault()
if (inputValue.trim() === '') return
const newTodo: Todo = {
id: Date.now(),
text: inputValue.trim(),
completed: false,
}
setTodos([...todos, newTodo])
setInputValue('')
}
return (
<div className="todo-app">
<h1>Todo List</h1>
<form onSubmit={addTodo} className="todo-form">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="What needs to be done?"
data-testid="todo-input"
/>
<button type="submit" data-testid="add-button">
Add
</button>
</form>
<ul data-testid="todo-list">
{todos.map((todo) => (
<li key={todo.id} data-testid="todo-item">
<span data-testid="todo-text">{todo.text}</span>
</li>
))}
</ul>
{todos.length === 0 && (
<p data-testid="empty-message">No todos yet. Add one above!</p>
)}
</div>
)
}
La Suite de Pruebas
Aquí aplicamos el patrón de pruebas resilientes. Observa cómo el código es declarativo:
// tests/todo.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Adding Todos', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
})
test('should add a new todo', async ({ page }) => {
// Escribir en el input
await page.getByTestId('todo-input').fill('Buy groceries')
// Click en el botón Add
await page.getByTestId('add-button').click()
// Verificar que se agregó el todo
await expect(page.getByTestId('todo-item')).toHaveCount(1)
await expect(page.getByTestId('todo-text')).toHaveText('Buy groceries')
})
test('should add todo by pressing Enter', async ({ page }) => {
await page.getByTestId('todo-input').fill('Learn Playwright')
await page.getByTestId('todo-input').press('Enter')
await expect(page.getByTestId('todo-item')).toHaveCount(1)
await expect(page.getByTestId('todo-text')).toHaveText('Learn Playwright')
})
test('should clear input after adding todo', async ({ page }) => {
await page.getByTestId('todo-input').fill('Test todo')
await page.getByTestId('add-button').click()
// El input debe estar vacío después de agregar
await expect(page.getByTestId('todo-input')).toHaveValue('')
})
test('should not add empty todo', async ({ page }) => {
// Click sin escribir nada
await page.getByTestId('add-button').click()
// No debe haber ningún todo
await expect(page.getByTestId('todo-item')).toHaveCount(0)
await expect(page.getByTestId('empty-message')).toBeVisible()
})
test('should not add whitespace-only todo', async ({ page }) => {
await page.getByTestId('todo-input').fill(' ')
await page.getByTestId('add-button').click()
await expect(page.getByTestId('todo-item')).toHaveCount(0)
})
test('should add multiple todos', async ({ page }) => {
const todos = ['First todo', 'Second todo', 'Third todo']
for (const todo of todos) {
await page.getByTestId('todo-input').fill(todo)
await page.getByTestId('add-button').click()
}
await expect(page.getByTestId('todo-item')).toHaveCount(3)
})
})Diagnóstico de Errores: Visualización de Fallos
Uno de los puntos más fuertes de Playwright es su capacidad de observabilidad. Cuando una prueba falla, el framework genera automáticamente:
- Capturas de pantalla (Screenshots) del momento exacto del error.
- Trazas (Traces) que permiten hacer un "viaje en el tiempo" por cada paso ejecutado.
- Videos de la interacción del usuario.
Nota Pro: El "UI Mode" de Playwright (npx playwright test --ui) es, en mi opinión, la mejor herramienta de desarrollo de tests creada hasta la fecha, permitiendo inspeccionar el DOM en cada paso del histórico de la prueba.
Evidencia en vídeo de playwright
por último todo éxito:

Conclusión
Playwright no es solo "otra herramienta de testing"; es una evolución necesaria para los equipos que buscan velocidad sin sacrificar estabilidad. En la próxima entrada, profundizaremos en el Page Object Model (POM) y cómo estructurar suites de pruebas escalables para proyectos de gran envergadura.
¿Qué problemas de estabilidad estás enfrentando hoy en tus pruebas? Hablemos en los comentarios.