# 自动化测试流程框架实施计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 建立一套完整的自动化测试流程框架,实现智能测试选择、容器化测试环境、多层次报告体系和企业微信缺陷管理集成。 **Architecture:** 采用容器化测试环境(Docker Compose),基于代码变更分析的智能测试选择器,多层次报告生成器(实时/汇总/趋势),与企业微信智能表格集成的缺陷管理闭环。 **Tech Stack:** - 测试框架:Playwright + TypeScript - 容器化:Docker + Docker Compose - CI/CD:Woodpecker CI - 缺陷管理:企业微信智能表格 + Webhook - 数据库:PostgreSQL(复用postgresql_dev) --- ## 📋 实施阶段概览 本实施计划分为4个阶段,共约6周时间: - **阶段1**(第1-2周):基础框架搭建 - **阶段2**(第3-4周):报告体系与缺陷管理 - **阶段3**(第5-6周):优化与完善 - **阶段4**(长期):持续优化 --- ## 阶段1:基础框架搭建(第1-2周) ### 任务1.1:创建测试环境配置文件 **目标**:创建容器化测试环境的Docker Compose配置文件 **Files:** - Create: `docker-compose.test.yml` - Create: `everything-is-suitable-admin/Dockerfile.test` - Create: `.env.test` **Step 1: 创建docker-compose.test.yml文件** ```yaml # docker-compose.test.yml version: '3.8' services: # 前端应用(测试环境) admin-frontend-test: build: context: ./everything-is-suitable-admin dockerfile: Dockerfile.test container_name: admin-frontend-test ports: - "5174:5174" environment: - NODE_ENV=test - VITE_API_BASE_URL=http://admin-api-test:8082 - VITE_MOCK_ENABLED=false depends_on: admin-api-test: condition: service_healthy networks: - test-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5174"] interval: 10s timeout: 5s retries: 3 # 后端API(测试环境) admin-api-test: build: context: ./everything-is-suitable-api/everything-is-suitable-admin-app dockerfile: Dockerfile container_name: admin-api-test ports: - "8083:8082" environment: - SPRING_PROFILES_ACTIVE=test - SPRING_R2DBC_URL=r2dbc:postgresql://host.docker.internal:5432/everything_suitable_test - SPRING_R2DBC_USERNAME=${DB_USERNAME:-postgres} - SPRING_R2DBC_PASSWORD=${DB_PASSWORD:-postgres} networks: - test-network extra_hosts: - "host.docker.internal:host-gateway" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8082/actuator/health"] interval: 10s timeout: 5s retries: 5 networks: test-network: driver: bridge ``` **Step 2: 创建前端测试Dockerfile** ```dockerfile # everything-is-suitable-admin/Dockerfile.test FROM node:18-alpine WORKDIR /app # 复制package文件 COPY package*.json ./ # 安装依赖 RUN npm ci # 复制源代码 COPY . . # 构建应用 RUN npm run build # 使用nginx提供静态文件服务 FROM nginx:alpine COPY --from=0 /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 5174 CMD ["nginx", "-g", "daemon off;"] ``` **Step 3: 创建测试环境变量文件** ```bash # .env.test NODE_ENV=test TEST_ENV=ci # 测试环境URL API_BASE_URL=http://localhost:8083 FRONTEND_BASE_URL=http://localhost:5174 # 数据库配置 DB_HOST=localhost DB_PORT=5432 DB_NAME=everything_suitable_test DB_USERNAME=postgres DB_PASSWORD=postgres # 企业微信配置 WECOM_WEBHOOK_URL=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY WECOM_TABLE_ID=YOUR_TABLE_ID WECOM_BOT_WEBHOOK=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_BOT_KEY ``` **Step 4: 验证配置文件语法** Run: `docker-compose -f docker-compose.test.yml config` Expected: 配置文件语法正确,无错误输出 **Step 5: 提交配置文件** ```bash git add docker-compose.test.yml everything-is-suitable-admin/Dockerfile.test .env.test git commit -m "feat: add test environment docker compose configuration" ``` --- ### 任务1.2:配置测试数据库 **目标**:创建测试数据库并初始化测试数据 **Files:** - Create: `scripts/init-test-database.sh` - Create: `scripts/init-test-data.ts` **Step 1: 创建数据库初始化脚本** ```bash #!/bin/bash # scripts/init-test-database.sh set -e echo "=== 初始化测试数据库 ===" # 检查postgresql_dev容器是否运行 if ! docker ps | grep -q postgresql_dev; then echo "❌ postgresql_dev容器未运行" echo "请先启动容器: docker start postgresql_dev" exit 1 fi # 创建测试数据库 echo "创建测试数据库..." docker exec postgresql_dev psql -U postgres -c "CREATE DATABASE everything_suitable_test;" || echo "数据库已存在" # 创建测试Schema echo "创建测试Schema..." docker exec postgresql_dev psql -U postgres -d everything_suitable_test -c "CREATE SCHEMA IF NOT EXISTS test_data;" echo "✅ 测试数据库初始化完成" ``` **Step 2: 创建测试数据初始化脚本** ```typescript // scripts/init-test-data.ts import { Pool } from 'pg'; const pool = new Pool({ host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), database: process.env.DB_NAME || 'everything_suitable_test', user: process.env.DB_USERNAME || 'postgres', password: process.env.DB_PASSWORD || 'postgres', }); async function initTestData() { const client = await pool.connect(); try { await client.query('BEGIN'); // 清理测试数据 console.log('清理测试数据...'); await client.query('TRUNCATE TABLE test_data.users CASCADE'); await client.query('TRUNCATE TABLE test_data.roles CASCADE'); await client.query('TRUNCATE TABLE test_data.menus CASCADE'); // 创建测试用户 console.log('创建测试用户...'); await client.query(` INSERT INTO test_data.users (username, password, email, status) VALUES ('admin', 'admin123', 'admin@example.com', 'active'), ('user1', 'user123', 'user1@example.com', 'active'), ('user2', 'user123', 'user2@example.com', 'active') `); // 创建测试角色 console.log('创建测试角色...'); await client.query(` INSERT INTO test_data.roles (name, code, status) VALUES ('管理员', 'admin', 1), ('普通用户', 'user', 1) `); // 创建测试菜单 console.log('创建测试菜单...'); await client.query(` INSERT INTO test_data.menus (name, path, type, status) VALUES ('用户管理', '/user-management', 1, 0), ('角色管理', '/role-management', 1, 0), ('菜单管理', '/menu-management', 1, 0) `); await client.query('COMMIT'); console.log('✅ 测试数据初始化完成'); } catch (error) { await client.query('ROLLBACK'); console.error('❌ 测试数据初始化失败:', error); throw error; } finally { client.release(); } } initTestData() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); ``` **Step 3: 运行数据库初始化脚本** Run: `chmod +x scripts/init-test-database.sh && ./scripts/init-test-database.sh` Expected: 测试数据库创建成功 **Step 4: 安装pg依赖并运行测试数据初始化** Run: `cd scripts && npm install pg && ts-node init-test-data.ts` Expected: 测试数据初始化成功 **Step 5: 提交数据库初始化脚本** ```bash git add scripts/init-test-database.sh scripts/init-test-data.ts git commit -m "feat: add test database initialization scripts" ``` --- ### 任务1.3:创建代码-测试映射配置 **目标**:建立代码文件与测试用例之间的映射关系 **Files:** - Create: `config/test-mapping.config.ts` **Step 1: 创建映射配置文件** ```typescript // config/test-mapping.config.ts export interface TestMapping { [sourceFile: string]: { tests: string[]; priority: 'high' | 'medium' | 'low'; modules: string[]; }; } export const testMapping: TestMapping = { // 用户管理模块 'everything-is-suitable-admin/src/views/UserManagement.vue': { tests: [ 'e2e/user-management/*.spec.ts', ], priority: 'high', modules: ['user-management'], }, 'everything-is-suitable-admin/src/api/user.ts': { tests: [ 'e2e/user-management/*.spec.ts', 'e2e/api/user-api.spec.ts', ], priority: 'high', modules: ['user-management', 'api'], }, 'everything-is-suitable-admin/src/stores/user.ts': { tests: [ 'e2e/user-management/*.spec.ts', ], priority: 'medium', modules: ['user-management'], }, // 角色管理模块 'everything-is-suitable-admin/src/views/RoleManagement.vue': { tests: [ 'e2e/role-management/*.spec.ts', ], priority: 'high', modules: ['role-management'], }, 'everything-is-suitable-admin/src/api/role.ts': { tests: [ 'e2e/role-management/*.spec.ts', 'e2e/api/role-api.spec.ts', ], priority: 'high', modules: ['role-management', 'api'], }, // 菜单管理模块 'everything-is-suitable-admin/src/views/MenuManagement.vue': { tests: [ 'e2e/menu-management/*.spec.ts', ], priority: 'high', modules: ['menu-management'], }, 'everything-is-suitable-admin/src/api/menu.ts': { tests: [ 'e2e/menu-management/*.spec.ts', 'e2e/api/menu-api.spec.ts', ], priority: 'high', modules: ['menu-management', 'api'], }, // 黄历功能模块 'everything-is-suitable-uniapp/src/pages/almanac/index.vue': { tests: [ 'e2e/almanac-functionality/*.spec.ts', ], priority: 'high', modules: ['almanac-functionality'], }, }; // 反向映射:模块 -> 测试文件 export const moduleToTests: Record = { 'user-management': ['e2e/user-management/*.spec.ts'], 'role-management': ['e2e/role-management/*.spec.ts'], 'menu-management': ['e2e/menu-management/*.spec.ts'], 'almanac-functionality': ['e2e/almanac-functionality/*.spec.ts'], 'api': ['e2e/api/*.spec.ts'], }; ``` **Step 2: 验证配置文件语法** Run: `npx tsc --noEmit config/test-mapping.config.ts` Expected: 无类型错误 **Step 3: 提交映射配置文件** ```bash git add config/test-mapping.config.ts git commit -m "feat: add test mapping configuration" ``` --- ### 任务1.4:实现智能测试选择器 **目标**:实现基于代码变更的智能测试选择器 **Files:** - Create: `scripts/smart-test-selector.ts` - Create: `scripts/cli/smart-test-selector-cli.ts` **Step 1: 创建智能测试选择器核心类** ```typescript // scripts/smart-test-selector.ts import { execSync } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import { testMapping, moduleToTests } from '../config/test-mapping.config'; export interface TestSelectionResult { selectedTests: string[]; affectedModules: string[]; changedFiles: string[]; analysisReport: string; } export class SmartTestSelector { private projectRoot: string; constructor(projectRoot: string = process.cwd()) { this.projectRoot = projectRoot; } /** * 根据代码变更选择测试用例 */ selectTestsByChanges( changedFiles: string[], options: { includeRelated?: boolean; priority?: 'high' | 'medium' | 'low' | 'all'; testLevel?: 'smoke' | 'functional' | 'all'; } = {} ): TestSelectionResult { const { includeRelated = true, priority = 'all', testLevel = 'all', } = options; const selectedTests = new Set(); const affectedModules = new Set(); // 分析每个变更文件 for (const file of changedFiles) { const normalizedPath = this.normalizePath(file); const mapping = this.findMapping(normalizedPath); if (mapping) { // 添加直接关联的测试 mapping.tests.forEach(test => selectedTests.add(test)); mapping.modules.forEach(module => affectedModules.add(module)); // 如果启用关联分析,添加相关模块的测试 if (includeRelated) { this.addRelatedTests(mapping.modules, selectedTests, affectedModules); } } } // 根据优先级过滤 const filteredTests = this.filterByPriority( Array.from(selectedTests), priority ); // 根据测试级别过滤 const finalTests = this.filterByTestLevel(filteredTests, testLevel); // 生成分析报告 const analysisReport = this.generateAnalysisReport({ changedFiles, affectedModules: Array.from(affectedModules), selectedTests: finalTests, }); return { selectedTests: finalTests, affectedModules: Array.from(affectedModules), changedFiles, analysisReport, }; } /** * 从Git获取变更文件 */ getChangedFilesFromGit( baseBranch: string = 'origin/main', headBranch: string = 'HEAD' ): string[] { try { const output = execSync( `git diff --name-only ${baseBranch}...${headBranch}`, { encoding: 'utf-8', cwd: this.projectRoot } ); return output .split('\n') .filter(file => file.trim() && this.isSourceFile(file)); } catch (error) { console.error('Failed to get changed files from git:', error); return []; } } /** * 规范化文件路径 */ private normalizePath(filePath: string): string { return filePath.replace(/\\/g, '/').replace(/^\.\//, ''); } /** * 查找文件对应的测试映射 */ private findMapping(normalizedPath: string) { // 精确匹配 if (testMapping[normalizedPath]) { return testMapping[normalizedPath]; } // 模糊匹配(支持通配符) for (const [pattern, mapping] of Object.entries(testMapping)) { if (this.matchPattern(normalizedPath, pattern)) { return mapping; } } return null; } /** * 简单的模式匹配 */ private matchPattern(path: string, pattern: string): boolean { const regex = new RegExp( '^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$' ); return regex.test(path); } /** * 添加相关测试 */ private addRelatedTests( modules: string[], selectedTests: Set, affectedModules: Set ): void { for (const module of modules) { const relatedTests = moduleToTests[module]; if (relatedTests) { relatedTests.forEach(test => selectedTests.add(test)); } } } /** * 根据优先级过滤测试 */ private filterByPriority( tests: string[], priority: 'high' | 'medium' | 'low' | 'all' ): string[] { if (priority === 'all') { return tests; } const priorityMap = { high: ['@p0', '@smoke'], medium: ['@p1', '@functional'], low: ['@p2', '@edge'], }; const targetTags = priorityMap[priority]; return tests.filter(test => targetTags.some(tag => test.includes(tag)) ); } /** * 根据测试级别过滤 */ private filterByTestLevel( tests: string[], level: 'smoke' | 'functional' | 'all' ): string[] { if (level === 'all') { return tests; } const levelMap = { smoke: '@smoke', functional: '@functional', }; const targetTag = levelMap[level]; return tests.filter(test => test.includes(targetTag)); } /** * 判断是否为源代码文件 */ private isSourceFile(filePath: string): boolean { const extensions = ['.ts', '.tsx', '.js', '.jsx', '.vue', '.java']; return extensions.some(ext => filePath.endsWith(ext)); } /** * 生成分析报告 */ private generateAnalysisReport(data: { changedFiles: string[]; affectedModules: string[]; selectedTests: string[]; }): string { return ` # 智能测试选择分析报告 ## 变更文件 (${data.changedFiles.length}个) ${data.changedFiles.map(f => `- ${f}`).join('\n')} ## 受影响模块 (${data.affectedModules.length}个) ${data.affectedModules.map(m => `- ${m}`).join('\n')} ## 选中测试用例 (${data.selectedTests.length}个) ${data.selectedTests.map(t => `- ${t}`).join('\n')} ## 执行建议 - 优先执行冒烟测试(@smoke标签) - 然后执行功能测试(@functional标签) - 最后执行边缘场景测试(@edge标签) `.trim(); } } ``` **Step 2: 创建CLI工具** ```typescript // scripts/cli/smart-test-selector-cli.ts import * as fs from 'fs'; import * as yargs from 'yargs'; import { SmartTestSelector } from '../smart-test-selector'; const argv = yargs .option('input', { alias: 'i', type: 'string', description: '变更文件列表文件路径', }) .option('output', { alias: 'o', type: 'string', description: '输出文件路径', default: 'selected-tests.json', }) .option('report', { alias: 'r', type: 'string', description: '分析报告输出路径', default: 'test-selection-report.md', }) .option('priority', { alias: 'p', type: 'string', choices: ['high', 'medium', 'low', 'all'], default: 'all', description: '测试优先级过滤', }) .option('level', { alias: 'l', type: 'string', choices: ['smoke', 'functional', 'all'], default: 'all', description: '测试级别过滤', }) .argv as any; async function main() { const selector = new SmartTestSelector(); let changedFiles: string[] = []; if (argv.input) { // 从文件读取变更文件列表 const content = fs.readFileSync(argv.input, 'utf-8'); changedFiles = content.split('\n').filter(f => f.trim()); } else { // 从Git获取变更文件 changedFiles = selector.getChangedFilesFromGit(); } console.log(`📊 分析 ${changedFiles.length} 个变更文件...`); const result = selector.selectTestsByChanges(changedFiles, { priority: argv.priority, testLevel: argv.level, }); // 保存结果 fs.writeFileSync(argv.output, JSON.stringify(result, null, 2)); console.log(`✅ 测试选择结果已保存到: ${argv.output}`); // 保存报告 fs.writeFileSync(argv.report, result.analysisReport); console.log(`✅ 分析报告已保存到: ${argv.report}`); // 输出摘要 console.log('\n=== 选择结果摘要 ==='); console.log(`变更文件: ${result.changedFiles.length} 个`); console.log(`受影响模块: ${result.affectedModules.length} 个`); console.log(`选中测试: ${result.selectedTests.length} 个`); } main().catch(console.error); ``` **Step 3: 编译TypeScript文件** Run: `npx tsc scripts/smart-test-selector.ts scripts/cli/smart-test-selector-cli.ts --outDir dist/scripts` Expected: 编译成功,无错误 **Step 4: 测试智能测试选择器** Run: `node dist/scripts/cli/smart-test-selector-cli.js --help` Expected: 显示CLI帮助信息 **Step 5: 提交智能测试选择器代码** ```bash git add scripts/smart-test-selector.ts scripts/cli/smart-test-selector-cli.ts git commit -m "feat: implement smart test selector" ``` --- ### 任务1.5:创建测试执行脚本 **目标**:创建测试执行脚本,支持智能测试和全量测试 **Files:** - Create: `scripts/run-selected-tests.ts` - Create: `scripts/run-all-tests.ts` **Step 1: 创建智能测试执行脚本** ```typescript // scripts/run-selected-tests.ts import { execSync } from 'child_process'; import * as fs from 'fs'; interface SelectedTests { smoke: string[]; functional: string[]; edge: string[]; } export class TestExecutor { /** * 执行智能选择的测试 */ async runSelectedTests(testsFile: string): Promise { const selectedTests: SelectedTests = JSON.parse( fs.readFileSync(testsFile, 'utf-8') ); console.log('=== 开始执行智能测试 ===\n'); // 1. 执行冒烟测试(优先级最高) if (selectedTests.smoke.length > 0) { console.log('📦 执行冒烟测试...'); await this.runTests(selectedTests.smoke, 'smoke'); } // 2. 执行功能测试 if (selectedTests.functional.length > 0) { console.log('📦 执行功能测试...'); await this.runTests(selectedTests.functional, 'functional'); } // 3. 执行边缘场景测试(可选) if (selectedTests.edge.length > 0 && process.env.RUN_EDGE_TESTS === 'true') { console.log('📦 执行边缘场景测试...'); await this.runTests(selectedTests.edge, 'edge'); } console.log('\n✅ 智能测试执行完成'); } /** * 执行指定测试用例 */ private async runTests( testPatterns: string[], level: string ): Promise { for (const pattern of testPatterns) { try { console.log(` 执行: ${pattern}`); execSync( `npx playwright test "${pattern}" --project=chromium --reporter=html`, { stdio: 'inherit', env: { ...process.env, TEST_LEVEL: level, }, } ); } catch (error) { console.error(` ❌ 测试失败: ${pattern}`); // 继续执行其他测试 } } } /** * 执行全量测试 */ async runAllTests(): Promise { console.log('=== 开始执行全量测试 ===\n'); execSync('npm run test:e2e', { stdio: 'inherit', }); console.log('\n✅ 全量测试执行完成'); } } // 主函数 async function main() { const executor = new TestExecutor(); const testsFile = process.argv[2] || 'selected-tests.json'; if (fs.existsSync(testsFile)) { await executor.runSelectedTests(testsFile); } else { await executor.runAllTests(); } } main().catch(console.error); ``` **Step 2: 创建全量测试执行脚本** ```typescript // scripts/run-all-tests.ts import { execSync } from 'child_process'; console.log('=== 开始执行全量测试 ===\n'); try { execSync('npm run test:e2e', { stdio: 'inherit', }); console.log('\n✅ 全量测试执行完成'); } catch (error) { console.error('\n❌ 全量测试执行失败'); process.exit(1); } ``` **Step 3: 添加npm脚本** 在`package.json`中添加: ```json { "scripts": { "test:smart": "ts-node scripts/run-selected-tests.ts", "test:all": "ts-node scripts/run-all-tests.ts" } } ``` **Step 4: 测试执行脚本** Run: `npm run test:smart -- selected-tests.json` Expected: 脚本运行正常(如果没有测试文件会提示) **Step 5: 提交测试执行脚本** ```bash git add scripts/run-selected-tests.ts scripts/run-all-tests.ts package.json git commit -m "feat: add test execution scripts" ``` --- ### 任务1.6:集成到Woodpecker CI **目标**:将智能测试流程集成到Woodpecker CI **Files:** - Modify: `.woodpecker.yml` **Step 1: 更新Woodpecker CI配置** 在`.woodpecker.yml`中添加智能测试步骤: ```yaml # .woodpecker.yml (追加内容) steps: # ... 现有步骤 ... # 智能测试选择 smart-test-selection: image: node:18-alpine commands: - npm ci - | # 获取变更文件 if [ "$CI_BUILD_EVENT" = "cron" ]; then echo "[]" > changed-files.txt else git diff --name-only origin/main...HEAD > changed-files.txt || echo "[]" > changed-files.txt fi - | # 智能选择测试用例 node scripts/cli/smart-test-selector-cli.js \ --input changed-files.txt \ --output selected-tests.json \ --report test-selection-report.md when: event: [push, pull_request] # 执行智能测试 run-smart-tests: image: node:18-alpine environment: - TEST_ENV=ci - API_BASE_URL=http://localhost:8083 - FRONTEND_BASE_URL=http://localhost:5174 commands: - npm ci - npx playwright install --with-deps chromium - | if [ -f selected-tests.json ]; then npm run test:smart selected-tests.json else npm run test:all fi depends_on: - start-test-environment - smart-test-selection ``` **Step 2: 验证CI配置语法** Run: `woodpecker-cli lint .woodpecker.yml` Expected: 配置文件语法正确 **Step 3: 提交CI配置更新** ```bash git add .woodpecker.yml git commit -m "feat: integrate smart test selection into Woodpecker CI" ``` --- ## 阶段1完成检查点 完成以上所有任务后,您应该拥有: - ✅ 容器化测试环境配置(docker-compose.test.yml) - ✅ 测试数据库初始化脚本 - ✅ 代码-测试映射配置 - ✅ 智能测试选择器实现 - ✅ 测试执行脚本 - ✅ Woodpecker CI集成 **验证步骤**: 1. 启动测试环境:`docker-compose -f docker-compose.test.yml up -d` 2. 初始化测试数据库:`./scripts/init-test-database.sh` 3. 运行智能测试选择:`npm run test:smart` 4. 检查CI配置:`woodpecker-cli lint .woodpecker.yml` --- ## 阶段2:报告体系与缺陷管理(第3-4周) ### 任务2.1:实现报告生成器 **目标**:创建多层次报告生成器(实时、汇总、趋势) **Files:** - Create: `scripts/report-generator.ts` - Create: `scripts/generate-trend-report.ts` **Step 1: 创建报告生成器核心类** ```typescript // scripts/report-generator.ts import * as fs from 'fs'; import * as path from 'path'; export interface TestResult { testName: string; status: 'passed' | 'failed' | 'skipped'; duration: number; module: string; priority: string; errorMessage?: string; screenshots?: string[]; logs?: string[]; } export interface TestReport { timestamp: string; totalTests: number; passed: number; failed: number; skipped: number; duration: number; modules: { [module: string]: { total: number; passed: number; failed: number; skipped: number; }; }; results: TestResult[]; } export class ReportGenerator { private outputDir: string; constructor(outputDir: string = './test-results') { this.outputDir = outputDir; this.ensureOutputDir(); } /** * 生成实时报告 */ generateRealtimeReport(result: TestResult): void { const reportPath = path.join(this.outputDir, 'realtime-report.json'); let report: TestReport; if (fs.existsSync(reportPath)) { report = JSON.parse(fs.readFileSync(reportPath, 'utf-8')); } else { report = this.createEmptyReport(); } report.totalTests++; report[result.status]++; report.duration += result.duration; if (!report.modules[result.module]) { report.modules[result.module] = { total: 0, passed: 0, failed: 0, skipped: 0, }; } report.modules[result.module].total++; report.modules[result.module][result.status]++; report.results.push(result); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); this.generateRealtimeHTML(report); } /** * 生成汇总报告 */ generateSummaryReport(): TestReport { const reportPath = path.join(this.outputDir, 'realtime-report.json'); if (!fs.existsSync(reportPath)) { throw new Error('No test results found'); } const report: TestReport = JSON.parse( fs.readFileSync(reportPath, 'utf-8') ); this.generateHTMLReport(report); this.generateJSONReport(report); this.generateJUnitReport(report); return report; } /** * 生成HTML报告 */ private generateHTMLReport(report: TestReport): void { const html = this.createHTMLTemplate(report); const reportPath = path.join(this.outputDir, 'reports', 'summary-report.html'); fs.writeFileSync(reportPath, html); } /** * 创建HTML模板 */ private createHTMLTemplate(report: TestReport): string { return ` 测试报告 - ${report.timestamp}

