Skip to content

Commit f04c3d6

Browse files
committed
fix(qa): sanitize ux evidence artifact paths
1 parent da03996 commit f04c3d6

2 files changed

Lines changed: 87 additions & 6 deletions

File tree

scripts/qa/ux-matrix-evidence-producer.ts

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,33 @@ function relativeToArtifactBase(artifactBase: string, filePath: string) {
112112
return path.relative(artifactBase, filePath).split(path.sep).join("/");
113113
}
114114

115+
function sanitizeArtifactText(
116+
value: string,
117+
params: {
118+
artifactBase?: string;
119+
repoRoot: string;
120+
},
121+
) {
122+
const roots = [
123+
{ from: path.resolve(params.repoRoot), to: "<repo-root>" },
124+
{ from: pathToFileURL(path.resolve(params.repoRoot)).href, to: "file://<repo-root>" },
125+
...(params.artifactBase
126+
? [
127+
{ from: path.resolve(params.artifactBase), to: "<artifact-base>" },
128+
{
129+
from: pathToFileURL(path.resolve(params.artifactBase)).href,
130+
to: "file://<artifact-base>",
131+
},
132+
]
133+
: []),
134+
{ from: os.homedir(), to: "<home>" },
135+
{ from: pathToFileURL(os.homedir()).href, to: "file://<home>" },
136+
].filter((entry) => entry.from && entry.from !== path.parse(entry.from).root);
137+
return roots
138+
.toSorted((a, b) => b.from.length - a.from.length)
139+
.reduce((text, entry) => text.replaceAll(entry.from, entry.to), value);
140+
}
141+
115142
function buildExecution(params: {
116143
artifacts: MatrixCell["artifacts"];
117144
source: string;
@@ -195,6 +222,7 @@ function buildEvidenceSummary(params: {
195222

196223
async function runCommandForCell(params: {
197224
args: string[];
225+
artifactBase: string;
198226
command: string;
199227
cwd: string;
200228
logPath: string;
@@ -209,13 +237,22 @@ async function runCommandForCell(params: {
209237
env: process.env,
210238
maxBuffer: 1024 * 1024,
211239
});
212-
await writeText(params.logPath, `$ ${commandLine}\n${stdout}${stderr}`);
240+
await writeText(
241+
params.logPath,
242+
sanitizeArtifactText(`$ ${commandLine}\n${stdout}${stderr}`, {
243+
artifactBase: params.artifactBase,
244+
repoRoot: params.cwd,
245+
}),
246+
);
213247
return {
214248
status: "pass" as const,
215249
wallMs: Date.now() - startedAt,
216250
};
217251
} catch (error) {
218-
const details = error instanceof Error ? error.message : String(error);
252+
const details = sanitizeArtifactText(error instanceof Error ? error.message : String(error), {
253+
artifactBase: params.artifactBase,
254+
repoRoot: params.cwd,
255+
});
219256
await writeText(params.logPath, `$ ${commandLine}\nblocked: ${details}\n`);
220257
return {
221258
failureReason: details,
@@ -242,6 +279,7 @@ async function captureControlUiScreenshot(params: {
242279
artifactBase: string;
243280
htmlPath: string;
244281
logPath: string;
282+
repoRoot: string;
245283
screenshotPath: string;
246284
}) {
247285
const startedAt = Date.now();
@@ -264,7 +302,10 @@ async function captureControlUiScreenshot(params: {
264302
wallMs: Date.now() - startedAt,
265303
};
266304
} catch (error) {
267-
const details = error instanceof Error ? error.message : String(error);
305+
const details = sanitizeArtifactText(error instanceof Error ? error.message : String(error), {
306+
artifactBase: params.artifactBase,
307+
repoRoot: params.repoRoot,
308+
});
268309
await writeText(params.logPath, `blocked: ${details}\n`);
269310
return {
270311
failureReason: details,
@@ -363,8 +404,10 @@ async function writeProducerArtifactFixtureHtml(params: {
363404
}
364405

365406
async function captureProducerArtifactFixtureProof(params: {
407+
artifactBase: string;
366408
htmlPath: string;
367409
logPath: string;
410+
repoRoot: string;
368411
screenshotPath: string;
369412
skipVisualProof: boolean;
370413
videoPath: string;
@@ -423,7 +466,10 @@ async function captureProducerArtifactFixtureProof(params: {
423466
wallMs: Date.now() - startedAt,
424467
};
425468
} catch (error) {
426-
const details = error instanceof Error ? error.message : String(error);
469+
const details = sanitizeArtifactText(error instanceof Error ? error.message : String(error), {
470+
artifactBase: params.artifactBase,
471+
repoRoot: params.repoRoot,
472+
});
427473
await writeText(params.logPath, `blocked: ${details}\n`);
428474
return {
429475
failureReason: details,
@@ -494,8 +540,9 @@ export async function runUxMatrixEvidenceProducer(options: ProducerOptions) {
494540
"logs.txt",
495541
);
496542
const cliResult = await runCommandForCell({
497-
command: process.execPath,
498543
args: ["openclaw.mjs", "--help"],
544+
artifactBase: options.artifactBase,
545+
command: process.execPath,
499546
cwd: options.repoRoot,
500547
logPath: cliLogPath,
501548
timeoutMs: 30_000,
@@ -515,6 +562,7 @@ export async function runUxMatrixEvidenceProducer(options: ProducerOptions) {
515562
artifactBase: options.artifactBase,
516563
htmlPath: matrixHtmlPath,
517564
logPath: path.join(screenshotCellDir, "logs.txt"),
565+
repoRoot: options.repoRoot,
518566
screenshotPath: matrixScreenshotPath,
519567
});
520568

@@ -575,8 +623,10 @@ export async function runUxMatrixEvidenceProducer(options: ProducerOptions) {
575623
});
576624

577625
const fixtureProofResult = await captureProducerArtifactFixtureProof({
626+
artifactBase: options.artifactBase,
578627
htmlPath: fixtureHtmlPath,
579628
logPath: path.join(fixtureProofDir, "logs.txt"),
629+
repoRoot: options.repoRoot,
580630
screenshotPath: path.join(fixtureProofDir, "producer-artifact-fixture.png"),
581631
skipVisualProof: options.skipVisualProof,
582632
videoPath: path.join(fixtureProofDir, "producer-artifact-fixture.webm"),

test/scripts/qa-ux-matrix-evidence-producer.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
// QA UX Matrix evidence producer tests cover operator-facing CLI behavior.
22
import { spawnSync } from "node:child_process";
3+
import fs from "node:fs";
4+
import os from "node:os";
35
import path from "node:path";
46
import { describe, expect, it } from "vitest";
57

8+
const repoRoot = path.resolve(__dirname, "../..");
9+
610
function runCli(...args: string[]) {
711
return spawnSync(
812
process.execPath,
913
["--import", "tsx", "scripts/qa/ux-matrix-evidence-producer.ts", ...args],
1014
{
11-
cwd: path.resolve(__dirname, "../.."),
15+
cwd: repoRoot,
1216
encoding: "utf8",
1317
},
1418
);
@@ -47,4 +51,31 @@ describe("QA UX Matrix evidence producer CLI", () => {
4751
expect(result.stderr.trim()).toBe("--artifact-base requires a value");
4852
expectNoNodeStack(result.stderr);
4953
});
54+
55+
it("sanitizes local checkout paths from generated evidence artifacts", () => {
56+
const artifactBase = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-ux-evidence-test-"));
57+
const fakeRepoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-ux-repo-test-"));
58+
try {
59+
const result = runCli(
60+
"--artifact-base",
61+
artifactBase,
62+
"--repo-root",
63+
fakeRepoRoot,
64+
"--skip-visual-proof",
65+
);
66+
67+
expect(result.status).toBe(0);
68+
const evidence = fs.readFileSync(path.join(artifactBase, "qa-evidence.json"), "utf8");
69+
const cliLog = fs.readFileSync(
70+
path.join(artifactBase, "surfaces", "cli", "stages", "entrypoint-help", "logs.txt"),
71+
"utf8",
72+
);
73+
expect(evidence).not.toContain(fakeRepoRoot);
74+
expect(cliLog).not.toContain(fakeRepoRoot);
75+
expect(`${evidence}\n${cliLog}`).toContain("<repo-root>");
76+
} finally {
77+
fs.rmSync(artifactBase, { recursive: true, force: true });
78+
fs.rmSync(fakeRepoRoot, { recursive: true, force: true });
79+
}
80+
});
5081
});

0 commit comments

Comments
 (0)