4f822e1turing-surface-cache: reaction-diffusion solutions at a chosen end time, shared through a cloud cacheJeremy Magland 1/** Screenshot the app (dist/) in headless Chrome after the boot-time solve
2 * finishes. Talks to the real cache.
3 * Usage: node scripts/screenshot.mjs out.png [light|dark] [tEnd] */
4import { createServer } from 'node:http';
5import { readFile } from 'node:fs/promises';
6import { extname, join } from 'node:path';
7import puppeteer from 'puppeteer-core';
9const out = process.argv[2] ?? 'demo.png';
10const scheme = process.argv[3] ?? 'light';
11const tEnd = process.argv[4] ?? '100';
12const DIST = new URL('../dist/', import.meta.url).pathname;
13const MIME = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css' };
15const server = createServer(async (req, res) => {
16 try {
17 const path = req.url === '/' ? '/index.html' : req.url.split('?')[0];
18 const data = await readFile(join(DIST, path));
19 res.writeHead(200, { 'content-type': MIME[extname(path)] ?? 'application/octet-stream' });
20 res.end(data);
21 } catch {
22 res.writeHead(404);
23 res.end();
24 }
25});
26await new Promise((r) => server.listen(0, '127.0.0.1', r));
27const port = server.address().port;
29const browser = await puppeteer.launch({
30 executablePath: process.env.CHROME_PATH ?? '/usr/bin/google-chrome',
31 args: ['--headless=new', '--no-sandbox', '--enable-unsafe-webgpu',
32 '--use-webgpu-adapter=swiftshader', '--enable-unsafe-swiftshader'],
b0546a9Fix the command line's file writing, and how it gets updatedJeremy Magland 33 // A wait is one CDP call lasting as long as the wait, so the default 180 s
34 // cap on a call is what a slow run trips over first.
35 protocolTimeout: 900_000,
4f822e1turing-surface-cache: reaction-diffusion solutions at a chosen end time, shared through a cloud cacheJeremy Magland 36});
37const page = await browser.newPage();
38await page.setViewport({ width: 1100, height: 900 });
39await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: scheme }]);
40page.on('console', (m) => console.log(' [page]', m.text()));
41// The ?tend hook accepts any end time, listed or not, so a short test run
42// can be screenshotted too.
43await page.goto(`http://127.0.0.1:${port}/index.html?tend=${tEnd}`, { waitUntil: 'load' });
44await page.waitForSelector('#tend');
45// Terminal statuses only — "checking the cloud cache…" is transient. On a
46// miss the page settles on empty windows; press the button so the screenshot
47// shows a pattern either way.
48const idle = /from the cloud cache|press Compute solution|failed/;
49await page.waitForFunction(
50 (re) => new RegExp(re).test(document.getElementById('status')?.textContent ?? '') ||
51 (document.getElementById('err')?.textContent?.length ?? 0) > 4,
52 { timeout: 600_000 },
53 idle.source,
54);
55if (/press Compute solution/.test(await page.$eval('#status', (el) => el.textContent))) {
56 await page.click('#solve');
57 await page.waitForFunction(
58 () => /Not uploaded|Uploaded \d|from the cloud cache|failed/.test(
59 document.getElementById('status')?.textContent ?? '') ||
60 (document.getElementById('err')?.textContent?.length ?? 0) > 4,
61 { timeout: 600_000 },
62 );
63}
64await new Promise((r) => setTimeout(r, 300));
65await page.screenshot({ path: out });
66console.log('screenshot:', out);
67console.log('status:', await page.$eval('#status', (el) => el.textContent));
68const err = await page.$eval('#err', (el) => el.textContent);
69if (err) console.log('err:', err);
70await browser.close();
71server.close();