import { expect, test, type APIRequestContext } from "@playwright/test"; import { adminApi, creds, ensureSampleBook } from "./helpers/api"; import { scanA11y } from "./helpers/axe"; let admin: APIRequestContext; let bookId: number; let title: string; test.beforeAll(async () => { admin = await adminApi(); bookId = await ensureSampleBook(admin, "e2e"); // 标题以 API 实况为准:后端 titleOf 可能改写文件名(下划线→空格等),书架卡按真实标题找。 title = ((await (await admin.get(`/api/books/${bookId}`)).json()) as { title: string }).title; }); test.afterAll(async () => { await admin?.dispose(); }); test("登录 → 书架 → CBZ 阅读翻页 → 书签 → 退出", async ({ page }) => { const { user, pass } = creds(); // 登录页 + axe await page.goto("/login"); await scanA11y(page, "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(/\/$/); // 书架 + axe;样本卡可见并点进阅读器。 // 卡片用「结构 + 文本」定位:可访问名匹配会同时命中卡片的《…》操作菜单钮(含书名 aria-label)。 const card = page.locator(".group > button", { hasText: title }); await expect(card).toBeVisible(); await scanA11y(page, "shelf"); await card.click(); await expect(page).toHaveURL(new RegExp(`/book/${bookId}$`)); // 阅读器(chrome 默认展开)+ axe const reader = page.getByLabel("漫画阅读器,点按左右两侧翻屏,点按中间显示工具栏"); await expect(reader).toBeVisible(); await expect(page.getByText("1/3")).toBeVisible(); await scanA11y(page, "reader-chrome"); // 右缘点击翻屏 → 页码前进。 // 单次 scrollBy 一屏未必把视口中心推过第 1 页底(页高 > 一屏),故按实况重试点击直到 2/3。 const box = await reader.boundingBox(); if (!box) throw new Error("阅读器容器无 boundingBox"); await expect(async () => { await page.mouse.click(box.x + box.width * 0.9, box.y + box.height / 2); await expect(page.getByText("2/3")).toBeVisible(); }).toPass({ timeout: 20_000 }); // 导航抽屉(本 fixture 无内嵌目录,抽屉直接落在书签面板)→ 加书签 → toast await page.getByRole("button", { name: "导航", exact: true }).click(); const nav = page.locator("#reader-nav"); await expect(nav).toBeVisible(); await nav.getByRole("button", { name: "加书签" }).click(); await expect(page.getByText("已加书签")).toBeVisible(); await page.getByRole("button", { name: "关闭" }).click(); // 回书架 → 退出登录 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/); });