/** * Does the app work in a browser? Serves dist/, opens index.html in headless * Chrome (hardware WebGPU if there is any, SwiftShader otherwise), solves * the quick case, and waits for the status line to report * success. Any console error, page error or failed request fails the run. A * screenshot goes to tmp/smoke-.png. * * Run after `vite build`: node scripts/smoke.mjs [case] [problem] (default quick room; problem room or cymbal) */ import { createServer } from 'node:http'; import { mkdir, readFile } from 'node:fs/promises'; import { extname, join } from 'node:path'; import puppeteer from 'puppeteer-core'; const DIST = new URL('../dist/', import.meta.url).pathname; const TMP = new URL('../tmp/', import.meta.url).pathname; const CHROME = process.env.CHROME_PATH ?? '/usr/bin/google-chrome'; const CASE = process.argv[2] ?? 'quick'; const PROBLEM = process.argv[3] ?? 'room'; const MIME = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml' }; const server = createServer(async (req, res) => { try { const path = req.url === '/' ? '/index.html' : req.url.split('?')[0]; const data = await readFile(join(DIST, path)); res.writeHead(200, { 'content-type': MIME[extname(path)] ?? 'application/octet-stream' }); res.end(data); } catch { res.writeHead(404); res.end('not found'); } }); await new Promise((r) => server.listen(0, '127.0.0.1', r)); const port = server.address().port; await mkdir(TMP, { recursive: true }); const flagSets = [ ['--headless=new', '--no-sandbox', '--enable-unsafe-webgpu', '--enable-features=Vulkan'], ['--headless=new', '--no-sandbox', '--enable-unsafe-webgpu', '--use-webgpu-adapter=swiftshader', '--enable-unsafe-swiftshader'], ]; if (process.env.SWIFTSHADER_ONLY) flagSets.shift(); let ok = false; for (const flags of flagSets) { const browser = await puppeteer.launch({ executablePath: CHROME, args: [...flags, '--window-size=1280,1400'], protocolTimeout: 1_800_000 }); const problems = []; try { const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 1400 }); page.on('console', (m) => { if (process.env.VERBOSE) console.log(` [${m.type()}] ${m.text()}`); if (m.type() === 'error') problems.push(`console: ${m.text()}`); }); page.on('pageerror', (e) => problems.push(`pageerror: ${e.message}`)); page.on('requestfailed', (r) => problems.push(`request failed: ${r.url()}`)); await page.goto(`http://127.0.0.1:${port}/index.html?noroom&problem=${PROBLEM}`, { waitUntil: 'load' }); await page.waitForFunction(() => /^ready/.test(document.getElementById('status')?.textContent ?? ''), { timeout: 60_000, polling: 250 }); console.log(await page.$eval('#device', (e) => e.textContent)); await page.select('#preset', CASE); await page.select('#ngrid', '61'); const t0 = Date.now(); await page.click('#solve'); let lastStatus = ''; const poll = setInterval(async () => { try { const s = await page.$eval('#status', (e) => e.textContent); if (s !== lastStatus && !/iteration/.test(s)) console.log(` ${s}`); lastStatus = s; } catch {} }, 2000); await page.waitForFunction(() => /^(done|failed)/.test(document.getElementById('status')?.textContent ?? ''), { timeout: 1_800_000, polling: 500 }); clearInterval(poll); const status = await page.$eval('#status', (e) => e.textContent); const err = await page.$eval('#err', (e) => e.textContent); const rows = await page.$$eval('#results tr', (trs) => trs.map((tr) => [...tr.children].map((td) => td.textContent).join(': '))); console.log(`status: ${status} (${((Date.now() - t0) / 1000).toFixed(1)} s)`); for (const r of rows) console.log(` ${r}`); if (err) problems.push(`error line: ${err}`); await new Promise((r) => setTimeout(r, 1000)); await page.screenshot({ path: join(TMP, `smoke-${PROBLEM}.png`), fullPage: true }); console.log(`screenshot: ${join(TMP, `smoke-${PROBLEM}.png`)}`); if (!/^done/.test(status)) problems.push(`status: ${status}`); ok = problems.length === 0; } catch (e) { problems.push(`harness: ${e.message}`); } finally { await browser.close(); } for (const p of problems) console.log(`problem: ${p}`); if (ok) break; console.log('retrying with next flag set…'); } server.close(); console.log(ok ? 'smoke test passed' : 'smoke test FAILED'); process.exit(ok ? 0 : 1);