// Screenshot helper: node scripts/shot.mjs [width] [height] [action] // action: "welcome" keeps the first-run dialog open, anything else starts audio first. import { chromium } from 'playwright'; const [, , url = 'http://localhost:5178/', out = '/tmp/shot.png', w = '1440', h = '900', action = 'start'] = process.argv; const browser = await chromium.launch({ channel: 'chrome', args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--autoplay-policy=no-user-gesture-required'], }); const page = await browser.newPage({ viewport: { width: +w, height: +h }, deviceScaleFactor: 1, colorScheme: 'dark', }); const logs = []; page.on('console', (m) => logs.push(`[${m.type()}] ${m.text()}`)); page.on('pageerror', (e) => logs.push(`[pageerror] ${e.message}\n${e.stack ?? ''}`)); page.on('requestfailed', (r) => logs.push(`[404?] ${r.url()} ${r.failure()?.errorText ?? ''}`)); await page.goto(url, { waitUntil: 'networkidle' }); await page.waitForTimeout(1200); if (action !== 'welcome') { const start = page.locator('#welcome button', { hasText: 'Enable audio' }); if (await start.count()) await start.click(); await page.waitForTimeout(2600); } const probe = await page.evaluate(() => { const c = document.querySelector('canvas'); const strip = document.getElementById('strip'); const path = document.querySelector('.path'); return { canvas: c ? { w: c.width, h: c.height, cssW: c.clientWidth, cssH: c.clientHeight } : null, stripH: strip?.clientHeight, pathH: path?.scrollHeight, pathClipped: path ? path.scrollHeight > path.parentElement.clientHeight : null, status: document.getElementById('status-text')?.textContent, hud: document.querySelector('.hud')?.innerText.replace(/\n/g, ' | '), ear: document.querySelector('.path .total')?.innerText.replace(/\t/g, ' '), }; }); await page.screenshot({ path: out }); console.log(JSON.stringify(probe, null, 2)); console.log('--- console ---'); console.log(logs.filter((l) => !l.includes('GL Driver')).join('\n') || '(clean)'); await browser.close();