E2E测试入门:别让用户帮你点鼠标了,找个机器人替你打工吧

一个因"手滑"漏测而半夜起来修 Bug 的前端,含泪写下这篇

事情发生在五一假期第一天的晚上。节前我改了一个看似人畜无害的功能:把登录页的"记住我"复选框从右边挪到了左边。产品说"就改个位置,不用测了吧",我也这么觉得。于是 git push,下班,回家撸串。

晚上十一点,运营群里突然炸了:"用户反馈登录不了!点了登录按钮没反应!"

我一脸懵地打开电脑,复现步骤:打开登录页 → 输入账号密码 → 勾选"记住我" → 点登录。真的没反应。

再试:不勾选"记住我",正常登录。

我盯着代码看了二十分钟,终于发现问题:复选框挪位置的时候,我不小心把它的 v-model 绑定到了一个错误的变量上,导致勾选时悄悄抛出了一个异常,这个异常被某个全局错误捕获器吞掉了,于是登录请求根本没发出去。

我改的是样式,但 bug 出在逻辑上。 谁能想到?

那天晚上我一边道歉一边修 bug,一边想:有没有一种可能,让机器替我把"登录流程"自动跑一遍?每次改完代码,它自动打开浏览器,输入测试账号,点勾选,点登录,然后检查是不是真的跳转了。如果有问题,它直接告诉我,而不是让用户来告诉我。

这玩意儿叫 E2E 测试。

一、E2E 测试是什么?就是让代码"扮演用户"

E2E(End-to-End,端到端) 测试,说人话就是:写一段代码,让它像真人一样打开浏览器,点击、输入、滚动、等待,然后断言页面上应该出现什么、不应该出现什么

你手工测试时做的每一步------打开页面、找到按钮、点下去、看弹窗------都可以被翻译成代码。然后这个代码可以:

  • 在你的电脑上跑
  • 在 CI 服务器上跑(比如你每次 git push,它自动跑)
  • 在多个浏览器里同时跑(Chrome、Firefox、Safari)
  • 在夜里自动跑,第二天早上给你发报告

这就是 E2E 测试的核心价值:自动化回归

它和另外两种测试的区别,我打个比方你就懂了:

  • 单元测试:测汽车的一个螺丝钉拧没拧紧。
  • 集成测试:测发动机装上去之后能不能转。
  • E2E 测试:真人坐进车里,点火、挂挡、踩油门、上路,看看车到底能不能把你送到公司。

单元测试和集成测试保"下限",E2E 测试保"上限"------它模拟的是真实用户体验。

二、为什么你需要 E2E 测试?三个扎心场景

场景 1:你改了一处代码,搞坏了另一处

这是最常见的前端"蝴蝶效应"。你改了全局的 CSS 变量,结果某个角落的对话框宽度变成了 0;你升级了一个依赖,结果另一个页面的日期选择器不弹了。手工回归测试一个 50 页的项目,你能保证每个页面都点一遍吗? 不能。机器人可以。

场景 2:多浏览器兼容,手点真的会疯

"Chrome 好好的,Safari 挂了"------这句话你肯定说过。现代前端项目,产品经理要求支持 Chrome、Safari、移动端 WebView。手工在每个环境里跑一遍核心流程?一下午就没了。E2E 测试可以一键在不同浏览器执行同样的脚本。

场景 3:重构时,你需要"安全网"

有一天你想把 Vuex 换成 Pinia,或者把 Vue Router 的 history 模式换一下。你怎么确保所有页面跳转都正常?把所有路由人工点一遍?不如写个 E2E 测试,让机器人去爬你的路由列表,挨个访问,检查有没有报错。

E2E 测试不是"写不写"的问题,而是"什么时候后悔没写"的问题。

三、工具选哪个?Playwright 还是 Cypress?

我入坑的时候用的是 Cypress ,后来彻底切到了 Playwright。不是 Cypress 不好,而是 Playwright 更适合我这种"怕麻烦"的人。

直接给你一个粗暴的建议:

  • 如果你是 Vue / React 初学者,项目比较小,只想快速体验 E2E → 可以先用 Cypress,它的交互式界面很友好,上手文档写得像故事书。
  • 如果你要测跨浏览器、多标签页、iframe、移动端模拟 → 无脑选 Playwright。微软出品,后来居上,我现在用它。

对比几个关键点:

