什么是端到端测试

端到端测试(End-to-End Testing,简称 E2E)从真实用户视角出发,模拟完整的操作流程——打开页面、点击、输入、跳转、提交,验证整个应用(前端 + 后端 + 数据库)集成后能否按预期工作。

与单元测试、集成测试相比,E2E 测试更接近真实使用场景,能发现跨模块、跨系统的集成问题,是保障核心业务链路稳定的最后一道防线。

测试类型测试对象速度覆盖
单元测试单个函数/组件快局部逻辑
集成测试模块间交互中接口对接
端到端测试完整业务流程慢用户核心路径
💡 提示
测试金字塔建议:单元测试为主,集成测试为辅,E2E 测试挑选 最关键、最频繁 的几条业务链路重点覆盖。

主流框架对比

目前最常用的两款 E2E 测试框架是 Playwright 与 Cypress,均支持自动等待、截图、视频录制与多浏览器运行。

能力PlaywrightCypress
多浏览器Chromium / Firefox / WebKitChromium / Firefox / Edge
多标签页/多窗口✅ 原生支持⚠️ 受限
网络拦截✅ 强大(Mock API)✅ 支持
移动端模拟✅ 内置设备模拟需额外配置
语言JS/TS/Python/Java/.NETJavaScript/TypeScript
✅ 建议
新项目推荐 Playwright:生态更完整、跨浏览器能力更强;已有 JS 团队且偏重调试体验可选 Cypress。

编写第一个 E2E 测试

以 Playwright 为例,先初始化项目并安装依赖:

npm init -y
npm i -D @playwright/test
npx playwright install

在 tests/ 目录新建测试文件,模拟用户完成一次登录并跳转首页:

const { test, expect } = require("@playwright/test");

test("用户登录后进入首页", async ({ page }) => {
  // 打开登录页
  await page.goto("https://example.com/login");

  // 填写表单并提交
  await page.fill("#username", "admin");
  await page.fill("#password", "123456");
  await page.click("button[type=submit]");

  // 等待跳转并断言
  await expect(page).toHaveURL(/\/home/);
  await expect(page.locator(".welcome")).toContainText("欢迎回来");
});

运行测试并生成 HTML 报告:

npx playwright test --reporter=html
✅ 最佳实践
  • 用 data-testid 定位元素,避免依赖易变的 CSS 类名。
  • 测试数据独立管理,避免依赖线上真实数据。
  • 接入 CI:每次提交后自动跑核心链路用例。

定位器与常用操作

定位器(Locator)是 Playwright 中查找元素的统一方式,推荐按可访问性或 角色定位,而不是脆弱的 CSS 选择器。

// 按文本 / 角色定位(推荐)
await page.getByRole("button", { name: "提交" }).click();
await page.getByText("用户名").fill("admin");

// 按 testid 定位(页面需添加 data-testid 属性)
await page.getByTestId("search-input").fill("vue");

// 按选择器定位(兜底)
await page.locator("form input[type=password]").fill("123456");

常用操作

await page.goto("https://example.com");   // 跳转
await page.fill("#name", "张三");          // 输入
await page.click("text=登录");             // 点击
await page.selectOption("#city", "beijing"); // 下拉选择
await page.check("#agree");                // 勾选
await page.hover(".menu");                 // 悬停
await page.keyboard.press("Enter");        // 键盘
await page.screenshot({ path: "shot.png" }); // 截图
⚠️ 注意
优先 getByRole / getByText;避免直接使用 xpath 与绝对 CSS 路径,维护成本高。

网络拦截与 Mock 接口

E2E 测试中常需要拦截网络请求:mock 第三方接口、模拟慢网络、断言请求参数,让测试稳定且可复现。

Mock 一个接口

test("登录接口被 Mock", async ({ page }) => {
  await page.route("**/api/login", (route) => {
    route.fulfill({
      status: 200,
      contentType: "application/json",
      body: JSON.stringify({ token: "fake-token", user: { name: "admin" } }),
    });
  });

  await page.goto("/login");
  await page.fill("#username", "admin");
  await page.fill("#password", "wrong-password");
  await page.click("button[type=submit]");

  // 即使密码错误,也因为 Mock 而登录成功
  await expect(page).toHaveURL(/\/home/);
});

断言请求参数

test("提交表单时携带正确参数", async ({ page }) => {
  let requestBody = null;
  await page.route("**/api/submit", (route) => {
    requestBody = route.request().postData();
    route.continue();
  });

  await page.goto("/form");
  await page.fill("#email", "a@b.com");
  await page.click("button[type=submit]");
  await page.waitForTimeout(500);

  expect(requestBody).toContain("a@b.com");
});

