/ proof-of-concept / fmm3dbie-webgpu-poc
Sign in
proof-of-concept / fmm3dbie-webgpu-poc
fmm3dbie-webgpu-poc / scripts / smoke.mjs
94 lines · 4.3 KBBlameHistoryRaw
1/**
2 * Does the app work in a browser? Serves dist/, opens index.html in headless
3 * Chrome (hardware WebGPU if there is any, SwiftShader otherwise), solves
4 * the quick case, and waits for the status line to report
5 * success. Any console error, page error or failed request fails the run. A
6 * screenshot goes to tmp/smoke.png.
7 *
8 * Run after `vite build`: node scripts/smoke.mjs [case] (default quick)
9 */
10import { createServer } from 'node:http';
11import { mkdir, readFile } from 'node:fs/promises';
12import { extname, join } from 'node:path';
13import puppeteer from 'puppeteer-core';
15const DIST = new URL('../dist/', import.meta.url).pathname;
16const TMP = new URL('../tmp/', import.meta.url).pathname;
17const CHROME = process.env.CHROME_PATH ?? '/usr/bin/google-chrome';
18const CASE = process.argv[2] ?? 'quick';
19const MIME = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml' };
21const server = createServer(async (req, res) => {
22 try {
23 const path = req.url === '/' ? '/index.html' : req.url.split('?')[0];
24 const data = await readFile(join(DIST, path));
25 res.writeHead(200, { 'content-type': MIME[extname(path)] ?? 'application/octet-stream' });
26 res.end(data);
27 } catch {
28 res.writeHead(404);
29 res.end('not found');
30 }
31});
32await new Promise((r) => server.listen(0, '127.0.0.1', r));
33const port = server.address().port;
34await mkdir(TMP, { recursive: true });
36const flagSets = [
37 ['--headless=new', '--no-sandbox', '--enable-unsafe-webgpu', '--enable-features=Vulkan'],
38 ['--headless=new', '--no-sandbox', '--enable-unsafe-webgpu', '--use-webgpu-adapter=swiftshader', '--enable-unsafe-swiftshader'],
39];
40if (process.env.SWIFTSHADER_ONLY) flagSets.shift();
42let ok = false;
43for (const flags of flagSets) {
44 const browser = await puppeteer.launch({ executablePath: CHROME, args: [...flags, '--window-size=1280,1400'], protocolTimeout: 1_800_000 });
45 const problems = [];
46 try {
47 const page = await browser.newPage();
48 await page.setViewport({ width: 1280, height: 1400 });
49 page.on('console', (m) => {
50 if (process.env.VERBOSE) console.log(` [${m.type()}] ${m.text()}`);
51 if (m.type() === 'error') problems.push(`console: ${m.text()}`);
52 });
53 page.on('pageerror', (e) => problems.push(`pageerror: ${e.message}`));
54 page.on('requestfailed', (r) => problems.push(`request failed: ${r.url()}`));
55 await page.goto(`http://127.0.0.1:${port}/index.html?noroom`, { waitUntil: 'load' });
56 await page.waitForFunction(() => /^ready/.test(document.getElementById('status')?.textContent ?? ''), { timeout: 60_000, polling: 250 });
57 console.log(await page.$eval('#device', (e) => e.textContent));
58 await page.select('#preset', CASE);
59 await page.select('#ngrid', '61');
60 const t0 = Date.now();
61 await page.click('#solve');
62 let lastStatus = '';
63 const poll = setInterval(async () => {
64 try {
65 const s = await page.$eval('#status', (e) => e.textContent);
66 if (s !== lastStatus && !/iteration/.test(s)) console.log(` ${s}`);
67 lastStatus = s;
68 } catch {}
69 }, 2000);
70 await page.waitForFunction(() => /^(done|failed)/.test(document.getElementById('status')?.textContent ?? ''), { timeout: 1_800_000, polling: 500 });
71 clearInterval(poll);
72 const status = await page.$eval('#status', (e) => e.textContent);
73 const err = await page.$eval('#err', (e) => e.textContent);
74 const rows = await page.$$eval('#results tr', (trs) => trs.map((tr) => [...tr.children].map((td) => td.textContent).join(': ')));
75 console.log(`status: ${status} (${((Date.now() - t0) / 1000).toFixed(1)} s)`);
76 for (const r of rows) console.log(` ${r}`);
77 if (err) problems.push(`error line: ${err}`);
78 await new Promise((r) => setTimeout(r, 1000));
79 await page.screenshot({ path: join(TMP, 'smoke.png'), fullPage: true });
80 console.log(`screenshot: ${join(TMP, 'smoke.png')}`);
81 if (!/^done/.test(status)) problems.push(`status: ${status}`);
82 ok = problems.length === 0;
83 } catch (e) {
84 problems.push(`harness: ${e.message}`);
85 } finally {
86 await browser.close();
87 }
88 for (const p of problems) console.log(`problem: ${p}`);
89 if (ok) break;
90 console.log('retrying with next flag set…');
92server.close();
93console.log(ok ? 'smoke test passed' : 'smoke test FAILED');
94process.exit(ok ? 0 : 1);
↑↓ move↵openescclose