对比项 Cypress Playwright
安装 npm install cypress --save-dev,首次运行要下载二进制 npm init playwright@latest 一步到位
调试体验 自带时间旅行,能看每一步的 DOM 截图 有 Trace Viewer,类似录屏回放,也很强
多标签页 有限支持,跨域有限制 原生完美支持
浏览器 Chrome、Firefox、Edge,Safari 支持较差 Chrome、Firefox、Safari、Edge 全支持
移动端模拟 需要插件 内置 iPhone、Pixel 等设备
网络拦截 优秀 同样优秀
测试速度 中等 更快(更稳定的并行)

有一次要测试一个嵌了 Stripe 支付 iframe 的流程。Cypress 折腾两天 ------ iframe 内的元素死活选不中。Playwright 两小时写完整个支付流程测试。从那以后我就"叛变"了。

本文的代码示例全部用 Playwright 写,如果你之前没用过,正好借机入门。

四、手把手实战:给一个 Vue 3 待办应用写 E2E 测试

假设你写了一个简单的待办应用,功能如下:

  • 输入框可以添加新待办
  • 每个待办右侧有一个删除按钮
  • 待办可以勾选标记完成
  • 底部显示"剩余 x 项"

4.1 安装 Playwright

在你的 Vue 3 项目根目录运行:

bash 复制代码
npm init playwright@latest

它会问几个问题:测试文件夹放哪?要不要 GitHub Actions?(选 yes)要不要安装浏览器?(选 yes)

安装完后目录结构大概是这样:

bash 复制代码
your-project/
├── e2e/           # 测试文件放这里
│   └── example.spec.ts
├── playwright.config.ts   # 配置文件
└── tests-examples/        # 官方示例(可以删掉)

4.2 写第一个测试:添加待办

创建 e2e/todo.spec.ts

ts 复制代码
import { test, expect } from '@playwright/test'

test('用户可以添加新的待办项', async ({ page }) => {
  // 1. 打开你的应用(假设开发服务器跑在 http://localhost:5173)
  await page.goto('http://localhost:5173')

  // 2. 找到输入框,输入文字
  const input = page.getByPlaceholder('添加新待办')  // 根据你的 placeholder 调整
  await input.fill('买牛奶')

  // 3. 按回车提交
  await input.press('Enter')

  // 4. 断言:页面上出现了"买牛奶"这一项
  await expect(page.getByText('买牛奶')).toBeVisible()

  // 5. 断言:输入框被清空了
  await expect(input).toHaveValue('')
})

这里用了 Playwright 推荐的 getByRole / getByText / getByPlaceholder,比写 CSS 选择器更健壮。

4.3 测试勾选和删除

继续在同一个文件里加测试:

ts 复制代码
test('用户可以勾选待办', async ({ page }) => {
  await page.goto('http://localhost:5173')
  
  const input = page.getByPlaceholder('添加新待办')
  await input.fill('写 E2E 文章')
  await input.press('Enter')

  // 找到刚添加项旁的复选框,勾选它
  const checkbox = page.getByRole('checkbox', { name: '写 E2E 文章' })
  await checkbox.check()

  // 断言复选框已选中
  await expect(checkbox).toBeChecked()
})

test('用户可以删除待办', async ({ page }) => {
  await page.goto('http://localhost:5173')
  
  const input = page.getByPlaceholder('添加新待办')
  await input.fill('删除我')
  await input.press('Enter')

  // 找到删除按钮(假设每个待办项旁边有个删除按钮)
  const deleteBtn = page.getByRole('button', { name: '删除' }).first()
  await deleteBtn.click()

  // 断言"删除我"文字不再存在
  await expect(page.getByText('删除我')).toBeHidden()
})

4.4 测试"剩余 x 项"

ts 复制代码
test('底部正确显示剩余未完成数量', async ({ page }) => {
  await page.goto('http://localhost:5173')
  
  // 添加两个待办
  const input = page.getByPlaceholder('添加新待办')
  await input.fill('待办 A')
  await input.press('Enter')
  await input.fill('待办 B')
  await input.press('Enter')

  // 此时剩余应为 2
  await expect(page.getByText('剩余 2 项')).toBeVisible()

  // 勾选其中一个
  await page.getByRole('checkbox', { name: '待办 A' }).check()

  // 剩余变为 1
  await expect(page.getByText('剩余 1 项')).toBeVisible()
})

