// Cheap layout red/green only: real packaged static files, synthetic HTTP data. // Not media/auth/integration acceptance; run inside network-none browser container. import { chromium, expect } from '@playwright/test'; import assert from 'node:assert/strict'; import { createServer } from 'node:http'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; const stream = { id: 'layoutfixture', title: 'Synthetic local test pattern', status: 'ended', rtmpUrl: 'rtmp://127.0.0.1:1935/live', hlsUrl: 'http://127.0.0.1:8888/live/layoutfixture/index.m3u8' }; const data = { '/api/auth/me': { pubkey: 'ab'.repeat(32), displayName: null, isAdmin: true }, '/api/streams/layoutfixture': stream, '/api/streams/layoutfixture/recordings': [{ file: '2026-10-22_16-00-00-000000.mp4', path: 'live/layoutfixture/recording.mp4' }], '/api/podcasts': [{ id: 'fixture-podcast', title: 'Synthetic recording qualification', feed_url: 'http://127.0.0.1:8095/feeds/fixture-podcast/feed.xml' }], }; const server = createServer(async (req, res) => { const path = new URL(req.url, 'http://127.0.0.1:8095').pathname; if (path.startsWith('/api/')) { res.writeHead(path in data ? 200 : 404, { 'content-type': 'application/json' }); res.end(JSON.stringify(data[path] ?? { error: 'No synthetic route' })); return; } if (path.includes('..')) { res.writeHead(400).end(); return; } const file = path.startsWith('/assets/') ? path : '/index.html'; try { const bytes = await readFile(join('/fixture-public', file)); res.writeHead(200, { 'content-type': file.endsWith('.js') ? 'application/javascript' : file.endsWith('.css') ? 'text/css' : 'text/html' }); res.end(bytes); } catch { res.writeHead(404).end(); } }); await new Promise(resolve => server.listen(8095, '127.0.0.1', resolve)); const browser = await chromium.launch({ executablePath: process.env.CHROMIUM_EXECUTABLE, headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] }); try { const page = await browser.newPage(); await page.route('**/*', route => new URL(route.request().url()).origin === 'http://127.0.0.1:8095' ? route.continue() : route.abort()); for (const width of [390, 1440]) { await page.setViewportSize({ width, height: 900 }); await page.goto('http://127.0.0.1:8095/streams/layoutfixture'); await expect(page.getByRole('heading', { name: 'Recordings', exact: true })).toBeVisible(); const geometry = await page.evaluate(() => ({ width: innerWidth, scrollWidth: document.documentElement.scrollWidth, offenders: [...document.querySelectorAll('body *')].map(e => ({ tag: e.tagName, class: e.className, text: (e.textContent ?? '').trim().slice(0, 80), right: e.getBoundingClientRect().right })) .filter(e => e.right > innerWidth+1) })); console.log(JSON.stringify(geometry)); assert.equal(geometry.scrollWidth <= width+1, true, `horizontal overflow at ${width}px`); } console.log('PASS packaged static dashboard layout at390/1440 with synthetic API; not media/auth acceptance'); } finally { await browser.close(); await new Promise(resolve => server.close(resolve)); }