fix(qa): 修页面还原对比 harness(Mac死路径 + file://token崩)+ 全量28页跑通
当年只对了 account/team 两页就卡住,真因两个: - compare-all.mjs designRoot 写死成原开发者 Mac 路径 → 本机找不到 HTML 稿,改 repoRoot 自适配 - compare-page.mjs preparePage 对 file:// 设计稿也注入 token(file:// origin=null)→ 崩, 限定 token 只对 http 目标注入(登录后页面如 dashboard 当年对不了的根因) 全量 28 页 pixelmatch:22 优秀(<2%)+ 3 良好(2-5%);3 个大 diff 均为有意偏离 (login/register 认证去邮箱)或真实数据(dashboard 修正后 6.38%),非回归。 + _shot-extract-flow.mjs:提取新流程无头走查脚本。 报告:docs/todo/页面还原-截图对比收尾-2026-06-19.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
69163a8f7f
commit
352f99d479
@@ -0,0 +1,60 @@
|
||||
// 抓图:实体提取新流程 —— 蒙版三按钮 → 点「只提取」→ 卡片 → 故事板生成前拦截弹窗
|
||||
import { chromium } from "playwright";
|
||||
import { mkdirSync } from "node:fs";
|
||||
const BASE = "http://127.0.0.1:5173";
|
||||
const API = "http://127.0.0.1:8010";
|
||||
const PID = process.argv[2] || "c904386e-e282-4e7d-93ce-e68d94ad33e2"; // 蒙版态项目
|
||||
const OUT = "output/extract-flow";
|
||||
mkdirSync(OUT, { recursive: true });
|
||||
|
||||
const tok = (await (await fetch(`${API}/api/auth/login/`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "airshelf", password: "Restraint2026" }) })).json()).token;
|
||||
console.log("token", tok ? "OK" : "FAIL", "| project", PID);
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 });
|
||||
await ctx.addInitScript((t) => localStorage.setItem("airshelf_token", t), tok);
|
||||
const page = await ctx.newPage();
|
||||
page.on("pageerror", (e) => console.error(" pageerror:", e.message));
|
||||
|
||||
async function shot(name) { await page.screenshot({ path: `${OUT}/${name}.png`, fullPage: false }); console.log(" shot", name); }
|
||||
|
||||
try {
|
||||
// 1) 进资产趴 → 应见蒙版 + 三按钮
|
||||
await page.goto(`${BASE}/pipeline/${PID}?st=2#stage-2`, { waitUntil: "networkidle", timeout: 30000 });
|
||||
await page.waitForTimeout(1800);
|
||||
const gate = await page.locator(".extract-gate").count();
|
||||
const btns = await page.locator(".eg-btn").count();
|
||||
console.log(` [1] 蒙版闸门: extract-gate=${gate}, 按钮数=${btns}`);
|
||||
await shot("1-gate");
|
||||
|
||||
// 2) 点「只提取关键词」(第一个按钮,不出图、便宜)→ 转圈 → 卡片
|
||||
if (btns > 0) {
|
||||
await page.locator(".eg-btn").first().click();
|
||||
await page.waitForTimeout(900);
|
||||
await shot("2-loading"); // 转圈态
|
||||
// 等提取完(闸门消失或最多 ~40s)
|
||||
await page.locator(".extract-gate").waitFor({ state: "detached", timeout: 45000 }).catch(() => console.log(" (闸门 45s 未消失)"));
|
||||
await page.waitForTimeout(2000);
|
||||
const cards = await page.locator(".asset-sec").count();
|
||||
console.log(` [2] 提取后资产区块数=${cards}`);
|
||||
await shot("3-after-extract");
|
||||
}
|
||||
|
||||
// 3) 故事板生成前拦截弹窗(无资产 → 缺参考图 → 弹窗)
|
||||
await page.goto(`${BASE}/pipeline/${PID}?st=3#stage-3`, { waitUntil: "networkidle", timeout: 30000 });
|
||||
await page.waitForTimeout(1500);
|
||||
const sbBtn = page.locator("#sb-rerun-btn");
|
||||
if (await sbBtn.count()) {
|
||||
await sbBtn.click();
|
||||
await page.waitForTimeout(1000);
|
||||
const popup = await page.locator(".ref-gate-modal").count();
|
||||
console.log(` [3] 点生成故事板 → 拦截弹窗 ref-gate-modal=${popup}`);
|
||||
await shot("4-popup-intercept");
|
||||
} else {
|
||||
console.log(" [3] 故事板阶段没有生成按钮(可能需先确认资产),跳过弹窗截图");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("FAIL", e.message);
|
||||
}
|
||||
await browser.close();
|
||||
console.log("DONE ->", OUT);
|
||||
@@ -5,8 +5,8 @@ import { fileURLToPath } from "node:url";
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(here, "../../..");
|
||||
const designRoot = "file:///Users/maidong/Desktop/zyc/qiyuan_gitea/AirShelf/电商AI平台";
|
||||
const targetRoot = "http://127.0.0.1:5173/exact";
|
||||
const designRoot = `file:///${path.resolve(repoRoot, "电商AI平台").replace(/\\/g, "/")}`;
|
||||
const targetRoot = process.env.TARGET_ROOT || "http://127.0.0.1:5173/exact";
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(here, "pages.json"), "utf8"));
|
||||
|
||||
function arg(name, fallback = "") {
|
||||
|
||||
@@ -38,7 +38,7 @@ async function preparePage(page, url, shouldClearStorage, token) {
|
||||
// 先到同源根页注入 token,再进目标路由 —— 否则目标路由在「无 token 首跳」时
|
||||
// 会跑一遍登出/重定向 boot,某些路由(如 /model-photo)即便随后 reload 也回不来,
|
||||
// 导致截图停在登录页(假性 ~27% diff)。
|
||||
if (token) {
|
||||
if (token && /^https?:\/\//.test(url)) {
|
||||
await page.goto(new URL(url).origin + "/", { waitUntil: "domcontentloaded" });
|
||||
await page.evaluate((value) => localStorage.setItem("airshelf_token", value), token);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ async function preparePage(page, url, shouldClearStorage, token) {
|
||||
await page.goto(url, { waitUntil: "networkidle" });
|
||||
}
|
||||
// 注入登录 token,让受保护的真 React 路由 + /exact 镜像都能 hydrate 同一份真实数据
|
||||
if (token) {
|
||||
if (token && /^https?:\/\//.test(url)) {
|
||||
await page.evaluate((value) => localStorage.setItem("airshelf_token", value), token);
|
||||
await page.goto(url, { waitUntil: "networkidle" });
|
||||
await page.waitForTimeout(900); // 等异步数据 hydrate 落定
|
||||
|
||||
Reference in New Issue
Block a user