waybetter/dev-tools/runtime-start-check.mjs
CRT 5c64f2f6f8 dev-tools: the generic development harnesses (perf probes, close check, tab and webview stress)
Moved from wayBetter for X; an app passes its own origin, process name, scripts and fixtures.
2026-09-30 14:48:06 +07:00

57 lines
3.2 KiB
JavaScript

// Document-start check for an app's page runtime: the built runtime injected the way the app does
// it, as an init script that runs before the parser has created <html>, into static fixtures of the
// site. Every feature flag is switched on, then off, then back to its default; any console error,
// any message with the app's `[label]` prefix or any page error fails the check. Not a live test.
//
// import { chromium } from 'playwright';
// import { runtimeStartCheck } from '<waybetter>/dev-tools/runtime-start-check.mjs';
// const { problems, checked } = await runtimeStartCheck({
// chromium, runtime: code, global: 'MyApp', label: 'wayBetter for Site', origin: 'https://site.test',
// queued: 'window.__btPane={redundantNav:true};', // what native code queues before the runtime exists
// featureDefaults: { someFlag: true },
// pages: [{ name: 'home', path: '/home', html: '<!doctype html>…' }],
// verify: { expression: '({ … })', check: (result, page) => 'a problem' | null },
// });
/** Runs the check; resolves to `{ problems, checked }`. */
export async function runtimeStartCheck(options) {
const { chromium, runtime, global, label, origin, pages, featureDefaults = {}, queued = '', verify } = options;
const browser = await chromium.launch();
const context = await browser.newContext({ viewport: { width: 1100, height: 800 } });
if (queued) await context.addInitScript({ content: queued });
await context.addInitScript({ content: runtime });
const problems = [];
let checked = 0;
try {
for (const item of pages) {
const page = await context.newPage();
page.on('console', message => { if (message.type() === 'error' || message.text().includes(`[${label}]`)) problems.push(`${item.name}: ${message.text().slice(0, 240)}`); });
page.on('pageerror', error => problems.push(`${item.name}: page error ${String(error).slice(0, 240)}`));
await page.route(`${origin}/**`, route => route.fulfill({ contentType: 'text/html', body: item.html }));
await page.goto(`${origin}${item.path}`);
const result = await page.evaluate(async ({ name, defaults, expression }) => {
const runtime = window[name];
if (!runtime) return { runtime: false };
const all = value => Object.fromEntries(Object.keys(defaults).map(key => [key, value]));
runtime.features.apply(all(true));
await new Promise(resolve => setTimeout(resolve, 200));
runtime.features.apply(all(false));
await new Promise(resolve => setTimeout(resolve, 100));
runtime.features.apply(defaults);
await new Promise(resolve => setTimeout(resolve, 300));
// The app's own expression over the page (what it expects to be there after the toggles).
return { runtime: true, details: expression ? (0, eval)(expression) : null };
}, { name: global, defaults: featureDefaults, expression: verify?.expression ?? '' });
if (!result.runtime) problems.push(`${item.name}: runtime did not install`);
else {
const problem = verify?.check?.(result.details, item);
if (problem) problems.push(`${item.name}: ${problem}`);
}
checked += 1;
await page.close();
}
} finally {
await browser.close();
}
return { problems, checked };
}