// Wave 3 batch4 walkthrough (scratch): 外壳账户菜单 + 设置dirty + 商品创建抽屉多图. 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-batch4"); 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 = { shell: {}, settings: {}, drawer: {}, consoleErrors: [] }; // 外壳账户菜单(任意页) { const p = await ctx.newPage(); p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("shell:" + m.text()); }); p.on("pageerror", (e) => r.consoleErrors.push("shell:PAGEERR:" + e.message)); await p.goto(BASE + "/dashboard", { waitUntil: "load" }); await p.waitForTimeout(1500); const user = p.locator(".user").first(); if (await user.count()) { await user.click(); await p.waitForTimeout(350); r.shell.menuShown = await p.locator(".shell-account-menu.show, .shell-account-menu").count(); r.shell.menuItems = await p.locator(".shell-account-menu button").count(); await p.screenshot({ path: path.join(OUT, "account-menu.png") }); await p.keyboard.press("Escape"); await p.waitForTimeout(300); r.shell.closedByEsc = (await p.locator(".shell-account-menu.show").count()) === 0; } else r.shell = "no .user"; await p.close(); } // 设置 dirty 引擎 { const p = await ctx.newPage(); p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("settings:" + m.text()); }); p.on("pageerror", (e) => r.consoleErrors.push("settings:PAGEERR:" + e.message)); await p.goto(BASE + "/settings", { waitUntil: "load" }); await p.waitForTimeout(1500); const saveBtn = p.locator("button:has-text('保存所有变更'), button:has-text('保存')").first(); r.settings.saveBtnDisabledInitially = await saveBtn.isDisabled().catch(() => "n/a"); // 改一个开关(找第一个 switch/checkbox)触发 dirty const sw = p.locator(".switch input, input[type=checkbox]").first(); if (await sw.count()) { await sw.click({ force: true }); await p.waitForTimeout(400); r.settings.saveBtnEnabledAfterChange = !(await saveBtn.isDisabled().catch(() => true)); r.settings.dirtyCount = await p.locator(".save-count").count(); r.settings.navDirtyDot = await p.locator(".settings-nav a.has-changes, .nav-dot").count(); } await p.screenshot({ path: path.join(OUT, "settings-dirty.png") }); await p.close(); } // 商品创建抽屉多图 { const p = await ctx.newPage(); p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("drawer:" + m.text()); }); p.on("pageerror", (e) => r.consoleErrors.push("drawer:PAGEERR:" + e.message)); await p.goto(BASE + "/products", { waitUntil: "load" }); await p.waitForTimeout(1500); const newBtn = p.locator("button:has-text('新建商品'), button:has-text('新建')").first(); if (await newBtn.count()) { await newBtn.click(); await p.waitForTimeout(500); r.drawer.drawerShown = await p.locator(".drawer.show, .pc-drawer").count(); r.drawer.multipleInput = await p.locator("input[type=file][multiple]").count(); r.drawer.pfGrid = await p.locator(".pf-grid").count(); await p.screenshot({ path: path.join(OUT, "create-drawer.png") }); } else r.drawer = "no 新建商品 btn"; 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));