const BaseAction = require('../core/base-action'); /** * 重试块动作 - 将一组步骤作为整体进行重试 * * 配置示例: * - action: retryBlock * name: "支付流程" * maxRetries: 5 * retryDelay: 2000 * onRetryBefore: * - action: custom * handler: "regenerateCard" * steps: * - action: fillForm * fields: {...} * - action: click * selector: {...} */ class RetryBlockAction extends BaseAction { async execute() { const { steps = [], maxRetries = 3, retryDelay = 1000, onRetryBefore = [], onRetryAfter = [] } = this.config; const blockName = this.config.name || 'RetryBlock'; if (!steps || steps.length === 0) { throw new Error('RetryBlock 必须包含至少一个步骤'); } let lastError = null; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { if (attempt > 0) { this.log('info', `${blockName} - 第 ${attempt + 1} 次重试...`); // 执行重试前的钩子 if (onRetryBefore.length > 0) { this.log('debug', '执行重试前钩子...'); await this.executeHooks(onRetryBefore); } // 延迟 if (retryDelay > 0) { await new Promise(resolve => setTimeout(resolve, retryDelay)); } } // 执行步骤块 this.log('debug', `执行 ${steps.length} 个步骤...`); await this.executeSteps(steps); // 执行成功后的钩子(仅首次成功时) if (attempt > 0 && onRetryAfter.length > 0) { this.log('debug', '执行重试后钩子...'); await this.executeHooks(onRetryAfter); } // 成功,跳出循环 if (attempt > 0) { this.log('success', `✓ ${blockName} 在第 ${attempt + 1} 次尝试后成功`); } return { success: true, attempts: attempt + 1 }; } catch (error) { lastError = error; if (attempt < maxRetries) { this.log('warn', `${blockName} 执行失败: ${error.message}`); this.log('info', `准备重试 (${attempt + 1}/${maxRetries})...`); } else { this.log('error', `${blockName} 在 ${maxRetries + 1} 次尝试后仍然失败`); } } } // 所有重试都失败 throw new Error(`${blockName} 失败: ${lastError.message}`); } /** * 执行钩子函数 */ async executeHooks(hooks) { for (const hookConfig of hooks) { await this.executeStep(hookConfig); } } /** * 执行步骤列表 */ async executeSteps(steps) { for (const stepConfig of steps) { await this.executeStep(stepConfig); } } /** * 执行单个步骤 */ async executeStep(stepConfig) { const actionType = stepConfig.action; // 动态加载对应的 Action const ActionClass = this.getActionClass(actionType); const action = new ActionClass( this.page, stepConfig, this.context ); return await action.execute(); } /** * 根据 action 类型获取 Action 类 */ getActionClass(actionType) { const actionMap = { navigate: require('./navigate-action'), fillForm: require('./fill-form-action'), click: require('./click-action'), wait: require('./wait-action'), custom: require('./custom-action') }; const ActionClass = actionMap[actionType]; if (!ActionClass) { throw new Error(`未知的 action 类型: ${actionType}`); } return ActionClass; } } module.exports = RetryBlockAction;