// 对拍比较:把两批截图逐对做差值,给出"改完到底有没有动到画面"的判定。 // 用法:node tools/compare.mjs [--tag A] [--tag B] [--threshold 1.0] // 例: node tools/compare.mjs before after import { spawn } from "node:child_process"; import { readdir } from "node:fs/promises"; import { resolve } from "node:path"; const argv = process.argv.slice(2); const [dirA, dirB] = argv; if (!dirA || !dirB) { console.error("usage: node tools/compare.mjs [--threshold 1.0]"); process.exit(2); } const opt = (name, dflt) => { const i = argv.indexOf(`--${name}`); return i >= 0 ? argv[i + 1] : dflt; }; const threshold = Number(opt("threshold", "1.0")); function run(cmd, args) { return new Promise((resolve) => { const p = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] }); let out = ""; p.stdout.on("data", (d) => (out += d)); p.stderr.on("data", (d) => (out += d)); p.on("close", () => resolve(out)); }); } const A = resolve("tools/shots", dirA); const B = resolve("tools/shots", dirB); const files = (await readdir(A)).filter((f) => f.endsWith(".png") && !f.endsWith(".ref.png")).sort(); let worst = 0; let worstName = ""; let failed = 0; for (const f of files) { const a = resolve(A, f); const b = resolve(B, f); const out = await run(process.execPath, ["tools/diff.mjs", a, b]); const m = out.match(/mean\|diff\|=([\d.]+)/); const mean = m ? Number(m[1]) : NaN; const verdict = Number.isNaN(mean) ? "MISSING" : mean < threshold ? "same" : "CHANGED"; if (Number.isNaN(mean)) failed++; else if (mean > worst) { worst = mean; worstName = f; } console.log(`${f.padEnd(28)} mean|diff|=${String(mean).padStart(7)} ${verdict}`); } console.log(`\n${dirA} → ${dirB}:${files.length} 对;最大差异 ${worst.toFixed(3)}/255(${worstName || "无"});判定阈值 ${threshold}`); if (failed) { console.log(`${failed} 对缺少对照文件`); process.exit(1); }