/** * 测试报告生成器 * 生成多种格式的测试报告:HTML、JSON、JUnit XML、Markdown */ import * as fs from 'fs'; import * as path from 'path'; import { TestResult, TestStep } from './test-logger'; export interface TestSuite { name: string; tests: TestResult[]; startTime?: string; endTime?: string; } export interface ReportSummary { totalTests: number; passed: number; failed: number; skipped: number; totalDuration: number; passRate: number; startTime: string; endTime: string; } class TestReporter { private suites: TestSuite[] = []; private startTime: string = ''; private endTime: string = ''; startReport(): void { this.startTime = new Date().toISOString(); this.suites = []; } endReport(): void { this.endTime = new Date().toISOString(); } addTestSuite(suite: TestSuite): void { this.suites.push(suite); } recordTestResult(test: TestResult): void { // 查找或创建测试套件 let suite = this.suites.find(s => s.name === 'Default Suite'); if (!suite) { suite = { name: 'Default Suite', tests: [] }; this.suites.push(suite); } suite.tests.push(test); } generateSummary(): ReportSummary { const allTests = this.suites.flatMap(s => s.tests); const passed = allTests.filter(t => t.status === 'passed').length; const failed = allTests.filter(t => t.status === 'failed').length; const skipped = allTests.filter(t => t.status === 'skipped').length; const totalDuration = allTests.reduce((sum, t) => sum + t.duration, 0); return { totalTests: allTests.length, passed, failed, skipped, totalDuration, passRate: allTests.length > 0 ? (passed / allTests.length) * 100 : 0, startTime: this.startTime, endTime: this.endTime, }; } /** * 生成JSON格式报告 */ generateJSONReport(outputPath: string): void { const report = { summary: this.generateSummary(), suites: this.suites, generatedAt: new Date().toISOString(), }; this.ensureDirectoryExists(path.dirname(outputPath)); fs.writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8'); console.log(`JSON报告已生成: ${outputPath}`); } /** * 生成HTML格式报告 */ generateHTMLReport(outputPath: string): void { const summary = this.generateSummary(); const html = this.buildHTMLReport(summary); this.ensureDirectoryExists(path.dirname(outputPath)); fs.writeFileSync(outputPath, html, 'utf-8'); console.log(`HTML报告已生成: ${outputPath}`); } /** * 生成JUnit XML格式报告 */ generateJUnitReport(outputPath: string): void { const summary = this.generateSummary(); const xml = this.buildJUnitReport(summary); this.ensureDirectoryExists(path.dirname(outputPath)); fs.writeFileSync(outputPath, xml, 'utf-8'); console.log(`JUnit报告已生成: ${outputPath}`); } /** * 生成Markdown格式报告 */ generateMarkdownReport(outputPath: string): void { const summary = this.generateSummary(); const markdown = this.buildMarkdownReport(summary); this.ensureDirectoryExists(path.dirname(outputPath)); fs.writeFileSync(outputPath, markdown, 'utf-8'); console.log(`Markdown报告已生成: ${outputPath}`); } /** * 生成所有报告 */ generateAllReports(outputDir: string): void { this.endReport(); this.generateJSONReport(path.join(outputDir, 'e2e-report.json')); this.generateHTMLReport(path.join(outputDir, 'e2e-report.html')); this.generateJUnitReport(path.join(outputDir, 'junit-report.xml')); this.generateMarkdownReport(path.join(outputDir, 'e2e-report.md')); // 打印摘要 this.printSummary(); } private printSummary(): void { const summary = this.generateSummary(); console.log('\n========== 测试执行摘要 =========='); console.log(`总测试数: ${summary.totalTests}`); console.log(`通过: ${summary.passed} ✅`); console.log(`失败: ${summary.failed} ❌`); console.log(`跳过: ${summary.skipped} ⏭️`); console.log(`通过率: ${summary.passRate.toFixed(2)}%`); console.log(`总耗时: ${(summary.totalDuration / 1000).toFixed(2)}s`); console.log('===================================\n'); } private ensureDirectoryExists(dirPath: string): void { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } private buildHTMLReport(summary: ReportSummary): string { const allTests = this.suites.flatMap(s => s.tests); const statusColor = { passed: '#28a745', failed: '#dc3545', skipped: '#ffc107', }; return `
生成时间: ${new Date().toLocaleString('zh-CN')}
总耗时: ${(summary.totalDuration / 1000).toFixed(2)}秒