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

376 lines
10 KiB
TypeScript

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<ErrorType, RegExp[]>;
constructor() {
this.patterns = this.initializePatterns();
}
private initializePatterns(): Map<ErrorType, RegExp[]> {
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<string, ErrorAnalysis> {
const results = new Map<string, ErrorAnalysis>();
for (const testCase of testCases) {
if (testCase.status === 'failed' && testCase.error) {
results.set(testCase.id, this.analyze(testCase));
}
}
return results;
}
getErrorStatistics(analyses: Map<string, ErrorAnalysis>): {
byType: Record<ErrorType, number>;
byCategory: Record<ErrorCategory, number>;
bySeverity: Record<ErrorSeverity, number>;
autoFixableCount: number;
} {
const byType: Record<ErrorType, number> = {} as Record<ErrorType, number>;
const byCategory: Record<ErrorCategory, number> = {} as Record<ErrorCategory, number>;
const bySeverity: Record<ErrorSeverity, number> = {} as Record<ErrorSeverity, number>;
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 };
}
}