Realtime Testing Guide
How to Test WebSocket Reconnection with Playwright: Complete 2026 Guide
A scenario-by-scenario walkthrough of testing WebSocket reconnection logic with Playwright. Network interruption simulation, exponential backoff verification, message queue replay, heartbeat ping/pong monitoring, connection state transitions, and graceful degradation UI patterns.
“According to the 2025 State of JS survey, 83% of web applications with realtime features use WebSockets as their primary transport, making reconnection reliability a critical concern for production systems.”
State of JS 2025
WebSocket Reconnection Lifecycle
1. Why Testing WebSocket Reconnection Is Harder Than It Looks
WebSocket connections feel simple when they work. The client opens a connection, messages flow bidirectionally, and the UI updates in realtime. The complexity surfaces when the connection breaks. A production-grade WebSocket client must detect the disconnection, wait an appropriate interval before retrying, increase that interval on subsequent failures (exponential backoff), queue outbound messages during the outage, replay those messages once reconnected, update the UI to reflect the connection state, and eventually fall back to an alternative transport if reconnection fails entirely.
Testing this behavior with Playwright introduces six structural challenges. First, Playwright operates at the browser level, but WebSocket connections live at the network layer. You cannot simply click a button to simulate a network outage. You need to use Playwright's context.setOffline(true) or intercept the underlying HTTP upgrade request with page.route() to control connectivity programmatically.
Second, exponential backoff involves timing. Your test must verify that retries occur at the correct intervals (1 second, then 2 seconds, then 4 seconds) without relying on fragile fixed setTimeout waits that slow down your suite and break under CI load. Third, message queue replay requires your test to send messages while the connection is down and then verify they arrive in the correct order after reconnection. Fourth, heartbeat mechanisms (ping/pong frames) operate below the application layer, making them invisible to standard Playwright locators. Fifth, the UI must transition through multiple states (connected, reconnecting, offline) in rapid succession, and your assertions need to catch each transition without race conditions. Sixth, graceful degradation to HTTP polling is a completely separate code path that your test must also exercise.
WebSocket Reconnection Decision Flow
Connection Lost
close event or timeout
Check Retry Count
Under max retries?
Calculate Backoff
min(base * 2^n, cap)
Wait
Backoff interval + jitter
Attempt Reconnect
new WebSocket(url)
Success?
Replay queue or retry
2. Setting Up a Reliable Test Environment
Before writing any reconnection test, you need a controllable WebSocket server and a Playwright project configured to intercept network conditions. The server should support manual connection management so your tests can drop and restore connections on demand.
Install Dependencies
Test WebSocket Server Fixture
Create a lightweight WebSocket server that your tests can start, stop, and manipulate. This server echoes messages back and supports a /drop endpoint that forcibly closes all active connections, simulating a server-side outage.
Playwright Configuration
Configure Playwright to use the WebSocket server as a fixture that starts before tests and stops after. Use the webServer option in your Playwright config alongside a global setup that boots the WS server on a known port.
Test Environment Architecture
Playwright
Test runner + browser
App Server
localhost:3000
WS Server
localhost:8765
context.setOffline
Network simulation
Assertions
State + timing checks
Basic Disconnect and Reconnect
StraightforwardThe most fundamental reconnection test: verify that the WebSocket client detects a dropped connection and re-establishes it automatically. This scenario uses Playwright's context.setOffline() API to simulate a network outage and then restore connectivity.
Goal
Confirm that after a network interruption, the client reconnects without user intervention and the UI returns to a connected state.
Playwright Implementation
What to Assert Beyond the UI
Besides the visible status indicator, verify that the application fires the correct events internally. Use Playwright's page.evaluate() to check that the WebSocket readyState property transitions from CLOSED (3) back to OPEN (1). Confirm that no error toasts or error boundaries activate during the reconnection process.
Exponential Backoff Timing Verification
ComplexExponential backoff is the most critical reconnection behavior to test, and the hardest to get right. Your client should not hammer the server with immediate retries. Instead, it should wait progressively longer intervals: 1 second, 2 seconds, 4 seconds, 8 seconds, up to a configurable maximum. Most implementations also add random jitter to prevent the “thundering herd” problem where thousands of clients reconnect simultaneously after an outage.
Goal
Verify that retry intervals follow an exponential backoff schedule with jitter, and that the client stops retrying after reaching the maximum retry count.
Playwright Implementation
The test intercepts the WebSocket constructor to record timestamps of each reconnection attempt. By computing the intervals between timestamps, you can verify that the backoff schedule follows the expected exponential curve. The 0.8 multiplier on the comparison accounts for jitter: a retry scheduled for 2 seconds might fire at 1.6 seconds due to random jitter, and that is correct behavior.
Message Queue Replay After Reconnection
ModerateA well-built WebSocket client queues outbound messages while disconnected and replays them in order once the connection is restored. This ensures no user actions are lost during brief network interruptions. Testing this requires sending messages during the offline period and verifying they arrive at the server after reconnection.
Goal
Send three messages while the WebSocket is disconnected. Verify all three arrive at the server in the correct order after reconnection, and that the server's echo responses appear in the UI.
Playwright Implementation
What to Assert Beyond the UI
Use page.evaluate()to inspect the internal message queue length. Before going back online, it should be 3. After reconnection and replay, it should be 0. Also verify that messages sent during the offline period have a “pending” visual indicator (like a clock icon or muted text) that clears once the echo response arrives.
Message Queue Test: Playwright vs Assrt
test('replays queued messages', async ({ page, context }) => {
await page.goto('/chat');
await expect(page.locator('[data-testid="ws-status"]'))
.toHaveText('Connected');
await context.setOffline(true);
await page.fill('[data-testid="message-input"]', 'msg-1');
await page.click('[data-testid="send-button"]');
await page.fill('[data-testid="message-input"]', 'msg-2');
await page.click('[data-testid="send-button"]');
await context.setOffline(false);
await expect(page.locator('[data-testid="ws-status"]'))
.toHaveText('Connected', { timeout: 15000 });
const msgs = page.locator('[data-testid="messages"] > div');
await expect(msgs.nth(2)).toContainText('echo:msg-1');
await expect(msgs.nth(3)).toContainText('echo:msg-2');
});Heartbeat Ping/Pong Failure Detection
ComplexMany WebSocket implementations use application-level heartbeat messages to detect “zombie connections” where the TCP socket is technically open but the remote end has silently disconnected. The client sends a ping at a regular interval, and if no pong arrives within a timeout, it considers the connection dead and initiates reconnection. Testing this requires intercepting WebSocket frames at the protocol level.
Goal
Verify that when the server stops responding to heartbeat pings, the client detects the stale connection and triggers reconnection within the configured timeout.
Playwright Implementation
This test uses Chrome DevTools Protocol (CDP) sessions to observe WebSocket frames at the protocol level. The combination of CDP frame monitoring and application-level evaluation gives you full visibility into the heartbeat mechanism. Note that CDP WebSocket inspection is Chromium-specific; for cross-browser testing, rely on application-level heartbeat counters exposed via data attributes.
Connection State UI Transitions
ModerateUsers need clear visual feedback about their connection status. A production chat application typically shows three states: connected (green indicator), reconnecting (yellow with animation), and offline (red with a manual retry button). Your tests must verify that the UI transitions through these states in the correct order and that each state's visual treatment matches the design specification.
Playwright Implementation
What to Assert Beyond the UI
Verify that screen readers receive appropriate ARIA live announcements for each state change. The reconnecting and offline states should include an aria-live="polite"region that announces the status to assistive technology. Check with Playwright's getByRole('status') locator.
Graceful Degradation with Polling Fallback
ComplexWhen WebSocket reconnection fails after all retry attempts, a well-designed application falls back to HTTP long-polling or regular polling. This fallback ensures users can still receive updates, albeit with higher latency. Testing this fallback path requires blocking WebSocket connections entirely while keeping HTTP requests functional.
Goal
Confirm that after WebSocket reconnection fails exhaustively, the application switches to HTTP polling and continues to receive messages.
Playwright Implementation
Graceful Degradation: Playwright vs Assrt
test('falls back to polling', async ({ page }) => {
await page.goto('/chat');
await expect(page.locator('[data-testid="ws-status"]'))
.toHaveText('Connected');
await page.route('**/ws', (route) => {
if (route.request().headers()['upgrade'] === 'websocket') {
return route.abort('connectionrefused');
}
return route.continue();
});
await page.evaluate(() =>
(window as any).__wsClient?.close()
);
await expect(page.locator('[data-testid="transport-mode"]'))
.toHaveText('polling', { timeout: 60000 });
await expect(page.locator('[data-testid="degraded-banner"]'))
.toBeVisible();
});9. Common Pitfalls That Break WebSocket Reconnection Tests
WebSocket reconnection tests are among the most fragile in any test suite. These are the specific failures that surface repeatedly in real Playwright projects, drawn from GitHub issues and community discussions.
Pitfalls to Avoid
- Using fixed waitForTimeout instead of waiting for connection state changes. CI runners are slower than local machines, so hardcoded delays cause intermittent failures.
- Forgetting that context.setOffline() only affects HTTP and WebSocket requests originating from the browser. It does not affect the test server itself. Your WS server stays running and accepts connections from other sources.
- Not accounting for jitter in backoff timing assertions. Exact millisecond comparisons will fail because jitter is intentionally random. Use range-based assertions with a tolerance of 50% or more.
- Testing reconnection without first confirming the initial connection succeeded. If the initial connection fails silently, your disconnect test is actually testing first-connection failure, not reconnection.
- Leaving page.route() interceptors active across tests. A WebSocket route block in one test will leak into the next test if you do not call route.unroute() or use test isolation with separate browser contexts.
- Asserting WebSocket readyState immediately after calling close(). The close handshake is asynchronous. Wait for the close event or poll readyState in a waitForFunction loop.
- Running backoff tests in parallel. Two tests that both manipulate network state on the same browser context will interfere with each other. Use test.describe.serial() for reconnection test suites.
Fixing the Backoff Timing Assertion
The most common failure in reconnection test suites is the backoff timing assertion. When you assert that the first retry happens after 1000ms, you will see values like 847ms or 1150ms due to jitter, timer resolution in the browser, and CI runner load. The fix is straightforward: widen your assertion bounds. Instead of expecting the interval to be exactly 1000ms, assert that it falls between 500ms and 2000ms. For the second retry at 2000ms, assert between 1000ms and 4000ms. The pattern is: each interval should be at least 50% of the target and no more than 200% of the target.
10. Writing These Scenarios in Plain English with Assrt
The Playwright implementations above are precise but verbose. Each test requires intimate knowledge of CSS selectors, timing APIs, and Playwright-specific patterns like context.setOffline() and page.route(). With Assrt, you describe the same scenarios in plain English. Assrt compiles your intent into the same Playwright TypeScript, committed to your repo as real, runnable tests.
Assrt compiles each scenario block into the same Playwright TypeScript you saw in the preceding sections. The generated tests are committed to your repo as real code you can read, debug, and extend. When your application changes its WebSocket reconnection logic or updates its UI indicators, Assrt detects the failure, analyzes the new behavior, and opens a pull request with the updated test code. Your scenario files stay untouched.
Pre-flight Checklist for WebSocket Reconnection Tests
- Test WebSocket server fixture is running and accessible
- Initial connection is confirmed before testing reconnection
- Backoff assertions use range-based tolerance, not exact values
- Tests run in serial mode to avoid network state conflicts
- Message queue tests verify both order and completeness
- UI state assertions use locators, not hardcoded timeouts
- Heartbeat tests account for Chromium-only CDP limitations
- Fallback polling tests verify degraded mode banner
Start with the basic disconnect and reconnect scenario. Once it passes reliably in CI, add the message queue replay test, then the exponential backoff verification, then the heartbeat detection, then the UI state transitions, and finally the graceful degradation fallback. In a single afternoon you can build complete WebSocket reconnection coverage that most production applications never achieve by hand.
Related Guides
How to Test Ably Realtime
A practical guide to testing Ably Realtime messaging with Playwright. Covers token auth...
How to Test AI Chat Streaming UI
A practical guide to testing AI chat streaming interfaces with Playwright. Covers...
How to Test Collaborative Cursors
A practical guide to testing collaborative cursors with Playwright. Covers Liveblocks and...
Ready to automate your testing?
Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.