[codex] Improve CI test coverage (#212)

* Improve CI test coverage

* Stabilize live E2E harness

* Shard live E2E CI

* Cache live E2E CI dependencies

* Stabilize live E2E smoke CI

* Update generated live browser bundles

* Tighten live E2E smoke runtime

* Prevent live E2E smoke hangs

* Stabilize live E2E CI coverage

* Fix stale accept DOM cleanup

* Regenerate live browser outputs
This commit is contained in:
Paul Bakaus
2026-06-08 10:39:12 -07:00
committed by GitHub
parent 1aedbcf538
commit 82801a4894
30 changed files with 2585 additions and 262 deletions
+76 -27
View File
@@ -1747,6 +1747,10 @@ export async function runAgentLoop({
}),
signal,
});
if (completionType === 'agent_done' && acceptResult.handled === true && acceptResult.carbonize === true) {
await runLiveComplete({ tmp, scriptsDir, id: event.id });
log(`completed carbonize session ${event.id}`);
}
} catch (err) {
if (signal.aborted) return;
log('accept failed: ' + err.message);
@@ -1876,20 +1880,32 @@ export async function applySteerEdits(tmp, { file, edits }) {
async function handleSteerDeterministic(context) {
const { targetFileAbs, target } = context;
let body = await fs.readFile(targetFileAbs, 'utf-8');
const next = addSteerMarkerToSource(body, target);
if (next === body) return;
if (!next) {
const { classes = 'hero-title', tag = 'h1' } = target;
const classToken = classes.split(/\s+/)[0];
throw new Error(`steer target <${tag}.${classToken}> not found in ${targetFileAbs}`);
}
body = next;
await fs.writeFile(targetFileAbs, body, 'utf-8');
}
export function addSteerMarkerToSource(body, target = { classes: 'hero-title', tag: 'h1' }) {
const attr = `${STEER_MARKER_ATTR}="${STEER_MARKER_VALUE}"`;
if (body.includes(attr)) return;
if (body.includes(attr)) return body;
const { classes = 'hero-title', tag = 'h1' } = target;
const classToken = classes.split(/\s+/)[0];
const escapedTag = escapeRegExp(tag);
const escapedClass = escapeRegExp(classToken);
const classValue = `(?:["'][^"']*\\b${escapedClass}\\b[^"']*["']|\\{[^}]*\\b${escapedClass}\\b[^}]*\\})`;
const openTagRe = new RegExp(
`(<${tag}\\b(?=[^>]*\\b(?:className|class)=["'][^"']*\\b${classToken}\\b)[^>]*)(>)`,
`(<${escapedTag}\\b(?=[^>]*\\b(?:className|class)\\s*=\\s*${classValue})[^>]*)(>)`,
'i',
);
if (!openTagRe.test(body)) {
throw new Error(`steer target <${tag}.${classToken}> not found in ${targetFileAbs}`);
}
body = body.replace(openTagRe, `$1 ${attr}$2`);
await fs.writeFile(targetFileAbs, body, 'utf-8');
if (!openTagRe.test(body)) return null;
return body.replace(openTagRe, `$1 ${attr}$2`);
}
function findSteerTargetFileSync(tmp, target) {
@@ -1976,26 +1992,12 @@ async function runCarbonizeCleanup({ tmp, file, sessionId /* , variant */ }) {
}
// 2. Unwrap the temporary `<div data-impeccable-variant="N" ...>` placed
// around the accepted content. live-accept emits this wrapper with
// `style="display: contents"` so it doesn't affect layout. We strip the
// wrapper open/close lines and keep what's between.
// Match the opening div (any single line) followed by inner content
// followed by `</div>`, where the open carries data-impeccable-variant
// and is NOT inside a data-impeccable-variants wrapper (the variants
// wrapper has the trailing `s`).
body = body.replace(
/^([ \t]*)<div\b[^>]*\bdata-impeccable-variant="[^"]+"[^>]*>\n([\s\S]*?)\n[ \t]*<\/div>\n/m,
(match, indent, inner) => {
// Re-indent inner content to the wrapper's indent level.
const innerLines = inner.split('\n');
const innerIndent = (innerLines[0].match(/^\s*/) || [''])[0];
const dedented = innerLines.map((l) => {
if (l.startsWith(innerIndent)) return indent + l.slice(innerIndent.length);
return l;
}).join('\n');
return expandAcceptedVariantMarkup(dedented, indent) + '\n';
},
);
// around the accepted content. For JSX targets, live-accept also adds an
// outer `<div data-impeccable-carbonize>` so the carbonize block and accepted
// node occupy one child slot; strip that shell after the accepted node is
// clean.
body = unwrapDivAttributeWrapper(body, 'data-impeccable-variant', { expandSingleLineContainer: true });
body = unwrapDivAttributeWrapper(body, 'data-impeccable-carbonize');
// 3. Strip any `data-impeccable-hoist-id` attributes the normalize step
// may have injected when the model emitted inline styles. The hoisted
@@ -2007,6 +2009,49 @@ async function runCarbonizeCleanup({ tmp, file, sessionId /* , variant */ }) {
await fs.writeFile(filePath, body, 'utf-8');
}
function unwrapDivAttributeWrapper(body, attrName, { expandSingleLineContainer = false } = {}) {
const lines = String(body).split('\n');
const attrRe = new RegExp(`\\b${escapeRegExp(attrName)}=`);
for (let i = 0; i < lines.length; i++) {
if (!/<div\b/.test(lines[i]) || !attrRe.test(lines[i])) continue;
const indent = (lines[i].match(/^(\s*)/) || [''])[1];
let depth = countDivDepthDelta(lines[i]);
for (let j = i + 1; j < lines.length; j++) {
depth += countDivDepthDelta(lines[j]);
if (depth !== 0) continue;
let replacement = reindentWrapperBody(lines.slice(i + 1, j), indent).join('\n');
if (expandSingleLineContainer) {
replacement = expandAcceptedVariantMarkup(replacement, indent);
}
lines.splice(i, j - i + 1, ...replacement.split('\n'));
return lines.join('\n');
}
}
return body;
}
function countDivDepthDelta(line) {
return countMatches(line, /<div\b/g) - countMatches(line, /<\/div>/g);
}
function countMatches(value, re) {
return [...String(value || '').matchAll(re)].length;
}
function reindentWrapperBody(lines, indent) {
const firstContentLine = lines.find((line) => line.trim() !== '');
const innerIndent = (firstContentLine?.match(/^(\s*)/) || [''])[1] || '';
return lines.map((line) => {
if (line.trim() === '') return '';
if (innerIndent && line.startsWith(innerIndent)) return indent + line.slice(innerIndent.length);
return indent + line.trimStart();
});
}
function expandAcceptedVariantMarkup(source, indent) {
const lines = source.split('\n');
if (lines.length !== 1) return source;
@@ -2083,3 +2128,7 @@ async function runAccept({ tmp, scriptsDir, id, variant, discard, paramValues, p
const last = stdout.trim().split('\n').filter(Boolean).pop();
return JSON.parse(last);
}
async function runLiveComplete({ tmp, scriptsDir, id }) {
await execFileP(process.execPath, [path.join(scriptsDir, 'live-complete.mjs'), '--id', id], { cwd: tmp });
}
+58 -8
View File
@@ -51,9 +51,27 @@ export function stageFixture(name, fixture) {
return tmp;
}
export function runInstall(tmp, command) {
export function runInstall(tmp, command, { timeoutMs = readTimeoutEnv('IMPECCABLE_E2E_INSTALL_TIMEOUT_MS', 180_000) } = {}) {
const [cmd, ...args] = command;
execFileSync(cmd, args, { cwd: tmp, stdio: 'inherit' });
const installArgs = addNpmInstallDefaults(cmd, args);
try {
execFileSync(cmd, installArgs, { cwd: tmp, stdio: 'inherit', timeout: timeoutMs });
} catch (err) {
if (err.signal === 'SIGTERM' || err.signal === 'SIGKILL' || err.killed) {
err.message = `fixture dependency install timed out after ${timeoutMs}ms: ${cmd} ${installArgs.join(' ')}`;
}
throw err;
}
}
function addNpmInstallDefaults(cmd, args) {
if (cmd !== 'npm') return args;
if (!['install', 'ci'].includes(args[0])) return args;
const out = [...args];
for (const flag of ['--prefer-offline', '--no-progress']) {
if (!out.some((arg) => arg === flag || arg.startsWith(flag + '='))) out.push(flag);
}
return out;
}
// ---------------------------------------------------------------------------
@@ -121,11 +139,15 @@ export function startDevServer(tmp, runtime) {
child.stderr.on('data', capture);
const ready = new Promise((resolve, reject) => {
const readyTimeoutMs = readTimeoutEnv(
'IMPECCABLE_E2E_DEV_READY_TIMEOUT_MS',
runtime.readyTimeoutMs ?? 120_000,
);
const timeout = setTimeout(() => {
reject(new Error(
`dev server ready timeout (${runtime.readyTimeoutMs}ms). Tail:\n${bufLog.join('')}`,
`dev server ready timeout (${readyTimeoutMs}ms). Tail:\n${bufLog.join('')}`,
));
}, runtime.readyTimeoutMs ?? 120_000);
}, readyTimeoutMs);
const checkMatch = (buf) => {
const m = buf.toString().match(readyRe);
@@ -145,13 +167,27 @@ export function startDevServer(tmp, runtime) {
return { child, ready, log: () => bufLog.join('') };
}
function readTimeoutEnv(name, fallback) {
const raw = process.env[name];
if (raw == null || raw === '') return fallback;
const parsed = Number(raw);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
export async function stopDevServer(child) {
if (!child || child.killed) return;
const exited = new Promise((resolve) => child.once('exit', resolve));
if (!child || child.exitCode != null || child.signalCode != null) return;
let didExit = false;
const exited = new Promise((resolve) => child.once('exit', () => {
didExit = true;
resolve();
}));
child.kill('SIGTERM');
const timeoutPromise = new Promise((resolve) => setTimeout(resolve, 5_000));
await Promise.race([exited, timeoutPromise]);
if (!child.killed) child.kill('SIGKILL');
if (!didExit && child.exitCode == null && child.signalCode == null) {
child.kill('SIGKILL');
await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 1_000))]);
}
}
// ---------------------------------------------------------------------------
@@ -196,20 +232,27 @@ export async function bootFixtureSession({ name, fixture, browser, agent, wrapTa
};
try {
const startedAt = Date.now();
log(`installing deps`);
runInstall(tmp, runtime.install);
log(`deps installed in ${formatDuration(Date.now() - startedAt)}`);
const liveStartedAt = Date.now();
log(`starting live-server`);
live = startLiveServer(tmp);
log(`live-server ready in ${formatDuration(Date.now() - liveStartedAt)}`);
const injectStartedAt = Date.now();
log(`live-inject --port ${live.port}`);
const injectResult = runInject(tmp, live.port);
if (!injectResult.ok) throw new Error('live-inject failed: ' + JSON.stringify(injectResult));
log(`live-inject complete in ${formatDuration(Date.now() - injectStartedAt)}`);
const devStartedAt = Date.now();
log(`spawning dev server: ${runtime.devCommand.join(' ')}`);
dev = startDevServer(tmp, runtime);
const { port: devPort } = await dev.ready;
log(`dev server ready on ${devPort}`);
log(`dev server ready on ${devPort} in ${formatDuration(Date.now() - devStartedAt)}`);
// Agent loop runs concurrently — abort on teardown.
agentAbort = new AbortController();
@@ -239,10 +282,12 @@ export async function bootFixtureSession({ name, fixture, browser, agent, wrapTa
if (msg.type() === 'error') consoleErrors.push(`console.error: ${msg.text()}`);
});
const pageStartedAt = Date.now();
await page.goto(`${scheme}://127.0.0.1:${devPort}`, {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
log(`page loaded in ${formatDuration(Date.now() - pageStartedAt)}`);
return {
tmp,
@@ -260,3 +305,8 @@ export async function bootFixtureSession({ name, fixture, browser, agent, wrapTa
throw err;
}
}
function formatDuration(ms) {
if (ms < 1_000) return `${ms}ms`;
return `${(ms / 1_000).toFixed(1)}s`;
}
+51 -23
View File
@@ -139,9 +139,21 @@ function installLiveQueryHelpersInPage() {
};
}
export async function installLiveQueryHelpers(page) {
export async function installLiveQueryHelpers(page, { timeout = 5_000 } = {}) {
await page.addInitScript(installLiveQueryHelpersInPage).catch(() => {});
await page.evaluate(installLiveQueryHelpersInPage);
await withTimeout(
page.evaluate(installLiveQueryHelpersInPage),
timeout,
'install live query helpers',
);
}
function withTimeout(promise, timeout, label) {
let timer;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeout}ms`)), timeout);
});
return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timer));
}
async function clickLiveControl(page, selector) {
@@ -605,14 +617,14 @@ export async function clickPrev(page) {
}
async function clickBarButton(page, label) {
await installLiveQueryHelpers(page);
const button = page.locator(`${BAR_ID} button`, { hasText: label });
const textMatch = label instanceof RegExp
? { kind: 'regex', source: label.source, flags: label.flags }
: { kind: 'text', value: String(label) };
let lastErr;
for (let attempt = 0; attempt < 3; attempt++) {
try {
await installLiveQueryHelpers(page);
const button = page.locator(`${BAR_ID} button`, { hasText: label });
await button.click({ timeout: 5_000 });
return;
} catch (err) {
@@ -637,11 +649,19 @@ async function clickBarButton(page, label) {
}
async function dispatchBarButton(page, label) {
await installLiveQueryHelpers(page);
const textMatch = label instanceof RegExp
? { kind: 'regex', source: label.source, flags: label.flags }
: { kind: 'text', value: String(label) };
return page.evaluate(findAndClickBarButton, { barSel: BAR_ID, textMatch });
try {
await installLiveQueryHelpers(page);
const textMatch = label instanceof RegExp
? { kind: 'regex', source: label.source, flags: label.flags }
: { kind: 'text', value: String(label) };
return await withTimeout(
page.evaluate(findAndClickBarButton, { barSel: BAR_ID, textMatch }),
5_000,
'dispatch bar button',
);
} catch {
return false;
}
}
function findAndClickBarButton({ barSel, textMatch }) {
@@ -662,20 +682,28 @@ function findAndClickBarButton({ barSel, textMatch }) {
* Read the currently visible variant index (the "i" in "i/N").
*/
export async function getVisibleVariant(page) {
await installLiveQueryHelpers(page);
return page.evaluate((barSel) => {
const wrapper = window.__impeccableLiveQuery('[data-impeccable-variants]');
if (wrapper) {
const variants = [...wrapper.querySelectorAll('[data-impeccable-variant]:not([data-impeccable-variant="original"])')];
const visible = variants.find((variant) => variant.style.display !== 'none');
const idx = visible ? parseInt(visible.dataset.impeccableVariant || '0', 10) : 0;
if (idx > 0) return idx;
}
const bar = window.__impeccableLiveQuery(barSel);
if (!bar) return null;
const m = (bar.textContent || '').match(/(\d+)\s*\/\s*(\d+)/);
return m ? parseInt(m[1], 10) : null;
}, BAR_ID);
try {
await installLiveQueryHelpers(page);
return await withTimeout(
page.evaluate((barSel) => {
const wrapper = window.__impeccableLiveQuery('[data-impeccable-variants]');
if (wrapper) {
const variants = [...wrapper.querySelectorAll('[data-impeccable-variant]:not([data-impeccable-variant="original"])')];
const visible = variants.find((variant) => variant.style.display !== 'none');
const idx = visible ? parseInt(visible.dataset.impeccableVariant || '0', 10) : 0;
if (idx > 0) return idx;
}
const bar = window.__impeccableLiveQuery(barSel);
if (!bar) return null;
const m = (bar.textContent || '').match(/(\d+)\s*\/\s*(\d+)/);
return m ? parseInt(m[1], 10) : null;
}, BAR_ID),
5_000,
'read visible variant',
);
} catch {
return null;
}
}
/**