Files
everything-is-suitable/everything-is-suitable-test/e2e/core/test-reporter.ts
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

209 lines
6.5 KiB
TypeScript

import { promises as fs } from 'fs';
import { join } from 'path';
export class TestReporter {
private results: TestResult[] = [];
private startTime: number = 0;
startReport(): void {
this.startTime = Date.now();
console.log('📊 开始生成测试报告');
}
recordResult(result: TestResult): void {
this.results.push(result);
}
async generateAllReports(outputDir: string): Promise<void> {
await this.generateJSONReport(join(outputDir, 'test-results.json'));
await this.generateHTMLReport(join(outputDir, 'test-results.html'));
await this.generateJUnitReport(join(outputDir, 'junit-report.xml'));
await this.generateSummaryReport(outputDir);
}
async generateJSONReport(outputPath: string): Promise<void> {
const report = {
summary: this.getSummary(),
results: this.results,
metadata: {
generatedAt: new Date().toISOString(),
duration: Date.now() - this.startTime
}
};
await fs.writeFile(outputPath, JSON.stringify(report, null, 2));
console.log(`✅ JSON报告已生成: ${outputPath}`);
}
async generateHTMLReport(outputPath: string): Promise<void> {
const summary = this.getSummary();
const html = this.generateHTML(summary, this.results);
await fs.writeFile(outputPath, html);
console.log(`✅ HTML报告已生成: ${outputPath}`);
}
async generateJUnitReport(outputPath: string): Promise<void> {
const summary = this.getSummary();
const xml = this.generateJUnitXML(summary, this.results);
await fs.writeFile(outputPath, xml);
console.log(`✅ JUnit报告已生成: ${outputPath}`);
}
async generateSummaryReport(outputDir: string): Promise<void> {
const summary = this.getSummary();
const summaryPath = join(outputDir, 'summary.txt');
const summaryText = this.generateSummaryText(summary);
await fs.writeFile(summaryPath, summaryText);
console.log(`✅ 摘要报告已生成: ${summaryPath}`);
}
private getSummary(): TestSummary {
const passed = this.results.filter(r => r.status === 'passed').length;
const failed = this.results.filter(r => r.status === 'failed').length;
const skipped = this.results.filter(r => r.status === 'skipped').length;
const total = this.results.length;
const duration = this.results.reduce((sum, r) => sum + r.duration, 0);
return {
total,
passed,
failed,
skipped,
passRate: total > 0 ? (passed / total * 100).toFixed(2) : '0',
duration,
startTime: new Date(this.startTime).toISOString(),
endTime: new Date().toISOString()
};
}
private generateHTML(summary: TestSummary, results: TestResult[]): string {
return `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>E2E测试报告</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.summary { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
.passed { color: green; }
.failed { color: red; }
.skipped { color: orange; }
.test-result { border: 1px solid #ddd; padding: 10px; margin: 10px 0; border-radius: 5px; }
.test-result.failed { background: #ffe6e6; }
.test-result.passed { background: #e6ffe6; }
.test-result.skipped { background: #fff3cd; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f2f2f2; }
</style>
</head>
<body>
<h1>E2E测试报告</h1>
<div class="summary">
<h2>测试摘要</h2>
<p>总测试数: ${summary.total}</p>
<p>通过: <span class="passed">${summary.passed}</span></p>
<p>失败: <span class="failed">${summary.failed}</span></p>
<p>跳过: <span class="skipped">${summary.skipped}</span></p>
<p>通过率: ${summary.passRate}%</p>
<p>总耗时: ${summary.duration}ms</p>
</div>
<h2>测试结果</h2>
${results.map(result => `
<div class="test-result ${result.status}">
<h3>${result.testName}</h3>
<p>状态: <span class="${result.status}">${result.status}</span></p>
<p>耗时: ${result.duration}ms</p>
${result.error ? `<p>错误: ${result.error.message}</p>` : ''}
${result.steps.length > 0 ? `
<h4>测试步骤</h4>
<ul>
${result.steps.map(step => `
<li>${step.name} - <span class="${step.status}">${step.status}</span></li>
`).join('')}
</ul>
` : ''}
</div>
`).join('')}
</body>
</html>
`;
}
private generateJUnitXML(summary: TestSummary, results: TestResult[]): string {
const testCases = results.map(result => {
const testCase = `
<testcase name="${result.testName}" time="${result.duration / 1000}">
${result.status === 'failed' ? `
<failure message="${result.error?.message || 'Test failed'}">
${result.error?.stack || ''}
</failure>
` : ''}
${result.status === 'skipped' ? '<skipped/>' : ''}
</testcase>`;
return testCase;
}).join('\n');
return `<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="E2E Tests" tests="${summary.total}" failures="${summary.failed}" skipped="${summary.skipped}" time="${summary.duration / 1000}">
${testCases}
</testsuite>
</testsuites>`;
}
private generateSummaryText(summary: TestSummary): string {
return `
========================================
E2E测试摘要报告
========================================
测试时间: ${summary.startTime} - ${summary.endTime}
总耗时: ${summary.duration}ms
测试统计:
----------------------------------------
总测试数: ${summary.total}
通过: ${summary.passed}
失败: ${summary.failed}
跳过: ${summary.skipped}
通过率: ${summary.passRate}%
========================================
`;
}
}
export interface TestResult {
testName: string;
status: 'passed' | 'failed' | 'skipped';
duration: number;
steps: TestStep[];
logs: string[];
screenshots: string[];
error?: Error;
}
export interface TestStep {
name: string;
status: 'passed' | 'failed';
duration: number;
}
export interface TestSummary {
total: number;
passed: number;
failed: number;
skipped: number;
passRate: string;
duration: number;
startTime: string;
endTime: string;
}
export const testReporter = new TestReporter();