From 02d58adbe24aabc9ebaf433daac019e9dca2bd71 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Wed, 19 Nov 2025 10:12:54 +0800 Subject: [PATCH] dasdasd --- .../actions/retry-block-action.js | 9 +- .../actions/scroll-action.js | 128 ++++++++++++++++++ .../configs/sites/windsurf.yaml | 44 +++--- .../core/workflow-engine.js | 2 + 4 files changed, 158 insertions(+), 25 deletions(-) create mode 100644 src/tools/automation-framework/actions/scroll-action.js diff --git a/src/tools/automation-framework/actions/retry-block-action.js b/src/tools/automation-framework/actions/retry-block-action.js index 76f8b19..0bf872c 100644 --- a/src/tools/automation-framework/actions/retry-block-action.js +++ b/src/tools/automation-framework/actions/retry-block-action.js @@ -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]; diff --git a/src/tools/automation-framework/actions/scroll-action.js b/src/tools/automation-framework/actions/scroll-action.js new file mode 100644 index 0000000..701951b --- /dev/null +++ b/src/tools/automation-framework/actions/scroll-action.js @@ -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; diff --git a/src/tools/automation-framework/configs/sites/windsurf.yaml b/src/tools/automation-framework/configs/sites/windsurf.yaml index 4db5bd0..d14e242 100644 --- a/src/tools/automation-framework/configs/sites/windsurf.yaml +++ b/src/tools/automation-framework/configs/sites/windsurf.yaml @@ -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: "验证支付结果" diff --git a/src/tools/automation-framework/core/workflow-engine.js b/src/tools/automation-framework/core/workflow-engine.js index c75b3fe..ec43690 100644 --- a/src/tools/automation-framework/core/workflow-engine.js +++ b/src/tools/automation-framework/core/workflow-engine.js @@ -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); } /**