学习目标:掌握 Claude Code + Playwright MCP Server 的端到端测试全链路——从环境搭建、无障碍快照交互、Page Object Model 架构设计,到三大 Test Agent(Planner/Generator/Healer)驱动的自愈测试流水线。学完即可在真实项目中落地 AI 驱动的 E2E 测试体系。
适用人群:有基础 TypeScript/JavaScript 经验、正在使用 Claude Code 进行前端开发的工程师;QA 自动化工程师希望引入 AI 辅助测试流程的团队。
前置条件: - Node.js ≥ 18,已安装 Claude Code CLI - 一个可本地运行的前端项目(React/Vue/Svelte 均可) - 基本的 CSS 选择器和 DOM 概念
传统 E2E 测试的痛点可以概括为三个字:慢、脆、贵。一条测试用例平均编写时间 30 分钟,UI 变动导致 40% 的选择器失效,维护成本占整个测试团队 60% 以上精力。
Claude Code 通过 Playwright MCP Server 拿到了"眼睛"和"手"——它能实时查看页面 DOM 结构(通过无障碍快照而非截图),精准点击元素,填写表单,断言结果。这意味着:
| 传统方式 | Claude Code + Playwright |
|---|---|
| 手写选择器,UI 变动就断 | AI 通过无障碍树定位,天然抗变动 |
| 人工设计测试用例 | Planner Agent 自动规划覆盖路径 |
| 断了手动修 | Healer Agent 自动修复选择器 |
| 测试与开发割裂 | 开发者一条命令完成自测闭环 |
flowchart LR
A[开发者提交代码] --> B[Claude Code 启动 Playwright MCP]
B --> C{Planner Agent<br/>规划测试路径}
C --> D[Generator Agent<br/>生成测试代码]
D --> E[Healer Agent<br/>修复失败用例]
E --> F[测试报告]
F -->|通过| G[合并代码]
F -->|失败| E
# 在项目根目录
npm init -y
npm install -D @playwright/test
npx playwright install chromium
Playwright MCP 是微软官方维护的 MCP 服务器,它将 Playwright 的浏览器自动化能力暴露为 MCP 工具,供 Claude Code 直接调用。
在项目根目录创建 .mcp.json(或写入 ~/.claude/.mcp.json 全局生效):
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp@latest"
]
}
}
}
如果你需要浏览器可见模式(调试时推荐),加入 --headed 参数:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp@latest",
"--headed"
]
}
}
}
Playwright MCP 支持以下关键配置参数:
| 参数 | 说明 | 默认值 |
|---|---|---|
--browser |
浏览器类型:chromium/firefox/webkit |
chromium |
--headed |
有头模式,可看到浏览器窗口 | false(headless) |
--vision |
启用视觉模式(截图代替无障碍快照) | false |
--viewport-size |
视口大小,格式 width,height |
1280,720 |
--user-data-dir |
用户数据目录(保持登录态) | 临时目录 |
一个面向 CI 的生产级配置:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp@latest",
"--browser", "chromium",
"--viewport-size", "1440,900"
]
}
}
}
启动 Claude Code 后输入:
> 请启动浏览器并访问 https://example.com,告诉我页面标题
Claude Code 会调用 browser_navigate 工具打开页面,然后调用 browser_snapshot 获取无障碍树来读取标题。如果看到类似以下输出,说明 MCP 连接成功:
浏览器已启动,页面标题为 "Example Domain"
页面包含一个标题 "Example Domain" 和一段说明文字
问题 1:npx @playwright/mcp 报错找不到包
# 确认 Playwright MCP 包存在
npm view @playwright/mcp version
# 如果不存在,使用完整路径
npx -y @playwright/mcp@latest
问题 2:headless 模式下页面渲染异常
添加环境变量强制 GPU 渲染:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"],
"env": {
"PLAYWRIGHT_CHROMIUM_SANDBOX": "1"
}
}
}
}
问题 3:WSL 环境无头浏览器启动失败
# 安装 WSL 依赖
sudo apt-get install -y libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libgbm1 libasound2
Playwright MCP 默认使用 Accessibility Snapshot(无障碍快照) 而非截图。这是一个关键设计决策:
ref 引用,AI 可以精确操作当 Claude Code 调用 browser_snapshot 时,返回的是这样的结构:
- navigation "Main"
- link "Home"
- link "Products"
- link "About"
- main
- heading "Welcome to Our Store" [level=1]
- textbox "Search products..." [ref=s1e3]
- button "Search" [ref=s1e4]
- list
- listitem
- article
- heading "Product A" [level=2]
- text "$29.99"
- button "Add to Cart" [ref=s1e8]
- listitem
- article
- heading "Product B" [level=2]
- text "$49.99"
- button "Add to Cart" [ref=s1e12]
每个 [ref=sXeY] 就是一个可操作元素的唯一引用。Claude Code 用这些 ref 来执行 browser_click、browser_type 等操作。
在 Claude Code 中使用以下 prompt:
请启动浏览器,访问 http://localhost:3000,获取页面快照,
列出所有可交互元素,并尝试完成一次完整的用户注册流程
Claude Code 的执行过程:
1. browser_navigate → http://localhost:3000
2. browser_snapshot → 获取页面结构
3. browser_click → 点击 "Sign Up" 按钮 [ref=s1e5]
4. browser_snapshot → 获取注册表单结构
5. browser_fill_form → 填写用户名、邮箱、密码
6. browser_click → 点击 "Submit" [ref=s2e12]
7. browser_snapshot → 验证注册成功提示
关键技巧:让 Claude Code 在每次关键操作后自动调用 browser_snapshot,这样它能根据页面变化做出决策,而不是盲目执行预定义步骤。
Playwright v1.56+ 引入了三个内置 AI Test Agent,它们可以直接在 Claude Code 中使用,形成完整的测试流水线。
作用:分析应用页面,自动规划出需要覆盖的测试场景和路径。
在 Claude Code 中使用:
请使用 Playwright 的 Planner Agent 分析 http://localhost:3000,
为电商购物流程规划测试场景
Planner 会自动遍历页面,生成结构化的测试计划:
## 测试计划:电商购物流程
### 场景 1:用户注册
- 访问注册页面
- 填写有效信息 → 验证注册成功
- 填写无效邮箱 → 验证错误提示
### 场景 2:商品浏览与搜索
- 访问商品列表页
- 输入关键词搜索 → 验证结果过滤
- 点击分类筛选 → 验证分类正确
### 场景 3:购物车操作
- 添加商品到购物车
- 修改数量 → 验证价格更新
- 删除商品 → 验证购物车更新
### 场景 4:结账流程
- 填写地址信息
- 选择支付方式
- 确认订单 → 验证订单创建
作用:基于测试计划,自动生成可运行的 Playwright 测试代码。
在 Claude Code 中触发:
请根据上面的测试计划,使用 Generator Agent 生成 Playwright 测试代码,
使用 Page Object Model 模式,保存到 tests/e2e/ 目录
Claude Code 会生成类似以下的完整测试文件:
// tests/e2e/shopping-cart.spec.ts
import { test, expect } from '@playwright/test';
import { HomePage } from '../pages/home.page';
import { ProductPage } from '../pages/product.page';
import { CartPage } from '../pages/cart.page';
test.describe('购物车流程', () => {
let homePage: HomePage;
let productPage: ProductPage;
let cartPage: CartPage;
test.beforeEach(async ({ page }) => {
homePage = new HomePage(page);
productPage = new ProductPage(page);
cartPage = new CartPage(page);
await homePage.goto();
});
test('添加商品到购物车', async () => {
await homePage.searchProduct('无线耳机');
await productPage.selectFirstProduct();
await productPage.addToCart();
await productPage.expectCartCount(1);
});
test('修改购物车商品数量', async () => {
await homePage.searchProduct('无线耳机');
await productPage.selectFirstProduct();
await productPage.addToCart();
await cartPage.goto();
await cartPage.updateQuantity(0, 3);
await cartPage.expectTotalPrice();
});
test('删除购物车商品', async () => {
await homePage.searchProduct('无线耳机');
await productPage.selectFirstProduct();
await productPage.addToCart();
await cartPage.goto();
await cartPage.removeItem(0);
await cartPage.expectEmpty();
});
});
作用:当测试因 UI 变动而失败时,自动分析失败原因,重新定位元素,修复选择器。
这是三个 Agent 中最有价值的。传统 E2E 测试最大的维护成本就来自 UI 变动导致的选择器失效。Healer Agent 的工作流程:
flowchart TD
A[测试执行失败] --> B[Healer 获取当前页面快照]
B --> C[分析失败原因:<br/>选择器失效/元素移位/文本变更]
C --> D[在无障碍树中搜索匹配元素]
D --> E{找到匹配?}
E -->|是| F[更新选择器/定位器]
E -->|否| G[标记为需要人工介入]
F --> H[重新执行测试]
H --> I{通过?}
I -->|是| J[保存修复后的测试]
I -->|否| C
在 Claude Code 中使用 Healer:
测试 e2e/shopping-cart.spec.ts 中的"添加商品到购物车"用例失败了,
错误信息是 "Waiting for selector '.add-to-cart-btn' timed out"。
请使用 Healer Agent 修复这个测试
Claude Code 会:
btn-add-cart)tests/
├── e2e/ # E2E 测试用例
│ ├── auth.spec.ts
│ ├── shopping-cart.spec.ts
│ └── checkout.spec.ts
├── pages/ # Page Objects
│ ├── base.page.ts # 基础页面类
│ ├── home.page.ts
│ ├── product.page.ts
│ ├── cart.page.ts
│ └── checkout.page.ts
├── fixtures/ # 测试夹具
│ └── test.fixture.ts
└── helpers/ # 辅助工具
├── assertions.ts
└── test-data.ts
向 Claude Code 提供你的页面组件代码或路由结构,让它生成对应的 Page Object:
请查看 src/pages/ 目录下的所有页面组件,
为每个页面生成对应的 Page Object Model 类。
使用 data-testid 作为首选定位策略,无障碍角色作为备选。
生成到 tests/pages/ 目录。
生成的基础 Page Object 模板:
// tests/pages/base.page.ts
import { Page, Locator, expect } from '@playwright/test';
export class BasePage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
// 通用定位策略:优先 data-testid,备选无障碍角色
protected getByTestId(id: string): Locator {
return this.page.getByTestId(id);
}
protected getByRole(role: string, options?: Record<string, unknown>): Locator {
return this.page.getByRole(role as any, options as any);
}
protected getByText(text: string): Locator {
return this.page.getByText(text);
}
async goto(path: string = '/') {
await this.page.goto(path);
await this.page.waitForLoadState('networkidle');
}
async expectVisible(locator: Locator) {
await expect(locator).toBeVisible({ timeout: 10000 });
}
}
// tests/pages/home.page.ts
import { BasePage } from './base.page';
import { Page, Locator, expect } from '@playwright/test';
export class HomePage extends BasePage {
// 定位器 - 使用语义化属性
readonly searchInput: Locator;
readonly searchButton: Locator;
readonly productCards: Locator;
readonly cartBadge: Locator;
constructor(page: Page) {
super(page);
this.searchInput = this.getByTestId('search-input');
this.searchButton = this.getByTestId('search-button');
this.productCards = this.page.locator('[data-testid="product-card"]');
this.cartBadge = this.getByTestId('cart-badge');
}
async goto() {
await super.goto('/');
}
async searchProduct(query: string) {
await this.searchInput.fill(query);
await this.searchButton.click();
await this.page.waitForResponse('**/api/products**');
}
async getProductCount(): Promise<number> {
return this.productCards.count();
}
async getCartCount(): Promise<string> {
return this.cartBadge.textContent() ?? '0';
}
}
让 Claude Code 遵循以下定位器优先级(写进 CLAUDE.md 或项目的 .claude/instructions):
## E2E 测试定位器策略
按优先级从高到低:
1. `data-testid` 属性 — 最稳定,不受 UI 文案变动影响
2. 无障碍角色 — `getByRole('button', { name: 'Submit' })`
3. 无障碍标签 — `getByLabel('Email address')`
4. 文本内容 — `getByText('Welcome back')`(仅当文案不常变)
5. CSS 选择器 — 最后手段,必须包含语义化的 class 名
在组件代码中主动添加 data-testid:
// src/components/ProductCard.tsx
export function ProductCard({ product }: { product: Product }) {
return (
<article data-testid="product-card">
<h2 data-testid="product-name">{product.name}</h2>
<span data-testid="product-price">${product.price}</span>
<button
data-testid="add-to-cart"
onClick={() => addToCart(product.id)}
>
Add to Cart
</button>
</article>
);
}
# 创建 Next.js 电商项目(如果还没有)
npx create-next-app@latest my-store --typescript --tailwind --app
cd my-store
# 安装 Playwright
npm install -D @playwright/test
npx playwright install chromium
# 创建测试目录结构
mkdir -p tests/{e2e,pages,fixtures,helpers}
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { open: 'never' }],
['json', { outputFile: 'test-results/results.json' }],
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
在项目根目录启动 Claude Code,输入:
请分析 src/app/ 下的所有页面路由和组件,为我的电商应用生成完整的 E2E 测试套件。
要求:
1. 使用 Page Object Model 模式
2. 覆盖:注册、登录、商品浏览、搜索、购物车、结账
3. 每个场景包含正向和反向测试
4. 使用 data-testid 定位
5. 保存到 tests/ 目录
Claude Code 会:
1. 读取 src/app/ 目录下的路由和页面组件
2. 分析每个页面的交互元素
3. 规划测试场景
4. 生成 Page Object 类
5. 生成测试用例文件
6. 生成测试夹具(登录态等)
开发新功能后,让 Claude Code 自测:
我刚完成了商品评论功能的开发。
请:
1. 启动应用
2. 通过浏览器验证评论功能(发布评论、查看评论、删除评论)
3. 为评论功能生成 E2E 测试
4. 运行测试并报告结果
Claude Code 的执行流程:
sequenceDiagram
participant Dev as 开发者
participant CC as Claude Code
participant MCP as Playwright MCP
participant App as 本地应用
Dev->>CC: 验证评论功能 + 生成测试
CC->>MCP: browser_navigate /localhost:3000
MCP->>App: HTTP Request
App-->>MCP: HTML Response
MCP-->>CC: 页面加载完成
CC->>MCP: browser_snapshot
MCP-->>CC: 无障碍树 (含评论区域)
CC->>MCP: browser_click "Write Review"
CC->>MCP: browser_fill_form
CC->>MCP: browser_click "Submit"
CC->>MCP: browser_snapshot
MCP-->>CC: 验证评论已发布
CC->>CC: 生成 E2E 测试代码
CC->>App: npx playwright test
App-->>CC: 测试结果
CC-->>Dev: 报告:3个场景全部通过
将 AI 生成的测试接入 GitHub Actions:
# .github/workflows/e2e.yml
name: E2E Tests
on:
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Build application
run: npm run build
- name: Run E2E tests
run: npx playwright test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
在以下场景下让 Claude Code 使用 Healer Agent:
locator.waitFor: Timeout 错误在 Claude Code 中执行:
运行 npx playwright test,发现 tests/e2e/auth.spec.ts 失败。
请分析失败原因,使用 Healer 修复所有失败用例,
修复后重新运行测试确认通过
Claude Code 的处理步骤:
第一步:诊断
$ npx playwright test --reporter=json 2>&1 | tail -20
Error: locator.waitFor: Waiting for selector '[data-testid="login-btn"]' failed
at LoginPage.clickLogin (tests/pages/login.page.ts:15)
第二步:打开浏览器获取当前状态
Claude Code 调用 Playwright MCP:
- browser_navigate → 访问登录页
- browser_snapshot → 获取当前无障碍树
- 分析树结构,发现登录按钮现在是 <button role="button" name="Sign in">
第三步:更新 Page Object
// 修复前
readonly loginButton = this.getByTestId('login-btn');
// 修复后 - 开发者改了 data-testid
readonly loginButton = this.getByTestId('sign-in-btn');
第四步:重新运行验证
npx playwright test tests/e2e/auth.spec.ts --retries=0
当大规模重构后,一次性修复所有失败测试:
我们的 Button 组件从 <button class="btn"> 迁移到了 <Button variant="primary">,
导致 50+ 测试失败。请:
1. 运行全部测试,收集失败列表
2. 对每个失败用例使用 Healer 修复
3. 生成修复报告:哪些自动修复了,哪些需要人工介入
// tests/fixtures/auth.fixture.ts
import { test as base, Page } from '@playwright/test';
type AuthFixture = {
authenticatedPage: Page;
};
export const test = base.extend<AuthFixture>({
authenticatedPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'tests/fixtures/auth-state.json',
});
const page = await context.newPage();
await use(page);
await context.close();
},
});
生成登录态文件:
请使用浏览器完成登录流程,将登录状态保存到 tests/fixtures/auth-state.json
Claude Code 会通过 Playwright MCP 执行登录,然后调用 page.context().storageState() 保存状态。
// tests/helpers/mock-server.ts
import { Page } from '@playwright/test';
export async function mockProductAPI(page: Page) {
await page.route('**/api/products', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
products: [
{ id: 1, name: 'Wireless Headphones', price: 79.99 },
{ id: 2, name: 'USB-C Cable', price: 12.99 },
],
}),
});
});
}
让 Claude Code 基于项目的 API 路由自动生成 mock:
请分析 src/app/api/ 下的所有 API 路由,
为每个路由生成 mock 函数,包含正常响应和错误响应两种场景
// tests/e2e/visual-regression.spec.ts
import { test, expect } from '@playwright/test';
test('首页视觉回归', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('home-page.png', {
maxDiffPixelRatio: 0.01,
threshold: 0.2,
});
});
// playwright.config.ts 中的并行配置
export default defineConfig({
// 按文件级并行
fullyParallel: true,
workers: process.env.CI ? 4 : '50%',
// 分片执行(CI 多机器)
// shard: '1/4',
});
在 Claude Code 中运行分片:
# 第1个分片(共4个)
npx playwright test --shard=1/4
Playwright MCP 的无障碍快照每次约消耗 500-2000 token。减少不必要快照的策略:
| 策略 | 说明 | 节省 |
|---|---|---|
| 限制快照深度 | 使用 depth 参数只获取关键层级 |
40-60% |
| 精准快照目标 | 对特定元素做 browser_snapshot(target=ref) |
50-70% |
| 合并连续操作 | 填表时用 browser_fill_form 代替多次 browser_type |
30% |
| 缓存页面状态 | 相同页面跳转时复用快照 | 20-30% |
// playwright.config.ts 速度优化
export default defineConfig({
use: {
// 关闭不必要的资源加载
bypassCSP: true,
// 超时设置
navigationTimeout: 15000,
actionTimeout: 10000,
},
// 禁用不必要的跟踪
trace: 'off', // CI 中改为 'on-first-retry'
screenshot: 'off', // CI 中改为 'only-on-failure'
video: 'off', // CI 中改为 'retain-on-failure'
});
flowchart TB
subgraph 开发阶段
A[编写新功能] --> B[Claude Code 自测]
B --> C{通过?}
C -->|否| D[修复代码]
D --> B
C -->|是| E[提交 PR]
end
subgraph 测试生成
E --> F[Planner Agent<br/>规划测试场景]
F --> G[Generator Agent<br/>生成测试代码]
G --> H[运行测试验证]
end
subgraph 持续维护
I[CI 运行测试] --> J{全部通过?}
J -->|是| K[合并代码]
J -->|否| L[Healer Agent<br/>自动修复]
L --> M{修复成功?}
M -->|是| N[更新测试代码]
M -->|否| O[人工介入]
N --> I
O --> P[开发者修复]
P --> I
end
H --> I
| 目的 | Claude Code 命令/Prompt |
|---|---|
| 探索页面 | 启动浏览器访问 localhost:3000,获取页面快照 |
| 自测功能 | 验证注册功能是否正常工作 |
| 生成测试 | 为购物车功能生成 E2E 测试 |
| 修复失败 | 测试失败了,请修复所有失败用例 |
| 批量运行 | 运行全部 E2E 测试并生成报告 |
| 视觉测试 | 对首页做视觉回归测试 |
| 性能审计 | 检查首页加载性能,报告 LCP/FID/CLS |
训练营适用性评估:本教程适合"AI 辅助工程"中级训练营(3-4 天课时)或"测试自动化"专题工作坊(2 天)。建议前置半天的基础 Playwright 入门,后接半天的项目实战。对 QA 团队转型 AI 测试的培训尤为适用,预计核心教学时长 6-8 小时,含动手实验 3-4 小时。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
