#!/usr/bin/env node /** * The showcase display. Opens ONE real Chromium browser on /diashow (as a * logged-in guest) for the duration of a load run, so we validate the real * SSE → render path a projector/TV would use — not just the protocol. * * Run it alongside driver.mjs (separate terminal), started first: * node diashow-watch.mjs * # then in another terminal: * node driver.mjs * * It captures: * - periodic screenshots (so you can eyeball that new photos actually appear) * - browser console errors / page crashes / SSE disconnects surfaced in console * - a final count of slides rendered * * Env: * LT_BASE http://localhost:3101 * LT_WATCH_SEC 960 how long to keep the display open * LT_SHOT_EVERY_SEC 30 screenshot cadence * LT_OUT_DIR ./results/diashow */ import { chromium, devices } from '@playwright/test'; import { mkdirSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); const BASE = process.env.LT_BASE ?? 'http://localhost:3101'; const WATCH_SEC = parseInt(process.env.LT_WATCH_SEC ?? '960', 10); const SHOT_EVERY = parseInt(process.env.LT_SHOT_EVERY_SEC ?? '30', 10); const OUT = process.env.LT_OUT_DIR ?? join(__dirname, 'results', 'diashow'); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function joinGuest(name) { const res = await fetch(`${BASE}/api/v1/join`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ display_name: name }), }); if (res.status !== 201) throw new Error(`join failed ${res.status}`); return res.json(); } async function main() { mkdirSync(OUT, { recursive: true }); const guest = await joinGuest('Showcase Display'); console.log('[diashow] joined showcase guest, launching browser…'); const browser = await chromium.launch(); // A TV/projector is landscape; use a desktop-ish large viewport. const ctx = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const page = await ctx.newPage(); let consoleErrors = 0; page.on('console', (msg) => { if (msg.type() === 'error') { consoleErrors++; console.log('[diashow][console.error]', msg.text().slice(0, 200)); } }); page.on('pageerror', (e) => console.log('[diashow][pageerror]', String(e).slice(0, 200))); page.on('crash', () => console.log('[diashow][CRASH] page crashed')); // Seed auth in localStorage on the origin, then open /diashow. await page.goto(`${BASE}/join`, { waitUntil: 'domcontentloaded' }); await page.evaluate((g) => { localStorage.setItem('eventsnap_theme', 'dark'); 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 Display'); localStorage.setItem('eventsnap_guide_seen', '1'); }, guest); await page.goto(`${BASE}/diashow`, { waitUntil: 'domcontentloaded' }); console.log(`[diashow] on /diashow, watching for ${WATCH_SEC}s (shots every ${SHOT_EVERY}s)`); const start = Date.now(); let shot = 0; while ((Date.now() - start) / 1000 < WATCH_SEC) { await sleep(SHOT_EVERY * 1000); const elapsed = Math.round((Date.now() - start) / 1000); // best-effort slide count (diashow renders ; adjust selector if markup changes) let imgs = -1; try { imgs = await page.locator('img').count(); } catch { /* ignore */ } const path = join(OUT, `diashow-t${String(elapsed).padStart(4, '0')}.png`); await page.screenshot({ path }).catch(() => {}); console.log(`[diashow][t+${elapsed}s] imgs≈${imgs} consoleErrors=${consoleErrors} → ${path}`); shot++; } console.log(`[diashow] done. ${shot} screenshots, ${consoleErrors} console errors total.`); await browser.close(); } main().catch((e) => { console.error('[diashow] failed:', e); process.exit(1); });