feat(core): Wave 3.5 前端 — 注册页/登录双态 + 邀请制 API 接线(认证定调:无邮箱)
scaffolding: types.ts 加 Invitation 类型;api.ts 加 listInvitations/createInvitation/revokeInvitation + register() 加 invite_code;route-config AuthMode "login"|"register" + /register 返回 register 态; App.tsx onModeChange 按 mode 切 /login↔/register URL auth-screen.tsx(sub-agent):登录+注册双态(按 initialMode 渲染),去掉 /register→/login 强制改写; 注册表单 = 团队名/用户名/密码/确认密码/邀请码(去邮箱!)+ 左价值点 + 协议勾选 + 一致性校验, 有码提示「加入团队」无码「开新团队」;登录补第三方(微信/飞书+OR线)+ 切换行 + lead/mono;登录保持秒进 styles.css:仅加 .auth-* 注册态/第三方/切换行 CSS(restraint token) 验证: tsc 0 · build 0 · 走查 登录(无邮箱/第三方×2/切换/秒进698ms)+ 注册(5字段/0邮箱/0「邮箱」文案) + 切换态(URL→/register)全过 · 0 console error Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ba855cc219
commit
c0a8582f3b
@@ -0,0 +1,83 @@
|
||||
// Wave 3.5 auth walkthrough (scratch): 登录(秒进/无邮箱/第三方/切换) + 注册(无邮箱/5字段) + 切换态.
|
||||
import { chromium } from "playwright";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const BASE = process.env.BASE || "http://localhost:5173";
|
||||
const OUT = path.resolve("../../../_qa_shots/wave35");
|
||||
fs.mkdirSync(OUT, { recursive: true });
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const r = { login: {}, register: {}, switch: {}, consoleErrors: [] };
|
||||
|
||||
// 登录态(无 token,真在登录页)
|
||||
{
|
||||
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
||||
const p = await ctx.newPage();
|
||||
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("login:" + m.text()); });
|
||||
p.on("pageerror", (e) => r.consoleErrors.push("login:PAGEERR:" + e.message));
|
||||
await p.goto(BASE + "/login", { waitUntil: "load" });
|
||||
await p.waitForTimeout(500);
|
||||
r.login = {
|
||||
usernameField: await p.locator("#auth-username").count(),
|
||||
emailField: await p.locator("input[type=email]").count(),
|
||||
ssoButtons: await p.locator(".sso-btn").count(),
|
||||
switchRow: await p.locator(".switch-row").count(),
|
||||
};
|
||||
await p.screenshot({ path: path.join(OUT, "login.png") });
|
||||
// 登录秒进(demo 账号)
|
||||
await p.fill("#auth-username", "airshelf");
|
||||
await p.fill("#auth-pwd", "Restraint2026");
|
||||
const t0 = Date.now();
|
||||
await p.click("button.btn-cta");
|
||||
try {
|
||||
await p.waitForFunction(() => !location.pathname.startsWith("/login"), { timeout: 12000 });
|
||||
r.login.loginMs = Date.now() - t0;
|
||||
} catch { r.login.loginMs = -1; }
|
||||
await ctx.close();
|
||||
}
|
||||
|
||||
// 注册态
|
||||
{
|
||||
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
||||
const p = await ctx.newPage();
|
||||
p.on("console", (m) => { if (m.type() === "error") r.consoleErrors.push("reg:" + m.text()); });
|
||||
p.on("pageerror", (e) => r.consoleErrors.push("reg:PAGEERR:" + e.message));
|
||||
await p.goto(BASE + "/register", { waitUntil: "load" });
|
||||
await p.waitForTimeout(600);
|
||||
const bodyText = await p.locator("body").innerText().catch(() => "");
|
||||
r.register = {
|
||||
emailField: await p.locator("input[type=email]").count(),
|
||||
textInputs: await p.locator(".auth-form input[type=text], .auth-form input[type=password]").count(),
|
||||
hasTeamLabel: /团队名|团队名称/.test(bodyText),
|
||||
hasInvite: /邀请码/.test(bodyText),
|
||||
hasEmailText: /邮箱/.test(bodyText),
|
||||
ctaButton: await p.locator(".btn-cta").count(),
|
||||
switchRow: await p.locator(".switch-row").count(),
|
||||
};
|
||||
await p.screenshot({ path: path.join(OUT, "register.png") });
|
||||
await ctx.close();
|
||||
}
|
||||
|
||||
// 切换:login → 点切换行 → register
|
||||
{
|
||||
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
|
||||
const p = await ctx.newPage();
|
||||
await p.goto(BASE + "/login", { waitUntil: "load" });
|
||||
await p.waitForTimeout(500);
|
||||
const link = p.locator(".switch-row a, .switch-row button").first();
|
||||
if (await link.count()) {
|
||||
await link.click();
|
||||
await p.waitForTimeout(400);
|
||||
r.switch = {
|
||||
urlNowRegister: p.url().includes("/register"),
|
||||
registerFormShown: /邀请码/.test(await p.locator("body").innerText().catch(() => "")),
|
||||
};
|
||||
await p.screenshot({ path: path.join(OUT, "switched-to-register.png") });
|
||||
} else r.switch = "no switch link";
|
||||
await ctx.close();
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log(JSON.stringify(r, null, 2));
|
||||
fs.writeFileSync(path.join(OUT, "summary.json"), JSON.stringify(r, null, 2));
|
||||
Reference in New Issue
Block a user