/** * 注册页面 E2E 测试 * * 覆盖:页面渲染、表单验证、成功注册、跳转链接 * 每个测试独立,使用随机邮箱避免冲突。 */ import { expect, test, type APIRequestContext } from "@playwright/test" const PASSWORD = "Test123456!" const apiBase = process.env.E2E_API_BASE || "/api/v1" function uniqueEmail(prefix: string): string { return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}@example.com` } function uniqueUsername(prefix: string): string { return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}` } /** 登录操作,遇到 429 限流自动等待重试(最多等 65s) */ async function loginWithRetry( request: APIRequestContext, email: string, password: string, maxRetries = 2, ) { for (let i = 0; i <= maxRetries; i++) { const response = await request.post(`${apiBase}/auth/login`, { data: { email, password }, }) if (response.status() !== 429) return response console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`) await new Promise((r) => setTimeout(r, 65000)) } return request.post(`${apiBase}/auth/login`, { data: { email, password }, }) } test.describe("注册页面", () => { test.describe.configure({ timeout: 120_000 }) // ─── 页面渲染 ────────────────────────────────────── test("页面正常渲染 - 标题、表单元素、提交按钮", async ({ page }) => { await page.goto("/register") // 品牌标识 await expect(page.locator(".xx-auth-brand-name")).toHaveText("小虾智剪") // 标题/描述 await expect(page.getByText("创建账户,开启智能视频创作之旅")).toBeVisible() // 表单字段 await expect(page.getByLabel("邮箱")).toBeVisible() await expect(page.getByLabel("用户名")).toBeVisible() await expect(page.getByLabel("密码")).toBeVisible() await expect(page.getByLabel("确认密码")).toBeVisible() // 提交按钮 await expect(page.locator("button[type='submit']").filter({ hasText: "注册" })).toBeVisible() }) // ─── 表单验证 ────────────────────────────────────── test("空提交 - 显示必填错误", async ({ page }) => { await page.goto("/register") // 直接点击注册按钮 await page.locator("button[type='submit']").filter({ hasText: "注册" }).click() // 应显示必填错误 await expect(page.getByText("请输入邮箱")).toBeVisible() await expect(page.getByText("请输入用户名")).toBeVisible() await expect(page.getByText("请输入密码")).toBeVisible() await expect(page.getByText("请确认密码")).toBeVisible() }) test("无效邮箱格式 - 显示格式错误", async ({ page }) => { await page.goto("/register") await page.getByLabel("邮箱").fill("not-an-email") await page.getByLabel("用户名").fill("testuser") await page.getByLabel("密码").fill(PASSWORD) await page.getByLabel("确认密码").fill(PASSWORD) await page.locator("button[type='submit']").filter({ hasText: "注册" }).click() // 应显示邮箱格式错误 await expect(page.getByText("请输入有效的邮箱地址")).toBeVisible() }) test("密码太短 - 显示长度错误", async ({ page }) => { await page.goto("/register") await page.getByLabel("邮箱").fill(uniqueEmail("short-pwd")) await page.getByLabel("用户名").fill("testuser") await page.getByLabel("密码").fill("123") await page.getByLabel("确认密码").fill("123") await page.locator("button[type='submit']").filter({ hasText: "注册" }).click() // 应显示密码长度错误 await expect(page.getByText("密码至少 8 个字符")).toBeVisible() }) test("确认密码不一致 - 显示不一致错误", async ({ page }) => { await page.goto("/register") await page.getByLabel("邮箱").fill(uniqueEmail("pwd-mismatch")) await page.getByLabel("用户名").fill("testuser") await page.getByLabel("密码").fill(PASSWORD) await page.getByLabel("确认密码").fill("Different123!") await page.locator("button[type='submit']").filter({ hasText: "注册" }).click() // 应显示密码不一致错误 await expect(page.getByText("两次输入的密码不一致")).toBeVisible() }) test("用户名为空 - 显示必填错误", async ({ page }) => { await page.goto("/register") await page.getByLabel("邮箱").fill(uniqueEmail("empty-user")) await page.getByLabel("用户名").fill("") await page.getByLabel("密码").fill(PASSWORD) await page.getByLabel("确认密码").fill(PASSWORD) await page.locator("button[type='submit']").filter({ hasText: "注册" }).click() await expect(page.getByText("请输入用户名")).toBeVisible() }) // ─── 成功注册 ────────────────────────────────────── test("成功注册 - 提交有效表单", async ({ page, _request }) => { const email = uniqueEmail("reg-ui-ok") const username = uniqueUsername("reguiok") await page.goto("/register") await page.getByLabel("邮箱").fill(email) await page.getByLabel("用户名").fill(username) await page.getByLabel("密码").fill(PASSWORD) await page.getByLabel("确认密码").fill(PASSWORD) // 监听注册请求 const registerResponse = page.waitForResponse( (resp) => resp.url().includes("/auth/register") && resp.request().method() === "POST", { timeout: 15_000 }, ) await page.locator("button[type='submit']").filter({ hasText: "注册" }).click() const resp = await registerResponse expect(resp.ok(), `注册请求应返回 2xx,实际: ${resp.status()}`).toBeTruthy() // 注册成功后应跳转到登录页或显示成功消息 // 页面应停留在可识别的状态(成功提示或跳转) await expect .poll( async () => { const url = page.url() // 可能跳转到 login,也可能在当前页显示成功消息 if (url.includes("/login")) return "redirected" const hasSuccess = await page.getByText(/注册成功/).isVisible() return hasSuccess ? "success_msg" : url }, { timeout: 10_000 }, ) .toMatch(/redirected|success_msg/) }) test("注册已存在邮箱 - UI 显示错误", async ({ page, request }) => { const email = uniqueEmail("reg-ui-dup") const username1 = uniqueUsername("reguidup1") const username2 = uniqueUsername("reguidup2") // 先通过 API 注册一个账号 const firstReg = await request.post(`${apiBase}/auth/register`, { data: { email, password: PASSWORD, username: username1, display_name: "User 1", }, }) expect(firstReg.ok(), "第一次注册应成功").toBeTruthy() // 再在 UI 上用相同邮箱注册 await page.goto("/register") await page.getByLabel("邮箱").fill(email) await page.getByLabel("用户名").fill(username2) await page.getByLabel("密码").fill(PASSWORD) await page.getByLabel("确认密码").fill(PASSWORD) await page.locator("button[type='submit']").filter({ hasText: "注册" }).click() // 应显示错误提示(通过 antd message 或表单错误) await expect .poll( async () => { // 检查是否有错误消息 const hasError = await page.getByText(/注册失败|已注册|已存在|exists/).isVisible() return hasError ? "error_shown" : "waiting" }, { timeout: 10_000 }, ) .toBe("error_shown") }) // ─── 跳转链接 ────────────────────────────────────── test("跳转到登录页的链接", async ({ page }) => { await page.goto("/register") await page.getByRole("link", { name: "立即登录" }).click() await expect(page).toHaveURL(/\/login/) await expect(page.getByLabel("邮箱")).toBeVisible() }) test("登录页有跳转到注册页的链接(反向验证)", async ({ page }) => { await page.goto("/login") await page.getByRole("link", { name: "立即注册" }).click() await expect(page).toHaveURL(/\/register/) }) test("登录页有忘记密码链接", async ({ page }) => { await page.goto("/login") await expect(page.getByRole("link", { name: /忘记密码/ })).toBeVisible() await page.getByRole("link", { name: /忘记密码/ }).click() await expect(page).toHaveURL(/\/forgot-password/) }) // ─── 路由守卫 - 已登录用户访问注册页 ────────────── test("已登录用户访问注册页 - 可正常访问(注册页无守卫)", async ({ page, request }) => { const email = uniqueEmail("reg-auth") const username = uniqueUsername("regauth") // 注册 await request.post(`${apiBase}/auth/register`, { data: { email, password: PASSWORD, username, display_name: "Reg Auth Test", }, }) // 登录 const login = await loginWithRetry(request, email, PASSWORD) expect(login.ok(), "登录应成功").toBeTruthy() const loginData = await login.json() // 设置登录态 await page.addInitScript( ({ token, user }) => { localStorage.setItem("access_token", token) localStorage.setItem( "auth-storage", JSON.stringify({ state: { user, isAuthenticated: true }, version: 0, }), ) }, { token: loginData.access_token, user: { id: loginData.user_id, user_id: loginData.user_id, email, username, display_name: username, is_email_verified: true, email_verified: true, }, }, ) await page.goto("/register") // 注册页对已登录用户也可访问(注册页是公开页面) // 验证页面正常渲染 await expect(page.getByLabel("邮箱")).toBeVisible() await expect(page.locator("button[type='submit']").filter({ hasText: "注册" })).toBeVisible() }) })