/** * 测试日志记录器 * 提供结构化的测试日志记录 */ export interface LogEntry { timestamp: string; level: 'debug' | 'info' | 'warn' | 'error'; message: string; context?: Record; } export interface TestStep { name: string; status: 'pending' | 'running' | 'passed' | 'failed' | 'skipped'; startTime?: string; endTime?: string; duration?: number; logs: LogEntry[]; error?: Error; } export interface TestResult { testName: string; status: 'passed' | 'failed' | 'skipped'; startTime: string; endTime: string; duration: number; steps: TestStep[]; logs: LogEntry[]; screenshots: string[]; error?: Error; retryCount?: number; } class TestLogger { private logs: LogEntry[] = []; private steps: TestStep[] = []; private currentStep: TestStep | null = null; private currentTest: TestResult | null = null; private getTimestamp(): string { return new Date().toISOString(); } private addLog(level: LogEntry['level'], message: string, context?: Record): void { const entry: LogEntry = { timestamp: this.getTimestamp(), level, message, context, }; this.logs.push(entry); if (this.currentStep) { this.currentStep.logs.push(entry); } // 控制台输出 const consoleMessage = `[${entry.timestamp}] [${level.toUpperCase()}] ${message}`; switch (level) { case 'debug': console.debug(consoleMessage); break; case 'info': console.info(consoleMessage); break; case 'warn': console.warn(consoleMessage); break; case 'error': console.error(consoleMessage); break; } } debug(message: string, context?: Record): void { this.addLog('debug', message, context); } info(message: string, context?: Record): void { this.addLog('info', message, context); } warn(message: string, context?: Record): void { this.addLog('warn', message, context); } error(message: string, error?: Error, context?: Record): void { this.addLog('error', message, { ...context, error: error?.message, stack: error?.stack, }); } startTest(testName: string): void { this.currentTest = { testName, status: 'passed', startTime: this.getTimestamp(), endTime: '', duration: 0, steps: [], logs: [], screenshots: [], }; this.logs = []; this.steps = []; this.info(`开始测试: ${testName}`); } endTest(testName: string, status: 'passed' | 'failed' | 'skipped', error?: Error): void { if (this.currentTest) { this.currentTest.status = status; this.currentTest.endTime = this.getTimestamp(); this.currentTest.duration = new Date(this.currentTest.endTime).getTime() - new Date(this.currentTest.startTime).getTime(); this.currentTest.steps = this.steps; this.currentTest.logs = this.logs; if (error) { this.currentTest.error = error; } this.info(`测试结束: ${testName} - ${status}`, { duration: this.currentTest.duration, stepsCount: this.steps.length, }); } } startStep(stepName: string): void { if (this.currentStep) { this.endStep(this.currentStep.name, 'failed'); } this.currentStep = { name: stepName, status: 'running', startTime: this.getTimestamp(), logs: [], }; this.info(`开始步骤: ${stepName}`); } endStep(stepName: string, status: TestStep['status'], error?: Error): void { if (this.currentStep && this.currentStep.name === stepName) { this.currentStep.status = status; this.currentStep.endTime = this.getTimestamp(); if (this.currentStep.startTime) { this.currentStep.duration = new Date(this.currentStep.endTime).getTime() - new Date(this.currentStep.startTime).getTime(); } if (error) { this.currentStep.error = error; } this.steps.push(this.currentStep); this.info(`步骤结束: ${stepName} - ${status}`, { duration: this.currentStep.duration, }); this.currentStep = null; } } addScreenshot(path: string): void { if (this.currentTest) { this.currentTest.screenshots.push(path); } this.info(`截图已保存: ${path}`); } getCurrentTest(): TestResult | null { return this.currentTest; } getLogs(): LogEntry[] { return this.logs; } getSteps(): TestStep[] { return this.steps; } clear(): void { this.logs = []; this.steps = []; this.currentStep = null; this.currentTest = null; } /** * 生成测试执行摘要 */ generateSummary(): Record { const passed = this.steps.filter(s => s.status === 'passed').length; const failed = this.steps.filter(s => s.status === 'failed').length; const skipped = this.steps.filter(s => s.status === 'skipped').length; return { totalSteps: this.steps.length, passed, failed, skipped, totalLogs: this.logs.length, errors: this.logs.filter(l => l.level === 'error').length, warnings: this.logs.filter(l => l.level === 'warn').length, }; } } export const testLogger = new TestLogger();