自动化测试报告

生成时间: ${report.timestamp}

总耗时: ${(report.duration / 1000).toFixed(2)}秒

总测试数

${report.totalTests}

通过

${report.passed}

失败

${report.failed}

跳过

${report.skipped}
`.trim(); } /** * 辅助方法 */ private createEmptyReport(): TestReport { return { timestamp: new Date().toISOString(), totalTests: 0, passed: 0, failed: 0, skipped: 0, duration: 0, modules: {}, results: [], }; } private ensureOutputDir(): void { const dirs = [ this.outputDir, path.join(this.outputDir, 'reports'), path.join(this.outputDir, 'history'), ]; dirs.forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } }); } private generateJSONReport(report: TestReport): void { const reportPath = path.join(this.outputDir, 'reports', 'summary-report.json'); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); } private generateJUnitReport(report: TestReport): void { const xml = ` ${Object.entries(report.modules).map(([module, stats]) => ` `).join('')} `.trim(); const reportPath = path.join(this.outputDir, 'reports', 'junit-report.xml'); fs.writeFileSync(reportPath, xml); } private generateRealtimeHTML(report: TestReport): void { const html = ` 实时测试报告

实时测试进度

已执行: ${report.totalTests}

通过: ${report.passed}

失败: ${report.failed}

`.trim(); const reportPath = path.join(this.outputDir, 'reports', 'realtime-report.html'); fs.writeFileSync(reportPath, html); } } ``` **Step 2: 创建趋势报告生成脚本** ```typescript // scripts/generate-trend-report.ts import * as fs from 'fs'; import * as path from 'path'; import { ReportGenerator, TestReport } from './report-generator'; const reportGenerator = new ReportGenerator(); // 生成趋势报告 const historyDir = path.join('./test-results', 'history'); const currentReport = reportGenerator.generateSummaryReport(); // 保存当前报告到历史记录 const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const historyPath = path.join(historyDir, `report-${timestamp}.json`); fs.writeFileSync(historyPath, JSON.stringify(currentReport, null, 2)); console.log('✅ 趋势报告生成完成'); ``` **Step 3: 添加npm脚本** 在`package.json`中添加: ```json { "scripts": { "test:report": "ts-node scripts/generate-trend-report.ts" } } ``` **Step 4: 测试报告生成器** Run: `npm run test:report` Expected: 报告生成成功 **Step 5: 提交报告生成器代码** ```bash git add scripts/report-generator.ts scripts/generate-trend-report.ts package.json git commit -m "feat: implement multi-level report generator" ``` --- ### 任务2.2:实现企业微信智能表格集成 **目标**:实现与企业微信智能表格的集成,自动同步缺陷 **Files:** - Create: `scripts/wecom-integration.ts` - Create: `scripts/analyze-failures.ts` **Step 1: 创建企业微信集成类** ```typescript // scripts/wecom-integration.ts import axios from 'axios'; export interface WecomTableConfig { webhookUrl: string; tableId: string; } export interface DefectRecord { defectId: string; testName: string; module: string; priority: 'P0' | 'P1' | 'P2'; status: 'open' | 'in_progress' | 'fixed' | 'closed'; errorMessage: string; screenshots: string[]; logs: string[]; createdAt: string; updatedAt: string; reporter: string; testRunId: string; gitCommit?: string; gitBranch?: string; } export class WecomTableIntegration { private config: WecomTableConfig; constructor(config: WecomTableConfig) { this.config = config; } /** * 同步缺陷到企业微信智能表格 */ async syncDefect(defect: DefectRecord): Promise { try { const existingDefect = await this.findExistingDefect(defect.testName); if (existingDefect) { await this.updateDefect(existingDefect.id, defect); console.log(`✅ 更新缺陷: ${defect.testName}`); } else { await this.createDefect(defect); console.log(`✅ 创建缺陷: ${defect.testName}`); } } catch (error) { console.error('❌ 同步缺陷失败:', error); throw error; } } /** * 发送通知到企业微信群 */ async sendNotification(message: { title: string; content: string; mentionedList?: string[]; }): Promise { const payload = { msgtype: 'markdown', markdown: { content: `## ${message.title}\n\n${message.content}${ message.mentionedList ? `\n\n<@${message.mentionedList.join('><@')}>` : '' }`, }, }; await axios.post(this.config.webhookUrl, payload); } private async createDefect(defect: DefectRecord): Promise { const payload = { table_id: this.config.tableId, record: { fields: { 缺陷ID: defect.defectId, 测试用例: defect.testName, 模块: defect.module, 优先级: defect.priority, 状态: defect.status, 错误信息: defect.errorMessage, 创建时间: defect.createdAt, 更新时间: defect.updatedAt, 报告人: defect.reporter, }, }, }; await this.sendToWecom(payload); } private async updateDefect( defectId: string, defect: DefectRecord ): Promise { const payload = { table_id: this.config.tableId, record_id: defectId, record: { fields: { 状态: defect.status, 更新时间: defect.updatedAt, 错误信息: defect.errorMessage, }, }, }; await this.sendToWecom(payload); } private async findExistingDefect( testName: string ): Promise<{ id: string } | null> { // 实现查找逻辑 return null; } private async sendToWecom(payload: any): Promise { await axios.post(this.config.webhookUrl, payload, { headers: { 'Content-Type': 'application/json', }, }); } } ``` **Step 2: 创建缺陷分析脚本** ```typescript // scripts/analyze-failures.ts import * as fs from 'fs'; import * as path from 'path'; import { execSync } from 'child_process'; import { WecomTableIntegration, DefectRecord } from './wecom-integration'; export class FailureAnalyzer { private wecomIntegration: WecomTableIntegration; private testResultsDir: string; constructor( wecomConfig: any, testResultsDir: string = './test-results' ) { this.wecomIntegration = new WecomTableIntegration(wecomConfig); this.testResultsDir = testResultsDir; } async analyzeAndSync(): Promise { const failures = this.loadFailures(); if (failures.length === 0) { console.log('✅ 没有失败的测试用例'); return; } console.log(`📊 发现 ${failures.length} 个失败的测试用例`); for (const failure of failures) { const defect = this.createDefectRecord(failure); await this.wecomIntegration.syncDefect(defect); } await this.sendSummaryNotification(failures); } private loadFailures(): any[] { const reportPath = path.join(this.testResultsDir, 'realtime-report.json'); if (!fs.existsSync(reportPath)) { return []; } const report = JSON.parse(fs.readFileSync(reportPath, 'utf-8')); return report.results.filter((r: any) => r.status === 'failed'); } private createDefectRecord(failure: any): DefectRecord { const gitInfo = this.getGitInfo(); return { defectId: `DEF-${Date.now()}`, testName: failure.testName, module: failure.module, priority: this.determinePriority(failure), status: 'open', errorMessage: failure.errorMessage || 'Unknown error', screenshots: failure.screenshots || [], logs: failure.logs || [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), reporter: '自动化测试系统', testRunId: process.env.CI_BUILD_NUMBER || 'local', gitCommit: gitInfo.commit, gitBranch: gitInfo.branch, }; } private determinePriority(failure: any): 'P0' | 'P1' | 'P2' { if (failure.priority === 'p0' || failure.tags?.includes('@smoke')) { return 'P0'; } if (failure.priority === 'p1' || failure.tags?.includes('@functional')) { return 'P1'; } return 'P2'; } private getGitInfo(): { commit: string; branch: string } { try { const commit = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim(); const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8', }).trim(); return { commit, branch }; } catch (error) { return { commit: 'unknown', branch: 'unknown' }; } } private async sendSummaryNotification(failures: any[]): Promise { const p0Count = failures.filter(f => this.determinePriority(f) === 'P0').length; const p1Count = failures.filter(f => this.determinePriority(f) === 'P1').length; const p2Count = failures.filter(f => this.determinePriority(f) === 'P2').length; await this.wecomIntegration.sendNotification({ title: '🚨 测试失败通知', content: ` **测试执行完成,发现 ${failures.length} 个失败用例** - P0(核心功能): ${p0Count} 个 - P1(重要功能): ${p1Count} 个 - P2(次要功能): ${p2Count} 个 **失败详情:** ${failures.slice(0, 5).map(f => `- [${f.module}] ${f.testName}`).join('\n')} `.trim(), mentionedList: p0Count > 0 ? ['all'] : [], }); } } // 主函数 async function main() { const wecomConfig = { webhookUrl: process.env.WECOM_WEBHOOK_URL || '', tableId: process.env.WECOM_TABLE_ID || '', }; const analyzer = new FailureAnalyzer(wecomConfig); await analyzer.analyzeAndSync(); } main().catch(console.error); ``` **Step 3: 安装axios依赖** Run: `npm install axios` Expected: axios安装成功 **Step 4: 测试企业微信集成** Run: `WECOM_WEBHOOK_URL=your_url WECOM_TABLE_ID=your_id ts-node scripts/analyze-failures.ts` Expected: 脚本运行正常(如果没有失败用例会提示) **Step 5: 提交企业微信集成代码** ```bash git add scripts/wecom-integration.ts scripts/analyze-failures.ts package.json git commit -m "feat: implement WeChat Work smart table integration" ``` --- ## 阶段2完成检查点 完成以上所有任务后,您应该拥有: - ✅ 多层次报告生成器(实时、汇总、趋势) - ✅ 企业微信智能表格集成 - ✅ 缺陷自动同步功能 - ✅ 企业微信群通知功能 **验证步骤**: 1. 运行测试:`npm run test:smart` 2. 生成报告:`npm run test:report` 3. 检查报告文件:`ls test-results/reports/` 4. 测试企业微信通知(需要配置Webhook) --- ## 阶段3:优化与完善(第5-6周) ### 任务3.1:引入代码覆盖率分析 **目标**:使用代码覆盖率数据自动生成和维护代码-测试映射关系 **Files:** - Create: `scripts/coverage-analyzer.ts` - Modify: `config/test-mapping.config.ts` **Step 1: 创建覆盖率分析器** ```typescript // scripts/coverage-analyzer.ts import * as fs from 'fs'; import * as path from 'path'; export interface CoverageData { [file: string]: { lines: { covered: number; total: number }; functions: { covered: number; total: number }; branches: { covered: number; total: number }; }; } export class CoverageAnalyzer { /** * 从Playwright覆盖率报告生成映射 */ generateMappingFromCoverage(coverageFile: string): void { const coverage: CoverageData = JSON.parse( fs.readFileSync(coverageFile, 'utf-8') ); const mapping: any = {}; for (const [file, data] of Object.entries(coverage)) { const testFile = this.inferTestFile(file); if (testFile) { if (!mapping[file]) { mapping[file] = { tests: [], priority: this.determinePriority(data), modules: this.extractModule(file), }; } mapping[file].tests.push(testFile); } } // 更新test-mapping.config.ts this.updateMappingConfig(mapping); } private inferTestFile(sourceFile: string): string | null { // 根据源文件推断测试文件 const moduleName = this.extractModule(sourceFile); if (moduleName) { return `e2e/${moduleName}/*.spec.ts`; } return null; } private determinePriority(data: any): 'high' | 'medium' | 'low' { const lineCoverage = data.lines.covered / data.lines.total; if (lineCoverage > 0.8) return 'high'; if (lineCoverage > 0.5) return 'medium'; return 'low'; } private extractModule(file: string): string[] { const match = file.match(/src\/views\/(\w+)/); if (match) { const moduleName = match[1].toLowerCase().replace(/-/g, '-'); return [moduleName]; } return []; } private updateMappingConfig(mapping: any): void { const configPath = path.join(process.cwd(), 'config', 'test-mapping.config.ts'); // 读取现有配置 const existingConfig = fs.readFileSync(configPath, 'utf-8'); // 合并映射 const mergedMapping = { ...JSON.parse(existingConfig.match(/export const testMapping[^=]*=\s*([\s\S]*?);/)?.[1] || '{}'), ...mapping, }; // 写入新配置 const newConfig = existingConfig.replace( /export const testMapping[^=]*=\s*[\s\S]*?;/, `export const testMapping: TestMapping = ${JSON.stringify(mergedMapping, null, 2)};` ); fs.writeFileSync(configPath, newConfig); console.log('✅ 测试映射配置已更新'); } } ``` **Step 2: 运行覆盖率分析** Run: `npx playwright test --coverage && ts-node scripts/coverage-analyzer.ts coverage/coverage-final.json` Expected: 映射配置更新成功 **Step 3: 提交覆盖率分析器** ```bash git add scripts/coverage-analyzer.ts git commit -m "feat: add coverage analyzer for auto-generating test mapping" ``` --- ### 任务3.2:添加历史失败率分析 **目标**:分析历史测试失败率,优先执行容易失败的测试 **Files:** - Create: `scripts/failure-rate-analyzer.ts` - Modify: `scripts/smart-test-selector.ts` **Step 1: 创建失败率分析器** ```typescript // scripts/failure-rate-analyzer.ts import * as fs from 'fs'; import * as path from 'path'; export interface FailureRateData { [testName: string]: { totalRuns: number; failures: number; failureRate: number; lastFailure?: string; }; } export class FailureRateAnalyzer { private historyDir: string; constructor(historyDir: string = './test-results/history') { this.historyDir = historyDir; } /** * 分析历史失败率 */ analyzeFailureRate(): FailureRateData { const reports = this.loadHistoryReports(); const failureRateData: FailureRateData = {}; for (const report of reports) { for (const result of report.results) { if (!failureRateData[result.testName]) { failureRateData[result.testName] = { totalRuns: 0, failures: 0, failureRate: 0, }; } failureRateData[result.testName].totalRuns++; if (result.status === 'failed') { failureRateData[result.testName].failures++; failureRateData[result.testName].lastFailure = report.timestamp; } } } // 计算失败率 for (const data of Object.values(failureRateData)) { data.failureRate = data.failures / data.totalRuns; } return failureRateData; } /** * 获取高风险测试(失败率 > 20%) */ getHighRiskTests(): string[] { const failureRateData = this.analyzeFailureRate(); return Object.entries(failureRateData) .filter(([_, data]) => data.failureRate > 0.2) .map(([testName, _]) => testName); } private loadHistoryReports(): any[] { if (!fs.existsSync(this.historyDir)) { return []; } return fs.readdirSync(this.historyDir) .filter(f => f.endsWith('.json')) .map(f => JSON.parse(fs.readFileSync(path.join(this.historyDir, f), 'utf-8'))) .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); } } ``` **Step 2: 集成到智能测试选择器** 在`scripts/smart-test-selector.ts`中添加: ```typescript import { FailureRateAnalyzer } from './failure-rate-analyzer'; export class SmartTestSelector { private failureRateAnalyzer: FailureRateAnalyzer; constructor() { this.failureRateAnalyzer = new FailureRateAnalyzer(); } /** * 优先选择高风险测试 */ prioritizeHighRiskTests(tests: string[]): string[] { const highRiskTests = this.failureRateAnalyzer.getHighRiskTests(); // 将高风险测试放在前面 return [ ...tests.filter(t => highRiskTests.includes(t)), ...tests.filter(t => !highRiskTests.includes(t)), ]; } } ``` **Step 3: 提交失败率分析器** ```bash git add scripts/failure-rate-analyzer.ts scripts/smart-test-selector.ts git commit -m "feat: add failure rate analyzer for test prioritization" ``` --- ## 阶段3完成检查点 完成以上所有任务后,您应该拥有: - ✅ 代码覆盖率分析器 - ✅ 自动生成测试映射功能 - ✅ 历史失败率分析 - ✅ 高风险测试优先执行 **验证步骤**: 1. 运行覆盖率分析:`npm run test:coverage` 2. 检查映射更新:`git diff config/test-mapping.config.ts` 3. 查看失败率分析:`ts-node -e "import { FailureRateAnalyzer } from './scripts/failure-rate-analyzer'; const analyzer = new FailureRateAnalyzer(); console.log(analyzer.analyzeFailureRate())"` --- ## 阶段4:持续优化(长期) ### 任务4.1:完善文档和培训材料 **目标**:创建完整的使用文档和培训材料 **Files:** - Create: `docs/automated-testing-framework-guide.md` - Create: `docs/training-materials.md` **Step 1: 创建使用指南** ```markdown # 自动化测试流程框架使用指南 ## 快速开始 ### 1. 启动测试环境 \`\`\`bash docker-compose -f docker-compose.test.yml up -d \`\`\` ### 2. 初始化测试数据库 \`\`\`bash ./scripts/init-test-database.sh \`\`\` ### 3. 运行智能测试 \`\`\`bash npm run test:smart \`\`\` ### 4. 查看测试报告 \`\`\`bash open test-results/reports/summary-report.html \`\`\` ## 常见问题 ### Q: 如何添加新的测试用例? A: 在对应的模块目录下创建新的.spec.ts文件,并添加相应的标签。 ### Q: 如何更新测试映射? A: 运行覆盖率分析:`npm run test:coverage` ### Q: 如何配置企业微信通知? A: 在.env.test文件中配置WECOM_WEBHOOK_URL和WECOM_TABLE_ID。 ``` **Step 2: 创建培训材料** ```markdown # 自动化测试流程框架培训材料 ## 培训目标 1. 理解自动化测试流程框架的设计理念 2. 掌握智能测试选择器的使用方法 3. 学会编写符合规范的测试用例 4. 了解测试报告和缺陷管理流程 ## 培训内容 ### 第一部分:框架概述(30分钟) - 设计目标和核心价值 - 架构设计和技术栈 - 工作流程介绍 ### 第二部分:实践操作(60分钟) - 启动测试环境 - 编写测试用例 - 运行智能测试 - 查看测试报告 ### 第三部分:最佳实践(30分钟) - 测试用例设计规范 - 标签使用指南 - 常见问题解决 ## 培训考核 - 完成一个完整的测试用例编写 - 成功运行智能测试 - 理解测试报告内容 ``` **Step 3: 提交文档** ```bash git add docs/automated-testing-framework-guide.md docs/training-materials.md git commit -m "docs: add comprehensive guide and training materials" ``` --- ## 总结 本实施计划详细描述了自动化测试流程框架的完整实施过程,包括: ### 实施内容 1. **阶段1**(第1-2周):基础框架搭建 - 容器化测试环境 - 测试数据库配置 - 智能测试选择器 - CI/CD集成 2. **阶段2**(第3-4周):报告体系与缺陷管理 - 多层次报告生成器 - 企业微信智能表格集成 - 缺陷自动同步 3. **阶段3**(第5-6周):优化与完善 - 代码覆盖率分析 - 历史失败率分析 - 测试优先级优化 4. **阶段4**(长期):持续优化 - 文档完善 - 培训材料 - 持续改进 ### 预期成果 完成本实施计划后,您将拥有: - ✅ 完整的容器化测试环境 - ✅ 智能测试选择和执行系统 - ✅ 多层次测试报告体系 - ✅ 企业微信缺陷管理集成 - ✅ 完善的文档和培训材料 ### 下一步行动 **立即开始实施**: 1. 确认实施环境和权限 2. 按照阶段1的任务清单逐步执行 3. 每完成一个任务,进行验证和提交 4. 遇到问题及时反馈和调整 **需要帮助?** 如果在实施过程中遇到任何问题,请参考: - 设计文档:`docs/plans/2026-03-28-automated-testing-framework-design.md` - 使用指南:`docs/automated-testing-framework-guide.md` - 培训材料:`docs/training-materials.md` --- **实施计划版本**: v1.0 **创建日期**: 2026-03-28 **创建者**: 张翔(全栈质量保障与效能工程师)