08ea5fbe98
添加用户管理视图、API和状态管理文件
156 lines
3.6 KiB
TypeScript
156 lines
3.6 KiB
TypeScript
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<string, any>;
|
|
}
|
|
|
|
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';
|