import { TestCase, TestError, ErrorType } from '../models/test-result'; import { logger } from '../utils/logger'; export interface ErrorAnalysis { type: ErrorType; category: ErrorCategory; severity: ErrorSeverity; rootCause: string; suggestedFix: string; autoFixable: boolean; fixStrategy?: FixStrategy; } export enum ErrorCategory { INFRASTRUCTURE = 'infrastructure', APPLICATION = 'application', TEST_CODE = 'test_code', DATA = 'data', CONFIGURATION = 'configuration', TIMING = 'timing' } export enum ErrorSeverity { CRITICAL = 'critical', HIGH = 'high', MEDIUM = 'medium', LOW = 'low' } export interface FixStrategy { type: 'code' | 'config' | 'data' | 'wait' | 'retry'; description: string; actions: FixAction[]; } export interface FixAction { type: string; target: string; value?: string; description: string; } export class ErrorAnalyzer { private patterns: Map; constructor() { this.patterns = this.initializePatterns(); } private initializePatterns(): Map { return new Map([ [ErrorType.TIMEOUT, [ /timeout/i, /timed out/i, /exceeded.*timeout/i, /waiting.*failed/i ]], [ErrorType.ELEMENT_NOT_FOUND, [ /element.*not found/i, /no element.*matching/i, /selector.*not found/i, /unable to find/i, /waiting for selector/i ]], [ErrorType.API_ERROR, [ /api.*error/i, /request failed/i, /status code.*\d{3}/i, /http.*error/i, /response.*error/i ]], [ErrorType.ASSERTION_ERROR, [ /assertion.*failed/i, /expect.*received/i, /expected.*but received/i, /assertionerror/i ]], [ErrorType.NETWORK_ERROR, [ /network error/i, /connection refused/i, /econnrefused/i, /enotfound/i, /socket hang up/i ]], [ErrorType.AUTH_ERROR, [ /unauthorized/i, /forbidden/i, /authentication failed/i, /invalid token/i, /session expired/i ]], [ErrorType.DATA_ERROR, [ /json parse error/i, /invalid json/i, /data.*invalid/i, /schema.*validation/i ]], [ErrorType.ENVIRONMENT_ERROR, [ /environment.*error/i, /config.*error/i, /setup.*failed/i, /initialization.*failed/i ]] ]); } analyze(testCase: TestCase): ErrorAnalysis { if (!testCase.error) { return this.createDefaultAnalysis(); } const error = testCase.error; const type = this.detectErrorType(error); const category = this.categorizeError(type, error); const severity = this.assessSeverity(type, testCase); const rootCause = this.identifyRootCause(error, type); const suggestedFix = this.suggestFix(type, error, testCase); const autoFixable = this.isAutoFixable(type, error); const fixStrategy = autoFixable ? this.createFixStrategy(type, error, testCase) : undefined; const analysis: ErrorAnalysis = { type, category, severity, rootCause, suggestedFix, autoFixable, fixStrategy }; logger.debug('错误分析完成', { testId: testCase.id, type, category, severity, autoFixable }); return analysis; } private detectErrorType(error: TestError): ErrorType { const message = error.message.toLowerCase(); const patternEntries = Array.from(this.patterns.entries()); for (const [type, regexps] of patternEntries) { for (const regex of regexps) { if (regex.test(message)) { return type; } } } return ErrorType.UNKNOWN; } private categorizeError(type: ErrorType, error: TestError): ErrorCategory { switch (type) { case ErrorType.TIMEOUT: case ErrorType.NETWORK_ERROR: return ErrorCategory.INFRASTRUCTURE; case ErrorType.API_ERROR: case ErrorType.AUTH_ERROR: return ErrorCategory.APPLICATION; case ErrorType.ASSERTION_ERROR: return ErrorCategory.TEST_CODE; case ErrorType.DATA_ERROR: return ErrorCategory.DATA; case ErrorType.ENVIRONMENT_ERROR: return ErrorCategory.CONFIGURATION; case ErrorType.ELEMENT_NOT_FOUND: const message = error.message.toLowerCase(); if (message.includes('loading') || message.includes('spinner')) { return ErrorCategory.TIMING; } return ErrorCategory.TEST_CODE; default: return ErrorCategory.TEST_CODE; } } private assessSeverity(type: ErrorType, testCase: TestCase): ErrorSeverity { if (testCase.priority === 'high') { if (type === ErrorType.API_ERROR || type === ErrorType.AUTH_ERROR) { return ErrorSeverity.CRITICAL; } return ErrorSeverity.HIGH; } if (testCase.priority === 'medium') { if (type === ErrorType.TIMEOUT || type === ErrorType.NETWORK_ERROR) { return ErrorSeverity.HIGH; } return ErrorSeverity.MEDIUM; } return ErrorSeverity.LOW; } private identifyRootCause(error: TestError, type: ErrorType): string { const message = error.message; switch (type) { case ErrorType.TIMEOUT: if (message.includes('navigation')) { return '页面导航超时,可能是网络延迟或页面加载过慢'; } if (message.includes('element')) { return '元素等待超时,元素可能未及时渲染'; } return '操作超时,系统响应过慢'; case ErrorType.ELEMENT_NOT_FOUND: return '目标元素不存在,可能是选择器错误或页面结构变化'; case ErrorType.API_ERROR: const statusMatch = message.match(/status code[:\s]*(\d+)/i); if (statusMatch) { return `API 返回错误状态码: ${statusMatch[1]}`; } return 'API 请求失败'; case ErrorType.ASSERTION_ERROR: return '断言失败,实际结果与预期不符'; case ErrorType.NETWORK_ERROR: return '网络连接失败,服务可能未启动或不可达'; case ErrorType.AUTH_ERROR: return '认证失败,令牌可能已过期或无效'; case ErrorType.DATA_ERROR: return '数据格式错误,JSON 解析失败'; case ErrorType.ENVIRONMENT_ERROR: return '环境配置错误'; default: return '未知错误'; } } private suggestFix(type: ErrorType, error: TestError, testCase: TestCase): string { switch (type) { case ErrorType.TIMEOUT: return '增加超时时间或优化等待策略,确保元素加载完成后再操作'; case ErrorType.ELEMENT_NOT_FOUND: return '检查选择器是否正确,确认页面结构是否变化,添加更健壮的等待逻辑'; case ErrorType.API_ERROR: return '检查 API 服务状态,验证请求参数和认证信息'; case ErrorType.ASSERTION_ERROR: return '检查断言条件,确认预期值是否正确,可能需要更新测试预期'; case ErrorType.NETWORK_ERROR: return '确认服务已启动,检查网络连接和防火墙设置'; case ErrorType.AUTH_ERROR: return '刷新认证令牌,检查用户凭证和权限配置'; case ErrorType.DATA_ERROR: return '验证数据格式,检查 JSON 结构是否符合预期'; case ErrorType.ENVIRONMENT_ERROR: return '检查环境变量和配置文件,确保所有依赖已正确安装'; default: return '需要人工分析错误日志,确定具体问题'; } } private isAutoFixable(type: ErrorType, error: TestError): boolean { const autoFixableTypes = [ ErrorType.TIMEOUT, ErrorType.AUTH_ERROR ]; return autoFixableTypes.includes(type); } private createFixStrategy(type: ErrorType, error: TestError, testCase: TestCase): FixStrategy | undefined { switch (type) { case ErrorType.TIMEOUT: return { type: 'wait', description: '增加等待时间和重试策略', actions: [ { type: 'increase_timeout', target: testCase.id, value: '60000', description: '将超时时间增加到 60 秒' }, { type: 'add_retry', target: testCase.id, value: '3', description: '添加 3 次重试' } ] }; case ErrorType.AUTH_ERROR: return { type: 'config', description: '刷新认证令牌', actions: [ { type: 'refresh_token', target: 'auth', description: '重新获取认证令牌' } ] }; default: return undefined; } } private createDefaultAnalysis(): ErrorAnalysis { return { type: ErrorType.UNKNOWN, category: ErrorCategory.TEST_CODE, severity: ErrorSeverity.LOW, rootCause: '未知错误', suggestedFix: '需要人工分析', autoFixable: false }; } analyzeBatch(testCases: TestCase[]): Map { const results = new Map(); for (const testCase of testCases) { if (testCase.status === 'failed' && testCase.error) { results.set(testCase.id, this.analyze(testCase)); } } return results; } getErrorStatistics(analyses: Map): { byType: Record; byCategory: Record; bySeverity: Record; autoFixableCount: number; } { const byType: Record = {} as Record; const byCategory: Record = {} as Record; const bySeverity: Record = {} as Record; let autoFixableCount = 0; const analysisValues = Array.from(analyses.values()); for (const analysis of analysisValues) { byType[analysis.type] = (byType[analysis.type] || 0) + 1; byCategory[analysis.category] = (byCategory[analysis.category] || 0) + 1; bySeverity[analysis.severity] = (bySeverity[analysis.severity] || 0) + 1; if (analysis.autoFixable) { autoFixableCount++; } } return { byType, byCategory, bySeverity, autoFixableCount }; } }