Тестирование WebSocket в Playwright строится по двум паттернам: наблюдение за реальными соединениями через прослушивание событий framesent и framereceived, и полное мокирование сервера через page.routeWebSocket() (Playwright 1.48+): контролируешь какие сообщения получает приложение без живого бэкенда. Статья разбирает оба подхода, плюс тестирование обрыва соединения и логики переподключения, с полными TypeScript-примерами для каждого сценария.
Как выглядит тестирование WebSocket на практике
В отличие от HTTP-запросов, WebSocket работает как постоянное двунаправленное соединение. Одно соединение может передавать тысячи сообщений за время жизни. Тестирование WebSocket включает:
1. Проверку что соединение установлено
2. Проверку что приложение отправляет правильные сообщения
3. Проверку что приложение корректно обрабатывает входящие сообщения
4. Тестирование поведения при обрыве соединения
Прослушивание WebSocket-событий
Playwright генерирует события для WebSocket-соединений:
import { test, expect } from '@playwright/test';
test('chat app establishes WebSocket connection', async ({ page }) => {
// Слушаем WebSocket-соединения
const wsConnected = page.waitForEvent('websocket');
await page.goto('/chat');
const ws = await wsConnected;
expect(ws.url()).toContain('/ws/chat');
// WebSocket теперь открыт
console.log('Connected to:', ws.url());
});Захват WebSocket-сообщений
test('chat sends message via WebSocket', async ({ page }) => {
const messages: string[] = [];
page.on('websocket', ws => {
ws.on('framesent', frame => {
// Сообщения отправленные из браузера на сервер
if (frame.text) messages.push(frame.text);
});
});
await page.goto('/chat');
await page.getByPlaceholder('Type a message').fill('Hello, world!');
await page.keyboard.press('Enter');
// Проверяем что WebSocket-сообщение было отправлено
await expect.poll(() => messages).toContain(
JSON.stringify({ type: 'message', content: 'Hello, world!' })
);
});
test('chat receives message and displays it', async ({ page }) => {
const receivedFrames: string[] = [];
page.on('websocket', ws => {
ws.on('framereceived', frame => {
// Сообщения полученные браузером от сервера
if (frame.text) receivedFrames.push(frame.text);
});
});
await page.goto('/chat');
// Ждём хотя бы одно сообщение (например, подтверждение входа)
await expect.poll(() => receivedFrames.length).toBeGreaterThan(0);
// Проверяем что сообщение отображается в UI
const firstMessage = JSON.parse(receivedFrames[0]);
await expect(page.getByText(firstMessage.content)).toBeVisible();
});framesent: сообщения которые браузер отправил серверу.
framereceived: сообщения которые сервер отправил браузеру.
Мокирование WebSocket-ответов
Самый мощный паттерн: перехватить соединение и симулировать сообщения сервера без запуска реального сервера.
Playwright не имеет встроенного API для мокирования WebSocket, но можно внедрить мок в контекст браузера:
test('app shows notification when server sends alert', async ({ page }) => {
// Перехватываем WebSocket и симулируем поведение сервера
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;
// Симулируем открытие соединения
setTimeout(() => {
this.dispatchEvent(new Event('open'));
// Симулируем отправку уведомления сервером через 100мс
setTimeout(() => {
const messageEvent = new MessageEvent('message', {
data: JSON.stringify({ type: 'notification', text: 'New order received!' }),
});
this.dispatchEvent(messageEvent);
}, 100);
}, 0);
}
send(data: string) {
// Записываем отправленные сообщения если нужно
console.log('WebSocket send:', data);
}
close() {
this.readyState = 3;
this.dispatchEvent(new CloseEvent('close'));
}
} as any;
});
await page.goto('/dashboard');
// Проверяем что уведомление появляется когда приходит WebSocket-сообщение
await expect(page.getByRole('alert')).toBeVisible({ timeout: 2000 });
await expect(page.getByRole('alert')).toHaveText('New order received!');
});Тестирование отключения и переподключения
Приложения реального времени должны корректно обрабатывать потерю соединения:
test('app shows reconnecting state when WebSocket drops', async ({ page }) => {
let wsInstance: any;
await page.addInitScript(() => {
const OriginalWebSocket = window.WebSocket;
window.WebSocket = class extends OriginalWebSocket {
constructor(url: string) {
super(url);
// Экспортируем экземпляр для управления из теста
(window as any).__wsInstance = this;
}
} as any;
});
await page.goto('/chat');
// Ждём установки соединения
await page.waitForEvent('websocket');
// Принудительно закрываем WebSocket-соединение
await page.evaluate(() => {
(window as any).__wsInstance?.close();
});
// Проверяем что UI показывает состояние переподключения
await expect(page.getByText('Reconnecting...')).toBeVisible();
// Через некоторое время проверяем что попытка переподключения произошла
await expect(page.getByText('Connected')).toBeVisible({ timeout: 10000 });
});Использование routeWebSocket в новых версиях Playwright
Playwright 1.48+ представил page.routeWebSocket() для более удобного мокирования WebSocket:
// Playwright 1.48+
test('order status updates in real time', async ({ page }) => {
await page.routeWebSocket('/ws/orders', ws => {
ws.onopen = () => {
// Отправляем начальное состояние
ws.send(JSON.stringify({ orderId: '123', status: 'processing' }));
};
ws.onmessage = (message) => {
const data = JSON.parse(message.data);
if (data.type === 'subscribe' && data.orderId === '123') {
// Симулируем прогресс статуса
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('Processing');
await expect(page.getByTestId('status')).toHaveText('Shipped', { timeout: 2000 });
await expect(page.getByTestId('status')).toHaveText('Delivered', { timeout: 3000 });
});Перед использованием routeWebSocket проверь версию Playwright: в старых версиях этого метода нет.
Когда тестировать WebSocket
Не все фичи реального времени нуждаются в отдельных WebSocket-тестах. Приоритизируй:
- Установку соединения для критичных фич (чат, live-дашборд)
- Поведение при обрыве соединения (показывает ошибку? переподключается? теряет данные?)
- Порядок сообщений для последовательных операций
- Аутентификацию через WebSocket (отклоняется ли соединение без валидного auth?)
Пропускай WebSocket-тестирование когда фича работает надёжно и E2E UI-тест уже покрывает её, когда WebSocket от сторонней компании (сервер не под твоим контролем), или когда формат сообщений лучше покрывается юнит-тестом на бэкенде.
→ See also: Перехват сети, моки и стабы в Playwright | API-тестирование с Playwright APIRequestContext (без Postman) | Тестирование GraphQL API с Playwright: запросы, мутации и обработка ошибок