fix: 补齐候选诊断环境与证据保护(09-06-release-gate-integrity)

这个提交包含在:
2026-09-08 07:21:19 +08:00
父节点 66f25b1fa8
当前提交 99167127b5
共修改 33 个文件,包含 2915 行新增和 67 行删除
@@ -0,0 +1,58 @@
import assert from "node:assert/strict";
import test from "node:test";
import { observeGuestEntry } from "../browser-environment-probe.mjs";
function fixture({ refresh = false, failed = false, online = true, documentOnline = true, cookies = [], status = 200 } = {}) {
const handlers = {};
let navigated = false;
const page = {
on(event, handler) { handlers[event] = handler; },
async addInitScript() {},
async evaluate() { return online; },
async goto() {
assert.ok(handlers.request);
navigated = true;
handlers.console({ text: () => `QA_ENV_DOCUMENT_ONLINE:${documentOnline}` });
handlers.console({ text: () => "QA_ENV_DOCUMENT_ONLINE:true" });
if (refresh) {
const request = { url: () => "http://127.0.0.1/api/auth/refresh" };
handlers.request(request);
if (failed) handlers.requestfailed(request);
}
return { status: () => status };
},
getByTestId() { return { async waitFor() {} }; },
url() { return "http://127.0.0.1/login"; },
async close() {},
};
return { context: { async storageState() { return { cookies, origins: [] }; }, async newPage() { return page; } }, navigated: () => navigated };
}
test("在线新 context、表单及零 refresh 是独立事实", async () => {
const owned = fixture();
const result = await observeGuestEntry(owned.context, "http://127.0.0.1");
assert.equal(result.valid, true);
assert.equal(result.refreshRequestCount, 0);
assert.equal(result.noRouteMocks, true);
});
for (const failed of [false, true]) test(`表单可见但 refresh 已尝试必须拒绝,失败请求=${failed}`, async () => {
const result = await observeGuestEntry(fixture({ refresh: true, failed }).context, "http://127.0.0.1");
assert.equal(result.valid, false);
assert.equal(result.refreshRequestCount, 1);
assert.equal(result.refreshFailedCount, Number(failed));
assert.ok(result.failures.includes("refresh_attempt_observed"));
});
test("首次 document-start 离线不可被后续 true 覆盖", async () => {
const result = await observeGuestEntry(fixture({ documentOnline: false }).context, "http://127.0.0.1");
assert.equal(result.nativeOnlineSamples.documentStart, false);
assert.equal(result.valid, false);
});
for (const options of [{ online: false }, { cookies: [{ name: "has_session", value: "1" }] }]) test(`前置无效不导航 ${JSON.stringify(options)}`, async () => {
const owned = fixture(options);
assert.equal((await observeGuestEntry(owned.context, "http://127.0.0.1")).valid, false);
assert.equal(owned.navigated(), false);
});
test("非候选 HTTP 200 不合格,禁止公网探针", async () => {
assert.equal((await observeGuestEntry(fixture({ status: 500 }).context, "http://127.0.0.1")).valid, false);
await assert.rejects(observeGuestEntry(fixture().context, "https://example.invalid"), /requires_loopback/);
});
@@ -0,0 +1,129 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { copyFileSync, mkdirSync, readFileSync, rmSync, writeFileSync, unlinkSync, symlinkSync } from "node:fs";
import path from "node:path";
import test from "node:test";
import { validateExactProductionIdentity } from "../../../deploy/trialrun/production-preflight.mjs";
import { browserInventory, createDiagnosticScratch, sourceSnapshot, compareSnapshots } from "../full-local-diagnostic.mjs";
test("全仓快照发现已跟踪 PNG 覆盖、dirty 二次改写、index-only 和删除", context => {
const root = createDiagnosticScratch();
context.after(() => rmSync(root, { recursive: true }));
const git = args => { const result = spawnSync("git", args, { cwd: root, encoding: "utf8" }); assert.equal(result.status, 0, result.stderr); };
mkdirSync(path.join(root, "client")); mkdirSync(path.join(root, "output"));
writeFileSync(path.join(root, ".gitignore"), "output/\n");
writeFileSync(path.join(root, "client/source.ts"), "export {};\n");
const image = path.join(root, "output/archive.png"); writeFileSync(image, "archived");
git(["init", "-q"]); git(["add", "."]); git(["add", "-f", "output/archive.png"]);
git(["-c", "user.name=Fixture", "-c", "[email protected]", "-c", "core.hooksPath=/dev/null", "commit", "-qm", "fixture"]);
const before = sourceSnapshot(root);
assert.equal(before.dirty, false);
writeFileSync(path.join(root, "output/ignored.png"), "new run");
assert.equal(compareSnapshots(before, sourceSnapshot(root)).repositoryUnchanged, true);
writeFileSync(image, "first overwrite"); const first = sourceSnapshot(root);
assert.equal(compareSnapshots(before, first).sourceUnchanged, true);
assert.equal(compareSnapshots(before, first).repositoryUnchanged, false);
assert.ok(first.repository.porcelainV1Z.includes("output/archive.png\0"));
writeFileSync(image, "second overwrite"); const second = sourceSnapshot(root);
assert.equal(first.dirty, true); assert.equal(second.dirty, true);
assert.equal(first.repository.porcelainV1Z, second.repository.porcelainV1Z);
assert.equal(compareSnapshots(first, second).repositoryUnchanged, false);
git(["add", "-f", "output/archive.png"]); const staged = sourceSnapshot(root);
assert.equal(second.repository.trackedDigest, staged.repository.trackedDigest);
assert.notEqual(second.repository.indexDigest, staged.repository.indexDigest);
assert.equal(compareSnapshots(second, staged).repositoryUnchanged, false);
git(["update-index", "--assume-unchanged", "output/archive.png"]);
const flagged = sourceSnapshot(root);
assert.equal(staged.repository.trackedDigest, flagged.repository.trackedDigest);
assert.notEqual(staged.repository.indexDigest, flagged.repository.indexDigest);
assert.equal(compareSnapshots(staged, flagged).repositoryUnchanged, false);
git(["update-index", "--no-assume-unchanged", "output/archive.png"]);
unlinkSync(image); const deleted = sourceSnapshot(root);
assert.equal(deleted.repository.trackedFiles.find(entry => entry.file === "output/archive.png").kind, "missing");
assert.equal(compareSnapshots(staged, deleted).repositoryUnchanged, false);
symlinkSync("absent-target", image); const linked = sourceSnapshot(root);
assert.equal(linked.repository.trackedFiles.find(entry => entry.file === "output/archive.png").kind, "symlink");
assert.equal(compareSnapshots(deleted, linked).repositoryUnchanged, false);
assert.equal(compareSnapshots(null, linked).repositoryUnchanged, false);
assert.equal(compareSnapshots(null, linked).candidateIntegrityStatus, "UNKNOWN");
const incomplete = structuredClone(linked);
delete incomplete.repository.trackedDigest;
assert.equal(compareSnapshots(linked, incomplete).candidateIntegrityStatus, "UNKNOWN");
});
test("诊断临时根不污染原生产身份测试的合成合法路径", context => {
const scratch = createDiagnosticScratch();
context.after(() => rmSync(scratch, { recursive: true }));
const identity = {
deployServer: "production-node",
composeProjectName: "production-project",
remoteDir: path.join(scratch, "server-unit", "hyb-oa-deploy-safety-fixture", "app"),
};
const validate = remoteDir => validateExactProductionIdentity({
...identity,
remoteDir,
expectedDeployServer: identity.deployServer,
expectedComposeProjectName: identity.composeProjectName,
expectedRemoteDir: remoteDir,
});
assert.doesNotThrow(() => validate(identity.remoteDir));
assert.throws(() => validate(path.join(scratch, "hyb-full-local-diagnostic-fixture", "app")), /REMOTE_DIR_NON_PRODUCTION_IDENTITY/);
});
test("原聚合器在 workflow 失败后停止,同库继续参数会破坏该边界", context => {
const scratch = createDiagnosticScratch();
context.after(() => rmSync(scratch, { recursive: true }));
mkdirSync(path.join(scratch, "lib"));
copyFileSync(new URL("../../../server/scripts/regression-all.sh", import.meta.url), path.join(scratch, "regression-all.sh"));
copyFileSync(new URL("../../../server/scripts/lib/regression-report.sh", import.meta.url), path.join(scratch, "lib/regression-report.sh"));
const suites = ["smoke-test", "regression-dedup", "regression-workflow-actions", "regression-procurement", "regression-assets", "regression-audit", "regression-ocr", "regression-im-notifier"];
for (const suite of suites) {
writeFileSync(path.join(scratch, `${suite}.sh`), `#!/usr/bin/env bash\nprintf '%s\\n' '${suite}' >> "$QA_AGGREGATE_FIXTURE_LOG"\nexit ${suite === "regression-workflow-actions" ? 31 : 0}\n`, { mode: 0o755 });
}
const log = path.join(scratch, "invocations.log");
const environment = { PATH: process.env.PATH, HOME: scratch, TMPDIR: scratch, QA_AGGREGATE_FIXTURE_LOG: log };
const run = args => spawnSync("bash", [path.join(scratch, "regression-all.sh"), ...args], { cwd: scratch, env: environment, encoding: "utf8", timeout: 15000 });
const normal = run([]);
assert.equal(normal.status, 31, normal.stderr);
assert.deepEqual(readFileSync(log, "utf8").trim().split("\n"), suites.slice(0, 3));
writeFileSync(log, "");
const continued = run(["--json-report", path.join(scratch, "continued.json")]);
assert.equal(continued.status, 1, continued.stderr);
assert.deepEqual(readFileSync(log, "utf8").trim().split("\n"), suites);
});
const discovery = { cases: [
{ file: "client/tests/e2e/ordinary.spec.ts", id: "first", projectName: "", repeatEachIndex: 0 },
{ file: "client/tests/e2e/ordinary.spec.ts", id: "second", projectName: "", repeatEachIndex: 0 },
] };
test("缺浏览器回执不能从枚举伪造执行通过", () => {
const result = browserInventory(discovery, null);
assert.equal(result.reportPresent, false);
assert.deepEqual(result.cases.map(entry => entry.status), ["NOT_RUN", "NOT_RUN"]);
});
test("保留失败、serial 跳过、预期状态及重试事实", () => {
const execution = { errors: [], suites: [{ specs: [
{ id: "first", tests: [{ projectName: "", expectedStatus: "passed", status: "unexpected", results: [{ status: "failed", retry: 0, duration: 100 }] }] },
{ id: "second", tests: [{ projectName: "", expectedStatus: "passed", status: "skipped", results: [{ status: "skipped", retry: 0, duration: 0 }] }] },
] }] };
const result = browserInventory(discovery, execution);
assert.deepEqual(result.cases.map(entry => entry.status), ["failed", "skipped"]);
assert.equal(result.cases[1].reportedStatus, "skipped");
assert.equal(result.cases[0].results[0].retry, 0);
});
test("部分回执只为实际返回的用例计结果", () => {
const result = browserInventory(discovery, { errors: [{}], suites: [{ specs: [{ id: "first", tests: [{ results: [{ status: "passed" }] }] }] }] });
assert.deepEqual(result.cases.map(entry => entry.status), ["passed", "NOT_RUN"]);
assert.equal(result.executionErrors, 1);
});
test("重复和未知用例作为报告问题单列", () => {
const result = browserInventory(discovery, { suites: [{ specs: [
{ id: "unknown", tests: [{}, {}] },
] }] });
assert.deepEqual(result.issues, ["duplicate_case", "unknown_cases"]);
assert.ok(result.cases.every(entry => entry.status === "NOT_RUN"));
});
@@ -0,0 +1,115 @@
import assert from "node:assert/strict";
import test from "node:test";
import { spawnSync } from "node:child_process";
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs";
import path from "node:path";
import os from "node:os";
import { fileURLToPath } from "node:url";
import { createServer } from "node:net";
import { topologyMatches, validateEnvironmentFacts, validateEnvironmentReceipt, verifyManagedEnvironment, requestSupervisorObservation } from "../full-local-environment.mjs";
function receipt() {
return {
schemaVersion: 1, profile: "isolated-online-v1", policyDigest: "policy",
supervisorObservation: { source: "live-supervisor-unix-ipc", sampledAt: "2026-09-07T00:00:00Z", namespace: "parent" },
namespace: { inheritedRunner: "parent", ownedChild: "child", runnerCreated: true, currentMatchesOwnedChild: true, topologyMatched: true },
nativeOnlineProbe: { valid: true, online: true, browserNamespace: "child", browserMatchesOwnedChild: true, noNavigatorOverride: true },
bootstrapObservation: {
valid: true, observationPoint: "guest-entry-black-box", freshContext: true, initialSessionHint: false,
listenerBeforeNavigation: true, noRouteMocks: true, loginFormVisible: true, refreshRequestCount: 0, loginRequestCount: 0,
nativeOnlineSamples: { beforeNavigation: true, documentStart: true, afterForm: true },
browserNamespace: "child", browserMatchesOwnedChild: true, noNavigatorOverride: true, documentStatus: 200, stayedOnLogin: true,
},
};
}
test("环境回执必须消费事实而非 valid 标记", () => {
assert.deepEqual(validateEnvironmentReceipt(receipt(), "policy"), []);
for (const section of ["namespace", "nativeOnlineProbe", "bootstrapObservation", "supervisorObservation"]) {
for (const key of Object.keys(receipt()[section])) {
const value = receipt();
delete value[section][key];
assert.ok(validateEnvironmentReceipt(value, "policy").length, `${section}.${key}`);
}
}
for (const mutate of [
value => { value.bootstrapObservation.refreshRequestCount = 1; },
value => { value.bootstrapObservation.nativeOnlineSamples.documentStart = false; },
value => { value.namespace.ownedChild = "parent"; },
value => { value.nativeOnlineProbe.browserNamespace = "other"; },
value => { value.policyDigest = "old"; },
]) {
const value = receipt(); mutate(value);
assert.ok(validateEnvironmentReceipt(value, "policy").length);
}
});
test("父 runner 实时 IPC 绑定本轮、进程和当前策略,错误响应拒绝", async context => {
const scratch = mkdtempSync(path.join(os.tmpdir(), "hyb-env-ipc-test-"));
const owner = { scratch, runId: "current", supervisorPid: process.pid, policyDigest: "policy" };
let observation = { runId: owner.runId, pid: owner.supervisorPid, policyDigest: owner.policyDigest, namespace: "parent" };
const server = createServer(connection => connection.end(JSON.stringify(observation)));
context.after(async () => { await new Promise(resolve => server.close(resolve)); rmSync(scratch, { recursive: true, force: true }); });
await new Promise(resolve => server.listen(path.join(scratch, "supervisor.sock"), resolve));
assert.equal((await requestSupervisorObservation(owner)).namespace, "parent");
for (const key of ["runId", "pid", "policyDigest", "namespace"]) {
const previous = observation;
observation = { ...previous, [key]: null };
await assert.rejects(requestSupervisorObservation(owner), /response_invalid/);
observation = previous;
}
await assert.rejects(requestSupervisorObservation({ ...owner, scratch: path.join(scratch, "missing") }), /unavailable/);
});
test("缺 context 不接受 GATE_PASSED 或已有 true", () => {
assert.throws(() => verifyManagedEnvironment(""), /context_required/);
const context = { schemaVersion: 1, profile: "isolated-online-v1", policyDigest: "policy", inheritedNamespace: "parent", ownedNamespace: "child" };
const facts = { policyDigest: "policy", supervisorRecognized: true, descendantOfSupervisor: true, supervisorNamespace: "parent", currentNamespace: "child", topologyMatched: true };
assert.deepEqual(validateEnvironmentFacts(context, facts), []);
for (const key of Object.keys(facts)) assert.ok(validateEnvironmentFacts(context, { ...facts, [key]: null }).length, key);
});
test("拓扑只接纳 owned veth 且禁止默认出口", () => {
const addresses = [{ ifname: "lo" }, ...["qaonline", "qapeer"].map((ifname, index) => ({ ifname, flags: ["UP"], operstate: "UP", addr_info: [{ local: `192.0.2.${index + 1}`, prefixlen: 30 }] }))];
assert.equal(topologyMatches(addresses, [], []), true);
assert.equal(topologyMatches(addresses, [{ dst: "default" }], []), false);
assert.equal(topologyMatches(addresses, [], [{ gateway: "::1" }]), false);
assert.equal(topologyMatches([...addresses, { ifname: "eth0" }], [], []), false);
assert.equal(topologyMatches(addresses.slice(0, 2), [], []), false);
});
for (const mismatch of [false, true]) test(`namespace 创建失败不降级,清理须匹配 owner:mismatch=${mismatch}`, context => {
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
const temporary = mkdtempSync(path.join(os.tmpdir(), "hyb-env-command-test-"));
const report = mkdtempSync(path.join(root, "artifacts/qa-flow/environment-command-test-"));
const commands = path.join(temporary, "commands.jsonl");
mkdirSync(path.join(temporary, "bin"));
context.after(() => {
try {
const owned = JSON.parse(readFileSync(path.join(report, "environment/context.private.json")));
assert.ok(path.basename(owned.scratch).startsWith("hyb-qa-managed-"));
rmSync(owned.scratch, { recursive: true, force: true });
} finally {
rmSync(temporary, { recursive: true, force: true });
rmSync(report, { recursive: true, force: true });
}
});
writeFileSync(path.join(temporary, "bin/docker"), `#!${process.execPath}
const fs = require('node:fs');
const args = process.argv.slice(2);
fs.appendFileSync(${JSON.stringify(commands)},JSON.stringify(args)+'\\n');
if (args[0] === 'image') console.log('sha256:synthetic');
if (args[0] === 'run') { fs.writeFileSync(${JSON.stringify(path.join(temporary, "owner"))},args[args.indexOf('--name')+1]); console.log('a'.repeat(64)); }
if (args[0] === 'inspect') console.log('a'.repeat(64)+'|'+(${mismatch} ? 'other-owner' : fs.readFileSync(${JSON.stringify(path.join(temporary, "owner"))},'utf8'))+'|none');
`, { mode: 0o755 });
writeFileSync(path.join(temporary, "bin/unshare"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
const result = spawnSync(process.execPath, [path.join(root, "scripts/qa/full-local-environment.mjs"), "full-local-diagnostic"], {
cwd: root, encoding: "utf8", timeout: 30000,
env: { PATH: `${path.join(temporary, "bin")}:${process.env.PATH}`, HOME: temporary, QA_DIAGNOSTIC_REPORT_DIR: report, QA_DIAGNOSTIC_REASON: "synthetic test", QA_DIAGNOSTIC_OPERATOR: "fixture", QA_DIAGNOSTIC_APPROVAL_REF: "fixture-only", ENVIRONMENT_PASSED: "1" },
});
assert.equal(result.status, 2, result.stderr);
const receipt = JSON.parse(readFileSync(path.join(report, "environment/result.json")));
assert.equal(receipt.status, "ENVIRONMENT_INVALID");
assert.equal(receipt.normalReleaseEligible, false);
assert.equal(receipt.cleanup.containerRemoved, !mismatch);
const calls = readFileSync(commands, "utf8").trim().split("\n").map(JSON.parse);
assert.equal(calls.some(args => args[0] === "rm"), !mismatch);
assert.ok(calls.find(args => args[0] === "run").includes("--pull=never"));
});
@@ -0,0 +1,332 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
for (const [flag, protectedLine] of [
["FAIL_ENVIRONMENT_VERIFY", ' node "$ROOT_DIR/scripts/qa/full-local-environment.mjs" verify'],
["FAIL_ENVIRONMENT_PROBE", ' node "$ROOT_DIR/scripts/qa/browser-environment-probe.mjs" guest "$QA_BASE_URL"'],
["FAIL_ENVIRONMENT_CONSUME", ' node "$ROOT_DIR/scripts/qa/full-local-environment.mjs" report-guest > "$QA_FULL_REPORT_DIR/environment-before-browser.json"'],
]) test(`环境保护拒绝并对真实源码删除敏感:${flag}`, context => {
const assertBlocked = result => {
assert.notEqual(result.status, 0);
assert.ok(!result.commands.some(command => command.args.includes("test:e2e:full")));
};
const owned = fixture(context, { [flag]: "1", QA_ENVIRONMENT_CONTEXT: "synthetic-reference", ENVIRONMENT_PASSED: "1", ONLINE: "1" });
assertBlocked(owned.run());
const source = path.join(owned.root, "scripts/qa/run-web-gates.sh");
const original = readFileSync(source, "utf8");
assert.ok(original.includes(protectedLine));
writeFileSync(source, original.replaceAll(protectedLine, ":"));
assert.throws(() => assertBlocked(owned.run()), assert.AssertionError);
});
function fixture(context, overrides = {}) {
const root = mkdtempSync(path.join(os.tmpdir(), "hyb-full-local-test-"));
context.after(() => rmSync(root, { recursive: true, force: true }));
for (const directory of ["bin", "tmp", "scripts/qa", "server/scripts", "client/dist", "client/tests/e2e", "output"]) mkdirSync(path.join(root, directory), { recursive: true });
writeFileSync(path.join(root, "output/archive.png"), "historical evidence");
for (const file of ["run-web-gates.sh", "run-pwa-cross-version-upgrade.sh", "pwa-cross-version-fixture.sh", "verify-full-local-discovery.mjs", "full-local-diagnostic.mjs", "full-local-diagnostic-stage.sh"]) copyFileSync(path.join(projectRoot, "scripts/qa", file), path.join(root, "scripts/qa", file));
for (const file of ["pwa-cross-version-upgrade.spec.ts", "ordinary.spec.ts"]) writeFileSync(path.join(root, "client/tests/e2e", file), "");
writeFileSync(path.join(root, "client/dist/keep.txt"), "original-dist");
for (const [file, stage] of [["regression-workflow-actions.sh", "workflow-actions"], ["regression-all.sh", "server-regressions"]]) {
writeFileSync(path.join(root, "server/scripts", file), `#!${process.execPath}
const fs = require('node:fs');
fs.appendFileSync(process.env.HYB_QA_FIXTURE_ROOT+'/commands.jsonl',JSON.stringify({command:'regression',stage:'${stage}',args:process.argv.slice(2),database:process.env.QA_DB_NAME})+'\\n');
process.exit((process.env.FAIL_STAGES??'').split(',').includes('${stage}') ? 31 : 0);
`, { mode: 0o755 });
}
const fake = `#!${process.execPath}
const fs = require('node:fs');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const root = process.env.HYB_QA_FIXTURE_ROOT;
if (!root || !path.basename(root).startsWith('hyb-full-local-test-') || path.dirname(process.argv[1]) !== path.join(root, 'bin')) throw Error('fixture_only');
const command = path.basename(process.argv[1]);
const args = process.argv.slice(2);
fs.appendFileSync(path.join(root, 'commands.jsonl'), JSON.stringify({command,args,visualOutput:process.env.QA_VISUAL_OUTPUT_DIR,stage:process.env.QA_DIAGNOSTIC_STAGE,database:process.env.QA_DB_NAME,sha:process.env.VITE_BUILD_SHA,inputs:[process.env.PWA_BUILD_A_DIR,process.env.PWA_BUILD_B_DIR,process.env.PWA_BUILD_A_SHA,process.env.PWA_BUILD_B_SHA]})+'\\n');
if (command === 'node' && args[0]?.endsWith('full-local-environment.mjs')) {
if (process.env.FAIL_ENVIRONMENT) process.exit(41);
if (process.env.FAIL_ENVIRONMENT_VERIFY && args[1] === 'verify') process.exit(41);
if (process.env.FAIL_ENVIRONMENT_CONSUME && args[1] === 'report-guest') process.exit(41);
if (['full-local','full-local-diagnostic'].includes(args[1])) {
const result = spawnSync('bash',[path.join(root,'scripts/qa/run-web-gates.sh'),args[1]],{env:{...process.env,QA_ENVIRONMENT_CONTEXT:'synthetic-reference'},stdio:'inherit'});
process.exit(result.status ?? 1);
}
console.log(JSON.stringify({bootstrapObservation:{valid:true}}));
process.exit(0);
}
if (command === 'node' && args[0]?.endsWith('browser-environment-probe.mjs')) process.exit(process.env.FAIL_ENVIRONMENT_PROBE ? 42 : 0);
if (command === 'node' && (args[0]?.endsWith('verify-full-local-discovery.mjs') || args[0]?.endsWith('full-local-diagnostic.mjs') || args[0] === '-e')) {
const result = spawnSync(process.execPath, args, {env:process.env, stdio:'inherit'});
process.exit(result.status ?? 1);
}
if (command === 'npx' && args[0] === 'vite' && args.includes('--outDir')) {
if (process.env.FAIL_BUILD_SHA === process.env.VITE_BUILD_SHA) process.exit(23);
const directory = args[args.indexOf('--outDir')+1];
fs.mkdirSync(directory, {recursive:true});
fs.writeFileSync(path.join(directory,'index.html'),'synthetic');
if (!process.env.OMIT_SW) fs.writeFileSync(path.join(directory,'sw.js'),'synthetic');
}
if (command === 'npx' && args.includes('--list')) {
for (const name of ['PWA_BUILD_A_DIR','PWA_BUILD_B_DIR','PWA_BUILD_A_SHA','PWA_BUILD_B_SHA']) if (!process.env[name]) process.exit(24);
if (process.env.FAIL_DISCOVERY) process.exit(25);
let files = fs.readdirSync(path.join(root,'client/tests/e2e'));
if (process.env.OMIT_PWA) files = files.filter(file=>!file.startsWith('pwa-'));
const report = {errors:[],suites:files.map(file=>({file,specs:[{file,id:file,title:file,line:1,tests:[{projectName:''}]}]}))};
fs.writeFileSync(process.env.PLAYWRIGHT_JSON_OUTPUT_FILE,JSON.stringify(report));
}
if (command === 'psql' && args.some(arg=>arg.includes('count(*)'))) console.log('0');
if (command === 'dropdb' && process.env.FAIL_CLEANUP_STAGE && process.env.FAIL_CLEANUP_STAGE === process.env.QA_DIAGNOSTIC_STAGE) process.exit(33);
if (command === 'createdb' && process.env.FAIL_PREPARE_STAGE && process.env.FAIL_PREPARE_STAGE === process.env.QA_DIAGNOSTIC_STAGE) process.exit(34);
const failStage = (process.env.FAIL_STAGES??'').split(',').includes(process.env.QA_DIAGNOSTIC_STAGE);
if (command === 'npm' && args[0] === 'test' && failStage) process.exit(32);
if (command === 'npx' && args[0] === 'tsc' && failStage) process.exit(32);
if (command === 'npm' && args.includes('test:e2e:full')) {
if (process.env.MUTATE_ARCHIVE) fs.writeFileSync(path.join(root,'output/archive.png'),'overwritten');
if (process.env.PLAYWRIGHT_JSON_OUTPUT_FILE) {
const discovery = JSON.parse(fs.readFileSync(path.join(root,process.env.QA_DIAGNOSTIC_STAGE?'report/full-local-report/discovery.json':'report/discovery.json')));
for (const suite of discovery.suites) for (const spec of suite.specs) for (const test of spec.tests) Object.assign(test,{status:process.env.FAIL_BROWSER?'unexpected':'expected',expectedStatus:'passed',results:[{status:process.env.FAIL_BROWSER?'failed':'passed',retry:0,duration:1}]});
fs.writeFileSync(process.env.PLAYWRIGHT_JSON_OUTPUT_FILE,JSON.stringify(discovery));
}
if (process.env.FAIL_BROWSER) process.exit(26);
}
if (!['node','npm','npx','curl','psql','createdb','dropdb'].includes(command)) throw Error('unexpected_fixture_command');
`;
writeFileSync(path.join(root, "bin/fake"), fake, { mode: 0o755 });
for (const command of ["node", "npm", "npx", "curl", "psql", "createdb", "dropdb"]) symlinkSync("fake", path.join(root, "bin", command));
const env = {
PATH: `${path.join(root, "bin")}:${process.env.PATH}`, HOME: root, LANG: "C.UTF-8",
HYB_QA_FIXTURE_ROOT: root, TMPDIR: path.join(root, "tmp"),
QA_DB_CLIENT: "host", QA_DB_NAME: "hyb_oa_webgate_fixture", QA_HOST: "127.0.0.1",
QA_FULL_REPORT_DIR: path.join(root, "report"), PWA_REPORT_DIR: path.join(root, "report"),
QA_SERVER_LOG: path.join(root, "server.log"), QA_SERVER_PID_FILE: path.join(root, "server.pid"),
...overrides,
};
writeFileSync(path.join(root, ".gitignore"), "bin/\ntmp/\nreport/\noutput/\ncommands.jsonl\n*.log\n*.pid\n");
for (const args of [["init", "-q"], ["add", "."], ["add", "-f", "output/archive.png"], ["-c", "user.name=QA Fixture", "-c", "[email protected]", "commit", "-qm", "synthetic fixture"]]) {
assert.equal(spawnSync("git", args, { cwd: root, encoding: "utf8" }).status, 0);
}
return {
root,
run(script = "run-web-gates.sh", args = ["full-local"]) {
const result = spawnSync("bash", [path.join(root, "scripts/qa", script), ...args], { env, cwd: root, encoding: "utf8", timeout: 30000 });
return { ...result, commands: existsSync(path.join(root, "commands.jsonl")) ? readFileSync(path.join(root, "commands.jsonl"), "utf8").trim().split("\n").map((line) => JSON.parse(line)) : [] };
},
};
}
for (const ci of [undefined, "1"]) {
test(`full-local 准备 A/B、保留并发、CI=${ci ?? "unset"} 仍强制零重试`, (context) => {
const owned = fixture(context, ci ? { CI: ci } : {});
const result = owned.run();
assert.equal(result.status, 0, result.stderr);
const builds = result.commands.filter((command) => command.args.includes("--outDir"));
assert.deepEqual(builds.map((command) => command.sha), ["111111111111", "222222222222"]);
const discovery = result.commands.find((command) => command.args.includes("--list"));
const browser = result.commands.find((command) => command.args.includes("test:e2e:full"));
assert.ok(!browser.args.some(argument => argument.startsWith("--trace")));
for (const command of [discovery, browser]) {
assert.ok(command);
assert.ok(command.args.includes("--retries=0"));
assert.ok(!command.args.some((argument) => argument.startsWith("--workers") || argument.startsWith("--grep") || argument.startsWith("--shard")));
assert.ok(command.inputs.every(Boolean));
assert.equal(command.visualOutput, path.join(owned.root, "report/visual"));
assert.notEqual(command.inputs[0], command.inputs[1]);
assert.notEqual(command.inputs[2], command.inputs[3]);
}
assert.ok(result.commands.indexOf(discovery) < result.commands.findIndex((command) => command.command === "createdb"));
const environment = result.commands.find(command => command.args[0]?.endsWith("full-local-environment.mjs") && command.args[1] === "verify");
const probe = result.commands.find(command => command.args[0]?.endsWith("browser-environment-probe.mjs"));
const consume = result.commands.find(command => command.args[0]?.endsWith("full-local-environment.mjs") && command.args[1] === "report-guest");
assert.ok(result.commands.indexOf(environment) < result.commands.indexOf(discovery));
assert.ok(result.commands.indexOf(probe) < result.commands.indexOf(consume));
assert.ok(result.commands.indexOf(consume) < result.commands.indexOf(browser));
assert.ok(result.commands.filter((command) => command.command === "npm" && !command.args.includes("test:e2e:full")).every((command) => command.inputs.every((value) => value === null)));
assert.equal(readFileSync(path.join(owned.root, "client/dist/keep.txt"), "utf8"), "original-dist");
assert.deepEqual(readdirSync(path.join(owned.root, "tmp")), []);
assert.ok(existsSync(path.join(owned.root, "report/pwa-build-a.log")));
});
}
for (const overrides of [{ FAIL_BUILD_SHA: "111111111111" }, { FAIL_BUILD_SHA: "222222222222" }, { OMIT_SW: "1" }, { FAIL_DISCOVERY: "1" }, { OMIT_PWA: "1" }]) {
test(`输入或完整发现失败阻止后续阶段 ${JSON.stringify(overrides)}`, (context) => {
const owned = fixture(context, overrides);
const result = owned.run();
assert.notEqual(result.status, 0);
assert.ok(!result.commands.some((command) => command.command === "createdb" || command.args.includes("test:e2e:full")));
assert.deepEqual(readdirSync(path.join(owned.root, "tmp")), []);
assert.ok(existsSync(path.join(owned.root, "report/pwa-build-a.log")));
});
}
test("浏览器失败保留非零并清理本轮输入", (context) => {
const owned = fixture(context, { FAIL_BROWSER: "1" });
const result = owned.run();
assert.equal(result.status, 26);
assert.ok(result.commands.some((command) => command.command === "dropdb"));
assert.deepEqual(readdirSync(path.join(owned.root, "tmp")), []);
assert.ok(existsSync(path.join(owned.root, "report/discovery-coverage.json")));
});
test("原专用 runner 共用输入逻辑且保留自身单 worker", (context) => {
const owned = fixture(context);
const result = owned.run("run-pwa-cross-version-upgrade.sh", []);
assert.equal(result.status, 0, result.stderr);
const browser = result.commands.find((command) => command.args.includes("tests/e2e/pwa-cross-version-upgrade.spec.ts"));
assert.ok(browser.args.includes("--workers=1"));
assert.ok(browser.args.includes("--retries=0"));
assert.ok(browser.inputs.every(Boolean));
assert.deepEqual(readdirSync(path.join(owned.root, "tmp")), []);
});
test("恢复静默排除 PWA 的真实源码变异必须使 full-local 失败", (context) => {
const owned = fixture(context);
const source = path.join(owned.root, "scripts/qa/run-web-gates.sh");
const original = readFileSync(source, "utf8");
const mutated = original.replace("npx playwright test --list --retries=0", "OMIT_PWA=1 npx playwright test --list --retries=0");
assert.notEqual(mutated, original);
writeFileSync(source, mutated);
assert.notEqual(owned.run().status, 0);
});
const diagnosticApproval = {
QA_DIAGNOSTIC_REASON: "synthetic coverage inventory",
QA_DIAGNOSTIC_OPERATOR: "fixture",
QA_DIAGNOSTIC_APPROVAL_REF: "synthetic-test-approval",
};
function runDiagnostic(context, overrides = {}) {
const owned = fixture(context, { ...diagnosticApproval, ...overrides });
const result = owned.run("run-web-gates.sh", ["full-local-diagnostic"]);
const reportFile = path.join(owned.root, "report/diagnostic-results.json");
return { owned, result, report: existsSync(reportFile) ? JSON.parse(readFileSync(reportFile, "utf8")) : null };
}
test("普通 full-local 遇 workflow 失败仍立即停止", context => {
const owned = fixture(context, { FAIL_STAGES: "workflow-actions" });
const result = owned.run();
assert.equal(result.status, 31, result.stderr);
assert.ok(!result.commands.some(command => command.stage === "server-regressions" || command.args.includes("test:e2e:full")));
});
test("诊断全绿仍 PARTIAL/2,每个写库阶段使用全新独占库并清理", context => {
const { result, report, owned } = runDiagnostic(context, { CI: "1", GATE_PASSED: "1" });
assert.equal(result.status, 2, result.stderr);
assert.equal(report.status, "PARTIAL");
assert.ok(report.stages.every(stage => stage.status === "PASS"), JSON.stringify(report.stages));
assert.equal(report.sourceUnchanged, true);
assert.equal(report.repositoryUnchanged, true);
assert.equal(report.cleanAtStart, true);
assert.equal(report.cleanAtEnd, true);
assert.equal(report.browser.cases.length, 2);
assert.ok(report.browser.cases.every(entry => entry.status === "passed"));
const created = result.commands.filter(command => command.command === "createdb");
assert.equal(new Set(created.map(command => command.database)).size, 3);
assert.equal(result.commands.filter(command => command.command === "dropdb").length, 3);
const browser = result.commands.find(command => command.args.includes("test:e2e:full"));
assert.ok(browser.args.includes("--retries=0"));
assert.deepEqual(browser.args.filter(argument => argument.startsWith("--trace")), ["--trace=on"]);
assert.ok(!browser.args.some(argument => /^(--workers|--grep|--shard)/.test(argument)));
assert.deepEqual(result.commands.find(command => command.command === "regression" && command.stage === "server-regressions").args, []);
assert.deepEqual(readdirSync(path.join(owned.root, "tmp")), []);
});
test("诊断跨过独立测试失败,浏览器失败与来源分别留痕", context => {
const { result, report } = runDiagnostic(context, { FAIL_STAGES: "server-unit,workflow-actions,server-regressions", FAIL_BROWSER: "1" });
assert.equal(result.status, 2, result.stderr);
for (const id of ["server-unit", "workflow-actions", "server-regressions", "browser"]) {
const stage = report.stages.find(stage => stage.id === id);
assert.equal(stage.status, "FAIL");
assert.equal(stage.failureKind, "test");
}
assert.ok(report.browser.cases.every(entry => entry.status === "failed"));
});
test("环境有效与 tracked output 被改写必须独立记录", context => {
const { result, report } = runDiagnostic(context, { MUTATE_ARCHIVE: "1" });
assert.equal(result.status, 2);
assert.equal(report.environmentQualified, true);
assert.equal(report.sourceUnchanged, true);
assert.equal(report.repositoryUnchanged, false);
assert.equal(report.cleanAtStart, true);
assert.equal(report.cleanAtEnd, false);
assert.equal(report.candidateIntegrityStatus, "CHANGED");
assert.ok(report.sourceAfter.repository.porcelainV1Z.includes("output/archive.png\0"));
});
for (const missing of Object.keys(diagnosticApproval)) {
test(`诊断缺授权记录拒绝 ${missing}`, context => {
const { result, report } = runDiagnostic(context, { [missing]: " " });
assert.notEqual(result.status, 0);
assert.equal(report, null);
assert.ok(!result.commands.some(command => command.command === "createdb" || command.command === "npx"));
});
}
test("PWA 前置失败只阻断浏览器依赖,其他测试仍执行", context => {
const { result, report } = runDiagnostic(context, { FAIL_BUILD_SHA: "111111111111" });
assert.equal(result.status, 2);
assert.equal(report.stages.find(stage => stage.id === "inputs").status, "FAIL");
assert.equal(report.stages.find(stage => stage.id === "server-unit").status, "PASS");
assert.equal(report.stages.find(stage => stage.id === "browser").status, "NOT_RUN");
assert.ok(!result.commands.some(command => command.args.includes("test:e2e:full")));
});
test("建库失败不执行该套件,后续独立新库仍可验证", context => {
const { result, report } = runDiagnostic(context, { FAIL_PREPARE_STAGE: "workflow-actions" });
assert.equal(result.status, 2);
assert.equal(report.stages.find(stage => stage.id === "workflow-actions").failureKind, "environment");
assert.ok(!result.commands.some(command => command.command === "regression" && command.stage === "workflow-actions"));
assert.equal(report.stages.find(stage => stage.id === "browser").status, "PASS");
});
test("清理失败必须记录,不能被测试退出 0 吞掉", context => {
const { result, report } = runDiagnostic(context, { FAIL_CLEANUP_STAGE: "workflow-actions" });
assert.equal(result.status, 2);
assert.equal(report.stages.find(stage => stage.id === "workflow-actions").failureKind, "cleanup");
assert.equal(report.scratchCleanup, "PASS");
});
test("必要构建失败明确阻断写库及浏览器,不把缺环境记为产品红项", context => {
const { result, report } = runDiagnostic(context, { FAIL_STAGES: "client-build" });
assert.equal(result.status, 2);
assert.equal(report.stages.find(stage => stage.id === "client-build").failureKind, "environment");
for (const id of ["workflow-actions", "server-regressions", "browser"]) {
assert.equal(report.stages.find(stage => stage.id === id).status, "NOT_RUN");
}
assert.ok(report.browser.cases.every(entry => entry.status === "NOT_RUN"));
assert.ok(!result.commands.some(command => command.command === "createdb"));
});
test("移除诊断固定非零保护的源码变异可观测", context => {
const owned = fixture(context, diagnosticApproval);
const source = path.join(owned.root, "scripts/qa/full-local-diagnostic.mjs");
const original = readFileSync(source, "utf8");
const mutated = original.replace("process.exitCode = 2;", "process.exitCode = 0;");
assert.notEqual(mutated, original);
writeFileSync(source, mutated);
const result = owned.run("run-web-gates.sh", ["full-local-diagnostic"]);
assert.equal(result.status, 0, result.stderr);
assert.throws(() => assert.equal(result.status, 2));
});
test("移除诊断 trace 实参的源码变异会被真实命令捕获拒绝", context => {
const owned = fixture(context, diagnosticApproval);
const source = path.join(owned.root, "scripts/qa/full-local-diagnostic-stage.sh");
const original = readFileSync(source, "utf8");
const mutated = original.replace("qa_run_full_browser --trace=on", "qa_run_full_browser");
assert.notEqual(mutated, original);
writeFileSync(source, mutated);
const result = owned.run("run-web-gates.sh", ["full-local-diagnostic"]);
assert.equal(result.status, 2, result.stderr);
const browser = result.commands.find(command => command.args.includes("test:e2e:full"));
assert.ok(browser);
assert.ok(!browser.args.includes("--trace=on"));
assert.throws(() => assert.deepEqual(browser.args.filter(argument => argument.startsWith("--trace")), ["--trace=on"]));
});
@@ -0,0 +1,38 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdirSync, mkdtempSync, writeFileSync, rmSync, symlinkSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { validateRegisteredEvidence } from "../check-trellis-governance.mjs";
function fixture(context) {
const root = mkdtempSync(path.join(os.tmpdir(), "hyb-evidence-test-"));
context.after(() => rmSync(root, { recursive: true, force: true }));
mkdirSync(path.join(root, "output"));
const digest = createHash("sha256").update("archive").digest("hex");
const rows = ["first.png", "second.png"].map(name => `| \`${name}\` | \`${digest}\` |`);
const document = "| 截图 | SHA-256 |\n| --- | --- |\n" + rows.join("\n") + "\n";
writeFileSync(path.join(root, "evidence.md"), document);
for (const name of ["first.png", "second.png"]) writeFileSync(path.join(root, "output", name), "archive");
return { root, document, check: () => validateRegisteredEvidence(root, [{ document: "evidence.md", directory: "output", count: 2 }]) };
}
test("归档登记摘要匹配时计数明确", context => {
assert.deepEqual(fixture(context).check(), { checked: 2, errors: [] });
});
for (const mutation of ["overwrite", "missing", "symlink", "table-missing", "row-missing", "duplicate", "escape", "invalid-hash"]) test(`证据错误必须被机器发现:${mutation}`, context => {
const owned = fixture(context); const image = path.join(owned.root, "output/first.png");
if (mutation === "overwrite") writeFileSync(image, "new run");
if (["missing", "symlink"].includes(mutation)) rmSync(image);
if (mutation === "symlink") symlinkSync("second.png", image);
const changes = {
"table-missing": "history only\n",
"row-missing": owned.document.split("\n").slice(0, 3).join("\n"),
duplicate: owned.document.replace("second.png", "first.png"),
escape: owned.document.replace("first.png", "../first.png"),
"invalid-hash": owned.document.replace(/`[a-f0-9]{64}`/, "`unknown`"),
};
if (mutation in changes) writeFileSync(path.join(owned.root, "evidence.md"), changes[mutation]);
assert.ok(owned.check().errors.length > 0);
});
@@ -0,0 +1,85 @@
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { fullLocalSpecFiles, verifyFullLocalDiscovery } from "../verify-full-local-discovery.mjs";
function fixture(context) {
const root = mkdtempSync(path.join(os.tmpdir(), "hyb-discovery-test-"));
context.after(() => rmSync(root, { recursive: true, force: true }));
const files = ["pwa-cross-version-upgrade.spec.ts", "nested/ordinary.test.ts", "live-smoke.spec.ts", "manual/private.spec.ts"];
for (const file of files) {
const target = path.join(root, "client/tests/e2e", file);
mkdirSync(path.dirname(target), { recursive: true });
writeFileSync(target, "");
}
return root;
}
function report(files) {
return {
errors: [],
suites: files.map((file, index) => ({ file, specs: [{ file, id: `case-${index}`, title: `case ${index}`, line: 1, tests: [{ projectName: "" }] }] })),
};
}
test("归属来自源码:包含专用输入和 live 名称,不包含 manual", (context) => {
const root = fixture(context);
const files = fullLocalSpecFiles(root);
assert.equal(files.length, 3);
assert.ok(files.some((file) => file.endsWith("pwa-cross-version-upgrade.spec.ts")));
assert.ok(files.some((file) => file.endsWith("live-smoke.spec.ts")));
const summary = verifyFullLocalDiscovery(root, report(files));
assert.equal(summary.status, "PASS");
assert.equal(summary.kind, "FULL_LOCAL_DISCOVERY_ONLY");
assert.equal(summary.cases.length, 3);
});
test("只报 manifest 子集或排除 PWA 必须失败", (context) => {
const root = fixture(context);
const files = fullLocalSpecFiles(root).filter((file) => !file.includes("pwa-cross-version"));
const summary = verifyFullLocalDiscovery(root, report(files));
assert.equal(summary.status, "FAIL");
assert.deepEqual(summary.missingSpecs, ["client/tests/e2e/pwa-cross-version-upgrade.spec.ts"]);
});
test("新增非 manual 文件不能被旧清单遗漏", (context) => {
const root = fixture(context);
const original = report(fullLocalSpecFiles(root));
writeFileSync(path.join(root, "client/tests/e2e/newly-added.spec.ts"), "");
assert.deepEqual(verifyFullLocalDiscovery(root, original).missingSpecs, ["client/tests/e2e/newly-added.spec.ts"]);
});
test("完整文件集合也不能掩盖收集错误或零用例", (context) => {
const root = fixture(context);
const failed = report(fullLocalSpecFiles(root));
failed.errors.push({ message: "PWA_BUILD_A_DIR_missing" });
assert.ok(verifyFullLocalDiscovery(root, failed).issues.includes("discovery_errors"));
failed.errors = [];
failed.suites[0].specs[0].tests = [];
assert.ok(verifyFullLocalDiscovery(root, failed).issues.includes("spec_without_cases"));
assert.equal(verifyFullLocalDiscovery(root, {}).status, "FAIL");
});
test("manual、未知或越界来源不能冒充完整集合", (context) => {
const root = fixture(context);
for (const extra of ["manual/private.spec.ts", "unknown.spec.ts", "../../../outside.spec.ts"]) {
const summary = verifyFullLocalDiscovery(root, report([...fullLocalSpecFiles(root), extra]));
assert.equal(summary.status, "FAIL");
assert.ok(summary.issues.includes("unexpected_specs"));
}
});
test("支持 Playwright 的根相对、测试目录相对与绝对文件来源", (context) => {
const root = fixture(context);
const files = fullLocalSpecFiles(root);
const sources = [files[0].replace(/^client\//u, ""), files[1].replace(/^client\/tests\/e2e\//u, ""), path.join(root, files[2])];
assert.equal(verifyFullLocalDiscovery(root, report(sources)).status, "PASS");
});
test("符号链接必须有明确归属,不能静默跳过", (context) => {
const root = fixture(context);
symlinkSync("live-smoke.spec.ts", path.join(root, "client/tests/e2e/linked.spec.ts"));
assert.throws(() => fullLocalSpecFiles(root), /explicit_ownership/u);
});
@@ -0,0 +1,38 @@
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import { mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import * as fs from "node:fs";
import os from "node:os";
import path from "node:path";
import vm from "node:vm";
import test from "node:test";
const require = createRequire(new URL("../../../client/package.json", import.meta.url));
const ts = require("typescript");
for (const configured of [false, true]) test(`截图输出按 spec/加载隔离,配置输出根=${configured}`, context => {
const root = mkdtempSync(path.join(os.tmpdir(), "hyb-visual-path-test-"));
context.after(() => rmSync(root, { recursive: true, force: true }));
mkdirSync(path.join(root, "client"));
const outputRoot = path.join(root, configured ? "report-visual" : "output/playwright");
const directories = [];
for (const [file, marker, output] of [
["internal-readiness-ui.spec.ts", "const WECOM_UA", "[SCREENSHOT_DIR]"],
["pc-finance-admin-visual-wave4.spec.ts", "const visualUser", "[outputDir, taskOutputDir]"],
]) {
const source = readFileSync(new URL(`../../../client/tests/e2e/${file}`, import.meta.url), "utf8");
assert.ok(source.includes(marker));
const prefix = source.slice(0, source.indexOf(marker));
assert.ok(!prefix.includes("07-18-small-team-internal-readiness"));
const script = ts.transpileModule(prefix + `\nglobalThis.observed = ${output};`, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true } }).outputText;
for (let attempt = 0; attempt < 2; attempt += 1) {
const sandbox = { exports: {}, process: { cwd: () => path.join(root, "client"), env: configured ? { QA_VISUAL_OUTPUT_DIR: outputRoot } : {} }, require: name => name === "node:fs" ? fs : name === "node:path" ? path : { test: { use() {} } } };
vm.runInNewContext(script, sandbox);
for (const directory of sandbox.observed) {
assert.ok(directory.startsWith(`${outputRoot}${path.sep}`));
assert.ok(!directory.includes("07-18-small-team-internal-readiness"));
assert.ok(!directories.includes(directory));
directories.push(directory);
}
}
}
});