// Wave 3 batch3 walkthrough (scratch): products 视图切换/多选 + projects 进度条/批量 + dashboard. 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-batch3"); 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 = { products: {}, projects: {}, dashboard: {}, consoleErrors: [] }; async function page(route, tag) { const p = await ctx.newPage(); p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push(`${tag}:` + m.text()); }); p.on("pageerror", (e) => r.consoleErrors.push(`${tag}:PAGEERR:` + e.message)); await p.goto(BASE + route, { waitUntil: "load" }); await p.waitForTimeout(1800); return p; } // products { const p = await page("/products", "products"); r.products.viewTog = await p.locator(".view-tog").count(); r.products.productCards = await p.locator(".product-card").count(); await p.screenshot({ path: path.join(OUT, "products-grid.png") }); // 切列表视图 const listBtn = p.locator(".view-tog button").last(); if (await listBtn.count()) { await listBtn.click(); await p.waitForTimeout(400); r.products.listViewApplied = await p.locator(".product-grid.list-view").count(); await p.screenshot({ path: path.join(OUT, "products-list.png") }); } // 分类多选 chip r.products.catChip = await p.locator(".chip-wrap").count(); await p.close(); } // projects { const p = await page("/projects", "projects"); r.projects.progBars = await p.locator(".proj-card .prog, .prog").count(); r.projects.projCards = await p.locator(".proj-card").count(); await p.screenshot({ path: path.join(OUT, "projects-grid.png") }); // 进编辑态(找含「编辑」/「批量」的按钮) const editBtn = p.locator("button:has-text('编辑'), button:has-text('批量'), .btn-edit-toggle").first(); if (await editBtn.count()) { await editBtn.click(); await p.waitForTimeout(400); r.projects.cardChecks = await p.locator(".card-check, .row-check").count(); await p.screenshot({ path: path.join(OUT, "projects-edit.png") }); } await p.close(); } // dashboard { const p = await page("/dashboard", "dashboard"); r.dashboard.kpiStats = await p.locator(".stat, .stats .stat").count(); r.dashboard.recentRows = await p.locator(".recent-row").count(); await p.screenshot({ path: path.join(OUT, "dashboard.png") }); 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));