import * as fs from 'fs'; import * as path from 'path'; interface PlaywrightResult { stats: { expected: number; unexpected: number; skipped: number; }; suites: any[]; } export class ReportGenerator { private jsonPath: string; private outputPath: string; constructor(jsonPath: string, outputPath: string) { this.jsonPath = jsonPath; this.outputPath = outputPath; } generate(): void { if (!fs.existsSync(this.jsonPath)) { console.error(`JSON报告文件不存在: ${this.jsonPath}`); return; } const jsonContent = fs.readFileSync(this.jsonPath, 'utf-8'); const result: PlaywrightResult = JSON.parse(jsonContent); const testResults = this.extractTestResults(result); const report = this.generateReport(result, testResults); this.writeReport(report); } private extractTestResults(result: PlaywrightResult): any[] { const results: any[] = []; const processSuite = (suite: any) => { if (suite.specs) { suite.specs.forEach((spec: any) => { if (spec.tests) { spec.tests.forEach((test: any) => { if (!test.title) return; const testResult = test.results && test.results[0]; results.push({ name: test.title, status: testResult?.status || 'skipped', duration: testResult?.duration || 0, type: this.getTestType(test.title) }); }); } }); } if (suite.suites) { suite.suites.forEach(processSuite); } }; result.suites.forEach(processSuite); return results; } private getTestType(title: string): 'performance' | 'seo' | 'accessibility' | 'form' { if (!title) return 'form'; if (title.includes('性能')) return 'performance'; if (title.includes('SEO')) return 'seo'; if (title.includes('可访问性')) return 'accessibility'; if (title.includes('表单')) return 'form'; return 'form'; } private generateReport(result: PlaywrightResult, testResults: any[]): string { const passed = result.stats.expected - result.stats.unexpected; const failed = result.stats.unexpected; const passRate = ((passed / result.stats.expected) * 100).toFixed(2); return ` # 测试执行报告 生成时间: ${new Date().toLocaleString('zh-CN')} ## 概览 - 总测试数: ${result.stats.expected} - 通过: ${passed} - 失败: ${failed} - 跳过: ${result.stats.skipped} - 通过率: ${passRate}% ## 性能测试结果 ${this.generatePerformanceSection(testResults)} ## 失败测试 ${this.generateFailuresSection(testResults)} ## 测试详情 ${this.generateDetailsSection(testResults)} `; } private generatePerformanceSection(testResults: any[]): string { const performanceTests = testResults.filter(r => r.type === 'performance'); if (performanceTests.length === 0) { return '无性能测试\n'; } return ` | 测试名称 | 状态 | 耗时(ms) | |---------|------|----------| ${performanceTests.map(t => `| ${t.name} | ${t.status} | ${t.duration.toFixed(0)} |`).join('\n')} `; } private generateFailuresSection(testResults: any[]): string { const failedTests = testResults.filter(r => r.status === 'failed'); if (failedTests.length === 0) { return '✅ 所有测试通过\n'; } return ` ### 失败测试列表 (${failedTests.length}) ${failedTests.map(t => `- ${t.name} (${t.duration.toFixed(0)}ms)`).join('\n')} `; } private generateDetailsSection(testResults: any[]): string { if (testResults.length === 0) { return '| 测试名称 | 类型 | 状态 | 耗时(ms) |\n|---------|------|------|----------|\n'; } return ` | 测试名称 | 类型 | 状态 | 耗时(ms) | |---------|------|------|----------| ${testResults.map(t => `| ${t.name} | ${t.type} | ${t.status} | ${t.duration.toFixed(0)} |`).join('\n')} `; } private writeReport(report: string): void { const reportDir = path.dirname(this.outputPath); if (!fs.existsSync(reportDir)) { fs.mkdirSync(reportDir, { recursive: true }); } fs.writeFileSync(this.outputPath, report); console.log(`报告已生成: ${this.outputPath}`); } } if (require.main === module) { const jsonPath = path.join(process.cwd(), 'test-framework', 'reports', 'results.json'); const outputPath = path.join(process.cwd(), 'test-framework', 'reports', 'custom-report.md'); const generator = new ReportGenerator(jsonPath, outputPath); generator.generate(); }