Use when a feature pushes updates over Server-Sent Events (text/event-stream) and needs tests: EventSource events and errors, the stream format, reconnect with Last-Event-ID replay, retry intervals and per-origin connection limits, plus k6 load tests. Two-way messaging is websocket-tests.
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Tests the SSE surfaces per the WHATWG SSE spec: stream format,
readyState lifecycle, Last-Event-ID reconnect-with-replay, and
the HTTP/1.1 connection-pool ceiling.
Last-Event-ID,
and HTTP/1.1 connection limits all behave as designed.Per the WHATWG SSE spec, response must use
Content-Type: text/event-stream (UTF-8) and stream lines:
| Field | Meaning |
|---|---|
data: | Appends to message payload (multiple data: lines join with newlines) |
event: | Custom event type (default = message) |
id: | Sets last event ID for reconnect replay |
retry: | Reconnect interval (ms) |
: | Comment line (kept-alive heartbeat) |
Empty line ends a message. Example:
event: order_update
id: 142
data: {"orderId":"o123","status":"shipped"}
event: order_update
id: 143
data: {"orderId":"o124","status":"shipped"}import { test, expect } from '@playwright/test';
test('client receives server-pushed events', async ({ page }) => {
await page.goto('https://localhost:3000/dashboard');
const events = await page.evaluate(() => {
return new Promise<any[]>((resolve) => {
const collected: any[] = [];
const es = new EventSource('/api/orders/stream');
es.addEventListener('order_update', (e: any) => {
collected.push(JSON.parse(e.data));
if (collected.length === 2) {
es.close();
resolve(collected);
}
});
});
});
expect(events).toHaveLength(2);
expect(events[0].orderId).toBe('o123');
});readyState lifecyclePer the WHATWG SSE spec, readyState values:
| Value | State |
|---|---|
| 0 | CONNECTING |
| 1 | OPEN |
| 2 | CLOSED |
test('readyState transitions through CONNECTING → OPEN', async ({ page }) => {
await page.goto('https://localhost:3000/dashboard');
const transitions = await page.evaluate(() => {
return new Promise<number[]>((resolve) => {
const states: number[] = [];
const es = new EventSource('/api/stream');
states.push(es.readyState); // 0
es.onopen = () => {
states.push(es.readyState); // 1
es.close();
states.push(es.readyState); // 2
resolve(states);
};
});
});
expect(transitions).toEqual([0, 1, 2]);
});Reconnect-with-replay via Last-Event-ID, the retry: interval,
204 No Content disable, and the HTTP/1.1 connection-pool ceiling
are in references/sse-test-recipes.md.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Wrong content-type | Browser doesn't recognize as SSE | Content-Type: text/event-stream (Step 1) |
| Skip newline-newline message terminator | Browser buffers indefinitely | Always end messages with \n\n (Step 1) |
No id: on events | Last-Event-ID replay impossible | Always emit id: (see references) |
Multiple EventSource per page on HTTP/1.1 | Connection pool starvation | One stream + multiplex via event: (see references) |
| Use SSE for two-way comms | One-way only; need WebSocket for client→server | Use websocket-tests skill instead |
Last-Event-ID only - server must persist event
IDs (or generate from timestamp) for replay to work.websocket-tests - bidirectional
alternativegrpc-streaming-test-author (qa-grpc) - typed
RPC streaming alternative