Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
browser-apis/websockets.md
1# WebSocket & Real-Time Testing23## Table of Contents451. [WebSocket Basics](#websocket-basics)62. [Mocking WebSocket Messages](#mocking-websocket-messages)73. [Testing Real-Time Features](#testing-real-time-features)84. [Server-Sent Events](#server-sent-events)95. [Reconnection Testing](#reconnection-testing)1011## WebSocket Basics1213### Wait for WebSocket Connection1415```typescript16test("chat connects via websocket", async ({ page }) => {17// Listen for WebSocket connection18const wsPromise = page.waitForEvent("websocket");1920await page.goto("/chat");2122const ws = await wsPromise;23expect(ws.url()).toContain("/ws/chat");2425// Wait for connection to be established26await ws.waitForEvent("framesent");27});28```2930### Monitor WebSocket Messages3132```typescript33test("receives real-time updates", async ({ page }) => {34const messages: string[] = [];3536// Set up listener before navigation37page.on("websocket", (ws) => {38ws.on("framereceived", (frame) => {39messages.push(frame.payload as string);40});41});4243await page.goto("/dashboard");4445// Wait for some messages46await expect.poll(() => messages.length).toBeGreaterThan(0);4748// Verify message format49const data = JSON.parse(messages[0]);50expect(data).toHaveProperty("type");51});52```5354### Capture Sent Messages5556```typescript57test("sends correct message format", async ({ page }) => {58const sentMessages: string[] = [];5960page.on("websocket", (ws) => {61ws.on("framesent", (frame) => {62sentMessages.push(frame.payload as string);63});64});6566await page.goto("/chat");67await page.getByLabel("Message").fill("Hello!");68await page.getByRole("button", { name: "Send" }).click();6970// Verify sent message71await expect.poll(() => sentMessages.length).toBeGreaterThan(0);7273const sent = JSON.parse(sentMessages[sentMessages.length - 1]);74expect(sent).toEqual({75type: "message",76content: "Hello!",77});78});79```8081## Mocking WebSocket Messages8283### Inject Messages via Page Evaluate8485```typescript86test("displays incoming chat message", async ({ page }) => {87await page.goto("/chat");8889// Wait for WebSocket to be ready90await page.waitForFunction(91() => (window as any).chatSocket?.readyState === 1,92);9394// Simulate incoming message95await page.evaluate(() => {96const event = new MessageEvent("message", {97data: JSON.stringify({98type: "message",99from: "Alice",100content: "Hello there!",101}),102});103(window as any).chatSocket.dispatchEvent(event);104});105106await expect(page.getByText("Alice: Hello there!")).toBeVisible();107});108```109110### Mock WebSocket with Route Handler111112```typescript113test("mock websocket entirely", async ({ page, context }) => {114// Intercept the WebSocket upgrade115await context.route("**/ws/**", async (route) => {116// For WebSocket routes, we can't fulfill directly117// Instead, use page.evaluate to mock the client-side118});119120// Alternative: Mock at application level121await page.addInitScript(() => {122const OriginalWebSocket = window.WebSocket;123(window as any).WebSocket = function (url: string) {124const ws = {125readyState: 1,126send: (data: string) => {127console.log("WS Send:", data);128},129close: () => {},130addEventListener: () => {},131removeEventListener: () => {},132};133setTimeout(() => ws.onopen?.(), 100);134return ws;135};136});137138await page.goto("/chat");139});140```141142### WebSocket Mock Fixture143144```typescript145// fixtures/websocket.fixture.ts146import { test as base, Page } from "@playwright/test";147148type WsMessage = { type: string; [key: string]: any };149150type WebSocketFixtures = {151mockWebSocket: {152injectMessage: (message: WsMessage) => Promise<void>;153getSentMessages: () => Promise<WsMessage[]>;154};155};156157export const test = base.extend<WebSocketFixtures>({158mockWebSocket: async ({ page }, use) => {159const sentMessages: WsMessage[] = [];160161// Capture sent messages162await page.addInitScript(() => {163(window as any).__wsSent = [];164const OriginalWebSocket = window.WebSocket;165window.WebSocket = function (url: string) {166const ws = new OriginalWebSocket(url);167const originalSend = ws.send.bind(ws);168ws.send = (data: string) => {169(window as any).__wsSent.push(JSON.parse(data));170originalSend(data);171};172(window as any).__ws = ws;173return ws;174} as any;175});176177await use({178injectMessage: async (message) => {179await page.evaluate((msg) => {180const event = new MessageEvent("message", {181data: JSON.stringify(msg),182});183(window as any).__ws?.dispatchEvent(event);184}, message);185},186getSentMessages: async () => {187return page.evaluate(() => (window as any).__wsSent || []);188},189});190},191});192193// Usage194test("chat with mocked websocket", async ({ page, mockWebSocket }) => {195await page.goto("/chat");196197// Inject incoming message198await mockWebSocket.injectMessage({199type: "message",200from: "Bob",201content: "Hi!",202});203204await expect(page.getByText("Bob: Hi!")).toBeVisible();205206// Send a reply207await page.getByLabel("Message").fill("Hello Bob!");208await page.getByRole("button", { name: "Send" }).click();209210// Verify sent message211const sent = await mockWebSocket.getSentMessages();212expect(sent).toContainEqual(213expect.objectContaining({ content: "Hello Bob!" }),214);215});216```217218## Testing Real-Time Features219220### Live Notifications221222```typescript223test("displays live notification", async ({ page }) => {224await page.goto("/dashboard");225226// Simulate notification via WebSocket227await page.evaluate(() => {228const event = new MessageEvent("message", {229data: JSON.stringify({230type: "notification",231title: "New Order",232message: "Order #123 received",233}),234});235(window as any).notificationSocket.dispatchEvent(event);236});237238await expect(page.getByRole("alert")).toContainText("Order #123 received");239});240```241242### Live Data Updates243244```typescript245test("updates stock price in real-time", async ({ page }) => {246await page.goto("/stocks/AAPL");247248const priceElement = page.getByTestId("stock-price");249const initialPrice = await priceElement.textContent();250251// Simulate price update252await page.evaluate(() => {253const event = new MessageEvent("message", {254data: JSON.stringify({255type: "price_update",256symbol: "AAPL",257price: 150.25,258}),259});260(window as any).stockSocket.dispatchEvent(event);261});262263await expect(priceElement).not.toHaveText(initialPrice!);264await expect(priceElement).toContainText("150.25");265});266```267268### Collaborative Editing269270```typescript271test("shows collaborator cursor", async ({ page }) => {272await page.goto("/document/123");273274// Simulate another user's cursor position275await page.evaluate(() => {276const event = new MessageEvent("message", {277data: JSON.stringify({278type: "cursor",279userId: "user-456",280userName: "Alice",281position: { x: 100, y: 200 },282}),283});284(window as any).docSocket.dispatchEvent(event);285});286287await expect(page.getByTestId("cursor-user-456")).toBeVisible();288await expect(page.getByText("Alice")).toBeVisible();289});290```291292## Server-Sent Events293294### Test SSE Updates295296```typescript297test("receives SSE updates", async ({ page }) => {298// Mock SSE endpoint299await page.route("**/api/events", (route) => {300route.fulfill({301status: 200,302headers: {303"Content-Type": "text/event-stream",304"Cache-Control": "no-cache",305Connection: "keep-alive",306},307body: `data: {"type":"update","value":42}\n\n`,308});309});310311await page.goto("/live-data");312313await expect(page.getByTestId("value")).toHaveText("42");314});315```316317### Simulate Multiple SSE Events318319```typescript320test("handles multiple SSE events", async ({ page }) => {321await page.route("**/api/events", async (route) => {322const encoder = new TextEncoder();323const events = [324`data: {"count":1}\n\n`,325`data: {"count":2}\n\n`,326`data: {"count":3}\n\n`,327];328329route.fulfill({330status: 200,331headers: { "Content-Type": "text/event-stream" },332body: events.join(""),333});334});335336await page.goto("/counter");337338// Should receive all events339await expect(page.getByTestId("count")).toHaveText("3");340});341```342343## Reconnection Testing344345### Test Connection Loss346347```typescript348test("handles connection loss gracefully", async ({ page }) => {349await page.goto("/chat");350351// Simulate connection close352await page.evaluate(() => {353(window as any).chatSocket.close();354});355356// Should show disconnected state357await expect(page.getByText("Reconnecting...")).toBeVisible();358});359```360361### Test Reconnection362363```typescript364test("reconnects after connection loss", async ({ page }) => {365await page.goto("/chat");366367// Simulate disconnect368await page.evaluate(() => {369(window as any).chatSocket.close();370});371372await expect(page.getByText("Reconnecting...")).toBeVisible();373374// Simulate reconnection375await page.evaluate(() => {376const event = new Event("open");377(window as any).chatSocket = { readyState: 1 };378(window as any).chatSocket.dispatchEvent?.(event);379});380381// Force component to re-check connection382await page.evaluate(() => {383window.dispatchEvent(new Event("online"));384});385386await expect(page.getByText("Connected")).toBeVisible();387});388```389390## Anti-Patterns to Avoid391392| Anti-Pattern | Problem | Solution |393| ------------------------------------- | ----------------------------- | ---------------------------------- |394| Not waiting for WebSocket ready | Messages sent too early | Wait for `readyState === 1` |395| Testing against real WebSocket server | Flaky, timing-dependent | Mock WebSocket messages |396| Ignoring connection state | Tests pass but feature broken | Test connected/disconnected states |397| No cleanup of listeners | Memory leaks in tests | Clean up event listeners |398399## Related References400401- **Network**: See [network-advanced.md](../advanced/network-advanced.md) for HTTP mocking patterns402- **Assertions**: See [assertions-waiting.md](../core/assertions-waiting.md) for polling patterns403- **Multi-User**: See [multi-user.md](../advanced/multi-user.md) for real-time collaboration testing with multiple users404