28 KiB
Executable File
第20-25页:测试验收篇
章节:03 测试验收 核心问题:AI 辅助下的测试策略是什么?如何让 AI 操作浏览器?如何生成 E2E 测试?
第20页:分隔页(03 测试验收)
原文内容
章节分隔页,标志进入"测试验收"部分
研究要点
- 这是4个核心问题中的第3个
- 测试验收是保证 AI 生成代码质量的关键
- 核心目标:自动化测试,减少人工验证
第21页:AI 辅助下的测试策略是什么?
原文内容
测试策略全景图:
| 测试类型 | 测试对象 | 工具(含 AI) | 负责人(人/Agent) |
|---|---|---|---|
| Lint 代码扫描 | 代码风格、代码逻辑和片段 | TS Lint/SonarQube | 程序 |
| Code Review | 代码逻辑、架构设计、规范(复杂度/安全/可维护性) | GitHub Copilot、Claude Code、CodeRabbit、GitHub PR | 开发者 + AI Review Agent |
| 单元测试 | 函数/类行为、边界条件、异常处理 | JUnit、pytest、Jest + AI 生成测试代码(Copilot/Claude) | 开发者 + Test Generation Agent |
| API 测试 | 接口契约(Schema)、输入输出、鉴权、错误码、业务规则 | Karate(主)、RestAssured(补充复杂逻辑) + AI 生成测试用例/数据 | QA + 后端开发 + API Test Agent |
| E2E 测试 | 用户关键路径(登录/下单/支付)、跨系统流程、前后端联动 | Playwright + AI 生成脚本/自愈(selector 修复、步骤补全) | QA + 自动化测试工程师 + E2E Agent |
深入解读
测试金字塔在 AI 时代的演变
传统测试金字塔:
E2E 测试(少量)
/ \
/ 集成测试 \
/ (中等数量) \
/ \
/ 单元测试 \
/ (大量) \
/_________________________\
AI 时代的测试策略:
- AI 生成大量单元测试:覆盖边界条件
- AI 生成 API 测试:基于 OpenAPI 规范
- AI 操作浏览器做 E2E 测试:模拟真实用户
- 人工专注于 Code Review:审查 AI 的审查结果
Lint 代码扫描
工具选择:
- 前端:ESLint、Prettier
- 后端:SonarQube、Checkstyle
- 多语言:MegaLinter
AI 的作用:
- 自动修复格式问题
- 检测潜在的 bug
- 提供修复建议
示例:
# 前端
npm run lint --fix
# 后端(Java)
mvn checkstyle:check
# SonarQube
sonar-scanner
Code Review
AI 辅助工具:
- GitHub Copilot Code Review:自动审查 PR
- Claude Code:可以审查整个代码库
- CodeRabbit:专业的 AI 代码审查工具
- GitHub PR + AI:集成 AI 审查功能
AI Review 的优势:
- 速度快:几分钟内完成审查
- 覆盖广:可以检查多个维度(安全、性能、规范)
- 一致性:不会遗漏
AI Review 的局限:
- 无法理解业务逻辑
- 可能误报
- 需要人工复核
示例:
# AI Review 报告
## 安全问题
- [高] 第 42 行:SQL 拼接存在注入风险
- [中] 第 78 行:敏感信息未加密
## 性能问题
- [中] 第 156 行:循环内查询数据库,建议批量查询
## 代码规范
- [低] 第 23 行:变量命名不符合规范
单元测试
AI 生成测试的优势:
- 快速生成大量测试用例
- 覆盖边界条件
- 生成 Mock 数据
工具:
- Java:JUnit 5 + Mockito
- Python:pytest
- 前端:Jest、Vitest
示例(AI 生成的单元测试):
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void should_create_user_successfully() {
// Given
CreateUserRequest request = new CreateUserRequest();
request.setUsername("testuser");
request.setEmail("test@example.com");
request.setPassword("Password123");
when(userRepository.existsByUsername("testuser")).thenReturn(false);
when(userRepository.save(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0));
// When
UserResponse response = userService.create(request);
// Then
assertNotNull(response);
assertEquals("testuser", response.getUsername());
verify(userRepository).save(any(User.class));
}
@Test
void should_throw_exception_when_username_exists() {
// Given
CreateUserRequest request = new CreateUserRequest();
request.setUsername("existinguser");
when(userRepository.existsByUsername("existinguser")).thenReturn(true);
// When & Then
assertThrows(BusinessException.class, () -> {
userService.create(request);
});
}
@Test
void should_validate_email_format() {
// Given
CreateUserRequest request = new CreateUserRequest();
request.setEmail("invalid-email");
// When & Then
assertThrows(ValidationException.class, () -> {
userService.create(request);
});
}
}
API 测试
工具选择:
- Karate:BDD 风格的 API 测试框架
- RestAssured:Java API 测试库
- Postman:可视化 API 测试
AI 的作用:
- 根据 OpenAPI 规范生成测试用例
- 生成测试数据
- 自动生成断言
示例(Karate 测试):
Feature: User API
Scenario: Create user successfully
Given url 'http://localhost:8080/api/v1/users'
And request { username: 'testuser', email: 'test@example.com', password: 'Password123' }
When method POST
Then status 201
And match response.data.username == 'testuser'
And match response.data.email == 'test@example.com'
Scenario: Create user with duplicate username
Given url 'http://localhost:8080/api/v1/users'
And request { username: 'existinguser', email: 'test2@example.com', password: 'Password123' }
When method POST
Then status 409
And match response.message == '用户名已存在'
E2E 测试
工具选择:
- Playwright:最流行的 E2E 测试框架
- Cypress:前端 E2E 测试
- Selenium:老牌 E2E 测试工具
AI 的作用:
- 生成测试脚本
- 自愈(selector 修复)
- 步骤补全
示例(Playwright 测试):
import { test, expect } from '@playwright/test';
test('用户注册流程', async ({ page }) => {
// 访问注册页面
await page.goto('http://localhost:3000/register');
// 填写表单
await page.fill('[name="username"]', 'testuser');
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'Password123');
await page.fill('[name="confirmPassword"]', 'Password123');
// 提交
await page.click('button[type="submit"]');
// 验证跳转
await expect(page).toHaveURL(/\/login/);
// 验证提示信息
await expect(page.locator('.alert-success')).toContainText('注册成功,请查收验证邮件');
});
实践建议
- 分层测试:单元测试 → API 测试 → E2E 测试
- AI 生成测试:利用 AI 快速生成大量测试用例
- 人工审查:AI 生成的测试需要人工审查
- 持续集成:所有测试集成到 CI/CD 流程
第22页:AI 如何操作浏览器?
原文内容
四种方案对比:
| 维度 | Playwright MCP | Chrome DevTools MCP | Browser Use | Computer Use |
|---|---|---|---|---|
| 原理 | 通过 Playwright 访问浏览器 Accessibility Tree,获取结构化 DOM 快照,AI 基于节点操作 | 通过 Chrome DevTools Protocol 直接与浏览器引擎通信,暴露 CDP 全部能力(DOM/网络/控制台) | Python 框架 + Playwright,AI 自主决策循环;支持 DOM 和截图双模 | AI 截取屏幕截图 → 视觉识别元素 → 输出坐标/按键操作 → 沙箱执行,OS 级通用 |
| 抽象层 | Accessibility Tree 结构化 DOM 快照 | CDP Protocol DevTools 协议原生 | DOM + 截图 视觉 + 结构化混合 | 截图 + 坐标 OS 级视觉理解 |
| 后端引擎 | Playwright | Puppeteer + CDP | Playwright + Python | Anthropic API + 沙箱 |
| 速度 | 快 ~0.9s/步 | 中 ~1.2s/步 | 中 ~1.5s/步 | 慢 0.8-2s/步 |
| Token 消耗 | 高 截图+结构全传 | 中 按需取数据 | 极低 CLI 模式 ~75 tok/步 | 高 截图编码开销大 |
| JS 重页面 | 中 — DOM 可读 | 中 — CDP 可取 | 中 — 视觉兜底 | 高 — 视觉理解 |
| 跨应用操作 | 仅浏览器 | 仅浏览器 | 仅浏览器 | 全桌面 |
深入解读
方案1:Playwright MCP
原理:
- 使用 Playwright 打开浏览器
- 获取 Accessibility Tree(可访问性树)
- AI 基于树节点进行操作
优势:
- 速度快(~0.9s/步)
- 结构化信息丰富
- 支持所有现代浏览器
劣势:
- Token 消耗高(需要传递完整 DOM 结构)
- 仅限浏览器操作
适用场景:
- E2E 测试
- 表单自动填充
- 数据抓取
示例:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// AI 获取 Accessibility Tree
const snapshot = await page.accessibility.snapshot();
console.log(snapshot);
// AI 基于树节点操作
await page.click('button[name="submit"]');
await page.fill('input[name="username"]', 'testuser');
方案2:Chrome DevTools MCP
原理:
- 使用 Chrome DevTools Protocol (CDP)
- 直接与浏览器引擎通信
- 暴露完整的浏览器能力
优势:
- 功能最强大(可以访问所有 CDP 能力)
- 可以监控网络请求、控制台日志
- 性能分析
劣势:
- 学习曲线陡峭
- 仅限 Chrome/Chromium
适用场景:
- 性能分析
- 网络请求监控
- 复杂的浏览器自动化
示例:
const CDP = require('chrome-remote-interface');
(async () => {
const client = await CDP();
const { Page, Runtime } = client;
// 导航到页面
await Page.navigate({ url: 'https://example.com' });
// 执行 JavaScript
const result = await Runtime.evaluate({
expression: 'document.title'
});
console.log(result.result.value);
// 监控网络请求
Page.requestWillBeSent(({ request }) => {
console.log('Request:', request.url);
});
await client.close();
})();
方案3:Browser Use
原理:
- Python 框架
- 基于 Playwright
- AI 自主决策循环
- 支持 DOM 和截图双模式
优势:
- Token 消耗极低(CLI 模式 ~75 tok/步)
- AI 自主决策
- 支持视觉理解
劣势:
- 仅限浏览器操作
- 依赖 Python 生态
适用场景:
- 复杂的网页自动化
- 需要视觉理解的场景
- Token 成本敏感的项目
示例:
from browser_use import Agent
agent = Agent(
task="Go to Reddit, search for 'r/LocalLLaMA' and click on the first post",
llm=llm
)
result = await agent.run()
print(result)
方案4:Computer Use(Anthropic)
原理:
- AI 截取屏幕截图
- 视觉识别元素
- 输出坐标/按键操作
- 沙箱执行
优势:
- 可以操作任何应用(不限于浏览器)
- 可以处理复杂的视觉场景
- OS 级通用
劣势:
- 速度慢(0.8-2s/步)
- Token 消耗高
- 依赖视觉识别准确性
适用场景:
- 桌面应用自动化
- 需要跨应用操作
- 复杂的视觉场景
示例:
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=[{
"type": "computer_20241022",
"name": "computer",
"display_width_px": 1024,
"display_height_px": 768,
}],
messages=[{
"role": "user",
"content": "Open Chrome and go to google.com"
}]
)
方案选择指南
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| E2E 测试 | Playwright MCP | 速度快,结构化信息丰富 |
| 性能分析 | Chrome DevTools MCP | 可以监控网络和性能 |
| 网页自动化 | Browser Use | Token 消耗低,AI 自主决策 |
| 桌面应用 | Computer Use | 可以操作任何应用 |
| Token 成本敏感 | Browser Use | 极低 Token 消耗 |
| 复杂视觉场景 | Computer Use | 视觉理解能力强 |
实践建议
- E2E 测试首选 Playwright MCP:速度快,生态成熟
- Token 成本敏感选 Browser Use:极低 Token 消耗
- 需要跨应用选 Computer Use:OS 级通用
- 性能分析选 Chrome DevTools MCP:功能最强大
第23页:如何用测试用例生成 E2E 测试?
原文内容
三步法:
Step 1: Browser Use 探索
- AI 自主遍历页面,记录操作轨迹、选择器、页面状态,输出 Playwright 脚本雏形
- 消耗 Token
Step 2: Playwright E2E 固化
- 将探索结果转为 Playwright 测试脚本,加入 CI,零 Token 可重复运行
Step 3: 截图视觉兜底
- Playwright 覆盖不到的视觉场景(布局、动效、图表),重回截图判断
深入解读
为什么需要三步法?
- Browser Use 探索:AI 自主探索,快速生成测试脚本雏形
- Playwright 固化:将探索结果固化,零 Token 可重复运行
- 截图视觉兜底:处理 Playwright 无法覆盖的视觉场景
Step 1: Browser Use 探索
流程:
- AI 打开浏览器
- AI 自主探索页面
- 记录操作轨迹(点击、输入、导航)
- 记录选择器(元素定位)
- 生成 Playwright 脚本雏形
示例:
# 原始指令
agent = Agent(
task="""
1. 打开 /login 页面
2. 输入用户名 test@example.com
3. 输入密码 123456
4. 点击登录按钮
5. 验证跳转到 /dashboard
6. 验证页面显示 "Welcome"
""",
llm=llm
)
# AI 探索后生成的 Playwright 脚本雏形
"""
test('login flow', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', '123456');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('text=Welcome')).toBeVisible();
});
"""
Token 消耗:
- 探索阶段:消耗大量 Token
- 每次探索:可能需要多次尝试
- 成本:每次探索约 $0.5-2
Step 2: Playwright E2E 固化
流程:
- 将 AI 生成的脚本雏形人工审查
- 优化选择器(使用更稳定的定位方式)
- 添加断言
- 加入 CI/CD
- 零 Token 可重复运行
优化后的 Playwright 脚本:
import { test, expect } from '@playwright/test';
test.describe('登录流程', () => {
test('成功登录并跳转到仪表盘', async ({ page }) => {
// 访问登录页
await page.goto('/login');
// 使用更稳定的选择器
await page.getByLabel('邮箱').fill('test@example.com');
await page.getByLabel('密码').fill('123456');
await page.getByRole('button', { name: '登录' }).click();
// 验证跳转
await expect(page).toHaveURL(/\/dashboard/);
// 验证页面内容
await expect(page.getByText('Welcome')).toBeVisible();
});
test('登录失败 - 密码错误', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('邮箱').fill('test@example.com');
await page.getByLabel('密码').fill('wrongpassword');
await page.getByRole('button', { name: '登录' }).click();
// 验证错误提示
await expect(page.getByText('密码错误')).toBeVisible();
});
});
成本:
- 固化阶段:人工审查,无 Token 消耗
- 运行阶段:零 Token
- CI/CD:每次运行约 10-30 秒
Step 3: 截图视觉兜底
场景:
- 视觉回归测试(布局偏移、样式异常)
- 动效/动画测试(加载态、过渡动画)
- Canvas/WebGL 测试(图表渲染、游戏画面)
流程:
- Playwright 截图
- 调用 AI 视觉模型分析
- 判断是否符合预期
示例:
test('视觉回归测试', async ({ page }) => {
await page.goto('/dashboard');
// 截图
const screenshot = await page.screenshot();
// 调用 AI 视觉模型分析
const result = await analyzeScreenshot(screenshot, {
expected: '仪表盘页面,包含侧边栏、顶部导航、主要内容区域',
checks: [
'侧边栏在左侧',
'顶部导航在顶部',
'主要内容区域在中间',
'没有明显的布局异常'
]
});
expect(result.passed).toBe(true);
});
// AI 视觉分析函数
async function analyzeScreenshot(screenshot: Buffer, options: any) {
const response = await openai.chat.completions.create({
model: 'gpt-4-vision-preview',
messages: [{
role: 'user',
content: [
{ type: 'text', text: `请分析这张截图:${options.expected}` },
{ type: 'image_url', image_url: { url: `data:image/png;base64,${screenshot.toString('base64')}` } }
]
}]
});
// 解析 AI 的响应
return parseAIResponse(response);
}
成本:
- 截图阶段:无 Token 消耗
- AI 分析:每次约 $0.01-0.05
- 适合关键页面的视觉验证
实践建议
- 先用 Browser Use 探索:快速生成测试脚本雏形
- 人工审查后固化:优化选择器,加入 CI/CD
- 关键页面截图验证:视觉回归测试
- 成本优化:探索阶段消耗 Token,固化后零 Token
第24页:Playwright E2E 示例
原文内容
完整的 Playwright E2E 测试示例:
import { test, expect, request } from '@playwright/test';
test.describe('User Login Flow', () => {
test('login success and dashboard visible', async ({ page }) => {
// 1. 打开登录页
await page.goto('https://example.com/login');
// 2. 填写表单
await page.getByPlaceholder('Email').fill('test@example.com');
await page.getByPlaceholder('Password').fill('123456');
// 3. 提交
await page.getByRole('button', { name: 'Login' }).click();
// 4. 等待跳转 & 断言
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByText('Welcome')).toBeVisible();
});
test('login via API + set session (更稳定)', async ({ page, request }) => {
// 通过 API 登录,设置 session
const response = await request.post('https://example.com/api/login', {
data: {
email: 'test@example.com',
password: '123456'
}
});
expect(response.ok()).toBeTruthy();
// 获取 session cookie
const cookies = await response.headers()['set-cookie'];
// 设置 cookie 到浏览器
await page.context().addCookies([{
name: 'session',
value: cookies,
domain: 'example.com',
path: '/'
}]);
// 访问仪表盘
await page.goto('https://example.com/dashboard');
await expect(page.getByText('Welcome')).toBeVisible();
});
});
深入解读
两种登录方式的对比
方式1:UI 登录
- 优点:测试真实的用户流程
- 缺点:慢,依赖 UI 元素
- 适用:测试登录页面本身
方式2:API 登录
- 优点:快,稳定,不依赖 UI
- 缺点:无法测试登录页面
- 适用:需要登录状态的其他页面测试
选择器最佳实践
推荐的选择器(从优到差):
// 1. 使用 data-testid(最稳定)
await page.getByTestId('submit-button').click();
// 2. 使用角色和名称(推荐)
await page.getByRole('button', { name: 'Login' }).click();
// 3. 使用标签文本(推荐)
await page.getByLabel('Email').fill('test@example.com');
// 4. 使用占位符(可用)
await page.getByPlaceholder('Enter your email').fill('test@example.com');
// 5. 使用文本(可用)
await page.getByText('Welcome').toBeVisible();
// 6. 使用 CSS 选择器(不推荐,容易变)
await page.locator('.btn-primary').click();
// 7. 使用 XPath(不推荐,容易变)
await page.locator('//button[@type="submit"]').click();
等待策略
自动等待:
// Playwright 自动等待元素可见、可操作
await page.getByRole('button', { name: 'Login' }).click();
显式等待:
// 等待 URL 变化
await expect(page).toHaveURL(/dashboard/);
// 等待元素可见
await expect(page.getByText('Welcome')).toBeVisible();
// 等待元素隐藏
await expect(page.getByText('Loading')).toBeHidden();
// 等待网络请求完成
await page.waitForLoadState('networkidle');
测试数据管理
方式1:硬编码
await page.getByLabel('Email').fill('test@example.com');
方式2:使用 Fixture
test.use({
storageState: 'auth.json'
});
test('已登录用户访问仪表盘', async ({ page }) => {
await page.goto('/dashboard');
// 已经是登录状态
});
方式3:使用 API 准备数据
test.beforeEach(async ({ request }) => {
// 通过 API 创建测试用户
await request.post('/api/users', {
data: {
email: 'test@example.com',
password: '123456'
}
});
});
test.afterEach(async ({ request }) => {
// 清理测试数据
await request.delete('/api/users/test@example.com');
});
并行测试
配置:
// playwright.config.ts
export default defineConfig({
workers: 4, // 4 个并行 worker
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
{ name: 'webkit', use: { browserName: 'webkit' } },
],
});
实践建议
- 优先使用稳定的选择器:data-testid、角色、标签
- 使用 API 登录:提高测试速度
- 测试数据管理:使用 Fixture 或 API 准备数据
- 并行测试:提高测试效率
- CI/CD 集成:每次提交自动运行测试
第25页:超级 Loop(E2E Loop)
原文内容
超级 Loop 流程图:
开始进入循环
↓
[Chat] 进入 Plan 模式
↓
装载需求规格/界面原型,给出指令进行 Plan
↓
[Chat] 符合要求,退出 Plan 模式,开始执行?
↓ 是
[模型] 读取 Plan,按照 Loop 规则进行实现
↓
[模型] 满足 Loop 准出要求?
↓ 否
重复多次 / 白天持续很久 / 夜间
↓ 是
[模型] 退出循环
↓
(给予 ByPass 权限)
核心思想:
- 通过 E2E 测试纳入更大的 Loop 范围
Loop 规则(放到 AGENTS.md 作为开发流程要求):
- Plan 等待确认:在 docs/plans 下根据模版创建 Plan
- 编写测试用例:在 docs/test-cases 下编写 E2E 测试用例
- 编写 API 测试:根据 Plan 实现 API 测试,运行成功并断言失败
- 编写实现:编写单元测试 + 实现代码,通过编译
- 运行 API 测试:运行测试,并修复失败的测试和其他错误
- 浏览器调试:使用 Playwright 调试,确认前端视觉效果和交互功能正常
- 编写 E2E 测试:编写 Playwright E2E 测试,覆盖本轮需求的测试场景
深入解读
什么是超级 Loop?
- 定义:在核心 Loop 基础上,增加 E2E 测试验证
- 目标:确保前后端联动的正确性
- 特点:覆盖完整的用户流程
超级 Loop vs 核心 Loop
| 维度 | 核心 Loop | 超级 Loop |
|---|---|---|
| 测试范围 | API 测试 | API 测试 + E2E 测试 |
| 验证层次 | 后端逻辑 | 前后端联动 |
| 复杂度 | 中等 | 高 |
| 运行时间 | 几分钟 | 几十分钟到几小时 |
| Token 消耗 | 中等 | 高 |
| 适用场景 | 后端开发 | 全栈开发 |
超级 Loop 的7个步骤
步骤1:Plan 等待确认
# Plan: 实现用户注册功能(含前端)
## 目标
实现用户注册功能,包括后端 API 和前端页面
## 任务列表
- [ ] 1. 实现 POST /api/v1/users API
- [ ] 2. 实现注册页面 /register
- [ ] 3. 实现表单验证
- [ ] 4. 实现注册成功跳转
- [ ] 5. 编写 API 测试
- [ ] 6. 编写 E2E 测试
## 验收标准
- 所有 API 测试通过
- 所有 E2E 测试通过
- 前端页面视觉效果符合设计稿
步骤2:编写测试用例
# docs/test-cases/register.md
## 测试用例
### TC-001: 成功注册
1. 访问 /register 页面
2. 输入用户名 testuser
3. 输入邮箱 test@example.com
4. 输入密码 Password123
5. 确认密码 Password123
6. 点击注册按钮
7. 验证跳转到 /login
8. 验证提示"注册成功"
### TC-002: 用户名已存在
1. 访问 /register 页面
2. 输入已存在的用户名 existinguser
3. 输入邮箱 test2@example.com
4. 输入密码 Password123
5. 确认密码 Password123
6. 点击注册按钮
7. 验证提示"用户名已存在"
### TC-003: 密码不匹配
1. 访问 /register 页面
2. 输入用户名 testuser2
3. 输入邮箱 test3@example.com
4. 输入密码 Password123
5. 确认密码 Password456
6. 点击注册按钮
7. 验证提示"密码不匹配"
步骤3:编写 API 测试
@Test
void testRegister() {
CreateUserRequest request = new CreateUserRequest();
request.setUsername("testuser");
request.setEmail("test@example.com");
request.setPassword("Password123");
Response<UserResponse> response = userClient.create(request);
assertEquals(201, response.getCode());
assertNotNull(response.getData().getId());
}
步骤4:编写实现
// 后端实现
@PostMapping("/users")
public Response<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
return Response.success(userAppService.create(request));
}
// 前端实现(React)
function RegisterPage() {
const [form] = Form.useForm();
const handleSubmit = async (values) => {
await axios.post('/api/v1/users', values);
message.success('注册成功');
navigate('/login');
};
return (
<Form form={form} onFinish={handleSubmit}>
<Form.Item name="username" rules={[{ required: true }]}>
<Input placeholder="用户名" />
</Form.Item>
<Form.Item name="email" rules={[{ required: true, type: 'email' }]}>
<Input placeholder="邮箱" />
</Form.Item>
<Form.Item name="password" rules={[{ required: true, min: 8 }]}>
<Input.Password placeholder="密码" />
</Form.Item>
<Button type="primary" htmlType="submit">注册</Button>
</Form>
);
}
步骤5:运行 API 测试
mvn test -Dtest=UserApiTest
# 预期:所有测试通过
步骤6:浏览器调试
// 使用 Playwright 调试前端
test('注册页面视觉效果', async ({ page }) => {
await page.goto('/register');
// 截图验证视觉效果
const screenshot = await page.screenshot();
// 调用 AI 视觉模型验证
// 验证表单验证
await page.fill('[name="username"]', 'testuser');
await page.fill('[name="email"]', 'invalid-email');
await page.click('button[type="submit"]');
await expect(page.getByText('请输入有效的邮箱')).toBeVisible();
});
步骤7:编写 E2E 测试
test('完整的注册流程', async ({ page }) => {
// 访问注册页面
await page.goto('/register');
// 填写表单
await page.getByLabel('用户名').fill('testuser');
await page.getByLabel('邮箱').fill('test@example.com');
await page.getByLabel('密码').fill('Password123');
await page.getByLabel('确认密码').fill('Password123');
// 提交
await page.getByRole('button', { name: '注册' }).click();
// 验证跳转
await expect(page).toHaveURL(/\/login/);
// 验证提示
await expect(page.getByText('注册成功')).toBeVisible();
});
实践建议
- 先写测试用例:明确验收标准
- API 测试先通过:确保后端逻辑正确
- 浏览器调试:验证前端视觉效果
- E2E 测试覆盖:确保前后端联动正确
- 超级 Loop 成本高:仅在必要时使用(全栈开发)
本章小结
测试验收的核心要点:
- AI 辅助测试策略:Lint → Code Review → 单元测试 → API 测试 → E2E 测试
- AI 操作浏览器:Playwright MCP、Chrome DevTools MCP、Browser Use、Computer Use
- 生成 E2E 测试:Browser Use 探索 → Playwright 固化 → 截图视觉兜底
- Playwright E2E 示例:稳定的选择器、API 登录、测试数据管理
- 超级 Loop:在核心 Loop 基础上增加 E2E 测试验证
关键工具:
- Playwright、Browser Use
- Karate、RestAssured
- SonarQube、ESLint
- CodeRabbit、GitHub Copilot
最佳实践:
- 分层测试:单元测试 → API 测试 → E2E 测试
- 先用 Browser Use 探索,再固化到 Playwright
- 使用稳定的选择器: