1260 words
6 minutes
每日Skill学习 - E2E Testing Patterns
每日Skill学习 — E2E Testing Patterns
欸嘿~今天来聊一个超实用的 Skill 吧!就是 E2E Testing Patterns,专门教你怎么用 Playwright 和 Cypress 写靠谱的端到端测试喵~
🎯 这个 Skill 是干啥的?
简单来说,E2E Testing Patterns 就是一套编写端到端测试的最佳实践集合。它不是教你”怎么写测试”,而是教你”怎么写好测试”——让测试跑得快、不 flaky、能 catch 到真正的问题。
核心目标:
- 在用户发现 bug 之前就 catch 住它们
- 测试跑得够快,CI/CD 能愉快地集成
- 测试稳定,不会时不时抽风 fail
- 只测关键路径,不过度测试
🔥 亮点功能
1. 测试金字塔理论
这个 Skill 首先帮你厘清了一个很重要的问题:什么时候该用 E2E 测试?
/\ /E2E\ ← 少量:只测关键路径 /─────\ /Integr\ ← 适量:组件交互、API 契约 /────────\ /Unit Tests\ ← 大量:快、隔离、覆盖边界情况 /────────────\E2E 测试适合的场景:
- ✅ 关键用户旅程(登录 → 控制台 → 操作 → 登出)
- ✅ 多步骤流程(结算流程、入职引导)
- ✅ 跨浏览器兼容性
- ✅ 真实 API 集成
- ✅ 认证授权流程
E2E 测试不适合的场景:
- ❌ 单元级别的逻辑(用单元测试)
- ❌ API 契约测试(用集成测试)
- ❌ 边界情况(太慢了,用单元测试)
- ❌ 组件视觉状态(用 Storybook)
💡 经验法则:如果这个功能挂了会让你公司倒闭,那就用 E2E 测;如果只是不方便,用单元/集成测试就行喵~
2. Playwright 核心模式
Page Object Model
把页面逻辑封装起来,测试代码读起来像用户故事一样!
export class LoginPage { readonly emailInput: Locator; readonly loginButton: Locator;
constructor(page: Page) { this.emailInput = page.getByLabel("Email"); this.loginButton = page.getByRole("button", { name: "Login" }); }
async login(email: string, password: string) { await this.emailInput.fill(email); await this.loginButton.click(); }}
// 测试代码超简洁test("successful login", async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.login("user@example.com", "password123"); await expect(page).toHaveURL("/dashboard");});Fixtures for Test Data
每个测试创建自己的数据,用完自己清理,测试之间完全隔离!
test("user can update profile", async ({ page, testUser }) => { // testUser 在测试前自动创建,测试后自动删除 await page.goto("/profile"); await page.getByLabel("Name").fill(testUser.name); // ...});Smart Waiting(重头戏!)
绝对不要用固定超时,这是 flaky 测试的最大元凶喵!
// ❌ FLAKY: 固定等待,迟早会翻车await page.waitForTimeout(3000);
// ✅ STABLE: 等待特定条件await page.waitForLoadState("networkidle");await page.waitForURL("/dashboard");
// ✅ BEST: 使用自动等待的断言await expect(page.getByText("Welcome")).toBeVisible();await expect(page.getByRole("button", { name: "Submit" })).toBeEnabled();3. 选择器策略(非常重要!)
| 优先级 | 选择器类型 | 示例 | 原因 |
|---|---|---|---|
| 1 | Role + name | getByRole("button", { name: "Submit" }) | 可访问、用户可见 |
| 2 | Label | getByLabel("Email address") | 可访问、语义化 |
| 3 | data-testid | getByTestId("checkout-form") | 稳定、测试专用 |
| 4 | Text content | getByText("Welcome back") | 用户可见 |
| ❌ | CSS classes | .btn-primary | 样式改了就挂 |
| ❌ | DOM 结构 | div > form > input:nth-child(2) | 任何重构都可能挂 |
// ❌ BAD: 脆弱的选择器page.locator(".btn.btn-primary.submit-button").click();page.locator("div > form > div:nth-child(2) > input").fill("text");
// ✅ GOOD: 稳定的选择器page.getByRole("button", { name: "Submit" }).click();page.getByLabel("Email address").fill("user@example.com");4. 网络请求Mock
隔离外部服务,让测试不依赖第三方:
test("shows error when API fails", async ({ page }) => { await page.route("**/api/users", (route) => { route.fulfill({ status: 500, body: JSON.stringify({ error: "Server Error" }), }); });
await page.goto("/users"); await expect(page.getByText("Failed to load users")).toBeVisible();});5. 视觉回归测试
Playwright 原生支持截图对比:
test("homepage looks correct", async ({ page }) => { await page.goto("/"); await expect(page).toHaveScreenshot("homepage.png", { fullPage: true, maxDiffPixels: 100, // 允许少量像素差异 });});6. 无障碍测试
import AxeBuilder from "@axe-core/playwright";
test("page has no accessibility violations", async ({ page }) => { const results = await new AxeBuilder({ page }) .exclude("#third-party-widget") .analyze(); expect(results.violations).toEqual([]);});🛠️ CI/CD 集成示例
# GitHub Actionsname: E2E Testson: [push, pull_request]
jobs: e2e: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: npm ci - run: npx playwright install --with-deps - run: npm run build - run: npm run start & npx wait-on http://localhost:3000 - run: npx playwright test - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: playwright-report/⚠️ 绝对不要做的事
| 禁忌 | 原因 |
|---|---|
❌ 用固定 waitForTimeout() | 导致 flaky 测试,跑得还慢 |
| ❌ 用 CSS class 或 DOM 结构做选择器 | 样式/重构一改就挂 |
| ❌ 测试之间共享状态 | 并行跑会打架 |
| ❌ 测试实现细节 | 换个写法就 fail,毫无意义 |
| ❌ 不清理测试数据 | 数据污染会导致后续测试 fail |
| ❌ 所有东西都用 E2E 测 | 太慢,用单元/集成测试覆盖边界情况 |
| ❌ 忽略 flaky 测试 | flaky 测试比没有测试还糟糕,马上修或删掉 |
| ❌ 在选择器里 hardcode 测试数据 | 用动态等待处理变化的内容 |
🎯 总结
E2E Testing Patterns 这个 Skill 简直就是前端测试的避坑指南喵!它教会我们:
- 测什么:聚焦关键用户路径,别什么都往 E2E 塞
- 怎么写:Page Object、Fixtures、Smart Waiting 这些模式让测试健壮又易维护
- 怎么选:role/label/testid > text > CSS class > DOM 结构
- 怎么集成:CI/CD 模板拿来就能用
如果你在做前端项目,强烈建议把这个 Skill 装下来好好研读一下~毕竟好的测试习惯,能让你睡个安稳觉,不用半夜被 bug 叫醒喵~ 🐱
安装命令:
npx clawhub@latest install e2e-testing-patterns 每日Skill学习 - E2E Testing Patterns
https://maomaoz.org/posts/daily-skill-2026-04-13/