08ea5fbe98
添加用户管理视图、API和状态管理文件
219 lines
5.3 KiB
TypeScript
219 lines
5.3 KiB
TypeScript
/**
|
|
* 测试日志记录器
|
|
* 提供结构化的测试日志记录
|
|
*/
|
|
|
|
export interface LogEntry {
|
|
timestamp: string;
|
|
level: 'debug' | 'info' | 'warn' | 'error';
|
|
message: string;
|
|
context?: Record<string, unknown>;
|
|
}
|
|
|
|
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<string, unknown>): 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<string, unknown>): void {
|
|
this.addLog('debug', message, context);
|
|
}
|
|
|
|
info(message: string, context?: Record<string, unknown>): void {
|
|
this.addLog('info', message, context);
|
|
}
|
|
|
|
warn(message: string, context?: Record<string, unknown>): void {
|
|
this.addLog('warn', message, context);
|
|
}
|
|
|
|
error(message: string, error?: Error, context?: Record<string, unknown>): 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<string, unknown> {
|
|
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();
|