CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
软件测试入门/课时/E2E 与 Playwright
RawBlame
课时目录12 节

软件测试入门

  1. 01测试金字塔
  2. 02JUnit 5 基础
  3. 03Mockito 与 Mock
  4. 04Spring Boot 测试
  5. 05MockMvc 接口测试
  6. 06集成测试
  7. 07TDD 与可维护性
  8. 08前端单元测试
  9. 09E2E 与 Playwright
  10. 10覆盖率与质量门禁
  11. 11CI 中的测试
  12. 12综合练习

E2E 与 Playwright

浏览器自动化、关键路径冒烟。

30 分钟

E2E 与 Playwright

这一课你将能做到

  1. 配置 Playwright 双服务环境(Next.js 前端 + Spring Boot 后端),写出「打开页 → 可见 → 跳转」的关键路径冒烟
  2. 用 getByRole / 自动等待 / waitForResponse 替代 sleep,把 flaky 压到最低
  3. 列出本站 3~5 条值得 E2E 守护的路径,并说清哪些不该进 E2E

为什么要学

单元与 MockMvc 再绿,仍可能出事:前端路由配错、环境变量 NEXT_PUBLIC_API_BASE 指错、CORS 没开、真浏览器才暴露的水合错误。人话:E2E 是用真实浏览器走用户的路——少、慢、贵,但只有它能抓住「整条链断了」。

它不能替代金字塔底部。本课目标是会写稳定的冒烟,并知道 CI 策略:PR 上仍以 mvn test + npm test 为主,E2E 放主分支或发布前跑(分轨思想见 test-ci)。

CodeForge 值得 E2E 守护的命门很清楚:访客能打开首页、能看到课程列表、注册登录能走通、提交代码能看到判题状态。每多一条用例就要多付一份维护税——文案改、路由改、数据改都会红,所以清单要像产品的命门列表,不是测试同学的 KPI。

先建立图像

E2E:  Browser ──► platform(Next.js :3000) ──HTTP──► backend(Spring Boot :8080)
         ▲                                            │
         └── Playwright 驱动:点击、填表、断言 ←──────┘
                                              MySQL / Redis

成本:分钟级;失败定位面横跨四层
策略:3~5 条命门;能下沉到组件测 / MockMvc 的就下沉
层级已被谁测E2E 要不要重复
业务分支Mockito 单测不重复
HTTP 契约MockMvc不重复字段细节
整页用户路径—E2E 主场

选择器优先级与 Testing Library 一致:role / label / 文本 ≫ CSS class / XPath。

跟着做

步骤 1:安装与最小配置

cd platform
npm install -D @playwright/test
npx playwright install chromium

playwright.config.ts——重点是 webServer 让脚本自动拉起前后端:

import { defineConfig } from "@playwright/test";

export default defineConfig({
  testDir: "./e2e",
  use: {
    baseURL: "http://localhost:3000",
    trace: "on-first-retry",   // 失败重试时录 trace,定位神器
  },
  retries: process.env.CI ? 1 : 0,
  webServer: [
    {
      command: "cd ../backend && mvn spring-boot:run",
      url: "http://localhost:8080/tracks",
      reuseExistingServer: true,
      timeout: 120_000,
    },
    {
      command: "npm run dev",
      url: "http://localhost:3000",
      reuseExistingServer: true,
      timeout: 120_000,
    },
  ],
});

reuseExistingServer: true 让你本地已手动起好的服务不被重复拉起;CI 里它会自己起全套。

步骤 2:第一条冒烟——访客视角

e2e/smoke.spec.ts:

import { test, expect } from "@playwright/test";

test("首页可见并能看到课程入口", async ({ page }) => {
  await page.goto("/");

  // 等待可见性,而不是 sleep
  await expect(
    page.getByRole("heading", { level: 1 })
  ).toBeVisible();

  await page.getByRole("link", { name: /课程|tracks/i }).first().click();
  await expect(page).toHaveURL(/tracks/);
});
npx playwright test

应看到:浏览器自动操作并通过。失败时看 test-results/ 里的 trace 与截图——先归因再改代码(步骤 6)。

步骤 3:课程详情路径

test("打开指定 track 可见课时标题", async ({ page }) => {
  await page.goto("/tracks/testing-essentials");

  await expect(
    page.getByRole("heading", { name: /测试/i })
  ).toBeVisible();
});

slug 用仓库里确定存在的数据(种子或迁移产生的),绝不依赖某人手工点过的脏库。

步骤 4:判题路径——等网络而不是猜时序

「提交代码 → 出现排队中徽章」涉及跨端请求,用 waitForResponse 精确同步:

test("提交代码后出现判题状态徽章", async ({ page }) => {
  // 前置:已在测试环境登录(按本站真实登录表单填写,或用测试后门账号)
  await page.goto("/tracks/testing-essentials/lessons/test-intro");

  const apiDone = page.waitForResponse(
    (res) => res.url().includes("/submissions") && res.ok()
  );

  await page
    .getByRole("textbox", { name: /代码/i })
    .fill("class Main { public static void main(String[] a) {} }");
  await page.getByRole("button", { name: /提交/ }).click();

  await apiDone;

  await expect(page.getByText(/排队中/)).toBeVisible();
});

网络慢时固定 waitForTimeout(3000) 会随机红;期望断言(expect(...).toBeVisible())自带重试轮询,事件等待(waitForResponse)精确到协议层——两种手段覆盖 95% 的时序问题。

