CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/server-sent-events-tests

Test Server-Sent Events (SSE) flows, one-way server-to-client push only (not bidirectional, use websocket-tests for client-to-server messaging): `EventSource` API on the browser side (`onmessage`, `onerror`, `readyState` 0/1/2), event stream format (`data:`, `event:`, `id:`, `retry:`), `Last-Event-ID` reconnect-with-replay header, content-type `text/event-stream`, and HTTP/1.1 connection-pool limits. Use Playwright for browser-side, raw HTTP client for server-side stream tests; k6 load testing (concurrent-stream capacity, connection churn, HTTP/1.1 vs HTTP/2 ceiling) lives in references/sse-load.md. Use when a feature pushes updates over `text/event-stream` and the reconnect interval, `Last-Event-ID` replay, per-origin connection ceiling, or concurrent-stream capacity has no coverage.

69

Quality

87%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

SKILL.md

name:
server-sent-events-tests
description:
Test Server-Sent Events (SSE) flows, one-way server-to-client push only (not bidirectional, use websocket-tests for client-to-server messaging): `EventSource` API on the browser side (`onmessage`, `onerror`, `readyState` 0/1/2), event stream format (`data:`, `event:`, `id:`, `retry:`), `Last-Event-ID` reconnect-with-replay header, content-type `text/event-stream`, and HTTP/1.1 connection-pool limits. Use Playwright for browser-side, raw HTTP client for server-side stream tests; k6 load testing (concurrent-stream capacity, connection churn, HTTP/1.1 vs HTTP/2 ceiling) lives in references/sse-load.md. Use when a feature pushes updates over `text/event-stream` and the reconnect interval, `Last-Event-ID` replay, per-origin connection ceiling, or concurrent-stream capacity has no coverage.
metadata:
{"keywords":"sse, server-sent-events, eventsource, one-way-streaming, realtime-protocols"}

server-sent-events-tests

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.

When to use

  • Real-time UI updates that don't need bidirectional communication (notifications, log tailers, build status, score tickers).
  • Pre-deploy gate: SSE retry interval, replay via Last-Event-ID, and HTTP/1.1 connection limits all behave as designed.

Step 1 - Server-side event stream format

Per the WHATWG SSE spec, response must use Content-Type: text/event-stream (UTF-8) and stream lines:

FieldMeaning
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"}

Step 2 - Browser test (Playwright)

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');
});

Step 3 - readyState lifecycle

Per the WHATWG SSE spec, readyState values:

ValueState
0CONNECTING
1OPEN
2CLOSED
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]);
});

Deeper recipes

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-patterns

Anti-patternWhy it failsFix
Wrong content-typeBrowser doesn't recognize as SSEContent-Type: text/event-stream (Step 1)
Skip newline-newline message terminatorBrowser buffers indefinitelyAlways end messages with \n\n (Step 1)
No id: on eventsLast-Event-ID replay impossibleAlways emit id: (see references)
Multiple EventSource per page on HTTP/1.1Connection pool starvationOne stream + multiplex via event: (see references)
Use SSE for two-way commsOne-way only; need WebSocket for client→serverUse websocket-tests skill instead

Limitations

  • SSE is HTTP-only; some intermediaries (legacy proxies) buffer responses, breaking real-time push.
  • No native binary support; SSE is text-only (use WebSocket if binary needed).
  • Reconnect uses Last-Event-ID only - server must persist event IDs (or generate from timestamp) for replay to work.

References

  • WHATWG SSE spec - EventSource API, stream format, replay, retry
  • SSE load testing (k6 concurrent-stream capacity, churn, HTTP/1.1 vs HTTP/2): references/sse-load.md
  • websocket-tests - bidirectional alternative
  • grpc-streaming-test-author (qa-grpc) - typed RPC streaming alternative

SKILL.md

tile.json