import { test, expect } from '@playwright/test'; // ISS-129 (v0.57.0): /api/events pushes full state snapshots over SSE — the current snapshot on // connect, then one after every sim tick (~1 s; the background tick runs even while idle since // v0.56.0, so no scale hook is needed). Reached via the same base URL as playwright.config.ts // (E2E_BASE_URL, defaulting to the live instance) — node-side fetch, no browser involved. const baseUrl = process.env.E2E_BASE_URL ?? 'https://flight.dev.kkhost.pl'; interface StateBody { simState: { clockMs: number }; } test('GET /api/events streams state snapshots over SSE', async () => { const res = await fetch(`${baseUrl}/api/events`, { headers: { Accept: 'text/event-stream' } }); expect(res.status).toBe(200); expect(res.headers.get('content-type')).toContain('text/event-stream'); expect(res.body).not.toBeNull(); const reader = res.body!.getReader(); const decoder = new TextDecoder(); let buffer = ''; const clocks: number[] = []; // simState.clockMs of each parsed snapshot event try { // Collect two snapshot events: the initial one, then at least one pushed by a background tick. const deadline = Date.now() + 15_000; while (clocks.length < 2 && Date.now() < deadline) { let chunk: ReadableStreamReadResult; try { chunk = await Promise.race([ reader.read(), new Promise((_, reject) => setTimeout(() => reject(new Error('SSE stream stalled')), Math.max(1, deadline - Date.now())), ), ]); } catch { break; // stalled or past the deadline — let the length assertion report it } if (chunk.done) break; // stream ended early — same buffer += decoder.decode(chunk.value, { stream: true }); let sep: number; while ((sep = buffer.indexOf('\n\n')) >= 0) { const frame = buffer.slice(0, sep); buffer = buffer.slice(sep + 2); for (const line of frame.split('\n')) { if (!line.startsWith('data: ')) continue; // heartbeats are comment lines (" : ping") const snap = JSON.parse(line.slice('data: '.length)) as StateBody; clocks.push(snap.simState.clockMs); } } } } finally { await reader.cancel(); // close the stream → server unsubscribes } // The initial snapshot arrives on connect; the second comes from a background tick within ~1 s. expect(clocks.length).toBeGreaterThanOrEqual(2); expect(clocks[1]).toBeGreaterThan(clocks[0]); // clock advanced between pushes — real updates, not replays });