Files
seaislee1209andClaude Opus 4.8 0083918207 feat(core): Wave 3 batch1 — 团队页5弹窗 + 账户页充值/筛选/列重排还原
团队页(P0,sub-agent):创建账户(role-choice双卡+随机生成+每日/月/总额三档)、
  分享凭据(cred-card+单项/一键复制)、编辑成员/重置密码(警示框)/月限额(5预设pill+实时剩余),
  全用 TeamModal 外壳 + team-page.css restraint token 重写(不复用 styles.css 旧暖米类)
账户页(P0,sub-agent):充值扫码弹窗(QR占位+金额+赠送+双按钮)、三表 filter-bar、
  项目/成员表列重排(role-pill/progress-mini/status-tag),CSS 在 account-page.css

验证: tsc 0 · build 0 · 走查 团队2弹窗(role-choice双卡/三额度/预设)+账户充值弹窗 开关ESC 全过
  · 3 筛选条在 · 0 console error

⚠️ 真实数据缺(未造假,已降级):Project 无 owner 字段→账户项目表去「成员+角色」列;
  TeamMember 无完成数/最近活跃→去这两列。需后端补字段才能完整还原(移交监督)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 04:51:29 +08:00

87 lines
3.6 KiB
JavaScript

// Wave 3 batch1 walkthrough (scratch): team 5 弹窗 + account 充值弹窗/筛选条.
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-batch1");
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 = { team: {}, account: {}, consoleErrors: [] };
// ---- TEAM ----
{
const p = await ctx.newPage();
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("team:" + m.text()); });
p.on("pageerror", (e) => r.consoleErrors.push("team:PAGEERROR:" + e.message));
await p.goto(BASE + "/team", { waitUntil: "load" });
await p.waitForTimeout(1500);
// 创建账户
if (await p.locator("#open-invite").count()) {
await p.click("#open-invite");
await p.waitForTimeout(450);
r.team.createAcct = {
modalShown: await p.locator(".modal-bg.show").count(),
roleChoiceCards: await p.locator(".role-choice, .role-choices .role-choice").count(),
quotaGrid: await p.locator(".quota-grid, .quota-cell").count(),
genButtons: await p.locator(".input-with-gen button, button:has-text('生成')").count(),
};
await p.screenshot({ path: path.join(OUT, "team-create-account.png") });
await p.keyboard.press("Escape");
await p.waitForTimeout(450);
r.team.createAcctClosedByEsc = (await p.locator(".modal-bg").count()) === 0;
} else r.team.createAcct = "no #open-invite";
// 月限额
if (await p.locator("#open-limit").count()) {
await p.click("#open-limit");
await p.waitForTimeout(450);
r.team.limit = {
modalShown: await p.locator(".modal-bg.show").count(),
presets: await p.locator(".limit-presets .lp, .limit-presets button").count(),
};
await p.screenshot({ path: path.join(OUT, "team-limit.png") });
await p.keyboard.press("Escape");
await p.waitForTimeout(300);
} else r.team.limit = "no #open-limit";
await p.close();
}
// ---- ACCOUNT ----
{
const p = await ctx.newPage();
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("account:" + m.text()); });
p.on("pageerror", (e) => r.consoleErrors.push("account:PAGEERROR:" + e.message));
await p.goto(BASE + "/account", { waitUntil: "load" });
await p.waitForTimeout(1500);
r.account.filterBars = await p.locator(".filter-bar").count();
await p.screenshot({ path: path.join(OUT, "account-page.png") });
// 充值弹窗:点支付方式按钮
const payBtn = p.locator(".pay-method-btn").first();
if (await payBtn.count()) {
await payBtn.click();
await p.waitForTimeout(450);
r.account.topup = {
modalShown: await p.locator(".modal-bg.show").count(),
qr: await p.locator(".topup-qr").count(),
topupModal: await p.locator(".topup-modal").count(),
};
await p.screenshot({ path: path.join(OUT, "account-topup.png") });
await p.keyboard.press("Escape");
await p.waitForTimeout(400);
r.account.topupClosedByEsc = (await p.locator(".modal-bg").count()) === 0;
} else r.account.topup = "no .pay-method-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));