chore(core/qa): function-audit toolchain + parity/audit reports + pixel-perfect skill

- qa/function-audit: playwright 行为审计工具(audit.mjs/verify-modals.mjs/pages.json)
  + 18 页审计产出(*.audit.md/json、summary、运行日志)
- qa/visual-parity: 调试/测量辅助脚本(_dbg*.mjs/_measure.mjs/_off.mjs)
- core/还原度核对报告.md: 18 页 pixelmatch 核对结果(含 vite 代理陈旧坑记录)
- core/还原与接口待办.md: 逐页还原度/真实数据/交互接入待办总表
- .claude/skills/pixel-perfect-react: 像素级还原 React 的 SKILL 文档
- frontend/public/_devlogin.html: 临时本地登录辅助页(可删)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-10 09:41:30 +08:00
co-authored by Claude Fable 5
parent 3fac38c5ef
commit 890cb9ab67
66 changed files with 11610 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
const url = process.argv[3];
const b = await chromium.launch();
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
const p = await ctx.newPage();
const errs = [];
p.on("console", m => { if (m.type()==="error") errs.push(m.text()); });
p.on("pageerror", e => errs.push("PAGEERR: "+e.message));
await p.goto("http://127.0.0.1:5173/");
await p.evaluate((t) => localStorage.setItem("airshelf_token", t), TOK);
await p.goto(url, { waitUntil: "networkidle" });
await p.waitForTimeout(600);
const info = await p.evaluate(() => ({
isLogin: !!document.querySelector(".auth-exact-page"),
h1: document.querySelector("h1")?.textContent?.slice(0,30),
token: localStorage.getItem("airshelf_token")?.length,
}));
console.log(url, JSON.stringify(info), "ERRORS:", errs.slice(0,5));
await b.close();
+21
View File
@@ -0,0 +1,21 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
const b = await chromium.launch();
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
const p = await ctx.newPage();
const errs = [];
p.on("console", m => { if (m.type()==="error") errs.push(m.text().slice(0,120)); });
p.on("pageerror", e => errs.push("PAGEERR: "+e.message.slice(0,150)));
await p.goto("http://127.0.0.1:5173/", { waitUntil: "domcontentloaded" });
await p.evaluate((t) => localStorage.setItem("airshelf_token", t), TOK);
for (const url of ["http://127.0.0.1:5173/model-photo", "http://127.0.0.1:5173/model-photo/demo-a"]) {
errs.length = 0;
await p.goto(url, { waitUntil: "networkidle" });
await p.waitForTimeout(700);
const info = await p.evaluate(() => {
const txt = document.body.innerText || "";
return { isLogin: txt.includes("AUTH/LOGIN") || txt.includes("使用团队邀请邮箱"), firstWords: txt.replace(/\s+/g," ").slice(0,80) };
});
console.log(url.split("5173")[1], JSON.stringify(info), "ERR:", errs.slice(0,3));
}
await b.close();
+20
View File
@@ -0,0 +1,20 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
const b = await chromium.launch();
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
// check: does loading /exact/model-photo.html mutate the token?
const sp = await ctx.newPage();
await sp.goto("http://127.0.0.1:5173/", { waitUntil: "domcontentloaded" });
await sp.evaluate((t) => localStorage.setItem("airshelf_token", t), TOK);
const before = await sp.evaluate(() => localStorage.getItem("airshelf_token")?.length);
await sp.goto("http://127.0.0.1:5173/exact/model-photo.html", { waitUntil: "networkidle" });
await sp.waitForTimeout(1500);
const after = await sp.evaluate(() => localStorage.getItem("airshelf_token")?.length ?? "NULL");
console.log("token before source load:", before, "| after /exact/model-photo.html load:", after);
// compare with demo-a source
await sp.evaluate((t) => localStorage.setItem("airshelf_token", t), TOK);
await sp.goto("http://127.0.0.1:5173/exact/model-photo-demo-a.html", { waitUntil: "networkidle" });
await sp.waitForTimeout(1500);
const afterDemo = await sp.evaluate(() => localStorage.getItem("airshelf_token")?.length ?? "NULL");
console.log("after /exact/model-photo-demo-a.html load:", afterDemo);
await b.close();
+15
View File
@@ -0,0 +1,15 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
async function prepare(page, url, token) {
if (token) { await page.goto(new URL(url).origin + "/", { waitUntil: "domcontentloaded" }); await page.evaluate((v)=>localStorage.setItem("airshelf_token",v), token); }
await page.goto(url, { waitUntil: "networkidle" });
if (token) { await page.evaluate((v)=>localStorage.setItem("airshelf_token",v), token); await page.goto(url, { waitUntil: "networkidle" }); await page.waitForTimeout(900); }
}
const b = await chromium.launch();
const ctx = await b.newContext({ viewport:{width:1440,height:900}, deviceScaleFactor:1, colorScheme:"light", reducedMotion:"reduce" });
const sp = await ctx.newPage(); const tp = await ctx.newPage();
await prepare(sp, "http://127.0.0.1:5173/exact/model-photo.html", TOK);
await prepare(tp, "http://127.0.0.1:5173/model-photo", TOK);
const t = await tp.evaluate(()=>{const x=document.body.innerText||"";return {login:x.includes("AUTH/LOGIN"),first:x.replace(/\s+/g," ").slice(0,60),tok:localStorage.getItem("airshelf_token")?.length};});
console.log("TARGET after exact-compare-flow:", JSON.stringify(t));
await b.close();
+19
View File
@@ -0,0 +1,19 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
const b = await chromium.launch();
const ctx = await b.newContext({ viewport:{width:1440,height:900} });
const p = await ctx.newPage();
await p.goto("http://127.0.0.1:5173/", { waitUntil:"domcontentloaded" });
await p.evaluate((t)=>localStorage.setItem("airshelf_token",t), TOK);
// mirror model-photo
await p.goto("http://127.0.0.1:5173/exact/model-photo.html", { waitUntil:"networkidle" });
await p.waitForTimeout(1800);
const mirror = await p.evaluate(()=>{
const items=[...document.querySelectorAll(".mp-ps-item, .ps-item, [class*='ps-item'], .pl-item")].map(e=>e.innerText.replace(/\s+/g,' ').slice(0,30));
return { count: items.length, items: items.slice(0,10) };
});
console.log("MIRROR model-photo product list:", JSON.stringify(mirror));
// real API
const api = await p.evaluate(async (t)=>{ const r=await fetch("/api/products/",{headers:{Authorization:"Token "+t}}); const d=await r.json(); return (d.results||[]).map(x=>x.title); }, TOK);
console.log("REAL /api/products/:", JSON.stringify(api));
await b.close();
+18
View File
@@ -0,0 +1,18 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
const b = await chromium.launch();
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 });
const p = await ctx.newPage();
await p.goto("http://127.0.0.1:5173/");
await p.evaluate((t) => localStorage.setItem("airshelf_token", t), TOK);
await p.goto("http://127.0.0.1:5173/products/new", { waitUntil: "networkidle" });
await p.waitForTimeout(800);
const r = await p.evaluate(() => {
const d = document.querySelector(".pc-drawer");
if (!d) return { found: false, body: document.body.className, hasLogin: !!document.querySelector(".auth-exact-page") };
const cs = getComputedStyle(d);
const rect = d.getBoundingClientRect();
return { found: true, width: cs.width, transform: cs.transform, left: rect.left, right: rect.right, rectW: rect.width, cls: d.className };
});
console.log(JSON.stringify(r, null, 2));
await b.close();
+17
View File
@@ -0,0 +1,17 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
const b = await chromium.launch();
const ctx = await b.newContext({ viewport:{width:1440,height:900}, deviceScaleFactor:1 });
async function ys(url){
const p = await ctx.newPage();
await p.goto("http://127.0.0.1:5173/",{waitUntil:"domcontentloaded"});
await p.evaluate((t)=>localStorage.setItem("airshelf_token",t),TOK);
await p.goto(url,{waitUntil:"networkidle"}); await p.waitForTimeout(1000);
return await p.evaluate(()=>{
const g=(sel)=>{const e=document.querySelector(sel);return e?Math.round(e.getBoundingClientRect().top):null;};
return { h1:g("h1"), heroTop:g(".factory-hero,.fx-hero,[class*='hero']"), card:g(".factory-card,[class*='factory-card']"), cta:g(".factory-cta .btn,[class*='factory'] .btn-primary"), section:g(".section-h"), tabs:g(".tabs"), toolbar:g(".toolbar") };
});
}
console.log("MIRROR:", JSON.stringify(await ys("http://127.0.0.1:5173/exact/asset-factory.html")));
console.log("IMPL :", JSON.stringify(await ys("http://127.0.0.1:5173/asset-factory")));
await b.close();