- settings.tsx + settings-page.css: restore to settings.html (left-nav + sections), real user/team data - messages.tsx + messages-page.css: rich inbox restore (filters/detail/props grid) on real notifications - projects.tsx ProjectWizardPage + project-wizard-page.css: restore to projects-new.html - products.tsx ProductCreateUploadPage + product-create-page.css: restore to product-create-v2 baseline - ai-tools.tsx (AssetFactory/ImageWorkbench) + ai-tools-page.css: DRAFT unified studio shell; deviates from per-page baselines (image-optimize should be chat-stream; model-photo product+person picker) -> pending rework alongside P3 standalone image-gen decision - shot-p1.mjs: playwright visual-parity capture (react vs exact baseline; uses 127.0.0.1 not localhost) - verified: tsc --noEmit clean; screenshots confirm settings/wizard/product-create/messages faithful Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
2.4 KiB
JavaScript
63 lines
2.4 KiB
JavaScript
// shot-p1.mjs · P1 像素还原验收截图(React 实页 vs public/exact 基线)
|
|
// 用法: node shot-p1.mjs [outDir]
|
|
// 前置: 后端 8010 + 前端 5173 在跑;playwright 已装;chromium 已缓存。
|
|
import { chromium } from "playwright";
|
|
import { mkdirSync } from "node:fs";
|
|
|
|
const BASE = "http://127.0.0.1:5180";
|
|
const API = "http://127.0.0.1:8010";
|
|
const OUT = process.argv[2] || "shots-p1";
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
// [name, React 路由, 基线 html]
|
|
const PAGES = [
|
|
["settings", "/settings", "/exact/settings.html"],
|
|
["messages", "/messages", "/exact/messages.html"],
|
|
["asset-factory", "/asset-factory", "/exact/asset-factory.html"],
|
|
["image-optimize", "/image-optimize", "/exact/image-optimize.html"],
|
|
["model-photo", "/model-photo", "/exact/model-photo.html"],
|
|
["platform-cover", "/platform-cover", "/exact/platform-cover.html"],
|
|
["product-create", "/products/new", "/exact/product-create-upload.html"],
|
|
["project-wizard", "/projects/new", "/exact/projects-new.html"]
|
|
];
|
|
|
|
const res = await fetch(`${API}/api/auth/login/`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username: "airshelf", password: "Restraint2026" })
|
|
});
|
|
const data = await res.json();
|
|
const token = data.token;
|
|
if (!token) {
|
|
console.error("login failed:", JSON.stringify(data));
|
|
process.exit(1);
|
|
}
|
|
console.log("token ok");
|
|
|
|
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), token);
|
|
const page = await ctx.newPage();
|
|
page.on("pageerror", (e) => console.error(" pageerror:", e.message));
|
|
|
|
for (const [name, route, baseline] of PAGES) {
|
|
try {
|
|
await page.goto(BASE + route, { waitUntil: "networkidle", timeout: 30000 });
|
|
await page.waitForTimeout(1400);
|
|
await page.screenshot({ path: `${OUT}/${name}.react.png`, fullPage: true });
|
|
console.log("react ", name);
|
|
} catch (e) {
|
|
console.error("FAIL react", name, e.message);
|
|
}
|
|
try {
|
|
await page.goto(BASE + baseline, { waitUntil: "networkidle", timeout: 30000 });
|
|
await page.waitForTimeout(900);
|
|
await page.screenshot({ path: `${OUT}/${name}.baseline.png`, fullPage: true });
|
|
console.log("baseline", name);
|
|
} catch (e) {
|
|
console.error("FAIL baseline", name, e.message);
|
|
}
|
|
}
|
|
await browser.close();
|
|
console.log("DONE ->", OUT);
|