// End-to-end confirmation of the diashow SSE fix, reproducing the ORIGINAL failure: // kiosk opens /diashow directly BEFORE any photos exist, then a guest uploads. // Pre-fix: stream never opens, display stuck on "Noch keine Beiträge" forever. // Post-fix: stream opens on mount, the uploaded photo appears live. import { chromium } from '@playwright/test'; import { readFileSync } from 'node:fs'; const BASE = 'http://localhost:3101'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const j = (r) => r.json(); const adminLogin = () => fetch(`${BASE}/api/v1/admin/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: 'admin-test-pw' }) }).then(j).then((b) => b.jwt); const join = (name) => fetch(`${BASE}/api/v1/join`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ display_name: name }) }).then(j); async function truncate(admin) { await fetch(`${BASE}/api/v1/admin/__truncate`, { method: 'POST', headers: { Authorization: `Bearer ${admin}` } }); } async function upload(jwt) { const form = new FormData(); form.append('file', new Blob([readFileSync('/tmp/eventsnap-loadtest/photos/photo_000.jpg')], { type: 'image/jpeg' }), 'live.jpg'); form.append('caption', 'LIVE-PROBE'); const r = await fetch(`${BASE}/api/v1/upload`, { method: 'POST', headers: { Authorization: `Bearer ${jwt}` }, body: form }); return (await r.json()).id; } // Reproduce the failing scenario: empty event, then open /diashow directly. let admin = await adminLogin(); await truncate(admin); admin = await adminLogin(); const showcase = await join('Showcase Display'); const guest = await join('Uploading Guest'); const browser = await chromium.launch(); const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } }); const page = await ctx.newPage(); const streamReqs = []; page.on('request', (req) => { if (req.url().includes('/stream')) streamReqs.push(req.url().replace(BASE, '')); }); await page.goto(`${BASE}/join`, { waitUntil: 'domcontentloaded' }); await page.evaluate((g) => { localStorage.setItem('eventsnap_jwt', g.jwt); localStorage.setItem('eventsnap_pin', g.pin); localStorage.setItem('eventsnap_user_id', g.user_id); localStorage.setItem('eventsnap_display_name', 'Showcase'); localStorage.setItem('eventsnap_guide_seen', '1'); }, showcase); // Open /diashow DIRECTLY (never via /feed) on an EMPTY event. await page.goto(`${BASE}/diashow`, { waitUntil: 'domcontentloaded' }); await sleep(2500); const emptyBefore = (await page.locator('text=Noch keine Beiträge').count()) > 0; const streamOpened = streamReqs.length > 0; console.log(`\n1) direct /diashow on empty event:`); console.log(` "Noch keine Beiträge" shown: ${emptyBefore} (expected: true)`); console.log(` SSE stream opened: ${streamOpened} ${streamOpened ? '('+streamReqs.join(', ')+')' : ''} (expected: true — this is the fix)`); // Now a guest uploads a photo while the display is open. console.log(`\n2) guest uploads a photo (display already open)…`); await upload(guest.jwt); // Wait for compression → upload-processed SSE → debounced refresh → slide appears. let appeared = false; for (let i = 0; i < 15; i++) { await sleep(1000); const stillEmpty = (await page.locator('text=Noch keine Beiträge').count()) > 0; const imgs = await page.locator('img').count(); if (!stillEmpty && imgs > 0) { appeared = true; console.log(` photo appeared live after ~${i + 1}s (img rendered, placeholder gone)`); break; } } if (!appeared) console.log(` ✗ photo did NOT appear within 15s`); await page.screenshot({ path: 'results/diashow-fix-confirm.png' }); await browser.close(); console.log(`\n════ VERDICT ════`); console.log(`SSE opens on direct /diashow: ${streamOpened ? 'YES ✓' : 'NO ✗'}`); console.log(`Live photo appears on showcase: ${appeared ? 'YES ✓' : 'NO ✗'}`); console.log(streamOpened && appeared ? 'FIX CONFIRMED' : 'FIX NOT confirmed');