import * as fs from 'fs'; import * as path from 'path'; export interface TestResult { testId: string; testName: string; status: 'passed' | 'failed' | 'skipped'; duration: number; error?: string; stackTrace?: string; retries: number; timestamp: string; } export interface TestSuite { suiteId: string; suiteName: string; tests: TestResult[]; duration: number; passed: number; failed: number; skipped: number; timestamp: string; } export interface TestReport { reportId: string; reportName: string; testSuites: TestSuite[]; totalTests: number; totalPassed: number; totalFailed: number; totalSkipped: number; totalDuration: number; passRate: number; timestamp: string; environment: { node: string; platform: string; ci: boolean; }; metadata?: Record; } export interface TrendData { date: string; totalTests: number; passed: number; failed: number; skipped: number; passRate: number; duration: number; } export interface ReportGeneratorOptions { outputDir: string; reportName: string; includeTrend?: boolean; trendDataDays?: number; } export abstract class BaseReportGenerator { protected outputDir: string; protected reportName: string; constructor(options: ReportGeneratorOptions) { this.outputDir = options.outputDir; this.reportName = options.reportName; this.ensureOutputDir(); } protected ensureOutputDir(): void { if (!fs.existsSync(this.outputDir)) { fs.mkdirSync(this.outputDir, { recursive: true }); } } abstract generate(report: TestReport): string; abstract getExtension(): string; protected getOutputPath(): string { return path.join(this.outputDir, `${this.reportName}.${this.getExtension()}`); } protected writeToFile(content: string): string { const outputPath = this.getOutputPath(); fs.writeFileSync(outputPath, content, 'utf-8'); return outputPath; } protected formatDuration(ms: number): string { if (ms < 1000) { return `${ms}ms`; } else if (ms < 60000) { return `${(ms / 1000).toFixed(2)}s`; } else { const minutes = Math.floor(ms / 60000); const seconds = ((ms % 60000) / 1000).toFixed(0); return `${minutes}m ${seconds}s`; } } protected formatTimestamp(timestamp: string): string { const date = new Date(timestamp); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', }); } protected calculatePassRate(passed: number, total: number): number { if (total === 0) return 0; return Math.round((passed / total) * 100 * 100) / 100; } protected getStatusIcon(status: string): string { switch (status) { case 'passed': return '✅'; case 'failed': return '❌'; case 'skipped': return '⏭️'; default: return '❓'; } } } export class ReportGeneratorFactory { static create( type: 'html' | 'json' | 'junit', options: ReportGeneratorOptions ): BaseReportGenerator { switch (type) { case 'html': return new HTMLReportGenerator(options); case 'json': return new JSONReportGenerator(options); case 'junit': return new JUnitReportGenerator(options); default: throw new Error(`Unsupported report type: ${type}`); } } } import { HTMLReportGenerator } from './html-report-generator'; import { JSONReportGenerator } from './json-report-generator'; import { JUnitReportGenerator } from './junit-report-generator';