import { FullResult } from '@playwright/test'; import { testLogger, TestLog, LogLevel } from './test-logger'; import { testConfig } from './test-config'; import * as fs from 'fs/promises'; import * as path from 'path'; export interface TestSummary { total: number; passed: number; failed: number; skipped: number; duration: number; startTime: string; endTime: string; } export interface TestReport { summary: TestSummary; testLogs: TestLog[]; environment: { name: string; baseURL: string; mockEnabled: boolean; mockMode: string; }; errors: Array<{ testName: string; error: Error; timestamp: string; }>; screenshots: string[]; } class TestReporter { private static instance: TestReporter; private report: TestReport; private startTime: string = ''; private constructor() { this.report = this.initializeReport(); } static getInstance(): TestReporter { if (!TestReporter.instance) { TestReporter.instance = new TestReporter(); } return TestReporter.instance; } private initializeReport(): TestReport { return { summary: { total: 0, passed: 0, failed: 0, skipped: 0, duration: 0, startTime: new Date().toISOString(), endTime: '' }, testLogs: [], environment: { name: testConfig.getEnvironment().name, baseURL: testConfig.getBaseURL(), mockEnabled: testConfig.isMockEnabled(), mockMode: testConfig.getMockMode() }, errors: [], screenshots: [] }; } startReport(): void { this.startTime = new Date().toISOString(); this.report.summary.startTime = this.startTime; testLogger.info('开始生成测试报告'); } endReport(): void { const endTime = new Date().toISOString(); this.report.summary.endTime = endTime; this.report.summary.duration = new Date(endTime).getTime() - new Date(this.startTime).getTime(); this.report.testLogs = testLogger.getAllTestLogs(); const errorLogs = testLogger.getLogsByLevel(LogLevel.ERROR); this.report.errors = errorLogs.map(log => ({ testName: log.test || 'unknown', error: new Error(log.message), timestamp: log.timestamp })); testLogger.info('测试报告生成完成', { total: this.report.summary.total, passed: this.report.summary.passed, failed: this.report.summary.failed, skipped: this.report.summary.skipped, duration: this.report.summary.duration }); } updateSummary(results: FullResult): void { this.report.summary.total = results.expected; this.report.summary.passed = results.expected - results.failed - results.skipped; this.report.summary.failed = results.failed; this.report.summary.skipped = results.skipped; } addScreenshot(screenshotPath: string): void { this.report.screenshots.push(screenshotPath); } getReport(): TestReport { return this.report; } getSummary(): TestSummary { return this.report.summary; } async generateJSONReport(outputPath: string): Promise { const dir = path.dirname(outputPath); await fs.mkdir(dir, { recursive: true }); const jsonContent = JSON.stringify(this.report, null, 2); await fs.writeFile(outputPath, jsonContent, 'utf-8'); testLogger.info(`JSON报告已生成: ${outputPath}`); } async generateHTMLReport(outputPath: string): Promise { const dir = path.dirname(outputPath); await fs.mkdir(dir, { recursive: true }); const htmlContent = this.generateHTMLContent(); await fs.writeFile(outputPath, htmlContent, 'utf-8'); testLogger.info(`HTML报告已生成: ${outputPath}`); } private generateHTMLContent(): string { const { summary, testLogs, environment, errors, screenshots } = this.report; return ` E2E测试报告

E2E测试报告

生成时间: ${new Date().toLocaleString('zh-CN')} | 测试环境: ${environment.name} | Mock模式: ${environment.mockMode}

总测试数

${summary.total}

通过

${summary.passed}

失败

${summary.failed}

跳过

${summary.skipped}

总耗时

${(summary.duration / 1000).toFixed(2)}s

测试环境

${environment.name}
${environment.baseURL}
${environment.mockEnabled ? '是' : '否'}
${environment.mockMode}

测试结果

${testLogs.map(log => `
${log.testName} ${log.status}
开始时间: ${new Date(log.startTime).toLocaleString('zh-CN')}
结束时间: ${new Date(log.endTime).toLocaleString('zh-CN')}
耗时: ${(log.duration / 1000).toFixed(2)}s
${log.steps.length > 0 ? `

测试步骤

${log.steps.map(step => `
${step.name}
耗时: ${(step.duration / 1000).toFixed(2)}s
`).join('')}
` : ''}
`).join('')}
${errors.length > 0 ? `

错误详情 (${errors.length})

${errors.map(error => `
${error.testName}
${error.error.message}
${new Date(error.timestamp).toLocaleString('zh-CN')}
`).join('')}
` : ''} ${screenshots.length > 0 ? `

截图 (${screenshots.length})

${screenshots.map(screenshot => `
Screenshot
${screenshot}
`).join('')}
` : ''}
`; } async generateAllReports(outputDir: string): Promise { await fs.mkdir(outputDir, { recursive: true }); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); await this.generateJSONReport(path.join(outputDir, `e2e-report-${timestamp}.json`)); await this.generateHTMLReport(path.join(outputDir, `e2e-report-${timestamp}.html`)); testLogger.info(`所有报告已生成到目录: ${outputDir}`); } } export const testReporter = TestReporter.getInstance();