模拟慢网络

// 只对图片资源模拟慢速
await page.route("**/*.png", async (route) => {
  await new Promise((r) => setTimeout(r, 2000));
  route.continue();
});
✅ 建议
只 Mock 第三方或不可控的接口;自己后端的核心链路尽量走真实请求,保证测试有效性。

Cypress 快速上手

Cypress 以开发者体验著称:自带时间旅行调试、自动等待、直观的断言语法。

初始化

npm i -D cypress
npx cypress open   # 打开交互式界面

编写用例

describe("登录流程", () => {
  it("正确凭证可以登录", () => {
    cy.visit("/login");

    cy.get("#username").type("admin");
    cy.get("#password").type("123456");
    cy.get("button[type=submit]").click();

    cy.url().should("include", "/home");
    cy.contains(".welcome", "欢迎回来").should("be.visible");
  });

  it("密码错误提示错误信息", () => {
    cy.visit("/login");
    cy.get("#username").type("admin");
    cy.get("#password").type("bad");
    cy.get("button[type=submit]").click();

    cy.contains(".error", "用户名或密码错误").should("be.visible");
  });
});

关键命令

命令作用
cy.visit(url)访问页面
cy.get(sel)获取元素(自动重试)
cy.intercept()网络拦截 / Mock
cy.fixture()加载测试数据文件
cy.screenshot()截图
⚠️ 注意
Cypress 默认不支持多标签页/跨域 iframe,多窗口场景建议用 Playwright。

登录态与测试数据管理

E2E 测试最耗时的是反复登录与数据准备。正确做法是:复用登录态、按需造数据、跑完清理。

复用登录态(Playwright storageState)

// global-setup.js —— 先登录一次并保存 cookie
const { chromium } = require("@playwright/test");

module.exports = async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto("https://example.com/login");
  await page.fill("#username", "admin");
  await page.fill("#password", "123456");
  await page.click("button[type=submit]");
  await page.waitForURL("**/home");
  await page.context().storageState({ path: "auth.json" });
  await browser.close();
};

// playwright.config.js 中配置
// use: { storageState: "auth.json" }

测试数据:独立且可清理

test("创建订单", async ({ page, request }) => {
  // 通过 API 准备数据(比 UI 造数快且稳定)
  const order = await request.post("/api/orders", { data: { amount: 99 } });
  const orderId = (await order.json()).id;

  // 走 UI 验证订单可见
  await page.goto("/orders");
  await expect(page.getByText("¥99.00")).toBeVisible();

  // 清理数据
  await request.delete(`/api/orders/${orderId}`);
});
✅ 最佳实践
  • 优先用 API 造数据,而不是 UI 一步步点出来。
  • 测试账号使用专用环境,避免污染生产数据。
  • 用例独立:互相之间不依赖执行顺序。

接入 CI 持续集成

E2E 测试只有跑在 CI 里才有意义:每次代码提交自动执行,回归问题第一时间暴露。

GitHub Actions 示例

name: E2E Tests
on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: npx playwright install --with-deps

      # 启动被测应用(示例:先构建再起服务)
      - run: npm run build && npm run start &

      - run: npx playwright test
        env:
          BASE_URL: http://localhost:3000

      # 失败时保留报告与截图供排查
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

并行加速

// playwright.config.js —— 分片并行
export default {
  workers: process.env.CI ? 4 : undefined,
  fullyParallel: true,
};

// CI 中拆分多台机器
// npx playwright test --shard=1/4
⚠️ 注意
CI 中一定要 playwright install --with-deps,否则缺少系统依赖会导致浏览器启动失败。

调试与最佳实践

E2E 用例不稳定时,善用 Playwright 的调试工具能快速定位问题。

调试手段

# 有头模式 + 慢动作,直观观察每一步
npx playwright test --headed --slow-mo=500

# 只跑单个用例
npx playwright test -g "登录"

# 失败后自动保留截图与 trace
npx playwright test --trace on

# 查看 Trace Viewer 分析失败原因
npx playwright show-trace test-results/**/trace.zip

让测试稳定的原则

原则说明
避免固定等待用 expect(...).toBeVisible() 自动等待,不用 sleep
独立用例每个用例自己准备数据,不依赖其他用例结果
语义化定位用 role / testid,不用易变的样式类名
控制网络Mock 第三方服务,避免外部环境抖动导致失败
维护用例数量只保留高价值核心链路,控制执行时长
💡 提示
E2E 的价值在于守住核心流程,不在于数量。100 个脆弱的用例不如 10 个稳定的核心链路用例。
第 1 / 9 节