import { expect, test, type APIRequestContext } from "@playwright/test"; import { adminApi, creds, ensureBook, FIXTURE_TXT } from "./helpers/api"; import { scanA11y } from "./helpers/axe"; let admin: APIRequestContext; let bookId: number; test.beforeAll(async () => { admin = await adminApi(); // 文件名避免与 CBZ 样本书同名:fixture 用 e2e-sample-txt.txt(title=e2e-sample-txt) bookId = await ensureBook(admin, "e2e", "e2e-sample-txt.txt", FIXTURE_TXT); }); test.afterAll(async () => { await admin?.dispose(); }); test("登录 → txt 阅读 → 全书搜索 → 跳转", async ({ page }) => { const { user, pass } = creds(); await page.goto("/login"); await page.fill("#login-user", user); await page.fill("#login-pass", pass); await page.getByRole("button", { name: "登录", exact: true }).click(); await expect(page).toHaveURL(/\/$/); const card = page.locator(".group > button", { hasText: "e2e-sample-txt" }); await expect(card).toBeVisible(); await card.click(); await expect(page).toHaveURL(new RegExp(`/book/${bookId}$`)); // 导航抽屉 → 搜索 tab await page.getByRole("button", { name: "导航", exact: true }).click(); const nav = page.locator("#reader-nav"); await expect(nav).toBeVisible(); await nav.getByRole("tab", { name: "搜索" }).click(); await nav.getByRole("textbox", { name: "搜索全书" }).fill("zebra-xc42"); // 大小写不敏感:第一章 2 处 + 第二章 1 处 = 3 行 await expect(nav.getByRole("listitem")).toHaveCount(3, { timeout: 10_000 }); expect(await nav.locator("mark").count()).toBe(3); // 高亮 // 点第二条 → 跳到第一章/第二章(第二条命中)的章,章计数 2/3,抽屉关闭 await nav.getByRole("button").filter({ hasText: "第二章" }).first().click(); await expect(page.getByText("2/3")).toBeVisible({ timeout: 10_000 }); await expect(nav).not.toBeVisible(); await scanA11y(page, "reader-chrome"); // 退出 await page.getByRole("link", { name: "书架" }).click(); await expect(page).toHaveURL(/\/$/); await page.getByRole("button", { name: new RegExp(`账户 ${user}`) }).click(); await page.getByText("退出登录").click(); await expect(page).toHaveURL(/\/login/); });