147 lines
3.7 KiB
JavaScript
147 lines
3.7 KiB
JavaScript
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;
|