端到端测试实战:Playwright、UI 模式与浏览器 MCP
有了单元测试的底子,端到端测试(集成测试)负责验证"用户能否完成任务":用真实浏览器跑完整流程——发送消息、切换会话、流式回复。这节讲 Playwright 配置与实战、UI 模式调试,以及不写代码的验证方式:让 AI 通过浏览器 MCP 直接操作页面。
Playwright 与版本现状
Playwright 是微软开源的端到端测试框架,一套 API 驱动 Chromium、Firefox、WebKit 三种真实浏览器引擎。版本节奏(2026-11-07 核实):最新 1.65.0(2026-11-05 发布,新增以 Bun 运行测试的 --bun 模式、failures 报告器等能力);1.63(2026-09)是一个大节点——内置 Chromium 153 / Firefox 155 / WebKit 26.6,新增命名测试锁(named test locks)、跨 frame 定位、仅可见元素定位、--add-reporter 追加报告器等能力,并停止支持 Ubuntu 20.04。本仓库锁定 ^1.62.1(1.62 已带 component stories、AbortSignal 取消、WebP 截图等能力)。
E2E 与单元测试是两条独立工具链:单元测试跑在 jsdom(模拟 DOM),E2E 跑在真实浏览器(真实渲染、真实网络、真实时序)。所以仓库里有两套配置、两套脚本:
// 根目录 package.json
{
"scripts": {
"test": "vitest run",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug",
"test:e2e:headed": "playwright test --headed",
"test:e2e:report": "playwright show-report"
}
}
json
首次使用先装浏览器二进制:pnpm exec playwright install(或只装 chromium 快速开始)。
playwright.config.ts 精讲
真实仓库的完整配置,几个段落各有分工:
// playwright.config.ts(仓库根目录,节选注释版)
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
timeout: 30 * 1000, // 单条测试超时
expect: { timeout: 5000 }, // 断言轮询超时
use: {
baseURL: 'http://localhost:5173',
screenshot: 'only-on-failure', // 失败时才截图——过程截图不留垃圾
video: 'retain-on-failure',
trace: 'on-first-retry', // 首次重试时录 trace
viewport: { width: 1280, height: 720 },
},
fullyParallel: true,
forbidOnly: !!process.env.CI, // CI 里禁止 it.only
retries: process.env.CI ? 2 : 0, // CI 重试两次抗 flaky
workers: process.env.CI ? 1 : undefined, // CI 单 worker 求稳定
reporter: [['html'], ['list'], ['json', { outputFile: 'test-results/results.json' }]],
globalSetup: './e2e/global-setup.ts',
globalTeardown: './e2e/global-teardown.ts',
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, // 本地复用已起的 dev server
},
})
typescript
四个要点:
- 浏览器矩阵:桌面三大引擎 + 两台移动设备(Pixel 5 / iPhone 12),同一条 spec 在 5 个 project 各跑一遍,跨浏览器兼容性一次验证。
- webServer 自动拉起:测试启动前自动跑
pnpm dev并等待 5173 就绪——不需要手动先起 dev server 再跑测试;本地若已有服务在跑则直接复用,CI 里强制新起。 - 失败产物策略:截图与视频都是
only-on-failure/retain-on-failure——成功不留产物,这是控制垃圾文件的第一道闸(后面还有一道,见文末)。 - CI 分叉:
forbidOnly/retries/workers三项按process.env.CI区分——本地快(并行、不重试),CI 稳(单 worker、失败重试两次)。
E2E 用例实战
场景清单设计覆盖:核心会话创建、消息发送、会话切换、流式消息交互、语言切换、页面布局。有一个值得注意的现象:AI 还主动设计了页面性能相关的检查——因为仓库 rules 里提过性能关注点,AI 读规则后把它纳入了测试范围。这说明规则文件不仅约束"怎么写测试",还会影响"测什么"。
真实 spec(节选自 e2e/playground-core.spec.ts 与 playground-simple.spec.ts):
import { test, expect, Page } from '@playwright/test'
/** 等待 playground 应用完全加载:容器出现 + Vue 挂载出子节点 */
async function waitForPlaygroundReady(page: Page) {
await page.goto('/playground.html')
await page.waitForSelector('.pg-app', { timeout: 10000 })
await page.waitForFunction(
() => {
const app = document.querySelector('.pg-app')
return app && app.children.length > 0
},
{ timeout: 10000 },
)
}
test.describe('Playground核心用户流程', () => {
test.beforeEach(async ({ page }) => {
await waitForPlaygroundReady(page)
})
test('能够发送消息', async ({ page }) => {
const textarea = page.locator('textarea').first()
await expect(textarea).toBeVisible()
await textarea.fill('测试消息:你好AI')
await textarea.press('Enter')
await page.waitForTimeout(3000)
const afterValue = await textarea.inputValue()
if (afterValue === '') {
console.log('✅ 消息发送成功(输入框已清空)')
} else {
console.log('✅ 消息发送完成(输入框保留内容或其他UI策略)')
}
await expect(page.locator('.pg-app')).toBeVisible()
})
test('左侧会话列表可以切换', async ({ page }) => {
const activeConv = page.locator('.pg-conv-item.active')
const initialTitle = await activeConv.textContent()
const conversations = page.locator('.pg-conv-item')
const count = await conversations.count()
if (count > 1) {
await conversations.nth(1).click()
await page.waitForTimeout(500)
const newTitle = await page.locator('.pg-conv-item.active').textContent()
expect(newTitle).not.toBe(initialTitle)
}
})
test('可以创建新对话', async ({ page }) => {
const initialCount = await page.locator('.pg-conv-item').count()
await page.locator('.pg-btn-new-chat').click()
await page.waitForTimeout(1000)
const newCount = await page.locator('.pg-conv-item').count()
expect(newCount).toBeGreaterThan(initialCount)
})
})
typescript
三个写法要点:
- 两段式等待:
waitForSelector等 CSS 容器 +waitForFunction等 Vue 挂载出子节点——只等容器会出现"容器在、内容没挂"的假成功。 - 防御式断言:mock 环境下输入框清空策略可能变化,测试兼容两种 UI 策略都算通过,但核心断言(应用仍可见、会话数增加)不放松。
- 写 spec 前先跑真实页面确认选择器:
.pg-*类名来自 playground 的真实 DOM,AI 写 spec 时会先读页面结构。
让 AI 写 E2E 同样只需要一条指令,把场景描述清楚即可:
为 playground 编写端到端测试,覆盖:消息发送、流式消息、会话创建与切换等核心流程。
text
UI 模式:看着浏览器跑测试
pnpm test:e2e:ui 打开 Playwright 的交互界面,这是调试 E2E 最舒服的方式:
- 左上角运行按钮,左侧是测试用例树,每条用例展开是逐步骤的执行轨迹
- 鼠标 hover 到某个步骤,右侧主区域即时回放该时刻的页面快照——像时间轴拖片一样看每一步页面上发生了什么
- 可以单条运行某个用例,逐行查看执行到哪一行、每行的结果
- 底部还提供 locator 调试区,试选择器不用改代码
这个界面是给专业编程人员看的(对应 --debug 断点调试)。不写代码的人怎么验证页面?用浏览器 MCP。
不写代码的 E2E:浏览器 MCP 辅助验证
把浏览器交给 AI 操作,普通用户、不常写测试的开发者都能做功能验证。推荐两个 MCP(第 3-03 节 MCP 集成的延续):
| MCP | 出品方 | 定位 |
|---|---|---|
| Playwright MCP | Microsoft Playwright 团队 | 让 AI 视觉化控制浏览器:导航、点击、填表、截图,与 Playwright 同引擎 |
| Chrome DevTools MCP | Google Chrome 团队(Apache-2.0) | 更专业的一档:直连 DevTools 协议,性能审计、控制台、网络、内存深查 |
两者互补:验证交互流程用 Playwright MCP,排查性能与页面细节用 Chrome DevTools MCP。同类工具还有 browser-use(开源自主浏览器 agent 框架,GitHub 120k+ star(2026-11-07 核实),基于 Playwright,给任意 LLM 接管浏览器完成自然语言任务)以及操作移动端模拟器的各种 agent,按需搜索选型。
集成方式与普通 MCP 一致:把 mcp 配置加进 Claude Code 的 MCP 列表 → 新会话里 AI 自动安装 → AI 就"有了手脚"。实测要点:
- 明确告诉 AI 调试进程已启动(dev server 在跑),让它直接操作 playground
- Chrome 启动过程中可能弹出 remote debugging 授权页,需要手动点允许——这是唯一需要人工介入的一步
- 可以指定用哪个 MCP 执行(多个浏览器类 MCP 并存时)
实测让 AI 用浏览器 agent 操作 playground 点击发送消息:执行完成后它产生了大量截图——那是 AI 为了自证结果逐步截的图,功能验证全部正常。
测试 AI 的副作用治理:.temp 红线
上面那批截图就是问题本身:测试 AI 会在项目根目录留垃圾文件。解决方式和防监守自盗同构——发现问题、解决问题、把约束固化进配置:
AI 测试过程中产生了大量截图文件,污染了项目根目录。
请删除这些截图,添加 .gitignore 规则,并更新 CLAUDE.md 约束 AI 的行为:
后续所有测试过程产物统一写入 .temp/ 目录。
text
执行结果:根目录截图清理干净、.temp/ 目录建立并加入 .gitignore、CLAUDE.md 落下"临时文件红线"。最终固化的规则(仓库现行版):
- 所有测试/过程产物(截图、日志、脚本)一律写
.temp/(已 gitignore),完成后清理,禁止落项目根目录 - Playwright MCP 截图必须指定
.temp/子路径;提交前确认根目录无残留
这件事的方法论价值大于规则本身:AI 的每个副作用都要变成下一条约束。测试产生截图 → 截图进 .temp;AI 监守自盗 → 规则文件 + 红灯流程(上一节)。用得越久,规则越厚,AI 越"乖"——这就是 CLAUDE.md、AGENTS.md 这类 AI 约束配置文件的正确用法。
E2E 在质量防线中的位置
E2E 跑真实浏览器、分钟级耗时,在质量防线里的位置:
- 不放 pre-commit:太慢,提交钩子只跑 lint 与变更相关的单元测试(13-03)
- 放 CI 独立 job:单元测试 job 先行,E2E job 随后;配置里已备好 CI 参数(
workers: 1、retries: 2、forbidOnly) - 只测核心流程:登录、下单、发消息全链路这类"坏了就是事故"的路径;按钮颜色对不对是组件测试的事
四层防线全景:编辑器实时检查(ESLint)→ 单元/组件测试(Vitest + VTU/TL,含防监守自盗纪律)→ Git Hooks(lint-staged + related tests)→ CI(矩阵单测 + 覆盖率门禁 + E2E 独立 job)。
小结
- E2E 是独立工具链:Playwright 跑真实浏览器,与 Vitest(jsdom)双轨并行
- 配置四要点:5 项目浏览器矩阵(含移动端)、webServer 自动拉起 dev、失败才留截图/视频/trace、CI 参数分叉(单 worker + 重试)
- 用例写法:两段式等待(容器 + 子节点挂载)、防御式断言兼容 UI 策略变化、选择器先从真实 DOM 确认
- UI 模式(
--ui)逐步骤回放调试;不写代码的验证走浏览器 MCP:Playwright MCP(流程验证)/ Chrome DevTools MCP(深度排查),browser-use 等自主 agent 同类可选 - 测试 AI 的副作用要变成约束:截图垃圾 → .temp 红线固化进 CLAUDE.md
- E2E 只测核心流程,放 CI 独立 job,不进 pre-commit
第 13 章到此结束。下一章进入发布管理——把测试通过的组件库发布到 npm。
↑