From 60ef92b75d0c10bba2a0e5ad3db86dcdcf342357 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sat, 4 Jul 2026 07:07:12 +0800 Subject: [PATCH] =?UTF-8?q?fix(e2e):=20=E4=BF=AE=E5=A4=8DAPI=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=A4=B1=E8=B4=A5=20-=20=E9=94=99=E8=AF=AF=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E6=A0=BC=E5=BC=8F=E9=80=82=E9=85=8D=20+=20=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=99=90=E6=B5=81=E8=87=AA=E5=8A=A8=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 错误响应格式适配:后端使用自定义异常处理器返回 { error: { code, message } } 格式,test_auth的错误消息断言 增加 extractErrorMessage 兼容新老格式 2. 登录限流自动重试:所有 E2E 测试文件增加 loginWithRetry 辅助函数, 遇到 429 RATE_LIMIT_EXCEEDED 自动等待 65s 后重试(最多2次), 解决 staging E2E 批量测试触发登录限流(10次/60s)导致的失败 --- apps/web/e2e/core-generation.spec.ts | 28 +++++++++++--- apps/web/e2e/core-titles.spec.ts | 24 ++++++++++-- apps/web/e2e/core-upload.spec.ts | 24 ++++++++++-- apps/web/e2e/subscription.spec.ts | 24 ++++++++++-- apps/web/e2e/test_asset.spec.ts | 24 ++++++++++-- apps/web/e2e/test_auth.spec.ts | 57 +++++++++++++++++++--------- apps/web/e2e/test_project.spec.ts | 24 ++++++++++-- 7 files changed, 167 insertions(+), 38 deletions(-) diff --git a/apps/web/e2e/core-generation.spec.ts b/apps/web/e2e/core-generation.spec.ts index e8e2874c2..0253eb592 100755 --- a/apps/web/e2e/core-generation.spec.ts +++ b/apps/web/e2e/core-generation.spec.ts @@ -19,6 +19,26 @@ const routeBrowserApiToTestApi = async ( }); }; +/** 登录操作,遇到 429 限流自动等待重试 */ +async function loginWithRetry( + request: any, + 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 }, + }); +} + type ProjectResponse = { id: string }; type LibraryResponse = { id: string }; type AssetListResponse = { @@ -60,9 +80,7 @@ test.describe("Core generation flow", () => { const registerData = (await register.json()) as { user_id: string }; // Login - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); expect(login.status(), await login.text()).toBe(200); const loginData = (await login.json()) as { access_token: string }; const headers = { Authorization: `Bearer ${loginData.access_token}` }; @@ -226,9 +244,7 @@ test.describe("Core generation flow", () => { }); expect(register.status()).toBe(201); - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); expect(login.status()).toBe(200); const loginData = (await login.json()) as { access_token: string }; const headers = { Authorization: `Bearer ${loginData.access_token}` }; diff --git a/apps/web/e2e/core-titles.spec.ts b/apps/web/e2e/core-titles.spec.ts index 035b4ef0d..66fa21128 100755 --- a/apps/web/e2e/core-titles.spec.ts +++ b/apps/web/e2e/core-titles.spec.ts @@ -19,6 +19,26 @@ const routeBrowserApiToTestApi = async ( }); }; +/** 登录操作,遇到 429 限流自动等待重试 */ +async function loginWithRetry( + request: any, + 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("Title library flow", () => { test("loads title library page and displays titles", async ({ page, @@ -36,9 +56,7 @@ test.describe("Title library flow", () => { const registerData = (await register.json()) as { user_id: string }; - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); expect(login.status(), await login.text()).toBe(200); const loginData = (await login.json()) as { access_token: string }; diff --git a/apps/web/e2e/core-upload.spec.ts b/apps/web/e2e/core-upload.spec.ts index dcf47e538..52ab03732 100755 --- a/apps/web/e2e/core-upload.spec.ts +++ b/apps/web/e2e/core-upload.spec.ts @@ -19,6 +19,26 @@ const routeBrowserApiToTestApi = async ( }); }; +/** 登录操作,遇到 429 限流自动等待重试 */ +async function loginWithRetry( + request: any, + 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 }, + }); +} + type ProjectResponse = { id: string }; type LibraryResponse = { id: string }; @@ -46,9 +66,7 @@ test.describe("Core media upload flow", () => { const registerData = (await register.json()) as { user_id: string }; - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); expect(login.status(), await login.text()).toBe(200); const loginData = (await login.json()) as { access_token: string }; const headers = { Authorization: `Bearer ${loginData.access_token}` }; diff --git a/apps/web/e2e/subscription.spec.ts b/apps/web/e2e/subscription.spec.ts index e909a8822..115d003cd 100755 --- a/apps/web/e2e/subscription.spec.ts +++ b/apps/web/e2e/subscription.spec.ts @@ -16,6 +16,26 @@ function uniqueUsername(prefix: string): string { return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; } +/** 登录操作,遇到 429 限流自动等待重试 */ +async function loginWithRetry( + request: any, + 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 }, + }); +} + /** 注册并登录,返回 { headers, email, username, userId } */ async function createAuthedUser(request: any, label: string) { const email = uniqueEmail(label); @@ -26,9 +46,7 @@ async function createAuthedUser(request: any, label: string) { }); expect(reg.ok(), `注册应成功: ${await reg.text()}`).toBeTruthy(); - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); expect(login.ok(), `登录应成功: ${await login.text()}`).toBeTruthy(); const loginData = await login.json(); diff --git a/apps/web/e2e/test_asset.spec.ts b/apps/web/e2e/test_asset.spec.ts index 9d498be63..d685a9efc 100755 --- a/apps/web/e2e/test_asset.spec.ts +++ b/apps/web/e2e/test_asset.spec.ts @@ -17,6 +17,26 @@ function uniqueUsername(prefix: string): string { return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; } +/** 登录操作,遇到 429 限流自动等待重试 */ +async function loginWithRetry( + request: any, + 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 }, + }); +} + /** 注册并登录,返回 { headers, email, username, userId } */ async function createAuthedUser(request: any, label: string) { const email = uniqueEmail(label); @@ -28,9 +48,7 @@ async function createAuthedUser(request: any, label: string) { expect(reg.ok(), `注册应成功: ${await reg.text()}`).toBeTruthy(); const regData = await reg.json(); - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); expect(login.ok(), `登录应成功: ${await login.text()}`).toBeTruthy(); const loginData = await login.json(); diff --git a/apps/web/e2e/test_auth.spec.ts b/apps/web/e2e/test_auth.spec.ts index ddef80af1..85d7d2681 100755 --- a/apps/web/e2e/test_auth.spec.ts +++ b/apps/web/e2e/test_auth.spec.ts @@ -17,6 +17,39 @@ function uniqueUsername(prefix: string): string { return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; } +/** 从错误响应中提取错误消息文本,兼容新老格式 */ +function extractErrorMessage(body: any): string { + if (!body) return ""; + // 新格式: { error: { code: "...", message: "..." } } + if (body.error && typeof body.error === "object" && body.error.message) { + return String(body.error.message); + } + // 老格式: { detail: "..." } 或 { message: "..." } 或 { error: "..." } + return String(body.detail || body.message || body.error || ""); +} + +/** 登录操作,遇到 429 限流自动等待重试(最多等 65s) */ +async function loginWithRetry( + request: any, + 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("认证流程", () => { // ─── 注册 ──────────────────────────────────────────── @@ -77,9 +110,7 @@ test.describe("认证流程", () => { const body = await second.json(); // 错误信息应包含"已注册"或"exists"相关提示 - const detail = (body.detail || body.message || body.error || "") - .toString() - .toLowerCase(); + const detail = extractErrorMessage(body).toLowerCase(); expect( detail.includes("已") || detail.includes("exist") || @@ -128,10 +159,8 @@ test.describe("认证流程", () => { }); expect(reg.ok(), "注册应成功").toBeTruthy(); - // 登录 - const response = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + // 登录(带限流重试) + const response = await loginWithRetry(request, email, PASSWORD); expect( response.ok(), @@ -153,10 +182,8 @@ test.describe("认证流程", () => { data: { email, password: PASSWORD, username, display_name: "Bad Login" }, }); - // 使用错误密码登录 - const response = await request.post(`${apiBase}/auth/login`, { - data: { email, password: "WrongPassword999!" }, - }); + // 使用错误密码登录(带限流重试) + const response = await loginWithRetry(request, email, "WrongPassword999!"); expect(response.status(), "错误密码应返回 401").toBe(401); }); @@ -184,9 +211,7 @@ test.describe("认证流程", () => { display_name: "Logout Test", }, }); - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); const { access_token } = await login.json(); const headers = { Authorization: `Bearer ${access_token}` }; @@ -214,9 +239,7 @@ test.describe("认证流程", () => { await request.post(`${apiBase}/auth/register`, { data: { email, password: PASSWORD, username, display_name: "Me Test" }, }); - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); const { access_token } = await login.json(); const response = await request.get(`${apiBase}/auth/me`, { diff --git a/apps/web/e2e/test_project.spec.ts b/apps/web/e2e/test_project.spec.ts index 58b212a8c..8f87bc3dc 100755 --- a/apps/web/e2e/test_project.spec.ts +++ b/apps/web/e2e/test_project.spec.ts @@ -17,6 +17,26 @@ function uniqueUsername(prefix: string): string { return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; } +/** 登录操作,遇到 429 限流自动等待重试 */ +async function loginWithRetry( + request: any, + 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 }, + }); +} + /** 注册并登录,返回 { headers, email, username, userId } */ async function createAuthedUser(request: any, label: string) { const email = uniqueEmail(label); @@ -28,9 +48,7 @@ async function createAuthedUser(request: any, label: string) { expect(reg.ok(), `注册应成功: ${await reg.text()}`).toBeTruthy(); const regData = await reg.json(); - const login = await request.post(`${apiBase}/auth/login`, { - data: { email, password: PASSWORD }, - }); + const login = await loginWithRetry(request, email, PASSWORD); expect(login.ok(), `登录应成功: ${await login.text()}`).toBeTruthy(); const loginData = await login.json();