How to Test WebSockets and Live UI Updates
Short answer
Live dashboards fail on missed events after reconnect, out-of-order message handling, and stale UI that looks fresh—not on whether one message renders once. Use test hooks or probe fallbacks instead of arbitrary timeouts, simulate disconnect/reconnect, and assert event ordering via probe or monotonic sequence ids.
Part of Testing Guides by UI patterns.
Who this is for
Teams shipping real-time dashboards, chat, collaborative editors, trading blotters, or ops consoles with WebSockets, SSE, or Socket.IO. Any UI that updates without full page reload.
Why testing live updates matters
Stale live UI causes operational and financial harm:
- Wrong balances — reconnect misses ledger events; trader acts on stale P&L.
- Duplicate actions — double message delivery creates duplicate orders.
- Missed alerts — incident banner never appears after WS drop; on-call blind.
- Collaboration conflicts — two users edit; last-write wins without CRDT merge visibility.
- Compliance — audit stream gap after reconnect; regulatory replay incomplete.
Assert probe state and sequence monotonicity—not single snapshot after waitForTimeout.
Complexity map
| Scenario | Edge case | Why tests break | Approach |
|---|---|---|---|
| Initial snapshot + stream | Gap on connect | Missing rows | Probe baseline then events |
| Reconnect | Missed messages while down | Stale count | Assert resync banner + probe |
| Out-of-order | seq 3 before 2 | Corrupt state | Probe rejects or buffers |
| Duplicate delivery | Same event id twice | Double increment | Idempotent probe |
| Heartbeat timeout | Silent drop | Frozen UI | Force close socket |
| Auth on WS | Token expires mid-session | 401 on subscribe | Refresh token helper |
| Room/channel join | Wrong tenant room | Cross-tenant leak | Probe isolation |
| SSE vs WS | Different client path | Untested fallback | Separate spec or feature flag |
| Backpressure | Burst 100 events | UI freeze | Probe final aggregate |
| Optimistic UI | Rollback on error | Wrong display | Poll probe after error event |
| Binary frames | Protobuf | Hard to assert | Decode via test hook |
| CI proxy | WS blocked | False pass locally | ws:// test gateway |
Wait strategies (no arbitrary sleep)
Pattern A — test hook promise
await page.goto('/dashboard');
await page.evaluate(() => window.__TEST_WS__.ready());
await page.evaluate(() => window.__TEST_WS__.emit('balance_update', { accountId: 'a1', cents: 5000 }));
await expect(page.getByTestId('balance-a1')).toHaveText('$50.00');
Expose __TEST_WS__ only when NODE_ENV=test.
Pattern B — probe poll
await request.post('/api/test/inject-event', {
data: { runId, type: 'balance_update', accountId: 'a1', cents: 5000 },
});
await expect.poll(async () => {
const v = await page.getByTestId('balance-a1').textContent();
return v;
}).toBe('$50.00');
Server injects WS message to connected test client or writes DB that UI polls.
Pattern C — waitForEvent (Playwright)
page.on('websocket', ws => {
ws.on('framereceived', frame => {
if (frame.payload.includes('balance_update')) resolved = true;
});
});
Use when you control WS endpoint and payload format.
Reconnect scenario
await page.evaluate(() => window.__TEST_WS__.disconnect());
await expect(page.getByRole('status')).toContainText(/reconnecting|connection lost/i);
await page.evaluate(() => window.__TEST_WS__.reconnect());
await expect.poll(async () =>
(await request.get(`/api/test/ws-sync-state?runId=${runId}`)).json()
).toMatchObject({ synced: true, lastSeq: expect.any(Number) });
Inject events during disconnect; after reconnect probe must show gap filled or explicit conflict UI.
Requirement slices to cover
Track event_type (balance_update, order_fill, chat_message) and reconnect sync_outcome. If prod shows frequent reconnects but tests never simulate drop, run /testchimp evolve—link data grids scenarios when live rows update admin tables.
CI checklist
- Test gateway or inject route—no prod WS in PR CI
- Reconnect spec mandatory for financial/ops UIs
- Idempotent handling spec for duplicate event ids
- Auth refresh spec if WS uses short-lived JWT
- Never
waitForTimeout(5000)as primary wait - Cross-tenant negative: user B must not receive user A events (probe)
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
| Fixed sleep after action | Race | poll probe or hook |
| Assert DOM once | Miss reconnect gap | Disconnect test |
| Prod WS in CI | Flaky external | Test gateway |
| Ignore ordering | Corrupt state | seq id monotonic |
| UI count only | Miss duplicates | Probe id set |
| Skip auth refresh | Mid-test drop | Token refresh Arrange |
Example scenario
Situation: Ops dashboard loses WebSocket during incident; reconnects after 30s burst of alerts.
Expected outcome: All alert ids appear once; timeline ordered; no stale 'all clear' banner.
Why UI-only automation breaks: UI shows updated count but missed alert id 8842—on-call never sees critical page.
- Arrange: Seed alert stream ids 8840-8850; connect dashboard.
- Act: Disconnect WS, inject 8841-8850 server-side, reconnect.
- Assert: Probe timeline ids monotonic include 8842; DOM role=alert count matches probe; no duplicate ids.
TestChimp workflow: Instrument ws_reconnect with gap_seconds; expand tests when prod reconnect rate rises.
Same Arrange/Act/Assert pattern as expired-coupon checkout.
Connect scenarios to your QA workflow
Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).
Related scenarios
- Data grids — live row updates
- AI streaming responses — SSE token streams
- Audit compliance — event trail completeness
- Fintech web apps — balance updates
External references
Frequently asked questions
How do I wait for WebSocket messages in Playwright?
Prefer test hook that resolves when handler runs, or poll probe until DB/UI reflects event. Optionally listen to page websocket framereceived for known payload markers—avoid fixed timeouts.
How do I test reconnect after connection drop?
Force disconnect via test hook, inject events while offline, reconnect, assert resync state via probe and ordered event ids in UI timeline.
SSE vs WebSocket in E2E?
If prod uses both via feature flag, cover both or assert probe parity on critical event types. SSE may use EventSource hooks similar to WS test gateway.
How do I prevent duplicate event bugs?
Send same event id twice in inject route; probe and UI must show single logical effect—idempotent handlers.
How do auth expiring tokens affect WS tests?
Seed short-lived token, advance clock or wait to expiry, assert refresh and resubscribe without stale UI—probe subscription active.
Which event_types should TrueCoverage prioritize?
Compare prod ws event_type distribution to test inject coverage—expand scenarios for high-traffic types like balance_update or order_status.
Can I inject WS messages without browser WS?
Yes—server inject route writes DB and pushes to test client, or UI polls—probe remains authoritative for financial UIs.
Apply these patterns in your repo
Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.