- 后端
- 前端
- 搜索引擎
【免费下载链接】openlibrary
One webpage for every book ever published!
导读
本文以 Open Library 仓库中的 tests/e2e/README.md 为主线,系统讲解该项目如何用 Playwright 驱动真实浏览器对运行中的 Open Library 实例做端到端(E2E)测试,并深入剖析其"匿名 + 登录"双轨测试模式、login()会话注入机制、桌面/移动双项目拆分策略,以及基于 axe-core 的 WCAG 2.1 AA 可访问性扫描方案。读完本文,你将掌握如何在本地 Docker 栈上跑通这套 E2E 测试、如何为登录态页面编写测试、如何给可访问性缺陷提交"先证明失败、再修复变绿"的规范 PR。
一、测试套件概览:一套驱动真实浏览器的 Playwright 规格
tests/e2e/目录下的 spec 文件(home.spec.ts、work.spec.ts、search.spec.ts、login.spec.ts、subjects.spec.ts、edition.spec.ts、author.spec.ts、my-books.spec.ts 等)不是单元测试,而是"驱动真实浏览器、访问正在运行的 Open Library"的 Playwright 规格。它们覆盖了全站最重要的冒烟路径:首页加载、全局头部、搜索触发、登录表单、图书(Work)页、作者页、搜索结果页、主题页、My Books 页,并附有独立的可访问性规格 a11y.spec.ts 和可选的可视化回归规格 visual.spec.ts。
测试的顶层配置集中在仓库根目录的 playwright.config.ts:testDir指向./tests/e2e,单个用例超时 30 秒、不自动重试,报告采用list(终端流水)+html(离线 HTML 报告)双 reporter。默认baseURL为http://localhost:8080,可通过OL_BASE_URL环境变量覆盖。
二、运行测试:本地 Docker 栈 + 一次性安装浏览器
2.1 三条命令跑通全量测试
按 tests/e2e/README.md 的说明,先启动本地服务栈,安装一次浏览器,再执行测试:
OL_MOUNT_DIR="$(pwd)" docker compose up -d web infobase db memcached home covers npx playwright install chromium chromium-headless-shell npm run test:e2e- 第一条命令启动 Open Library 的核心服务:
web(前端/应用服务)、infobase(数据库服务层)、db(PostgreSQL)、memcached(缓存)、home(首页服务)和covers(封面服务)。OL_MOUNT_DIR="$(pwd)"用于把当前仓库目录挂载进容器,这一点在 compose.override.yaml 的多处 volume 配置中都有体现(如${OL_MOUNT_DIR:-.}:/openlibrary)。 - 第二条命令为 Playwright 安装 Chromium 内核及其 headless 变体。仓库选用 Chromium(含 Pixel 5 移动仿真)而非 WebKit,原因是 WebKit headless 在某些 macOS 上启动会超时,Chromium 更稳定,这一取舍已写进 playwright.config.ts 的注释。
- 第三条命令对应 package.json 中的
"test:e2e": "playwright test",即运行tests/e2e目录下的全部 Playwright 用例。
2.2 指定被测环境
测试默认访问http://localhost:8080。要指向其他环境(如 staging 或 production 镜像),设置OL_BASE_URL即可:
OL_BASE_URL=https://staging.openlibrary.org npm run test:e2e2.3 桌面与移动双项目:保证同一用例不跑两遍
playwright.config.ts中配置了两个 project,核心思路是"每一条用例只在一个 project 中执行,绝不重复":
| Project | 筛选方式 | 视口 |
|---|---|---|
desktop | grepInvert: /@mobile/(跑除@mobile外的所有用例) | Desktop Chrome,1280×800 |
mobile | grep: /@mobile/(只跑带@mobile标记的用例) | Pixel 5 仿真,390×844 |
于是,绝大多数用例在 desktop 上以桌面视口运行;而针对响应式布局的移动端检查(例如登录表单输入框在窄视口下不被裁切、Work 标题无横向滚动),用@mobile标签标记后只在 mobile project 上运行。从源码中可以看到这类用例的典型写法,如 login.spec.ts 的mobile: form fields are visible and not clipped @mobile,以及 work.spec.ts 中的@mobile用例。
三、登录态测试:login()会话注入与凭据配置
3.1 双轨测试模式
多数 spec 遵循同一结构:先以匿名身份检查页面,再在test.describe('when logged in')块内重复检查登录后的状态。例如 home.spec.ts 中匿名时断言头部出现Log In / Sign Up链接,登录后则断言头部显示账户菜单、不再出现登录入口。
3.2 核心机制:login()通过 JSON 端点注入会话
README 明确指出:login(page)(定义于 tests/e2e/helpers.ts)通过page.request.post('/account/login.json')提交凭据,由于page.request与浏览器 context 共享同一个 cookie jar,登录返回的 session cookie 会直接落入浏览器上下文——下一次page.goto()就已经是认证状态,全程无需驱动登录表单:
import { collectConsoleErrors, login } from './helpers'; test.describe('when logged in', () => { test.beforeEach(({ page }) => login(page)); test('loads', async ({ page }) => { /* ... */ }); });从 helpers.ts 的实现可见,login()内部还做了两件事:无可用凭据时通过test.skip()跳过用例,并断言login.json返回 200,否则用响应体文本给出失败原因。
3.3 环境变量与凭据回退
README 给出了登录相关环境变量的对照表,结合 helpers.ts 可以还原完整的取值逻辑:
| 变量 | 用途 | 本地回退值 |
|---|---|---|
OL_E2E_USERNAME/OL_E2E_PASSWORD | 供login()会话助手使用(infogami 用户名 + 密码) | openlibrary/openlibrary(dev 栈种子账户) |
OL_E2E_S3_ACCESS/OL_E2E_S3_SECRET | 改为使用 IA S3 密钥调用login() | 无(两者同时设置才启用) |
OL_E2E_EMAIL | 驱动登录表单的用例,按邮箱走 IA 认证 | openlibrary@example.com |
关键回退逻辑是:当OL_BASE_URL未设置或指向localhost/127.0.0.1时视为本地环境,采用种子凭据openlibrary/openlibrary;指向其他主机时必须显式设置凭据,否则登录态用例自动跳过(HAS_CREDENTIALS为 false)。登录表单用例则依赖OL_E2E_EMAIL,例如 login.spec.ts 用E2E_EMAIL走完整表单流程并断言跳转到/people/.../books(My Books)。
3.4 模拟 IA 认证与bad_password哨兵
本地 dev 栈的 IA 认证由 docker/mockservices/main.py 提供。它接受任意邮箱 + 任意非空密码,唯一例外是哨兵值bad_password——该值会被拒绝并返回失败响应(源码中_DEV_BAD_PASSWORD = "bad_password",authenticate分支对空密码与bad_password一律判失败)。因此登录失败路径在 dev 与生产环境都能稳定复现:login.spec.ts 用bad_password验证"密码错误时停留在登录页并显示错误提示、会话仍为匿名"。该哨兵行为同样有对应的 mock 服务单测覆盖(docker/mockservices/tests/test_e2e.py)。
3.5 控制台错误收集
helpers.ts 还提供了collectConsoleErrors(page):监听浏览器 console 的 error 级消息与pageerror,但过滤两类基础设施噪音——Failed to load resource:(本地开发环境访问不到的外部服务/资源,如 IA availability API、CDN 资源)与violates the following Content Security Policy(被本地拦截、线上不存在的 archive.org iframe 的 CSP 违规)。多数用例以expect(errors()).toHaveLength(0)收尾,把"页面加载无 JS 报错"固化为冒烟断言。
四、观察与调试:--ui、--headed与 HTML 报告
README 强调--ui与--headed是两回事:
npx playwright test --ui # 交互式运行器 npx playwright test --headed # 弹出可见浏览器,实时驱动--ui以 headless 方式运行并录制 trace,供你事后逐动作回放:选中一个用例、点击某个操作,右侧面板会展示那一刻的真实 DOM。它不会弹出浏览器窗口,且面板在选中单个用例(而非整个describe块)之前保持空白。--headed才是真正打开 Chromium、亲眼看着浏览器执行用例的方式,可加--slow-mo=1000让每一步间隔 1 秒以便跟读。
任意一次运行结束后,npx playwright show-report会打开 HTML 报告(对应配置中reporter: [['list'], ['html', { open: 'never' }]]生成的文件)。
五、可访问性测试:axe-core + WCAG 2.1 AA
5.1 设计定位:只测"渲染出来的像素"能测的东西
a11y.ts 用@axe-core/playwright下的组件测试形成互补而非重复:组件测试运行在 jsdom 中,jsdom 只解析标记、从不做真实布局,因此只能检查 ARIA 连线;颜色对比度、焦点样式以及一切需要渲染像素才能判定的问题,只能在这套 E2E 可访问性测试里验证——这也是该套件存在的根本原因。
5.2 扫描一个页面
import { a11yCheck, expectNoViolations, THIRD_PARTY_FRAMES } from './a11y'; test('my page is accessible', async ({ page }) => { await page.goto('/my-page'); expectNoViolations(await a11yCheck(page, { exclude: THIRD_PARTY_FRAMES })); });expectNoViolations不会像 Playwright 默认那样输出难读的数组 diff,而是把每条违规渲染成可操作的信息块:影响级别(impact)、规则 id 与帮助链接、违规元素的原始 HTML 及其修复建议(见 a11y.ts 的formatViolation实现)。
5.3 可访问性修复 PR 的规范套路
一个合格的可访问性修复 PR 应当携带一个能证明修复、且去掉修复后会失败的测试,并把扫描范围收敛到你修复的那条规则,这样即使页面其他位置仍有未解决的违规,测试也能保持绿色:
test('iframes have accessible titles', async ({ page }) => { await page.goto('/'); expectNoViolations(await a11yCheck(page, { rules: ['frame-title'] })); });提交前务必验证:没有你的修复时该测试必须失败。因为一条作用域规则若匹配不到任何元素,会平凡地通过——这与"修复生效"在输出上完全无法区分。这一点 a11y.ts 的头部注释也强调过"scoping to the rule you fixed keeps the test green while unrelated violations are still outstanding"。
作为反面保险,a11y.spec.ts 还内置了一条"canary"用例:断言扫描引擎确实是 axe-core、且被评估的规则数 > 20 且包含color-contrast,防止"选择器写错导致零规则命中、所有无违规断言都空洞成立"的假阳性套件。
5.4a11yCheck选项
| 选项 | 作用 |
|---|---|
rules | 只运行指定的规则 id,完全跳过 WCAG tag 过滤 |
include | 指定要扫描的 CSS 选择器,默认扫描整页 |
exclude | 跳过指定 CSS 选择器,例如THIRD_PARTY_FRAMES |
disableRules | 跳过指定规则 id;修复类 PR 测试更推荐用rules |
实现上需要注意:axe-core 的withRules与withTags互斥,因此 a11y.ts 在传入rules时只调withRules,否则才应用 WCAG AA tag 过滤。
5.5 第三方内容处理
规格在导航前就通过page.route('https://archive.org/**', route => route.abort())拦截 archive.org 请求,确保捐赠横幅永不加载。原因有二:Axe 在 Playwright 下能看到跨域 iframe 内部(注入走的是 devtools 协议而非页面上下文),而横幅内容按 campaign 轮换,部分变体会在image-alt规则上失败——不拦截的话,扫描结果会随第三方当日内容随机翻转,与 Open Library 自身的代码改动无关。
THIRD_PARTY_FRAMES(值为['iframe'])进一步把不属于我们的第三方内嵌 iframe 排除出扫描。README 特别要求:必须显式传入该排除项,而不是依赖默认行为,这样任何跳过页面部分区域的测试都会在自己的用例体内把这一点写清楚。
六、补充:可选的可视化回归测试
除文档主体内容外,tests/e2e/还包含一个可选的可视化回归规格 visual.spec.ts,用于设计改版阶段的本地截图比对(不参与 CI 门禁):
OL_VISUAL=1 npx playwright test visual --update-snapshots # 生成基线 OL_VISUAL=1 npx playwright test visual # 与基线比对它对首页、搜索结果页(/search?q=lord+of+the+rings)、图书页(/works/OL286811W)做整页截图,并遮罩.bookcover img与.carousel等随 dev 数据变化的易变区域,允许 2% 的像素差异;同时分别产出 desktop(1280×800)与@mobile(390×844)两套快照,并对头部、页脚单独截图。
七、小结
Open Library 的 E2E 测试体系可以概括为三层设计:分层复用(desktop/mobile 双 project 按@mobile标签互斥,同一用例绝不跑两遍)、会话直连(login()借login.json把 session cookie 注入浏览器 context,摆脱脆弱的表单驱动登录)、像素级可访问性(axe-core 全页扫描 + 规则级修复 PR 范式 + 第三方内容拦截)。对于希望为该仓库贡献测试或修复可访问性缺陷的开发者,tests/e2e/README.md 与 playwright.config.ts、tests/e2e/helpers.ts、tests/e2e/a11y.ts 三份源码共同构成了最直接的实践蓝本。
- 后端
- 前端
- 搜索引擎
【免费下载链接】openlibrary
One webpage for every book ever published!
相关推荐
Backrest WebUI 端到端测试实战:基于 Playwright 的全栈浏览器测试体系解析
Backrest WebUI 端到端测试实战:基于 Playwright 的全栈浏览器测试体系解析 导读 Backrest 是一个基于 restic 的 Web
后端存储运维IronClaw E2E 测试体系:基于 Playwright + Mock LLM + Emulate 的浏览器级端到端测试实战
IronClaw E2E 测试体系:基于 Playwright + Mock LLM + Emulate 的浏览器级端到端测试实战 IronClaw 的端到端测
人工智能AI 应用交互助手AI AgentIronClaw E2E 测试套件实战:基于 Playwright 与 Reborn serve 的浏览器级端到端测试指南
IronClaw E2E 测试套件实战:基于 Playwright 与 Reborn serve 的浏览器级端到端测试指南 <output_article Ir
人工智能AI 应用交互助手AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考