Realtime Messaging Testing Guide
How to Test Ably Realtime with Playwright: Complete 2026 Guide
A scenario-by-scenario walkthrough of testing Ably Realtime messaging with Playwright. Token authentication, message ordering guarantees, channel presence, history replay, connection recovery, and the pitfalls that break real WebSocket test suites.
“Ably delivers over 500 million messages per day across thousands of production applications, powering realtime features from live chat to multiplayer gaming.”
Ably
Ably Realtime Pub/Sub Flow
1. Why Testing Ably Realtime Is Harder Than It Looks
Ably Realtime is a pub/sub messaging platform that maintains persistent WebSocket connections between clients and the Ably edge network. When your application publishes a message to a channel, Ably guarantees delivery to all subscribers on that channel in the order the messages were published. That ordering guarantee is the first thing that makes testing tricky: your test must verify not just that messages arrive, but that they arrive in the correct sequence, even when multiple publishers send concurrently.
The authentication model adds another layer of complexity. In production, you should never expose your Ably API key to the browser. Instead, your server generates short-lived tokens using the Ably REST API, and your client connects with those tokens. This token auth flow means your test must coordinate between a server endpoint that mints tokens and a browser client that uses them. If you skip this and test with a raw API key, you are testing a fundamentally different auth path than production uses.
Beyond auth and ordering, Ably offers channel presence (tracking which clients are attached to a channel), history replay (fetching messages published before a client attached), and automatic connection recovery (resuming a dropped WebSocket without message loss). Each of these features introduces its own timing challenges in a test environment. Presence events are asynchronous and may arrive out of band. History queries depend on Ably having persisted messages before your test reads them. Connection recovery depends on a reconnect happening within Ably's two-minute recovery window.
There are five structural reasons this combination is hard to test reliably. First, WebSocket connections are long-lived and bidirectional, so your test cannot simply intercept HTTP request/response pairs. Second, token authentication requires server-side setup that must complete before the browser client attempts to connect. Third, message ordering guarantees only hold within a single channel, and your test must account for the possibility of interleaved messages from parallel test runs. Fourth, presence events are eventually consistent, meaning a member's enter event might not propagate to all subscribers before your assertion runs. Fifth, Ably's connection state machine has multiple states (initialized, connecting, connected, disconnected, suspended, closed, failed) that your test must handle gracefully.
Ably Token Auth Flow
Browser Client
Needs Ably token
Your Server
POST /ably/auth
Ably REST API
requestToken
Token Issued
Short-lived credential
WebSocket
Connect with token
Channel Attach
Subscribe to messages
Ably Connection State Machine
Initialized
Client created
Connecting
WebSocket handshake
Connected
Ready for pub/sub
Disconnected
Network lost
Reconnecting
Auto-recovery
Connected
Messages resume
A good Ably Realtime test suite covers all of these surfaces. The sections below walk through each scenario you need, with runnable Playwright TypeScript code you can copy directly into your project.
2. Setting Up Your Test Environment
Before writing any test scenarios, you need a properly configured Ably application and a test harness that can coordinate between your server (which mints tokens) and your browser client (which consumes them). Ably provides a free tier that supports all the features covered in this guide, including presence, history, and connection recovery.
Ably Test Environment Setup Checklist
- Create a dedicated Ably application for testing (separate from production)
- Generate an API key with publish, subscribe, presence, and history capabilities
- Set up a token auth endpoint on your server (never expose the API key to browsers)
- Configure channel rules to enable persisted history on test channels
- Use unique channel names per test run to avoid cross-contamination
- Install the ably npm package in both your server and test projects
- Set connection and channel timeouts to reasonable values for CI
- Add Ably sandbox URLs to your CSP if using Content Security Policy
Environment Variables
Token Auth Endpoint
Your server must expose an endpoint that generates Ably TokenRequests. This is the recommended auth pattern for browsers because the API key never leaves your server. The Ably client library calls this endpoint automatically when it needs a new token or when the current token expires.
Playwright Configuration for Ably
Ably tests involve WebSocket connections that take time to establish. Configure Playwright with generous timeouts and set up a helper that creates unique channel names for each test to prevent interference between parallel runs.
Test Helper: Unique Channel Names
3. Scenario: Token Authentication Flow
The most fundamental scenario to test is whether your application correctly obtains an Ably token from your server and establishes a WebSocket connection. In production, the browser client never sees your Ably API key. Instead, it calls your auth endpoint, receives a TokenRequest object, and passes that to the Ably client library, which exchanges it for a short-lived token. This test verifies the entire chain: your auth endpoint returns a valid TokenRequest, the Ably SDK exchanges it, and the client reaches the “connected” state.
Token Authentication Flow
ModerateGoal
Load the application, verify that the Ably client authenticates via token auth (not API key), and confirm the connection reaches the “connected” state with the correct client ID.
Preconditions
- App running at
APP_BASE_URLwith token auth endpoint configured - Ably API key has publish, subscribe, and presence capabilities
- The frontend uses
authUrlorauthCallbackin the Ably client options
Playwright Implementation
What to Assert Beyond the UI
Token Auth Assertions
- Token auth endpoint returns a TokenRequest, not a raw token or API key
- The TokenRequest includes a nonce and MAC signature
- No API key appears in browser-accessible JavaScript
- The client ID in the token matches the authenticated user
- Connection reaches 'connected' state within 15 seconds
Token Auth: Playwright vs Assrt
import { test, expect } from '@playwright/test';
test('token auth: client connects with server-issued token', async ({ page }) => {
const tokenAuthPromise = page.waitForResponse(
(resp) => resp.url().includes('/api/ably/auth') && resp.status() === 200
);
await page.goto('/chat');
const tokenResponse = await tokenAuthPromise;
const tokenData = await tokenResponse.json();
expect(tokenData).toHaveProperty('keyName');
expect(tokenData).not.toHaveProperty('key');
await expect(page.getByTestId('ably-status')).toHaveText('connected', {
timeout: 15_000,
});
const hasApiKey = await page.evaluate(() => {
return (window as any).__ablyClient?.options?.key !== undefined;
});
expect(hasApiKey).toBe(false);
});4. Scenario: Message Ordering Guarantees
Ably guarantees that messages published to a channel are delivered to subscribers in the order they were published. This is critical for applications like live chat, collaborative editing, and financial tickers where out-of-order messages cause data corruption. Your test must publish a sequence of numbered messages from the server side and verify that the browser client receives them in exactly that order.
The challenge here is timing. Your test server publishes messages via the Ably REST API, but the browser receives them over a WebSocket connection. There is no HTTP request/response pair to intercept. Instead, you must evaluate JavaScript in the browser context to read the messages that the Ably client has accumulated, and you must wait long enough for all messages to arrive before asserting on order.
Message Ordering Guarantees
ModerateGoal
Publish 20 numbered messages to a channel from the server, and verify the browser subscriber receives all 20 in the exact published order.
Playwright Implementation
What to Assert Beyond the UI
Beyond checking the DOM, verify that the underlying message array in JavaScript maintains strict ordering. If your application renders messages into a list, also verify the DOM order matches. The key assertion is that receivedIndices is a strictly ascending sequence from 0 to 19 with no gaps and no duplicates.
Message Ordering: Playwright vs Assrt
import { test, expect } from '@playwright/test';
import { uniqueChannel, createRestClient } from '../helpers/ably-helpers';
test('message ordering: 20 messages arrive in sequence', async ({ page }) => {
const channelName = uniqueChannel('ordering');
await page.goto(`/chat?channel=${channelName}`);
await expect(page.getByTestId('ably-status')).toHaveText('connected');
const rest = createRestClient();
const channel = rest.channels.get(channelName);
for (let i = 0; i < 20; i++) {
await channel.publish('message', { index: i, text: `Message ${i}` });
}
await page.waitForFunction(
(n) => ((window as any).__receivedMessages || []).length >= n,
20, { timeout: 30_000 }
);
const indices = await page.evaluate(() =>
((window as any).__receivedMessages || []).map(
(m: any) => m.data.index
)
);
expect(indices).toEqual(Array.from({ length: 20 }, (_, i) => i));
});5. Scenario: Channel Presence Enter and Leave
Ably's presence feature lets clients announce their arrival on a channel and track who else is there. When a client calls presence.enter(), all other subscribers on that channel receive a presence event. This powers features like “who's online” indicators, typing indicators, and multiplayer lobbies. Testing presence requires coordinating two browser contexts: one that enters the channel and another that observes the presence event.
Channel Presence Enter and Leave
ComplexGoal
Open two browser tabs on the same channel. Have the first user enter presence. Verify the second user sees the presence member. Have the first user leave. Verify the second user sees the departure.
Preconditions
- The Ably API key has the
presencecapability - Your app exposes a presence member list in the UI
- Each browser context uses a distinct
clientId
Playwright Implementation
What to Assert Beyond the UI
Presence events carry metadata including the client ID, an optional data payload, and the action type (enter, leave, update). Your test should verify that the action type is correct and that the client ID matches. If your app uses presence data (for example, a user's display name or avatar URL), verify that data payload too.
Channel Presence: Playwright vs Assrt
import { test, expect } from '@playwright/test';
import { uniqueChannel } from '../helpers/ably-helpers';
test('presence: enter and leave propagate', async ({ browser }) => {
const channelName = uniqueChannel('presence');
const ctxA = await browser.newContext();
const ctxB = await browser.newContext();
const pageA = await ctxA.newPage();
const pageB = await ctxB.newPage();
await pageA.goto(`/chat?channel=${channelName}&clientId=user-alice`);
await pageB.goto(`/chat?channel=${channelName}&clientId=user-bob`);
await expect(pageA.getByTestId('ably-status')).toHaveText('connected');
await expect(pageB.getByTestId('ably-status')).toHaveText('connected');
await pageA.getByRole('button', { name: /go online/i }).click();
await expect(pageB.getByTestId('presence-list'))
.toContainText('user-alice', { timeout: 10_000 });
await pageA.getByRole('button', { name: /go offline/i }).click();
await expect(pageB.getByTestId('presence-list'))
.not.toContainText('user-alice', { timeout: 10_000 });
await ctxA.close();
await ctxB.close();
});6. Scenario: History Replay on Channel Attach
Ably can persist messages to a channel so that clients who attach later can retrieve them. This is essential for applications where a user opens a chat room and needs to see the last N messages, or where a dashboard must display recent events even if the client was not connected when those events occurred. To use history, you must enable the “persisted history” channel rule in your Ably dashboard for the relevant channel namespace.
The testing challenge with history is timing. After you publish messages via the REST API, there is a small delay before those messages are available in the persisted history store. Your test must account for this by either waiting briefly after publishing or retrying the history query. Additionally, history results are paginated by default, so your test must handle pagination if you publish more messages than the default page size (100).
History Replay on Channel Attach
ComplexGoal
Publish 5 messages to a channel before the browser client connects. Then load the page and verify that the client retrieves all 5 messages from history in the correct order.
Playwright Implementation
7. Scenario: Connection Recovery After Disconnect
One of Ably's strongest features is automatic connection recovery. When a client loses its WebSocket connection (due to a network hiccup, laptop sleep, or mobile network switch), the Ably SDK automatically attempts to reconnect. If the reconnection happens within Ably's two-minute recovery window, the client receives any messages that were published during the disconnection, in order, without gaps. This is called “connection recovery” and it uses a connection key stored by the SDK.
Testing this requires simulating a network disconnection in the browser. Playwright provides page.context().setOffline(true) to simulate going offline, and page.context().setOffline(false) to restore connectivity. During the offline period, you publish messages from the server side. After reconnection, you verify that those messages arrive in the browser without loss.
Connection Recovery After Disconnect
ComplexGoal
Establish a connected Ably session, simulate a network dropout, publish messages during the offline period, restore connectivity, and verify that all messages are delivered after recovery with no gaps.
Playwright Implementation
Connection Recovery: Playwright vs Assrt
import { test, expect } from '@playwright/test';
import { uniqueChannel, createRestClient } from '../helpers/ably-helpers';
test('recovery: messages arrive after reconnect', async ({ page, context }) => {
const channelName = uniqueChannel('recovery');
await page.goto(`/chat?channel=${channelName}`);
await expect(page.getByTestId('ably-status')).toHaveText('connected');
const rest = createRestClient();
const ch = rest.channels.get(channelName);
await ch.publish('chat', { text: 'Before disconnect', index: 0 });
await expect(page.getByText('Before disconnect')).toBeVisible();
await context.setOffline(true);
await expect(page.getByTestId('ably-status')).toHaveText(/disconnected/);
await ch.publish('chat', { text: 'During disconnect 1', index: 1 });
await ch.publish('chat', { text: 'During disconnect 2', index: 2 });
await context.setOffline(false);
await expect(page.getByTestId('ably-status')).toHaveText('connected');
await expect(page.getByText('During disconnect 1')).toBeVisible();
await expect(page.getByText('During disconnect 2')).toBeVisible();
});8. Scenario: Error Handling and Token Expiry
Tokens issued by your auth endpoint have a TTL (time to live). When a token expires, the Ably SDK automatically requests a new one by calling your authUrl again. This renewal must be transparent to the user. Your test should verify that the client handles token expiry gracefully: the connection may briefly transition to “disconnected” during the token refresh, but it should reconnect without message loss.
You should also test what happens when your auth endpoint is unavailable or returns an error. In that case, the Ably client should surface a connection error, and your application should display an appropriate error state to the user. Silently failing is worse than a visible error because the user will think their messages are being sent when they are not.
Token Expiry and Renewal
ComplexGoal
Issue a token with a very short TTL (10 seconds), wait for it to expire, and verify the client automatically renews the token and maintains the connection.
Playwright Implementation
9. Common Pitfalls That Break Ably Test Suites
Realtime messaging tests fail in ways that are different from typical HTTP request/response test failures. The issues below are sourced from Ably's GitHub issue tracker, Stack Overflow discussions, and common patterns seen in production test suites.
Ably Test Suite Anti-Patterns
- Using the same channel name across parallel tests, causing message cross-contamination. Always use unique channel names per test.
- Asserting on message delivery immediately after publish without waiting for the WebSocket round trip. Add explicit waits for messages to appear.
- Exposing the Ably API key in browser-side code during tests. Use token auth even in testing to match your production auth flow.
- Not closing Ably client connections in test teardown, causing connection limit exhaustion. Always call client.close() in afterEach.
- Testing against Ably sandbox with real-time dependent assertions but no retry logic. The sandbox is shared infrastructure with variable latency.
- Forgetting to enable persisted history in channel rules before testing history replay. History queries return empty without this setting.
- Setting action timeouts too low for CI environments where WebSocket handshake takes longer. Use at least 15s for connection assertions.
- Not handling the 'suspended' connection state, which occurs when disconnection exceeds 2 minutes. Your test should distinguish suspended from failed.
Channel Name Collision: A Real-World Example
One of the most common failures in Ably test suites is channel name collision. When two tests use the same channel name (like "test-chat"), messages from one test leak into the other. This causes intermittent failures that are nearly impossible to debug because the tests pass individually but fail when run in parallel. The fix is simple: generate a unique channel name for every test that includes a timestamp and random suffix.
Connection Cleanup in Test Teardown
Ably enforces connection limits per application. If your test suite opens connections without closing them, you will hit the connection limit after a few test runs and all subsequent tests will fail with a “connection limit exceeded” error. This is especially problematic in CI where the test runner might crash before teardown runs. Use Playwright's test.afterEach hook to ensure connections are always closed.
Presence Timing: The Race Condition
Presence events are delivered asynchronously. If your test calls presence.enter() on one client and immediately checks the presence set on another client, the enter event may not have propagated yet. This creates a flaky test that passes 90% of the time. The fix is to subscribe to presence events on the observing client and wait for the specific event you expect, rather than polling the presence set.
10. Writing These Scenarios in Plain English with Assrt
The Playwright implementations above are thorough, but they contain a lot of boilerplate: setting up browser contexts, calling page.evaluate to access the Ably client, building unique channel names, and managing teardown. Assrt lets you express the same scenarios in plain English. Each scenario block describes the setup, the steps, and the expected outcomes. Assrt compiles these into the same Playwright TypeScript you saw above.
Assrt compiles each scenario block into the same Playwright TypeScript you saw in the preceding sections, committed to your repo as real tests you can read, run, and modify. When Ably changes its SDK API (for example, renaming presence.enter() or changing the connection state names), Assrt detects the failure, analyzes the new API surface, and opens a pull request with the updated implementation. Your scenario files stay untouched.
Start with the token auth scenario. Once it is green in your CI, add message ordering, then presence, then connection recovery, then token expiry handling. In a single afternoon you can have complete Ably Realtime coverage that most production applications never manage to achieve by hand.
Related Guides
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...
How to Test Intercom Messenger
A practical guide to testing Intercom Messenger with Playwright. Covers iframe traversal,...
Ready to automate your testing?
Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.