步骤 5:划定「只守这些」的清单

在 platform/e2e/README.md 里写死:

必跑冒烟:
1. 首页可打开(h1 可见)
2. /tracks 列表有课程入口
3. /tracks/<已知slug> 标题可见
4. 登录后能进受保护页面
5. 提交代码后判题徽章出现

明确不测:主题色、间距、hover 效果、每个按钮文案。

新增 E2E 前先改这张清单:说不出「命门」理由的,下沉到组件测或 MockMvc。

步骤 6:失败时的分层归因三问

E2E 一红,先别改业务代码,按顺序问:

  1. 环境? backend 起了吗、NEXT_PUBLIC_API_BASE 指对了吗、种子数据在吗?
  2. 选择器? 文案/结构是不是刚改过、role 还能定位吗?
  3. 真回归? 底层 MockMvc / 单测是否已红——底层红先修底层。

把这三问写成团队习惯,E2E 才不会变成「吓人但不解决问题」的红灯。

讲清楚要点

  1. E2E 失败面大,所以用例必须短、路径必须关键:一条用例走一个用户故事,失败时才有机会快速归因。
  2. 环境是变量,基址/账号/种子必须进配置与文档:baseURL、webServer、种子 slug 全部落文件,禁止口口相传。
  3. 自动等待是稳定性核心:expect 自带重试、waitForResponse 精确同步,固定 sleep 是 flaky 之源。
  4. 选择器用 role/文本:与组件测同一哲学——测用户感知,不绑实现细节。
  5. CI 分级:PR 跑快测,E2E 挂主分支/夜间/发布前——分钟数是钱,队列是士气。
  6. 金字塔顶部宜窄:每加一条 E2E 先问「能否下沉到 MockMvc / 组件测」,能沉就沉。

你可能会懵的地方

  • 本地绿、CI 红是为什么? 常见:CI 没起 backend、baseURL 环境变量不同、浏览器依赖没装、CI 机器更慢放大了时序问题。先看 trace artifact 与两个服务的启动日志,再谈重试。

  • 要不要测真实验证码 / OAuth 跳转? 真第三方易脆。测试环境用后门验证码开关、mock IdP,或只测「已带登录 Cookie 的页面」。第三方对接本身留给手动验收或专项集成测。

  • Playwright 和 Cypress 选哪个? 都能做 E2E。Playwright 多浏览器、自动等待成熟、webServer 配置对双端项目友好——本课用它;思维要点在两者间通用:少而稳、抗 flaky。

  • 一条 E2E 里断言二十个字段? 过度耦合。E2E 抽「关键可见性」;字段细节是 MockMvc 契约测的职责。

  • E2E 要不要跑每个 PR? 默认不。快测挡 PR;E2E 挂主分支 push / nightly / 发布前。若某条 E2E 稳定且快到可以挡 PR,先想想它是不是该下沉。

错法对照

错法问题正解
用 E2E 替代所有单测慢、难定位底多顶少
waitForTimeout 铺满随机 flaky自动等待 / 事件等待
选择器绑随机 class样式一改全红getByRole
依赖手工数据库状态不可重复种子数据 / 已知 slug
PR 全量 E2E 无分轨队列爆炸PR 快测 + E2E 分级

卡点急救

  1. webServer 起不来或超时:url 必须是「服务就绪后返回 2xx/3xx 的地址」,backend 用一个确定存在的 GET 路由;timeout 调到 120s 以上(首次 mvn 拉依赖很慢)。
  2. strict mode violation(多个元素匹配):getByRole 命中多个时用 .first()、getByRole(..., { exact: true }) 或收窄父容器;别用 page.locator("css=...").nth(3) 这类脆定位。
  3. 本地绿 CI 红:对比两端日志确认服务都起了;把 trace 打开(trace: "on-first-retry" + CI retries: 1),下载 artifact 逐步回放。

面试常问

  • E2E 测试 flaky 怎么治理? 四板斧:选择器用 role/文本不绑样式;等待用自动重试断言与事件等待,不用固定 sleep;数据用种子与已知 slug,不依赖共享脏库;CI 分轨 + 失败重试一次 + trace 回放定位。目标是让「红」几乎只代表真回归。

  • 为什么 E2E 少而关键,而不是多多益善? 每条 E2E 都有维护税:文案、路由、数据一变就红,且失败定位面横跨前后端与环境。金字塔要求顶部窄——把细节验证下沉到组件测和 MockMvc,E2E 只守「整条链是否通」。

  • E2E 在 CI 里应该放在什么频率? PR 只跑快测(mvn test / npm test)保证分钟级反馈;E2E 挂主分支 push、夜间或发布前。若要求 PR 必跑 E2E,需配缓存与并行分片,否则流水线会成为交付瓶颈。

自测三问

  1. 为什么「测每个按钮颜色」不适合 E2E?
  2. getByRole 相比 CSS 选择器的两大好处是什么?
  3. waitForResponse 与 waitForTimeout 的本质区别?

小结一句话

Playwright 守用户路径,少而稳;日常开发靠 JUnit 与 Vitest,发布前用 E2E 补最后一道门。

延伸

  • Playwright · Writing tests
  • Playwright · Best Practices
  • 下一课:test-coverage 用覆盖率报告找测试盲区,并设计不被数字绑架的质量门禁。
加载本节练习…
← 上一课前端单元测试下一课 →覆盖率与质量门禁