Files
pulse/tests/integration/scripts/run-playwright.mjs
T
pulse-triage[bot] bf1dbbf1a3 Tear down failed E2E setup without clearing another run's handoff
The npm Playwright entrypoint exited inside setup and bypassed its finally block, leaving a partial managed runtime and verification lock behind. Keep the setup failure, skip tests, attempt existing teardown once, and release only the handoff this invocation wrote when its contents still match. Preserve primary failures through teardown errors and do not stop a runtime after lock preparation fails. Add real child-process controls to exhaustive script smoke; test tiers, acquisition deadlines and browser coverage are unchanged.

Change-source: pulse-maintainer
2026-10-10 05:08:02 +01:00

116 lines
4.1 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const nodeCmd = process.execPath;
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
const playwrightArgs = process.argv.slice(2);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..', '..');
function buildRunScopedEnv(env = process.env) {
const configuredRuntimeStatePath = String(env.PULSE_E2E_RUNTIME_STATE_PATH || '').trim();
const configuredRunId = String(env.PULSE_E2E_RUN_ID || '').trim();
const configuredVerifyLockPath = String(env.HOT_DEV_VERIFY_LOCK_FILE || '').trim();
if (configuredRuntimeStatePath !== '') {
return {
...env,
PULSE_E2E_RUN_ID:
configuredRunId || path.basename(configuredRuntimeStatePath).replace(/\.[^.]+$/, ''),
HOT_DEV_VERIFY_LOCK_FILE:
configuredVerifyLockPath || path.join(repoRoot, 'tmp', 'hot-dev.verify.lock'),
};
}
const runId = `run-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
return {
...env,
PULSE_E2E_RUN_ID: String(env.PULSE_E2E_RUN_ID || runId).trim(),
PULSE_E2E_RUNTIME_STATE_PATH: path.join(repoRoot, 'tmp', `${runId}.runtime-state.json`),
HOT_DEV_VERIFY_LOCK_FILE:
configuredVerifyLockPath || path.join(repoRoot, 'tmp', 'hot-dev.verify.lock'),
};
}
function managedVerifyLockPath(env = process.env) {
const configuredPath = String(env.HOT_DEV_VERIFY_LOCK_FILE || '').trim();
return configuredPath || path.join(repoRoot, 'tmp', 'hot-dev.verify.lock');
}
async function writeManagedVerifyLock(env = process.env) {
if (!['1', 'true', 'yes', 'on'].includes(String(env.PULSE_E2E_USE_HOT_DEV || '').trim().toLowerCase())) {
return;
}
const lockPath = managedVerifyLockPath(env);
const contents = `pid=${process.pid}\ncreated_at=${new Date().toISOString()}\nrun_id=${String(env.PULSE_E2E_RUN_ID || '').trim()}\n`;
await fs.mkdir(path.dirname(lockPath), { recursive: true });
await fs.writeFile(lockPath, contents, 'utf8');
return { lockPath, contents };
}
async function clearManagedVerifyLock(lock) {
if (!lock) return;
try {
// Non-hot-dev runs never acquired this handoff. A later owner may also
// have replaced it while teardown ran; do not remove that owner's lock.
if (await fs.readFile(lock.lockPath, 'utf8') === lock.contents) {
await fs.unlink(lock.lockPath);
}
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
}
const run = (command, args, options = {}) =>
new Promise((resolve, reject) => {
const child = spawn(command, args, { stdio: 'inherit', ...options });
child.on('error', reject);
child.on('close', (code) => resolve(code ?? 1));
});
let exitCode = 0;
const childEnv = buildRunScopedEnv();
let verifyLock;
let setupStarted = false;
function reportFailure(phase, err) {
console.error(`[integration] ${phase} failed:`, err?.message || err);
if (exitCode === 0) exitCode = 1;
}
try {
verifyLock = await writeManagedVerifyLock(childEnv);
setupStarted = true;
exitCode = await run(nodeCmd, ['./scripts/pretest.mjs'], { env: childEnv });
// A failed setup may already have started a managed runtime. Keep its
// failure, skip Playwright, and still reach the existing teardown below.
if (exitCode === 0) {
exitCode = await run(npxCmd, ['playwright', 'test', ...playwrightArgs], { env: childEnv });
}
} catch (err) {
reportFailure('setup or test launch', err);
} finally {
try {
// If lock preparation failed, this invocation never started a runtime
// and must not stop a pre-existing one through posttest.
if (setupStarted) {
const posttestCode = await run(nodeCmd, ['./scripts/posttest.mjs'], { env: childEnv });
if (exitCode === 0) exitCode = posttestCode;
}
} catch (err) {
reportFailure('teardown launch', err);
} finally {
try {
await clearManagedVerifyLock(verifyLock);
} catch (err) {
reportFailure('verification lock cleanup', err);
}
}
}
process.exit(exitCode);