El testing de WebSockets con Playwright se divide en dos patrones: observar conexiones reales escuchando los eventos framesent y framereceived, y mockear el servidor completamente usando page.routeWebSocket() (Playwright 1.48+) para controlar qué mensajes recibe la app sin un backend en ejecución. Este artículo cubre ambos enfoques, más el testing del comportamiento ante caída y reconexión, con ejemplos completos en TypeScript para cada escenario.
Cómo se ve el testing de WebSockets en la práctica
A diferencia de las peticiones HTTP, los WebSockets son conexiones bidireccionales persistentes. Una sola conexión puede llevar miles de mensajes a lo largo de su vida útil. Probar WebSockets significa:
- Verificar que la conexión se establece
- Verificar que la app envía los mensajes correctos
- Verificar que la app maneja correctamente los mensajes entrantes
- Probar qué pasa cuando la conexión se cae
Escuchar eventos de WebSocket
Playwright lanza eventos para las conexiones WebSocket:
import { test, expect } from '@playwright/test';
test('la app de chat establece conexión WebSocket', async ({ page }) => {
// Escuchar conexiones WebSocket
const wsConnected = page.waitForEvent('websocket');
await page.goto('/chat');
const ws = await wsConnected;
expect(ws.url()).toContain('/ws/chat');
// El WebSocket ahora está abierto
console.log('Conectado a:', ws.url());
});Capturar mensajes de WebSocket
test('el chat envía mensaje via WebSocket', async ({ page }) => {
const messages: string[] = [];
page.on('websocket', ws => {
ws.on('framesent', frame => {
// Mensajes enviados desde el navegador al servidor
if (frame.text) messages.push(frame.text);
});
});
await page.goto('/chat');
await page.getByPlaceholder('Escribí un mensaje').fill('¡Hola, mundo!');
await page.keyboard.press('Enter');
// Verificar que el mensaje WebSocket fue enviado
await expect.poll(() => messages).toContain(
JSON.stringify({ type: 'message', content: '¡Hola, mundo!' })
);
});
test('el chat recibe mensaje y lo muestra', async ({ page }) => {
const receivedFrames: string[] = [];
page.on('websocket', ws => {
ws.on('framereceived', frame => {
// Mensajes recibidos del servidor por el navegador
if (frame.text) receivedFrames.push(frame.text);
});
});
await page.goto('/chat');
// Esperar que llegue al menos un mensaje (por ejemplo, confirmación de unión)
await expect.poll(() => receivedFrames.length).toBeGreaterThan(0);
// Verificar que el mensaje se muestra en la UI
const firstMessage = JSON.parse(receivedFrames[0]);
await expect(page.getByText(firstMessage.content)).toBeVisible();
});framesent: mensajes que el navegador envió al servidor.framereceived: mensajes que el servidor envió al navegador.
Mockear respuestas de WebSocket
El patrón más poderoso: interceptar la conexión y simular mensajes del servidor sin ejecutar un servidor real.
Playwright no tiene una API de mock de WebSocket incorporada, pero puedes inyectar un mock en el contexto del navegador:
test('la app muestra notificación cuando el servidor envía una alerta', async ({ page }) => {
// Interceptar WebSocket y simular comportamiento del servidor
await page.addInitScript(() => {
const OriginalWebSocket = window.WebSocket;
window.WebSocket = class MockWebSocket extends EventTarget {
url: string;
readyState = 1; // OPEN
constructor(url: string) {
super();
this.url = url;
// Simular apertura de conexión
setTimeout(() => {
this.dispatchEvent(new Event('open'));
// Simular que el servidor envía una notificación después de 100ms
setTimeout(() => {
const messageEvent = new MessageEvent('message', {
data: JSON.stringify({ type: 'notification', text: '¡Nuevo pedido recibido!' }),
});
this.dispatchEvent(messageEvent);
}, 100);
}, 0);
}
send(data: string) {
// Registrar mensajes enviados si es necesario
console.log('WebSocket send:', data);
}
close() {
this.readyState = 3;
this.dispatchEvent(new CloseEvent('close'));
}
} as any;
});
await page.goto('/dashboard');
// Verificar que la notificación aparece cuando llega el mensaje WebSocket
await expect(page.getByRole('alert')).toBeVisible({ timeout: 2000 });
await expect(page.getByRole('alert')).toHaveText('¡Nuevo pedido recibido!');
});Probar desconexión y reconexión
Las apps en tiempo real necesitan manejar la pérdida de conexión de manera adecuada:
test('la app muestra estado de reconexión cuando cae el WebSocket', async ({ page }) => {
await page.addInitScript(() => {
const OriginalWebSocket = window.WebSocket;
window.WebSocket = class extends OriginalWebSocket {
constructor(url: string) {
super(url);
// Exponer instancia para control desde el test
(window as any).__wsInstance = this;
}
} as any;
});
await page.goto('/chat');
// Esperar que se establezca la conexión
await page.waitForEvent('websocket');
// Forzar el cierre de la conexión WebSocket
await page.evaluate(() => {
(window as any).__wsInstance?.close();
});
// Verificar que la UI muestra el estado de reconexión
await expect(page.getByText('Reconectando...')).toBeVisible();
// Después de un tiempo, verificar que intenta reconectarse
await expect(page.getByText('Conectado')).toBeVisible({ timeout: 10000 });
});Usar routeWebSocket en versiones más nuevas de Playwright
Playwright 1.48+ introdujo page.routeWebSocket() para un mockeo de WebSocket más ergonómico:
// Playwright 1.48+
test('el estado del pedido se actualiza en tiempo real', async ({ page }) => {
await page.routeWebSocket('/ws/orders', ws => {
ws.onopen = () => {
// Enviar estado inicial
ws.send(JSON.stringify({ orderId: '123', status: 'processing' }));
};
ws.onmessage = (message) => {
const data = JSON.parse(message.data);
if (data.type === 'subscribe' && data.orderId === '123') {
// Simular progresión de estado
setTimeout(() => ws.send(JSON.stringify({ orderId: '123', status: 'shipped' })), 500);
setTimeout(() => ws.send(JSON.stringify({ orderId: '123', status: 'delivered' })), 1000);
}
};
});
await page.goto('/orders/123');
await expect(page.getByTestId('status')).toHaveText('Procesando');
await expect(page.getByTestId('status')).toHaveText('Enviado', { timeout: 2000 });
await expect(page.getByTestId('status')).toHaveText('Entregado', { timeout: 3000 });
});Verifica tu versión de Playwright antes de usar routeWebSocket: no está disponible en versiones anteriores.
Cuándo probar WebSockets
No todas las funcionalidades en tiempo real necesitan tests dedicados de WebSocket. Prioriza:
- El establecimiento de conexión para funcionalidades críticas (chat, dashboard en vivo)
- El comportamiento cuando la conexión cae (¿muestra error? ¿reconecta? ¿pierde datos?)
- El orden de mensajes para operaciones secuenciales
- La autenticación sobre WebSocket (¿se rechaza la conexión sin autenticación válida?)
Omite el testing a nivel de WebSocket cuando la funcionalidad funciona de manera confiable y el test E2E de UI ya la cubre, cuando el WebSocket es de terceros (no controlas el servidor), o cuando probar el formato del mensaje está mejor cubierto por un test unitario del backend.
→ Ver también: Interceptación de Red, Mocks y Stubs en Playwright | Pruebas de API con el APIRequestContext de Playwright (Sin Postman) | Pruebas de API GraphQL con Playwright: Consultas, Mutaciones y Manejo de Errores