Advanced: Network Mocking, Visual & Accessibility (Playwright + TypeScript, Ch.22)

Advanced: Network Mocking, Visual & Accessibility (Playwright + TypeScript, Ch.22)

进阶:网络拦截、视觉回归与无障碍测试 (Playwright + TypeScript, 第22章)

Welcome to Part 6. The framework is solid; now we add three powerful kinds of test that go beyond “click and assert text.” Code for this chapter is tagged ch-22 in the repo: https://github.com/aktibaba/playwright-qa-course — see src/tests/ui/: network-mock.spec.ts, visual.spec.ts, a11y.spec.ts. 欢迎来到第6部分。框架已经稳固,现在我们将添加三种超越“点击并断言文本”的强大测试类型。本章代码在仓库中标记为 ch-22:https://github.com/aktibaba/playwright-qa-course —— 请查看 src/tests/ui/ 下的 network-mock.spec.ts, visual.spec.ts 和 a11y.spec.ts。

Network mocking — test the UI in isolation. page.route intercepts requests so the UI runs against a response you control. That makes states that are awkward to set up in a real backend — empty, error, exotic data — trivial and deterministic: 网络拦截(Network Mocking)—— 在隔离环境下测试 UI。page.route 可以拦截请求,让 UI 针对你控制的响应进行运行。这使得在真实后端中难以设置的状态(如空数据、错误、特殊数据)变得简单且确定:

test("shows the empty state when the feed is empty", async ({ page }) => {
  await page.route("**/api/articles?*", (route) => 
    route.fulfill({ json: { articles: [], articlesCount: 0 } }),
  );
  await page.goto("/");
  await expect(page.getByText("Articles not available.")).toBeVisible();
});

test("survives an API error without crashing", async ({ page }) => {
  await page.route("**/api/articles?*", (route) => 
    route.fulfill({ status: 500, json: { errors: { body: ["boom"] } } }),
  );
  await page.goto("/");
  await expect(page.getByRole("link", { name: "Sign up" })).toBeVisible();
});

These need no database and no auth — the test owns the data. Use mocking for UI behavior on hard-to-produce responses; keep real-backend integration tests (Part 4) for the contract itself. Both, not either. 这些测试不需要数据库,也不需要身份验证——测试完全掌控数据。对于难以生成的响应,请使用 Mock 来测试 UI 行为;而对于契约本身的测试,请保留真实后端的集成测试(第4部分)。两者缺一不可。

Visual regression — catch the unintended. toHaveScreenshot pixel-compares a page against a committed baseline, catching changes no text assertion would — a broken layout, a wrong color, a clipped button: 视觉回归测试 —— 捕捉意外的变化。toHaveScreenshot 通过像素对比将页面与已提交的基准图进行比较,捕捉文本断言无法发现的问题——例如布局损坏、颜色错误或按钮被截断:

test("login page matches its baseline", async ({ page }) => {
  await page.goto("/#/login");
  await expect(page.getByRole("button", { name: "Login" })).toBeVisible();
  await page.evaluate(() => document.fonts.ready); // avoid web-font swap flicker
  await expect(page).toHaveScreenshot("login.png", { maxDiffPixelRatio: 0.02 });
});

Two things make visual tests trustworthy instead of flaky: Settle the page first. Waiting on document.fonts.ready removes the most common cause of jitter — a screenshot taken mid web-font swap. A small maxDiffPixelRatio absorbs sub-pixel anti-aliasing. Baselines are platform-specific. A macOS baseline won’t match Linux CI, so we test.skip visual specs on CI and document generating Linux baselines in the Playwright Docker image. Never commit a baseline from one OS and diff it on another. 两点关键使视觉测试变得可靠而非不稳定:首先是让页面稳定。等待 document.fonts.ready 移除了最常见的抖动原因——即在 Web 字体切换过程中截图。设置较小的 maxDiffPixelRatio 可以容忍亚像素抗锯齿带来的差异。基准图是与平台相关的。macOS 的基准图无法与 Linux CI 环境匹配,因此我们在 CI 上使用 test.skip 跳过视觉测试,并在 Playwright Docker 镜像中记录生成 Linux 基准图的方法。永远不要提交一个操作系统生成的基准图,却在另一个系统上进行对比。

Accessibility — and real bugs we fixed. We scan with @axe-core/playwright and fail on serious/critical violations: 无障碍测试 —— 以及我们修复的真实 Bug。我们使用 @axe-core/playwright 进行扫描,并在出现严重/关键违规时报错:

const results = await new AxeBuilder({ page })
  .withTags(["wcag2a", "wcag2aa"])
  .exclude(".pagination") // third-party widget, see below
  .analyze();

const serious = results.violations.filter(
  (v) => v.impact === "serious" || v.impact === "critical",
);
expect(serious).toEqual([]);

The first run failed — and the violations were real: Color contrast. The navbar links (2.1:1), the banner subtitle, muted dates, and the green feed toggle (3.0:1) all fell short of WCAG AA’s 4.5:1. We fixed the app (sut/): darkened the brand green and the muted greys to meet AA. Orphaned list items came from the react-paginate widget rendering its <ul> with role="navigation". That’s a third-party limitation we can’t fix from app code, so we .exclude(".pagination") with a comment and would report it upstream — triaging what you don’t own instead of letting it mask your own regressions. This is the realistic a11y workflow: scan, fix what’s yours, triage the rest. And fixing contrast is a genuine product improvement, not just a green test. 第一次运行就失败了——而且违规是真实的:颜色对比度问题。导航栏链接 (2.1:1)、横幅副标题、灰色日期以及绿色 Feed 切换按钮 (3.0:1) 均未达到 WCAG AA 标准要求的 4.5:1。我们修复了应用 (sut/):加深了品牌绿色和灰色以符合 AA 标准。孤立的列表项源自 react-paginate 组件,它将 <ul> 渲染为 role="navigation"。这是我们无法从应用代码中修复的第三方限制,因此我们添加注释并使用 .exclude(".pagination") 将其排除,并应向其上游报告——对非你掌控的代码进行分类处理,而不是让它掩盖你自己的回归问题。这就是现实中的无障碍测试工作流:扫描、修复自己的问题、分类处理其余部分。修复对比度是真正的产品改进,而不仅仅是为了让测试通过。

Next up: We’ve widened what we can assert. Chapter 23 — Stability & maintainability at scale: the utilities and habits that keep a large suite trustworthy — taming animations and async, safe waiting, and helpers that stop flakiness before it starts. Tag: ch-23. Following along? Star the repo and tell me which of the three — mocking, visual, or a11y — your suite is missing. 接下来:我们拓宽了断言的范围。第23章 —— 大规模测试的稳定性和可维护性:保持大型测试套件可靠的工具和习惯——驯服动画与异步、安全等待,以及在不稳定性发生前将其扼杀的辅助工具。标签:ch-23。在跟着学习吗?给仓库点个星,并告诉我你的测试套件中缺少这三种测试(Mock、视觉、无障碍)里的哪一种。