From b6b3751ee084f57f305a6145b26d9622484dec6f Mon Sep 17 00:00:00 2001 From: dengqichen Date: Tue, 18 Nov 2025 21:56:47 +0800 Subject: [PATCH] dasdasd --- .../core/config-validator.js | 146 ++++++++++++++++++ src/automation-framework/core/errors.js | 74 +++++++++ .../core/performance-tracker.js | 116 ++++++++++++++ .../core/workflow-engine.js | 37 ++++- 4 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 src/automation-framework/core/config-validator.js create mode 100644 src/automation-framework/core/errors.js create mode 100644 src/automation-framework/core/performance-tracker.js diff --git a/src/automation-framework/core/config-validator.js b/src/automation-framework/core/config-validator.js new file mode 100644 index 0000000..d857aa9 --- /dev/null +++ b/src/automation-framework/core/config-validator.js @@ -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; diff --git a/src/automation-framework/core/errors.js b/src/automation-framework/core/errors.js new file mode 100644 index 0000000..620f145 --- /dev/null +++ b/src/automation-framework/core/errors.js @@ -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 +}; diff --git a/src/automation-framework/core/performance-tracker.js b/src/automation-framework/core/performance-tracker.js new file mode 100644 index 0000000..66d7beb --- /dev/null +++ b/src/automation-framework/core/performance-tracker.js @@ -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; diff --git a/src/automation-framework/core/workflow-engine.js b/src/automation-framework/core/workflow-engine.js index 21d2ff9..c75b3fe 100644 --- a/src/automation-framework/core/workflow-engine.js +++ b/src/automation-framework/core/workflow-engine.js @@ -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() + }; } /**