import { expect, test, type APIRequestContext } from "@playwright/test"; const PASSWORD = "SmokePass123!"; const apiBase = process.env.E2E_API_BASE || "/api/v1"; const apiOrigin = apiBase.endsWith("/api/v1") ? apiBase.slice(0, -"/api/v1".length) : ""; const routeBrowserApiToTestApi = async ( page: import("@playwright/test").Page, ) => { if (!apiOrigin) return; await page.route("**/api/v1/**", async (route) => { const sourceUrl = new URL(route.request().url()); const response = await route.fetch({ url: `${apiOrigin}${sourceUrl.pathname}${sourceUrl.search}`, }); await route.fulfill({ response }); }); }; /** 登录操作,遇到 429 限流自动等待重试 */ 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("Title library flow", () => { test.describe.configure({ timeout: 120_000 }); test("loads title library page and displays titles", async ({ page, request, }) => { await routeBrowserApiToTestApi(page); const suffix = Date.now().toString(36); const email = `e2e-title-${suffix}@example.com`; const username = `e2e_title_${suffix}`; const register = await request.post(`${apiBase}/auth/register`, { data: { email, username, password: PASSWORD, display_name: username }, }); expect(register.status(), await register.text()).toBe(201); const registerData = (await register.json()) as { user_id: string }; 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}` }; // Create a title so the titles page has at least one title card to display // (titles are loaded from API; new users have none by default) const createTitle = await request.post(`${apiBase}/titles`, { headers, data: { name: `E2E 测试标题 ${suffix}`, text: `E2E 测试标题内容 ${suffix}`, category: "default", }, }); expect(createTitle.status(), await createTitle.text()).toBe(201); 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: registerData.user_id, user_id: registerData.user_id, email, username, display_name: username, is_email_verified: true, email_verified: true, }, }, ); await page.goto("/app/titles"); await expect(page.locator(".xx-titles-page")).toBeVisible({ timeout: 20_000, }); await expect(page.locator(".xx-title-card").first()).toBeVisible({ timeout: 10_000, }); const firstTitleText = await page .locator(".xx-title-card-text") .first() .textContent(); if (firstTitleText) { await page.getByPlaceholder("搜索标题关键词...").fill(firstTitleText); await expect(page.getByText(firstTitleText)).toBeVisible(); } await expect(page.locator(".xx-title-card-stat").first()).toBeVisible(); }); test("titles API creates and lists titles", async ({ request }) => { const suffix = Date.now().toString(36); const email = `e2e-title-api-${suffix}@example.com`; const username = `e2e_title_api_${suffix}`; const register = await request.post(`${apiBase}/auth/register`, { data: { email, username, password: PASSWORD, display_name: username }, }); expect(register.status()).toBe(201); 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}` }; const titleText = `E2E Test Title ${suffix}`; const createResp = await request.post(`${apiBase}/titles`, { headers, data: { name: titleText.slice(0, 50), text: titleText, category: "default", }, }); expect(createResp.status(), await createResp.text()).toBe(201); const created = (await createResp.json()) as { id: string; text: string; }; expect(created.id).toBeTruthy(); const listResp = await request.get(`${apiBase}/titles`, { headers }); expect(listResp.status()).toBe(200); const listData = (await listResp.json()) as { items: Array<{ id: string; text: string }>; }; const found = listData.items.find((t) => t.id === created.id); expect(found).toBeTruthy(); }); });