08ea5fbe98
添加用户管理视图、API和状态管理文件
376 lines
10 KiB
TypeScript
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 };
|
|
}
|
|
}
|