4.5 运行这些测试

在终端执行:

bash 复制代码
npx playwright test

你会看到 Playwright 启动浏览器,自动执行上面的操作,飞快地弹几下窗口,然后给出结果:

scss 复制代码
✓  用户可以添加新的待办项 (2.1s)
✓  用户可以勾选待办 (1.8s)
✓  用户可以删除待办 (2.0s)
✓  底部正确显示剩余未完成数量 (2.4s)
  4 passed (8.3s)

如果想看浏览器是怎么操作的(调试用),可以运行带 UI 的模式:

bash 复制代码
npx playwright test --ui

它会打开一个界面,你可以单步回放每个操作的截图。

如果想在多个浏览器上跑:

bash 复制代码
npx playwright test --project=chromium --project=firefox --project=webkit

一行命令,Chrome、Firefox、Safari 全部自动测完。

五、E2E 测试的"坑"与"解药"

E2E 测试不是银弹,它有几个让人头疼的地方,但都有解药。

坑 1:测试跑得慢

一个完整的 E2E 测试套件跑完可能要 5-20 分钟。解药:不要把所有 E2E 测试都放在每次 commit 时跑。只在 PR 合并前跑核心流程(登录、购物车、支付),全量回归留在每天晚上自动跑。

坑 2:测试"脆弱"(flaky tests)

有时候测试会随机失败:网络慢了一下、动画没结束、元素没加载出来。解药 :Playwright 自带自动等待机制,你不需要手动写 sleep。大部分情况下用 await expect(...).toBeVisible() 就已经在轮询等待了。更极端的情况可以用 page.waitForTimeout(1000),但尽量少用。

坑 3:需要维护测试数据

每次跑测试,数据库或 mock 数据状态要一致。解药 :测试前调用 API 重置数据,或者用 page.addInitScript 清空 localStorage。更专业的方式是用 Playwright 的 API 请求上下文,直接向后端发请求初始化测试账号。

六、实战建议:从 0 到 1 的路线图

如果你之前完全没写过 E2E 测试,不要一上来就想覆盖全站。按这个顺序来:

  1. 先写核心用户旅程(Happy Path):登录 → 访问主要页面 → 退出。这 3-5 个测试能拦住 80% 的灾难性 bug。
  2. 再写关键表单的增删改查:比如待办、评论、发帖。
  3. 然后写跨浏览器验证:确保你支持的所有浏览器核心流程都通。
  4. 最后写边界场景:网络断开重连、表单校验错误提示等。

记住一个原则:E2E 测试的数量不要膨胀。一个前端项目,10-30 个高质量的 E2E 测试,配合单元测试和集成测试,就能给你极大的信心。你要是写了两百个 E2E 测试,每次跑 40 分钟,没人愿意等------那就适得其反了。

七、结尾:从"手动点到手酸"到"一键信任"

回到开头那个改复选框位置引发的 bug。如果当时我有 E2E 测试,它会在 10 秒内告诉我:登录流程挂了。我就不会在撸串的时候被叫回去加班,也不会在群里道歉。

E2E 测试不是炫技,它是 给未来的自己买的一份保险。你永远不知道哪一次"小改动"会捅出什么篓子。把验证的苦活累活交给机器人,你只需要写一次测试代码,然后安心地 merge、上线、下班。

如果现在你的项目还没有任何 E2E 测试,今天就可以开始:

bash 复制代码
npm init playwright@latest

然后把你平时最常手工点的那个流程写成测试。明天你就知道,这半小时花得有多值。

相关推荐
菜蒙爱学习4 小时前
【Markdown】可用的所有 HTML 标准颜色
前端·html
里欧跑得慢4 小时前
CSS 嵌套:编写更优雅的样式代码
前端·css·flutter·web
里欧跑得慢4 小时前
CSS变量与自定义属性详解
前端·css·flutter·web
yanchGod4 小时前
CSS page-break-before 属性详解:控制打印分页的艺术
前端·javascript·css·html·css3·html5
练习时长一年4 小时前
分页插件冲突问题
服务器·前端·windows
dsyyyyy11014 小时前
CSS盒子模型
前端·css·css3
fengci.4 小时前
CTF+随机困难题目
android·开发语言·前端·学习·php
liulilittle4 小时前
LLAMA-CLI 运行千问3.6(R9-7945HX+64G+RTX40608G)
java·前端·llama