test: 零依赖对拍测试台(可在不改 dist/ 的前提下复现 WE 行为)
tools/serve.mjs 本地静态服务 + 测试驱动注入(模拟 WE 下发用户属性、冻结动画相位、藏 canvas 取参考帧) tools/cdp.mjs 最小 CDP 客户端(真实时间等待;--screenshot 的虚拟时间会在骨骼加载完成前就耗尽) tools/shot.mjs 截图/取状态,就绪判据可传入 tools/diff.mjs ffmpeg 差分:PSNR / SSIM / 平均亮度差 tools/capture.mjs 对拍矩阵:壁纸 × 分辨率 × (正常, 藏 canvas) 实测:xilian 1920x1080 spine 层贡献 mean|diff|=26.1/255,kv37 为 10.2/255, 同配置两次截图噪声底 0.68/255。
This commit is contained in:
1 parent
d8c7e492f7
commit
399f99edd7
7 files changed
+499
No files matched your search
@@ -0,0 +1,66 @@
|
||||
// 用 CDP 截图 / 取状态(真实时间等待,见 cdp.mjs 顶部注释)。
|
||||
// 用法:node tools/shot.mjs <out.png|-> <url> <W> <H> [waitMs] [options]
|
||||
// --wait "<js 表达式>" 就绪判据,默认为"骨骼已加载或已报错"
|
||||
// --eval "<js>" 截图前执行的表达式(结果会打印出来)
|
||||
// --settle <ms> 就绪后额外等待,确保至少画过一帧(默认 500)
|
||||
// --dom 不截图,打印 document.title 里的测试状态
|
||||
// --extra "<edge 旗标>" 追加 Edge 旗标
|
||||
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { withPage, sleep } from "./cdp.mjs";
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
const [out, url, w, h, waitMs = "30000"] = argv;
|
||||
if (!out || !url || !w || !h) {
|
||||
console.error('usage: node tools/shot.mjs <out.png|-> <url> <W> <H> [waitMs] [--wait js] [--eval js] [--settle ms] [--dom]');
|
||||
process.exit(2);
|
||||
}
|
||||
const opt = (name, dflt) => {
|
||||
const i = argv.indexOf(`--${name}`);
|
||||
return i >= 0 ? argv[i + 1] : dflt;
|
||||
};
|
||||
const DEFAULT_WAIT = '(window.__test ? !!(window.__test.successAt || window.__test.error) : document.readyState === "complete")';
|
||||
const waitFor = opt("wait", DEFAULT_WAIT);
|
||||
const settle = Number(opt("settle", "500"));
|
||||
const domMode = argv.includes("--dom");
|
||||
const extraArgs = [];
|
||||
if (opt("extra")) extraArgs.push(opt("extra"));
|
||||
|
||||
const result = await withPage({ width: Number(w), height: Number(h), extraArgs }, async ({ send, evaluate, consoleLines, exceptions }) => {
|
||||
await send("Page.navigate", { url });
|
||||
|
||||
const deadline = Date.now() + Number(waitMs);
|
||||
let ready = false;
|
||||
while (Date.now() < deadline) {
|
||||
if (await evaluate(waitFor)) { ready = true; break; }
|
||||
await sleep(150);
|
||||
}
|
||||
|
||||
const state = await evaluate('(function(){ try { return JSON.parse((document.title||"").replace(/^TESTSTATE /,"")) } catch(e){ return null } })()');
|
||||
await sleep(settle);
|
||||
|
||||
if (domMode) {
|
||||
const evalOut = opt("eval") ? await evaluate(opt("eval")) : undefined;
|
||||
return { ready, state, evalOut, consoleLines, exceptions };
|
||||
}
|
||||
|
||||
if (opt("eval")) await evaluate(opt("eval"));
|
||||
const shot = await send("Page.captureScreenshot", { format: "png", captureBeyondViewport: false });
|
||||
await writeFile(resolve(out), Buffer.from(shot.data, "base64"));
|
||||
return { ready, state, consoleLines, exceptions };
|
||||
});
|
||||
|
||||
const tag = result.ready ? "ready" : "TIMEOUT";
|
||||
if (domMode) {
|
||||
console.log(`state=${tag}`);
|
||||
if (result.evalOut !== undefined) console.log(`eval=${JSON.stringify(result.evalOut)}`);
|
||||
console.log(JSON.stringify(result.state, null, 2));
|
||||
} else {
|
||||
console.log(`${out} ${tag}`);
|
||||
if (result.state) console.log(` state: ${JSON.stringify(result.state)}`);
|
||||
}
|
||||
const noisy = result.consoleLines.filter((l) => /error|warn|uncaught|fail/i.test(l));
|
||||
if (noisy.length) console.log(" console: " + noisy.slice(0, 6).join(" | "));
|
||||
if (result.exceptions.length) console.log(" exceptions: " + result.exceptions.slice(0, 4).join(" | "));
|
||||
if (!result.ready) process.exit(1);
|
||||
Reference in new issue
Block a user