This commit is contained in:
dengqichen 2025-11-18 21:56:47 +08:00
parent 4309e8aadf
commit b6b3751ee0
4 changed files with 372 additions and 1 deletions

View 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;

View 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
};

View 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;

View File

@ -1,4 +1,6 @@
const ActionRegistry = require('./action-registry'); const ActionRegistry = require('./action-registry');
const ConfigValidator = require('./config-validator');
const PerformanceTracker = require('./performance-tracker');
// 导入内置动作 // 导入内置动作
const NavigateAction = require('../actions/navigate-action'); const NavigateAction = require('../actions/navigate-action');
@ -20,6 +22,19 @@ class WorkflowEngine {
this.actionRegistry = new ActionRegistry(); this.actionRegistry = new ActionRegistry();
this.currentStep = 0; 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(); this.registerBuiltinActions();
} }
@ -77,11 +92,21 @@ class WorkflowEngine {
const stepName = actionConfig.name || `步骤 ${this.currentStep}`; const stepName = actionConfig.name || `步骤 ${this.currentStep}`;
this.log('info', `[${this.currentStep}/${actions.length}] ${stepName}`); this.log('info', `[${this.currentStep}/${actions.length}] ${stepName}`);
// 开始性能追踪
this.performanceTracker.startStep(stepName, i);
try { try {
const result = await this.executeAction(actionConfig); 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 }); results.push({ step: i, success: true, result });
} catch (error) { } catch (error) {
// 记录失败
this.performanceTracker.endStep(false, error);
this.log('error', `步骤失败: ${error.message}`); this.log('error', `步骤失败: ${error.message}`);
// 检查是否是可选步骤 // 检查是否是可选步骤
@ -106,7 +131,17 @@ class WorkflowEngine {
} }
this.log('info', '工作流执行完成'); 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()
};
} }
/** /**