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() }) })