This commit is contained in:
dengqichen 2025-11-19 10:12:54 +08:00
parent acceddac7b
commit 02d58adbe2
4 changed files with 158 additions and 25 deletions

View File

@ -112,10 +112,10 @@ class RetryBlockAction extends BaseAction {
// 动态加载对应的 Action
const ActionClass = this.getActionClass(actionType);
// 修复BaseAction 构造函数签名是 (context, config)
const action = new ActionClass(
this.page,
stepConfig,
this.context
this.context, // 第一个参数context
stepConfig // 第二个参数config
);
return await action.execute();
@ -130,7 +130,8 @@ class RetryBlockAction extends BaseAction {
fillForm: require('./fill-form-action'),
click: require('./click-action'),
wait: require('./wait-action'),
custom: require('./custom-action')
custom: require('./custom-action'),
scroll: require('./scroll-action')
};
const ActionClass = actionMap[actionType];

View File

@ -0,0 +1,128 @@
const BaseAction = require('../core/base-action');
/**
* 滚动动作 - 页面滚动操作
*
* 支持多种滚动方式
* 1. 滚动到底部
* 2. 滚动到顶部
* 3. 滚动到指定元素
* 4. 滚动指定距离
*
* Example:
* - action: scroll
* type: bottom
*
* - action: scroll
* type: element
* selector: '#submit-button'
*
* - action: scroll
* type: distance
* x: 0
* y: 500
*/
class ScrollAction extends BaseAction {
async execute() {
const {
type = 'bottom',
selector,
x = 0,
y = 0,
behavior = 'smooth'
} = this.config;
this.log('debug', `执行滚动: ${type}`);
switch (type) {
case 'bottom':
await this.scrollToBottom(behavior);
break;
case 'top':
await this.scrollToTop(behavior);
break;
case 'element':
if (!selector) {
throw new Error('滚动到元素需要提供 selector');
}
await this.scrollToElement(selector, behavior);
break;
case 'distance':
await this.scrollByDistance(x, y, behavior);
break;
default:
throw new Error(`不支持的滚动类型: ${type}`);
}
// 等待滚动完成
await new Promise(resolve => setTimeout(resolve, 500));
this.log('debug', '✓ 滚动完成');
return { success: true };
}
/**
* 滚动到页面底部
*/
async scrollToBottom(behavior) {
await this.page.evaluate((b) => {
window.scrollTo({
top: document.body.scrollHeight,
left: 0,
behavior: b
});
}, behavior);
}
/**
* 滚动到页面顶部
*/
async scrollToTop(behavior) {
await this.page.evaluate((b) => {
window.scrollTo({
top: 0,
left: 0,
behavior: b
});
}, behavior);
}
/**
* 滚动到指定元素
*/
async scrollToElement(selector, behavior) {
const element = await this.page.$(selector);
if (!element) {
throw new Error(`元素不存在: ${selector}`);
}
await element.evaluate((el, b) => {
el.scrollIntoView({
behavior: b,
block: 'center',
inline: 'nearest'
});
}, behavior);
}
/**
* 滚动指定距离
*/
async scrollByDistance(x, y, behavior) {
await this.page.evaluate((dx, dy, b) => {
window.scrollBy({
top: dy,
left: dx,
behavior: b
});
}, x, y, behavior);
}
}
module.exports = ScrollAction;

View File

@ -137,9 +137,7 @@ workflow:
- action: click
name: "选择计划"
selector:
- text: 'Select'
selector: 'button, a'
- text: 'Continue'
- text: 'Select plan'
selector: 'button, a'
timeout: 30000
waitAfter: 2000
@ -152,22 +150,7 @@ workflow:
- css: 'input[type="radio"][value="card"]'
waitAfter: 3000
# 6.2 等待支付表单加载
- action: wait
name: "等待支付表单"
type: element
find:
- css: '#cardNumber'
timeout: 30000
# 6.3 处理 hCaptcha只需处理一次
- action: custom
name: "hCaptcha 验证"
handler: "handleHCaptcha"
params:
timeout: 120000
# ==================== 步骤 6.4: 提交支付(带重试) ====================
# ==================== 步骤 6.2: 提交支付(带重试) ====================
- action: retryBlock
name: "提交支付并验证"
maxRetries: 5
@ -178,6 +161,14 @@ workflow:
handler: "regenerateCard"
steps:
# 等待支付表单加载(每次重试都需要等待)
- action: wait
name: "等待支付表单"
type: element
find:
- css: '#cardNumber'
timeout: 30000
# 填写银行卡信息(使用重新生成的卡号)
- action: fillForm
name: "填写银行卡信息"
@ -208,15 +199,26 @@ workflow:
name: "选择澳门地址"
handler: "selectBillingAddress"
# 滚动到页面底部(确保订阅按钮可见)
- action: scroll
name: "滚动到订阅按钮"
type: bottom
# 提交支付
- action: click
name: "点击提交支付"
selector:
- text: 'Subscribe'
- text: '订阅'
- css: 'button[type="submit"]' # Stripe 订阅按钮
timeout: 15000
waitAfter: 2000
# 处理 hCaptcha点击订阅后出现
- action: custom
name: "hCaptcha 验证"
handler: "handleHCaptcha"
params:
timeout: 120000
# 验证支付结果(轮询检测成功或失败)
- action: verify
name: "验证支付结果"

View File

@ -11,6 +11,7 @@ const CustomAction = require('../actions/custom-action');
const RetryBlockAction = require('../actions/retry-block-action');
const ExtractAction = require('../actions/extract-action');
const VerifyAction = require('../actions/verify-action');
const ScrollAction = require('../actions/scroll-action');
/**
* 工作流引擎 - 执行配置驱动的自动化流程
@ -51,6 +52,7 @@ class WorkflowEngine {
this.actionRegistry.register('retryBlock', RetryBlockAction);
this.actionRegistry.register('extract', ExtractAction);
this.actionRegistry.register('verify', VerifyAction);
this.actionRegistry.register('scroll', ScrollAction);
}
/**