frontend-patterns/research/experiments/boundary-recovery/experiment.mjs
tegwick 3eecd74b0d Probe rendering and modal focus boundaries in pinned browser fixture
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9
2026-09-26 22:01:35 +02:00

124 lines
7.4 KiB
JavaScript

import {build} from 'esbuild';
import {chromium} from 'playwright';
import {createServer} from 'node:http';
import {once} from 'node:events';
import {readFile, writeFile} from 'node:fs/promises';
import {createHash} from 'node:crypto';
import {fileURLToPath} from 'node:url';
import assert from 'node:assert/strict';
const dir = fileURLToPath(new URL('.', import.meta.url));
const bundle = await build({entryPoints: [dir + 'fixture.jsx'], bundle: true, write: false,
define: {'process.env.NODE_ENV': '"development"'}, format: 'iife'});
const server = createServer((req, res) => {
if (req.url === '/bundle.js') {
res.writeHead(200, {'Content-Type': 'application/javascript'}); res.end(bundle.outputFiles[0].contents);
} else if (req.url === '/failure') {
res.writeHead(503, {'Content-Type': 'text/plain'}); res.end('synthetic unavailable');
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<!doctype html><html lang="en"><head><title>Boundary probe</title></head><body><div id="root"></div><script src="/bundle.js"></script></body></html>');
}
});
server.listen(0, '127.0.0.1'); await once(server, 'listening');
const url = `http://127.0.0.1:${server.address().port}`;
let browser;
const cases = [];
async function probe(id, query, action) {
const page = await browser.newPage();
page.setDefaultTimeout(5000);
const browserErrors = [];
page.on('pageerror', e => browserErrors.push(e.message));
try {
await page.goto(url + query);
await page.getByRole('button').first().waitFor();
const observed = await action(page);
cases.push({id, status: 'pass', ...observed, browserErrors});
} catch (error) {
cases.push({id, status: 'fail', error: error.message, observed: await page.evaluate(() => window.observed).catch(() => null), browserErrors});
} finally {await page.close();}
}
async function active(page, id) {
try { await page.waitForFunction(id => document.activeElement?.id === id, id, {timeout: 5000}); }
catch (error) { throw new Error(`Expected focus ${id}; got ${await page.evaluate(() => ({id: document.activeElement?.id, tag: document.activeElement?.tagName, dialogs: document.querySelectorAll('[role=dialog]').length})).then(JSON.stringify)}: ${error.message}`); }
}
try {
browser = await chromium.launch({headless: true,
...(process.env.FEP_CHROMIUM_PATH ? {executablePath: process.env.FEP_CHROMIUM_PATH} : {})});
await probe('X01-render-contained', '/', async page => {
await page.getByText('Break render', {exact: true}).click();
await page.getByTestId('inner').waitFor();
await page.getByText('Sibling 0', {exact: true}).click();
await page.getByText('Sibling 1', {exact: true}).waitFor();
const o = await page.evaluate(() => window.observed);
assert(o.caught.some(x => x.boundary === 'inner' && x.message === 'render fault'));
assert.equal(await page.getByTestId('outer').count(), 0);
return {observed: o, siblingUsable: true};
});
await probe('X02-event-not-contained', '/', async page => {
await page.getByText('Break event', {exact: true}).click();
await page.waitForFunction(() => window.observed.globalErrors.some(x => x.includes('event fault')));
const o = await page.evaluate(() => window.observed);
assert.equal(o.caught.length, 0); assert.equal(await page.getByTestId('panel').count(), 1);
return {observed: o, panelPresent: true};
});
await probe('X03-request-explicit-state', '/', async page => {
await page.getByText('Fail request', {exact: true}).click();
await page.getByRole('status').filter({hasText: 'request failed'}).waitFor();
const o = await page.evaluate(() => window.observed);
assert.deepEqual(o.requests, [503]); assert.equal(o.caught.length, 0);
return {observed: o, state: 'request failed'};
});
await probe('X04-fallback-escapes-inner', '/?brokenFallback=1', async page => {
await page.getByText('Break render', {exact: true}).click();
await page.getByTestId('outer').waitFor();
const o = await page.evaluate(() => window.observed);
assert(o.caught.some(x => x.boundary === 'outer' && x.message === 'fallback fault'));
return {observed: o, enclosingFallback: true};
});
await probe('X05-focus-existing-invoker', '/?mode=focus', async page => {
await page.locator('#invoker').focus(); await page.keyboard.press('Enter');
await active(page, 'remove'); await page.keyboard.press('Escape'); await active(page, 'invoker');
return {activeId: await page.evaluate(() => document.activeElement.id)};
});
await probe('X06-focus-removed-default', '/?mode=focus', async page => {
await page.locator('#invoker').focus(); await page.keyboard.press('Enter'); await active(page, 'remove');
await page.keyboard.press('Enter'); await page.getByRole('dialog').waitFor({state: 'detached'});
await page.waitForFunction(() => document.activeElement === document.body);
// Negative control: default restoration cannot choose a workflow target for the removed trigger.
assert.equal(await page.locator('#invoker').count(), 0);
return {activeTag: await page.evaluate(() => document.activeElement.tagName), workflowTargetSelected: false};
});
await probe('X07-focus-removed-explicit', '/?mode=focus&explicit=1', async page => {
await page.locator('#invoker').focus(); await page.keyboard.press('Enter'); await active(page, 'remove');
await page.keyboard.press('Enter'); await active(page, 'next');
return {activeId: await page.evaluate(() => document.activeElement.id), applicationPolicy: true};
});
await probe('X08-nested-return', '/?mode=focus', async page => {
await page.locator('#invoker').focus(); await page.keyboard.press('Enter'); await active(page, 'remove');
await page.keyboard.press('Tab'); await active(page, 'nested-invoker');
await page.keyboard.press('Enter'); await active(page, 'nested-close');
// Experimental timing variable, not a hidden retry: compare immediate versus settled Escape.
if (Number(process.env.FEP_NESTED_SETTLE_MS)) await page.waitForTimeout(Number(process.env.FEP_NESTED_SETTLE_MS));
await page.keyboard.press('Escape'); await active(page, 'nested-invoker');
assert.equal(await page.getByRole('dialog').count(), 1);
await page.keyboard.press('Escape'); await active(page, 'invoker');
return {innerReturn: 'nested-invoker', outerReturn: 'invoker', observed: await page.evaluate(() => window.observed)};
});
const files = {};
for (const name of ['fixture.jsx', 'experiment.mjs', 'package.json', 'package-lock.json']) {
files[name] = createHash('sha256').update(await readFile(dir + name)).digest('hex');
}
const result = {date: new Date().toISOString(), evidence: 'synthetic-browser-fixture-real-libraries',
node: process.version, browser: browser.version(), nestedSettleMs: Number(process.env.FEP_NESTED_SETTLE_MS || 0),
browserSelection: process.env.FEP_CHROMIUM_PATH ? 'explicit executable override' : 'playwright bundled',
packages: JSON.parse(await readFile(dir + 'package.json')).dependencies,
files, cases, passed: cases.filter(x => x.status === 'pass').length, total: cases.length};
const output = process.env.FEP_RESULT_PATH || '/tmp/fep-boundary-result.json';
await writeFile(output, JSON.stringify(result, null, 2) + '\n');
console.log(JSON.stringify({output, passed: result.passed, total: result.total, cases}, null, 2));
if (result.passed !== result.total) process.exitCode = 1;
} finally {
if (browser) await browser.close();
await new Promise(resolve => server.close(resolve));
}