Answer
Scenario: Testing Real-Time WebSocket Notifications
Strategy 1 — Wait for WebSocket Frame and Verify UI Update
TypeScript
import { test, expect } from '@playwright/test';
test('receive real-time notification via WebSocket', async ({ page }) => {
await page.goto('/dashboard');
// Wait for WebSocket connection to be established
const ws = await page.waitForEvent('websocket');
console.log('WebSocket URL:', ws.url());
// Wait for the server to send a notification frame
const frameSentPromise = ws.waitForEvent('framesent');
// Or wait for incoming frames
const frameReceivedPromise = ws.waitForEvent('framereceived');
// Trigger the action that causes a notification (via another tab, API, etc.)
const frame = await frameReceivedPromise;
const data = JSON.parse(frame.payload as string);
console.log('Received:', data);
expect(data.type).toBe('notification');
expect(data.message).toBeTruthy();
// Verify the notification appears in the UI
await expect(page.locator('.notification-badge')).toBeVisible();
await expect(page.locator('.notification-item').first()).toContainText(data.message);
});
Strategy 2 — Trigger Notification via Second Browser Tab
TypeScript
test('admin action triggers user notification', async ({ browser }) => {
// Create two contexts: user and admin
const userContext = await browser.newContext({ storageState: '.auth/user.json' });
const adminContext = await browser.newContext({ storageState: '.auth/admin.json' });
const userPage = await userContext.newPage();
const adminPage = await adminContext.newPage();
// User opens their notifications page
await userPage.goto('/dashboard');
await expect(userPage.locator('.notification-count')).toHaveText('0');
// Wait for WebSocket connection on user's page
const ws = await userPage.waitForEvent('websocket');
const notificationPromise = userPage.locator('.notification-popup').waitFor({ state: 'visible' });
// Admin triggers an action that sends a notification
await adminPage.goto('/admin/users');
await adminPage.locator(`[data-user-email="user@test.com"]`).getByRole('button', { name: 'Send Alert' }).click();
await adminPage.getByRole('button', { name: 'Confirm' }).click();
// User's page should show the notification via WebSocket
await notificationPromise;
await expect(userPage.locator('.notification-popup')).toContainText('New alert from admin');
await expect(userPage.locator('.notification-count')).toHaveText('1');
await userContext.close();
await adminContext.close();
});
Strategy 3 — Mock WebSocket Server (Pure Frontend Test)
TypeScript
test('UI updates when WebSocket message arrives', async ({ page }) => {
await page.goto('/live-feed');
// Intercept the WebSocket and inject a fake message
await page.evaluate(() => {
// Override WebSocket to send a test message immediately
const OrigWS = WebSocket;
(window as any).WebSocket = class extends OrigWS {
constructor(url: string) {
super(url);
setTimeout(() => {
const event = new MessageEvent('message', {
data: JSON.stringify({
type: 'order-update',
orderId: 'ORD-123',
status: 'shipped',
message: 'Your order has been shipped!',
}),
});
this.dispatchEvent(event);
}, 1000);
}
};
});
// Wait for notification to appear in UI
await expect(page.locator('.live-notification')).toBeVisible({ timeout: 5000 });
await expect(page.locator('.live-notification')).toContainText('Your order has been shipped!');
});
Strategy 4 — Trigger via API and Verify WebSocket Delivery
TypeScript
test('order status WebSocket notification flow', async ({ page, request }) => {
await page.goto('/orders/ORD-123');
// Assert initial status
await expect(page.locator('.order-status')).toHaveText('Processing');
// Trigger status change via API (simulates backend update)
await request.patch('/api/orders/ORD-123', {
data: { status: 'shipped' },
headers: { Authorization: `Bearer ${process.env.ADMIN_TOKEN}` },
});
// WebSocket pushes update — verify UI updates without page reload
await expect(page.locator('.order-status')).toHaveText('Shipped', { timeout: 10000 });
await expect(page.locator('.status-badge')).toHaveClass(/badge-shipped/);
});
