HTTP-level load driver simulating ~100 guests uploading ~1000 images in bursts over a window, plus SSE viewers and one real browser on /diashow. Correlates upload→upload-processed (pipeline latency), waits for the compression backlog to drain against DB ground truth, and emits per-status/latency metrics with pass/fail flags. Includes a realistic-JPEG generator and a diashow-SSE regression check (confirm-diashow-fix.mjs). Run artifacts are gitignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
106 lines
4.0 KiB
JavaScript
106 lines
4.0 KiB
JavaScript
#!/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 <img>; 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);
|
|
});
|