Several tests ran green while asserting nothing. Replace them with real assertions, and fix the SSE helper they depend on. sse-listener: exchange the JWT for a single-use ticket (POST /stream/ticket) and connect via ?ticket= — the helper still used the dead ?token= scheme, so every SSE-based assertion would have silently failed to receive events. like-comment: - "like is idempotent" asserted nothing (void feed; void b) → now seeds a real upload and pins the like contract: counted once per user, toggles off on repeat (guards double-count), and a second user's like is counted independently. - "comment → SSE to B" asserted length >= 0 (always true) → B now subscribes to the stream, A comments, and B must receive the new-comment event for that upload (comment_count === 1). ~30s due to reverse-proxy SSE buffering; timeout raised. sse-realtime: only checked a nav link was visible → now counts EventSource opens and asserts a fresh stream connection after hidden→visible (also fixes the sim, which set visibilityState but not document.hidden, so the close never fired). multi-tab "SSE delivers to both": only checked nav links → now asserts each tab opens its own stream connection (delivery isn't asserted — it hinges on the ~30s proxy buffering; connection establishment is the reliable, honest signal). safe-area: delete the /join probe whose only assertion was Array.isArray(x) === true (always true); the real sheet-level env() check already exists below it. All verified green against the live backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
2.4 KiB
TypeScript
52 lines
2.4 KiB
TypeScript
/**
|
|
* SSE reconnection after tab background. USER_JOURNEYS.md §17 / edge cases.
|
|
*
|
|
* The app closes the EventSource on `document.hidden` and reopens it (minting a
|
|
* fresh ticket + new EventSource) when the tab becomes visible again — see
|
|
* frontend/src/lib/sse.ts. We assert the reconnect by counting stream-open
|
|
* requests rather than event delivery, which keeps the test fast and independent
|
|
* of the reverse proxy's SSE buffering.
|
|
*/
|
|
import { test, expect } from '../../fixtures/test';
|
|
|
|
/** Count EventSource opens (GET /api/v1/stream?ticket=…), not the ticket POST. */
|
|
function trackStreamOpens(page: import('@playwright/test').Page): () => number {
|
|
let n = 0;
|
|
page.on('request', (req) => {
|
|
if (req.method() === 'GET' && req.url().includes('/api/v1/stream?')) n++;
|
|
});
|
|
return () => n;
|
|
}
|
|
|
|
test.describe('Feed — SSE behavior', () => {
|
|
test('backgrounding then foregrounding the tab opens a fresh SSE connection', async ({ page, guest, signIn }) => {
|
|
const g = await guest('SseReconnect');
|
|
const streamOpens = trackStreamOpens(page);
|
|
|
|
await signIn(page, g); // lands on /feed, which calls connectSse() on mount
|
|
// Initial connection established.
|
|
await expect.poll(streamOpens, { timeout: 10_000 }).toBeGreaterThanOrEqual(1);
|
|
const afterInitial = streamOpens();
|
|
|
|
// Background: the visibility handler reads document.hidden, so override that
|
|
// (not just visibilityState) before dispatching, or the close never fires.
|
|
await page.evaluate(() => {
|
|
Object.defineProperty(document, 'hidden', { configurable: true, get: () => true });
|
|
Object.defineProperty(document, 'visibilityState', { configurable: true, get: () => 'hidden' });
|
|
document.dispatchEvent(new Event('visibilitychange'));
|
|
});
|
|
// Foreground again → connectSse() mints a new ticket and opens a new EventSource.
|
|
await page.evaluate(() => {
|
|
Object.defineProperty(document, 'hidden', { configurable: true, get: () => false });
|
|
Object.defineProperty(document, 'visibilityState', { configurable: true, get: () => 'visible' });
|
|
document.dispatchEvent(new Event('visibilitychange'));
|
|
});
|
|
|
|
// A reconnect means a brand-new stream GET beyond the initial one.
|
|
await expect.poll(streamOpens, { timeout: 10_000 }).toBeGreaterThan(afterInitial);
|
|
|
|
// And the app is still functional.
|
|
await expect(page.getByRole('link', { name: 'Galerie' })).toBeVisible();
|
|
});
|
|
});
|