// Wave 3 batch2 walkthrough (scratch): library 详情/上传/批量 + ai-tools 模特库/pl-modal. import { chromium } from "playwright"; import fs from "node:fs"; import path from "node:path"; const BASE = process.env.BASE || "http://localhost:5173"; const TOKEN = process.env.TOKEN || ""; const OUT = path.resolve("../../../_qa_shots/wave3-batch2"); fs.mkdirSync(OUT, { recursive: true }); const browser = await chromium.launch({ headless: true }); const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: "light" }); const seed = await ctx.newPage(); await seed.goto(BASE + "/", { waitUntil: "domcontentloaded" }); await seed.evaluate((t) => localStorage.setItem("airshelf_token", t), TOKEN); await seed.close(); const r = { library: {}, aitools: {}, consoleErrors: [] }; // ---- LIBRARY ---- { const p = await ctx.newPage(); p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("lib:" + m.text()); }); p.on("pageerror", (e) => r.consoleErrors.push("lib:PAGEERR:" + e.message)); await p.goto(BASE + "/library", { waitUntil: "load" }); await p.waitForTimeout(1800); r.library.assetCards = await p.locator(".asset-card").count(); await p.screenshot({ path: path.join(OUT, "library.png") }); // 详情弹窗:点首张卡 if (r.library.assetCards > 0) { await p.locator(".asset-card").first().click(); await p.waitForTimeout(500); r.library.detailModal = await p.locator(".asset-detail-modal, .modal-bg.show").count(); await p.screenshot({ path: path.join(OUT, "library-detail.png") }); await p.keyboard.press("Escape"); await p.waitForTimeout(400); r.library.detailClosedByEsc = (await p.locator(".asset-detail-modal").count()) === 0; } // 上传弹窗:找含「上传」的按钮 const upBtn = p.locator("button:has-text('上传')").first(); if (await upBtn.count()) { await upBtn.click(); await p.waitForTimeout(450); r.library.uploadModal = await p.locator(".upload-modal, .modal-bg.show").count(); await p.screenshot({ path: path.join(OUT, "library-upload.png") }); await p.keyboard.press("Escape"); await p.waitForTimeout(300); } await p.close(); } // ---- AI-TOOLS (model-photo) ---- { const p = await ctx.newPage(); p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("ai:" + m.text()); }); p.on("pageerror", (e) => r.consoleErrors.push("ai:PAGEERR:" + e.message)); await p.goto(BASE + "/model-photo", { waitUntil: "load" }); await p.waitForTimeout(1800); r.aitools.actorLibBtn = await p.locator(".iw-actor-lib-btn").count(); r.aitools.plBtn = await p.locator(".iw-pl-btn").count(); await p.screenshot({ path: path.join(OUT, "model-photo.png") }); // 商品库 pl-modal if (await p.locator(".iw-pl-btn").count()) { await p.locator(".iw-pl-btn").first().click(); await p.waitForTimeout(500); r.aitools.plModal = await p.locator(".pl-modal, .pl-modal-bg").count(); await p.screenshot({ path: path.join(OUT, "model-photo-plmodal.png") }); await p.keyboard.press("Escape"); await p.waitForTimeout(400); } await p.close(); } await browser.close(); console.log(JSON.stringify(r, null, 2)); fs.writeFileSync(path.join(OUT, "summary.json"), JSON.stringify(r, null, 2));