// 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 , 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 '/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: '…' }], // 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 }; }