Q37 of 42 · Playwright
How would you test a feature that depends on real-time WebSocket data?
Short answer
Short answer: Two paths: (1) run a local mock WebSocket server (`ws` library or `mock-socket`) and point the app at it via env var — full handshake + message control. (2) Stub `window.WebSocket` via `page.addInitScript` for client-side simulation. Playwright also exposes `page.on('websocket')` to spy on real connections.
Detail
Playwright's WebSocket support is more complete than Cypress's. Three layers:
1. Spy on real WebSocket connections:
page.on('websocket', (ws) => {
console.log(`WS opened: ${ws.url()}`);
ws.on('framesent', (frame) => console.log('→', frame.payload));
ws.on('framereceived', (frame) => console.log('←', frame.payload));
ws.on('close', () => console.log('WS closed'));
});
Useful for asserting the right messages are sent without modifying behaviour.
2. Mock WebSocket on the client side via page.addInitScript:
await page.addInitScript(() => {
class MockWS extends EventTarget {
readyState = 1;
send() {}
close() {}
simulate(data: object) {
this.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(data) }));
}
}
const mock = new MockWS();
(window as any).WebSocket = function() { return mock; } as any;
(window as any).__mockWs = mock;
});
await page.goto('/dashboard');
await page.evaluate(() => (window as any).__mockWs.simulate({ type: 'orderUpdate', id: 'o1' }));
await expect(page.getByTestId('order-status')).toContainText('Updated');
The app thinks it has a real socket; you control every message from the test.
3. Run a local mock WebSocket server:
import { WebSocketServer } from 'ws';
let wss: WebSocketServer;
test.beforeAll(() => {
wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (msg) => ws.send(JSON.stringify({ echo: msg.toString() })));
});
});
test.afterAll(() => wss.close());
test('app reconnects after server restart', async ({ page }) => {
await page.goto('/?ws=ws://localhost:8080');
await expect(page.getByTestId('connection')).toHaveText('Connected');
// Restart the server to test reconnect
wss.close();
await new Promise((r) => setTimeout(r, 500));
wss = new WebSocketServer({ port: 8080 });
await expect(page.getByTestId('connection')).toHaveText('Connected', { timeout: 10_000 });
});
The local-server approach exercises the real handshake and reconnection logic.
Things to test in any approach:
- Initial state (empty / loading).
- Receiving a message updates the UI.
- Multiple messages in sequence preserve order.
- Reconnection after disconnection.
- Backpressure / message dropping under load.
- Stale-state handling when the user disconnects briefly.
Senior signal: knowing all three approaches, distinguishing client-stub (lightest) from local-server (full handshake), and using page.on('websocket') for spy-only assertions.