AI驱动的自动化测试框架Playwright+Vitest
·
整体测试架构
┌─────────────────────────────────────────────┐
│ AI 测试引擎层 │
│ Spec → 用例生成 → 数据生成 → 失败分析 │
└──────────────┬──────────────────────────────┘
│ 生成 .test.ts 文件 + fixtures
┌───────┴────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Vitest │ │ Playwright │
│ 单测/集成 │ │ E2E/验收 │
│ (快速反馈) │ │ (真实场景) │
└──────────────┘ └──────────────┘
│ │
▼ ▼
组件/API级别 页面/流程级别
核心原则:Vitest 管单元/集成,Playwright 管端到端,各跑各的,不要互相嵌套。
三层测试体系
第1层:Vitest — 单元测试 + API 集成测试
// AI 生成的 API 接口测试示例
// tests/api/employee.test.ts
import { describe, it, expect, beforeAll } from 'vitest'
import { createTestClient } from '../helpers/testClient'
describe('POST /api/v1/employee', () => {
let client: TestClient
beforeAll(() => {
client = createTestClient()
})
// AI 从 Spec 中的 TC001 自动生成
it('TC001: 通过ID精确查询成功', async () => {
const res = await client.post('/api/v1/employee?id=E001')
expect(res.status).toBe(200)
expect(res.body.code).toBe(0)
expect(res.body.data.id).toBe('E001')
expect(res.body.data).toHaveProperty('gender')
expect(res.body.data).toHaveProperty('aqfsmcjks')
})
// AI 从 Spec 中的 TC004 自动生成
it('TC004: 缺少必填参数应返回400', async () => {
const res = await client.post('/api/v1/employee')
expect(res.status).toBe(400)
expect(res.body.code).toBe(400)
})
// AI 从 Spec 中的 TC010 自动生成 — 安全测试
it('TC010: SQL注入防护', async () => {
const res = await client.post("/api/v1/employee?name=张' OR '1'='1")
expect(res.status).not.toBe(500)
})
})
Vitest 的优势:
- 速度快,毫秒级反馈
- 原生 ESM + TypeScript
- 和 Vite 项目零配置集成
- 适合 API 级别的集成测试(用 supertest 或 fetch mock)
第2层:Playwright — E2E 验收测试
// e2e/employee.spec.ts — AI 从 UI Spec + 测试流程文档生成
import { test, expect } from '@playwright/test'
test.describe('员工信息查询 - E2E验收', () => {
// AI 从测试流程文档的"测试准备"自动生成 beforeEach
test.beforeEach(async ({ page }) => {
await page.goto('/login')
await page.fill('[data-testid="username"]', 'test_admin')
await page.fill('[data-testid="password"]', 'test_pass')
await page.click('[data-testid="login-btn"]')
await page.waitForURL('/dashboard')
})
// AI 从 Spec + UI 设计文档联合生成
test('TC001: 通过ID精确查询 - 完整用户流程', async ({ page }) => {
await page.click('[data-testid="nav-employee"]')
await page.fill('[data-testid="search-id"]', 'E001')
await page.click('[data-testid="search-btn"]')
// 验证结果表格
await expect(page.locator('[data-testid="result-table"]')).toBeVisible()
await expect(page.locator('[data-testid="result-id"]')).toHaveText('E001')
})
// AI 生成边界场景
test('TC004: 空查询应显示错误提示', async ({ page }) => {
await page.click('[data-testid="nav-employee"]')
await page.click('[data-testid="search-btn"]') // 不填任何参数
await expect(page.locator('[data-testid="error-msg"]'))
.toContainText('请提供员工ID或姓名')
})
})
第3层:AI 驱动 — 用例生成 + 数据生成 + 失败分析
这是最关键的部分,决定了整个测试体系能不能"闭环"。
AI 测试引擎设计
1. 用例生成:Spec → Test Code
// ai-test-engine/generator.ts
interface SpecTestCase {
id: string // TC001
module: string // 功能/异常/边界/安全
scenario: string // 测试场景描述
preconditions: string
steps: string[]
expected: string[]
priority: string // P0/P1/P2
}
interface GenerateOptions {
spec: APISpec // 结构化的 API Spec
testCases: SpecTestCase[] // Spec 中的测试用例表
target: 'vitest' | 'playwright'
baseUrl: string
dataTestIds?: Record<string, string> // UI 元素映射表
}
async function generateTests(options: GenerateOptions): Promise<GeneratedSuite> {
// 按优先级分层
const p0Cases = options.testCases.filter(tc => tc.priority === 'P0')
const p1Cases = options.testCases.filter(tc => tc.priority === 'P1')
// 生成 Vitest 测试(API 级别)
if (options.target === 'vitest') {
return generateVitestSuite(options.spec, p0Cases.concat(p1Cases))
}
// 生成 Playwright 测试(E2E 级别)
if (options.target === 'playwright') {
return generatePlaywrightSuite(options.spec, p0Cases, options.dataTestIds!)
}
}
Prompt 模板:
你是一个资深测试工程师。根据以下 API Spec 和测试用例,生成 {target} 测试代码。
要求:
1. 严格使用 TypeScript
2. 每个测试用例对应一个 it/test 块
3. 断言必须覆盖 expected 中的每一项
4. 使用 data-testid 定位元素(Playwright)
5. 测试数据使用 fixture 文件,不要硬编码
API Spec:
{spec}
测试用例:
{testCases}
输出格式:完整的 .test.ts 文件内容
2. 数据生成:Spec → Fixtures
// ai-test-engine/data-generator.ts
// AI 根据 DB 设计文档 + API Spec 生成测试数据
// 生成的 fixtures/employee.ts
export const employeeFixtures = {
// 正常数据
validEmployee: {
id: 'E001',
name: '张三丰',
gender: '男',
aqfsmcjks: 100001,
cardNo: '6222021234567890',
status: 1
},
// 边界数据 — AI 从测试用例推导
boundaryCases: {
emptyId: { id: '', name: '张三' },
emptyName: { id: 'E001', name: '' },
bothEmpty: { id: '', name: '' },
sqlInjection: { name: "张' OR '1'='1" },
longName: { name: '张'.repeat(200) },
specialChars: { name: '<script>alert(1)</script>' }
},
// 性能测试数据 — AI 批量生成
bulkEmployees: Array.from({ length: 100000 }, (_, i) => ({
id: `E${String(i).padStart(5, '0')}`,
name: `员工${i}`,
gender: i % 2 === 0 ? '男' : '女',
aqfsmcjks: Math.floor(Math.random() * 1000000),
status: 1
}))
}
3. 失败分析:Test Result → Fix Suggestion
// ai-test-engine/failure-analyzer.ts
interface TestFailure {
testCaseId: string
testName: string
expected: string
actual: string
errorStack: string
screenshot?: string // Playwright 截图
networkLog?: object[] // Playwright 网络日志
}
async function analyzeFailure(failure: TestFailure): Promise<FixSuggestion> {
// AI 分析失败原因,返回:
// 1. 根因分析(参数校验缺失?SQL注入?字段映射错误?)
// 2. 建议的代码修改
// 3. 关联的 Spec 条目(用于回环到阶段4)
// 4. 影响范围(哪些其他用例可能受影响)
}
Prompt 模板:
以下是测试失败报告:
测试用例: {testCaseId} - {testName}
预期结果: {expected}
实际结果: {actual}
错误堆栈: {errorStack}
网络请求: {networkLog}
请分析:
1. 根因是什么?(代码bug / Spec定义不清 / 测试用例错误)
2. 如果是代码bug,给出修复建议(精确到文件和行号)
3. 如果是Spec问题,给出Spec修改建议
4. 哪些其他测试用例可能受影响?
相关 Spec:
{spec}
完整的测试流水线
# .github/workflows/ai-test.yml
name: AI-Driven Test Pipeline
on: [push, pull_request]
jobs:
unit-api-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install deps
run: npm ci
- name: Run Vitest (Unit + API)
run: npx vitest run --reporter=json --outputFile=results/vitest.json
- name: AI Analyze Failures
if: failure()
run: npx ts-node ai-test-engine/analyze-ci.ts --input results/vitest.json
e2e-acceptance:
runs-on: ubuntu-latest
needs: unit-api-test
steps:
- uses: actions/checkout@v4
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run E2E
run: npx playwright test --reporter=json
- name: Upload artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-failures
path: |
test-results/
playwright-report/
- name: AI Analyze E2E Failures
if: failure()
run: npx ts-node ai-test-engine/analyze-ci.ts --input results/playwright.json --screenshots test-results/
关键技术选型建议
| 环节 | 工具 | 理由 |
|---|---|---|
| 单测/集成 | Vitest | 快、原生 TS、和 Vite 项目零配置 |
| E2E/验收 | Playwright | 多浏览器、自动截图、网络拦截、trace viewer |
| API Mock | MSW (Mock Service Worker) | Vitest 和 Playwright 都能用,一套 mock 两边跑 |
| 测试数据 | Fixtures + AI 生成 | 不硬编码,AI 根据 Spec 生成边界数据 |
| 失败截图 | Playwright 内置 | 自动截图 + trace,AI 可直接分析 |
| 测试报告 | Playwright HTML Report + 自定义 AI 分析 | 人看 HTML 报告,AI 读 JSON 结果 |
需要注意的坑
1. Playwright 和 Vitest 不要混用同一个 runner
// ❌ 不要这样做
import { test } from 'vitest'
import { chromium } from 'playwright'
test('E2E', async () => {
const browser = await chromium.launch()
// ...
})
// ✅ 正确做法:E2E 用 @playwright/test
import { test } from '@playwright/test'
2. data-testid 必须在阶段5代码生成时就埋好
如果 AI 生成的前端代码没有 data-testid,Playwright 测试就无法稳定定位元素。这要求阶段5的代码生成 Prompt 必须包含"为所有交互元素添加 data-testid"的要求。
3. 测试数据隔离
每个测试用例必须能独立运行,不能依赖执行顺序。用 beforeEach 重置状态,不要用 beforeAll 共享可变数据。
总结
Playwright + Vitest + AI 测试框架的核心是三层分离——Vitest 管快、Playwright 管真、AI 管生成和分析。最大的工程化挑战不在框架选型,而在确保阶段5生成的代码有 data-testid、API 有稳定契约,这样测试才能自动化闭环。
更多推荐
所有评论(0)