dasdasd
This commit is contained in:
parent
4309e8aadf
commit
b6b3751ee0
146
src/automation-framework/core/config-validator.js
Normal file
146
src/automation-framework/core/config-validator.js
Normal file
@ -0,0 +1,146 @@
|
||||
/**
|
||||
* YAML 配置验证器
|
||||
*/
|
||||
class ConfigValidator {
|
||||
constructor() {
|
||||
// 定义每个 action 的必需字段
|
||||
this.actionSchemas = {
|
||||
navigate: {
|
||||
required: ['url'],
|
||||
optional: ['name', 'options', 'verifyUrl', 'verifyElements', 'waitAfter']
|
||||
},
|
||||
fillForm: {
|
||||
required: ['fields'],
|
||||
optional: ['name', 'humanLike']
|
||||
},
|
||||
click: {
|
||||
required: ['selector'],
|
||||
optional: ['name', 'verifyAfter', 'waitForPageChange', 'waitAfter', 'optional']
|
||||
},
|
||||
wait: {
|
||||
required: ['type'],
|
||||
optional: ['name', 'duration', 'find', 'timeout']
|
||||
},
|
||||
extract: {
|
||||
required: ['selector', 'contextKey'],
|
||||
optional: ['name', 'extractType', 'regex', 'saveTo', 'filter', 'multiple', 'required', 'attribute']
|
||||
},
|
||||
verify: {
|
||||
required: ['conditions'],
|
||||
optional: ['name', 'timeout', 'pollInterval', 'onSuccess', 'onFailure', 'onTimeout']
|
||||
},
|
||||
retryBlock: {
|
||||
required: ['steps'],
|
||||
optional: ['name', 'maxRetries', 'retryDelay', 'onRetryBefore', 'onRetryAfter']
|
||||
},
|
||||
custom: {
|
||||
required: ['handler'],
|
||||
optional: ['name', 'params', 'optional']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证完整配置
|
||||
*/
|
||||
validate(config) {
|
||||
const errors = [];
|
||||
|
||||
// 验证基本结构
|
||||
if (!config.site) {
|
||||
errors.push('缺少 site 配置');
|
||||
}
|
||||
|
||||
if (!config.workflow || !Array.isArray(config.workflow)) {
|
||||
errors.push('缺少 workflow 或 workflow 不是数组');
|
||||
return { valid: false, errors };
|
||||
}
|
||||
|
||||
// 验证每个步骤
|
||||
config.workflow.forEach((step, index) => {
|
||||
const stepErrors = this.validateStep(step, index);
|
||||
errors.push(...stepErrors);
|
||||
});
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证单个步骤
|
||||
*/
|
||||
validateStep(step, index) {
|
||||
const errors = [];
|
||||
const stepLabel = `步骤 ${index + 1} (${step.name || step.action})`;
|
||||
|
||||
// 检查是否有 action
|
||||
if (!step.action) {
|
||||
errors.push(`${stepLabel}: 缺少 action 字段`);
|
||||
return errors;
|
||||
}
|
||||
|
||||
// 检查 action 是否支持
|
||||
const schema = this.actionSchemas[step.action];
|
||||
if (!schema) {
|
||||
errors.push(`${stepLabel}: 未知的 action 类型 "${step.action}"`);
|
||||
return errors;
|
||||
}
|
||||
|
||||
// 检查必需字段
|
||||
schema.required.forEach(field => {
|
||||
if (step[field] === undefined) {
|
||||
errors.push(`${stepLabel}: 缺少必需字段 "${field}"`);
|
||||
}
|
||||
});
|
||||
|
||||
// 检查未知字段(警告)
|
||||
const allFields = [...schema.required, ...schema.optional, 'action'];
|
||||
Object.keys(step).forEach(field => {
|
||||
if (!allFields.includes(field)) {
|
||||
errors.push(`${stepLabel}: 警告 - 未知字段 "${field}"`);
|
||||
}
|
||||
});
|
||||
|
||||
// 特定 action 的额外验证
|
||||
if (step.action === 'verify') {
|
||||
if (step.conditions) {
|
||||
if (!step.conditions.success && !step.conditions.failure) {
|
||||
errors.push(`${stepLabel}: conditions 必须包含 success 或 failure`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (step.action === 'retryBlock') {
|
||||
if (step.steps && Array.isArray(step.steps)) {
|
||||
step.steps.forEach((subStep, subIndex) => {
|
||||
const subErrors = this.validateStep(subStep, subIndex);
|
||||
errors.push(...subErrors.map(e => `${stepLabel} -> ${e}`));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并打印结果
|
||||
*/
|
||||
validateAndReport(config, logger = console) {
|
||||
const result = this.validate(config);
|
||||
|
||||
if (result.valid) {
|
||||
logger.log('✓ 配置验证通过');
|
||||
return true;
|
||||
} else {
|
||||
logger.error('✗ 配置验证失败:');
|
||||
result.errors.forEach(error => {
|
||||
logger.error(` - ${error}`);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConfigValidator;
|
||||
74
src/automation-framework/core/errors.js
Normal file
74
src/automation-framework/core/errors.js
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 框架自定义错误类
|
||||
*/
|
||||
|
||||
class AutomationError extends Error {
|
||||
constructor(message, context = {}) {
|
||||
super(message);
|
||||
this.name = 'AutomationError';
|
||||
this.context = context;
|
||||
this.timestamp = new Date();
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
message: this.message,
|
||||
context: this.context,
|
||||
timestamp: this.timestamp,
|
||||
stack: this.stack
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ElementNotFoundError extends AutomationError {
|
||||
constructor(selector, context = {}) {
|
||||
super(`元素未找到: ${JSON.stringify(selector)}`, context);
|
||||
this.name = 'ElementNotFoundError';
|
||||
this.selector = selector;
|
||||
}
|
||||
}
|
||||
|
||||
class TimeoutError extends AutomationError {
|
||||
constructor(operation, timeout, context = {}) {
|
||||
super(`操作超时: ${operation} (${timeout}ms)`, context);
|
||||
this.name = 'TimeoutError';
|
||||
this.operation = operation;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
class ValidationError extends AutomationError {
|
||||
constructor(message, expected, actual, context = {}) {
|
||||
super(message, context);
|
||||
this.name = 'ValidationError';
|
||||
this.expected = expected;
|
||||
this.actual = actual;
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigurationError extends AutomationError {
|
||||
constructor(message, configPath, context = {}) {
|
||||
super(message, context);
|
||||
this.name = 'ConfigurationError';
|
||||
this.configPath = configPath;
|
||||
}
|
||||
}
|
||||
|
||||
class RetryExhaustedError extends AutomationError {
|
||||
constructor(operation, attempts, context = {}) {
|
||||
super(`重试次数用尽: ${operation} (${attempts} 次尝试)`, context);
|
||||
this.name = 'RetryExhaustedError';
|
||||
this.operation = operation;
|
||||
this.attempts = attempts;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AutomationError,
|
||||
ElementNotFoundError,
|
||||
TimeoutError,
|
||||
ValidationError,
|
||||
ConfigurationError,
|
||||
RetryExhaustedError
|
||||
};
|
||||
116
src/automation-framework/core/performance-tracker.js
Normal file
116
src/automation-framework/core/performance-tracker.js
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 性能追踪器
|
||||
*/
|
||||
class PerformanceTracker {
|
||||
constructor() {
|
||||
this.metrics = [];
|
||||
this.currentStep = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始追踪步骤
|
||||
*/
|
||||
startStep(stepName, stepIndex) {
|
||||
this.currentStep = {
|
||||
name: stepName,
|
||||
index: stepIndex,
|
||||
startTime: Date.now(),
|
||||
startMemory: process.memoryUsage().heapUsed
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束追踪步骤
|
||||
*/
|
||||
endStep(success = true, error = null) {
|
||||
if (!this.currentStep) return;
|
||||
|
||||
const endTime = Date.now();
|
||||
const endMemory = process.memoryUsage().heapUsed;
|
||||
|
||||
const metric = {
|
||||
...this.currentStep,
|
||||
endTime,
|
||||
duration: endTime - this.currentStep.startTime,
|
||||
memoryUsed: endMemory - this.currentStep.startMemory,
|
||||
success,
|
||||
error: error ? error.message : null
|
||||
};
|
||||
|
||||
this.metrics.push(metric);
|
||||
this.currentStep = null;
|
||||
|
||||
return metric;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats() {
|
||||
if (this.metrics.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalDuration = this.metrics.reduce((sum, m) => sum + m.duration, 0);
|
||||
const avgDuration = totalDuration / this.metrics.length;
|
||||
const successCount = this.metrics.filter(m => m.success).length;
|
||||
const failCount = this.metrics.filter(m => !m.success).length;
|
||||
|
||||
// 找出最慢的步骤
|
||||
const slowestStep = this.metrics.reduce((slowest, current) =>
|
||||
current.duration > slowest.duration ? current : slowest
|
||||
);
|
||||
|
||||
return {
|
||||
totalSteps: this.metrics.length,
|
||||
totalDuration,
|
||||
avgDuration,
|
||||
successCount,
|
||||
failCount,
|
||||
successRate: (successCount / this.metrics.length * 100).toFixed(2) + '%',
|
||||
slowestStep: {
|
||||
name: slowestStep.name,
|
||||
duration: slowestStep.duration
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成报告
|
||||
*/
|
||||
generateReport() {
|
||||
const stats = this.getStats();
|
||||
if (!stats) return '无性能数据';
|
||||
|
||||
let report = '\n========== 性能报告 ==========\n';
|
||||
report += `总步骤数: ${stats.totalSteps}\n`;
|
||||
report += `总耗时: ${(stats.totalDuration / 1000).toFixed(2)}秒\n`;
|
||||
report += `平均耗时: ${stats.avgDuration.toFixed(0)}ms/步骤\n`;
|
||||
report += `成功率: ${stats.successRate}\n`;
|
||||
report += `最慢步骤: ${stats.slowestStep.name} (${stats.slowestStep.duration}ms)\n`;
|
||||
report += '\n步骤明细:\n';
|
||||
|
||||
this.metrics.forEach((metric, index) => {
|
||||
const status = metric.success ? '✓' : '✗';
|
||||
report += ` ${index + 1}. ${status} ${metric.name} - ${metric.duration}ms\n`;
|
||||
if (metric.error) {
|
||||
report += ` 错误: ${metric.error}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
report += '===============================\n';
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为 JSON
|
||||
*/
|
||||
exportJSON() {
|
||||
return {
|
||||
stats: this.getStats(),
|
||||
metrics: this.metrics
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PerformanceTracker;
|
||||
@ -1,4 +1,6 @@
|
||||
const ActionRegistry = require('./action-registry');
|
||||
const ConfigValidator = require('./config-validator');
|
||||
const PerformanceTracker = require('./performance-tracker');
|
||||
|
||||
// 导入内置动作
|
||||
const NavigateAction = require('../actions/navigate-action');
|
||||
@ -20,6 +22,19 @@ class WorkflowEngine {
|
||||
this.actionRegistry = new ActionRegistry();
|
||||
this.currentStep = 0;
|
||||
|
||||
// 性能追踪
|
||||
this.performanceTracker = new PerformanceTracker();
|
||||
|
||||
// 验证配置
|
||||
if (context.validateConfig !== false) {
|
||||
const validator = new ConfigValidator();
|
||||
const result = validator.validate(config);
|
||||
if (!result.valid) {
|
||||
this.log('warn', '配置验证警告:');
|
||||
result.errors.forEach(err => this.log('warn', ` - ${err}`));
|
||||
}
|
||||
}
|
||||
|
||||
// 注册内置动作
|
||||
this.registerBuiltinActions();
|
||||
}
|
||||
@ -77,11 +92,21 @@ class WorkflowEngine {
|
||||
const stepName = actionConfig.name || `步骤 ${this.currentStep}`;
|
||||
this.log('info', `[${this.currentStep}/${actions.length}] ${stepName}`);
|
||||
|
||||
// 开始性能追踪
|
||||
this.performanceTracker.startStep(stepName, i);
|
||||
|
||||
try {
|
||||
const result = await this.executeAction(actionConfig);
|
||||
|
||||
// 记录成功
|
||||
const metric = this.performanceTracker.endStep(true);
|
||||
this.log('debug', `✓ 完成 (${metric.duration}ms)`);
|
||||
|
||||
results.push({ step: i, success: true, result });
|
||||
|
||||
} catch (error) {
|
||||
// 记录失败
|
||||
this.performanceTracker.endStep(false, error);
|
||||
this.log('error', `步骤失败: ${error.message}`);
|
||||
|
||||
// 检查是否是可选步骤
|
||||
@ -106,7 +131,17 @@ class WorkflowEngine {
|
||||
}
|
||||
|
||||
this.log('info', '工作流执行完成');
|
||||
return { success: true, results };
|
||||
|
||||
// 输出性能报告
|
||||
if (this.context.performanceReport !== false) {
|
||||
this.log('info', this.performanceTracker.generateReport());
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
results,
|
||||
performance: this.performanceTracker.exportJSON()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user