0fcb77b991
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 1m6s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Successful in 1m49s
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 3m1s
CI/CD Pipeline / Unit Tests (push) Successful in 3m20s
CI/CD Pipeline / Integration Tests (push) Successful in 1m29s
CI Build & Deploy Pipeline / Build Staging API Image (push) Successful in 7m4s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Successful in 7m54s
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 45s
CI Build & Deploy Pipeline / Staging E2E Tests (push) Failing after 2m50s
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Successful in 3m28s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
297 lines
10 KiB
TypeScript
297 lines
10 KiB
TypeScript
/**
|
|
* 注册页面 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()
|
|
})
|
|
})
|