| .. | ||
| actions | ||
| configs/sites | ||
| core | ||
| sites | ||
| index.js | ||
| README.md | ||
自动化框架使用教程
目录
框架概述
这是一个配置驱动的浏览器自动化框架,基于 Puppeteer 构建。核心理念:
- ✅ 90% 配置,10% 代码 - 大部分操作通过 YAML 配置
- ✅ 声明式编程 - 只需描述"做什么",不需要"怎么做"
- ✅ 可复用 - 一次配置,多处使用
- ✅ 易维护 - 修改配置即可调整流程
快速开始
1. 创建站点配置文件
在 src/automation-framework/configs/sites/ 创建 mysite.yaml:
# 站点信息
site:
name: MySite
url: https://mysite.com/register
# 工作流定义
workflow:
- action: navigate
name: "打开注册页面"
url: "https://mysite.com/register"
- action: fillForm
name: "填写注册信息"
fields:
username:
find:
- css: '#username'
value: "{{account.username}}"
email:
find:
- css: '#email'
value: "{{account.email}}"
- action: click
name: "提交注册"
selector:
- css: 'button[type="submit"]'
2. 创建站点适配器
在 src/automation-framework/sites/ 创建 mysite-adapter.js:
const SiteAdapter = require('../core/site-adapter');
class MySiteAdapter extends SiteAdapter {
constructor(context) {
super(context, 'mysite');
}
async beforeWorkflow() {
await super.beforeWorkflow();
// 生成测试数据
this.context.data = {
account: {
username: 'testuser',
email: 'test@example.com'
}
};
}
}
module.exports = MySiteAdapter;
3. 运行
const AutomationFactory = require('./src/automation-framework');
const factory = new AutomationFactory({
logger: console,
adspowerUserId: 'your-profile-id'
});
await factory.execute('mysite');
YAML 配置结构
基本结构
# 站点信息
site:
name: 站点名称
url: 站点URL
# 工作流定义
workflow:
- action: 动作类型
name: 步骤名称
# 动作特定参数
...
# 错误处理配置(可选)
errorHandling:
screenshot: true
retry:
enabled: true
maxAttempts: 3
delay: 2000
内置 Actions
navigate - 页面导航
用途:打开或跳转到指定页面
必需参数:
url(string): 目标URL,支持变量替换
可选参数:
name(string): 步骤名称options(object): 导航选项waitUntil(string): 等待条件,默认'networkidle2'timeout(number): 超时时间(毫秒),默认 30000
verifyUrl(string): 验证URL是否包含此字符串verifyElements(array): 验证这些元素存在waitAfter(number): 导航后等待时间(毫秒)
示例:
# 基本导航
- action: navigate
url: "https://example.com/register"
# 带验证的导航
- action: navigate
name: "打开注册页面"
url: "https://example.com/register"
verifyUrl: "/register"
verifyElements:
- '#username'
- '#email'
- 'button[type="submit"]'
waitAfter: 2000
# 自定义选项
- action: navigate
url: "{{config.baseUrl}}/login"
options:
waitUntil: 'domcontentloaded'
timeout: 60000
fillForm - 表单填写
用途:填写表单字段
必需参数:
fields(object): 字段配置对象,键为字段名
可选参数:
name(string): 步骤名称humanLike(boolean): 是否模拟人类输入,默认true
字段配置:
find(array): 选择器数组,按顺序尝试css(string): CSS 选择器xpath(string): XPath 选择器text(string): 按文本查找name(string): 按 name 属性查找
value(string): 字段值,支持变量替换clear(boolean): 填写前是否清空,默认true
示例:
# 基本表单填写
- action: fillForm
name: "填写注册信息"
fields:
username:
find:
- css: '#username'
value: "{{account.username}}"
email:
find:
- css: 'input[name="email"]'
- css: '#email'
value: "{{account.email}}"
# 模拟人类输入
- action: fillForm
name: "填写个人信息"
humanLike: true
fields:
firstName:
find:
- css: '#firstName'
value: "{{account.firstName}}"
clear: true
lastName:
find:
- css: '#lastName'
value: "{{account.lastName}}"
# 密码字段
- action: fillForm
name: "设置密码"
humanLike: false
fields:
password:
find:
- css: 'input[type="password"]'
value: "{{account.password}}"
passwordConfirm:
find:
- css: 'input[placeholder*="Confirm"]'
value: "{{account.password}}"
click - 点击操作
用途:点击页面元素
必需参数:
selector(array 或 object): 元素选择器
可选参数:
name(string): 步骤名称verifyAfter(object): 点击后验证appears(array): 应该出现的元素disappears(array): 应该消失的元素timeout(number): 验证超时时间
waitForPageChange(boolean): 是否等待页面变化checkSelector(object): 检查的选择器waitAfter(number): 点击后等待时间(毫秒)optional(boolean): 是否可选,默认false
选择器格式:
selector:
- css: '#submit-btn'
- xpath: '//button[@id="submit"]'
- text: 'Continue'
- name: 'submit'
示例:
# 简单点击
- action: click
selector:
- css: 'button[type="submit"]'
# 按文本点击
- action: click
name: "点击继续按钮"
selector:
- text: 'Continue'
- text: '继续'
waitAfter: 2000
# 带验证的点击
- action: click
name: "提交表单"
selector:
- css: '#submit-btn'
verifyAfter:
appears:
- '#success-message'
- '.confirmation'
disappears:
- '.loading-spinner'
timeout: 10000
# 可选点击(找不到元素不报错)
- action: click
name: "关闭弹窗"
selector:
- css: '.modal-close'
optional: true
waitAfter: 500
wait - 等待
用途:等待特定条件
必需参数:
type(string): 等待类型delay: 固定时间延迟element: 等待元素出现navigation: 等待页面导航
可选参数:
name(string): 步骤名称duration(number): 延迟时长(毫秒),用于delay类型find(array): 元素选择器,用于element类型timeout(number): 超时时间(毫秒),默认 30000
示例:
# 固定延迟
- action: wait
name: "等待2秒"
type: delay
duration: 2000
# 等待元素出现
- action: wait
name: "等待密码输入框"
type: element
find:
- css: 'input[type="password"]'
timeout: 15000
# 等待多个选择器之一
- action: wait
name: "等待确认页面"
type: element
find:
- css: '#confirmation'
- text: 'Success'
timeout: 30000
# 等待页面导航
- action: wait
name: "等待页面跳转"
type: navigation
timeout: 10000
extract - 数据提取
用途:从页面提取数据并保存到上下文
必需参数:
selector(string): CSS 选择器contextKey(string): 保存到上下文的键名
可选参数:
name(string): 步骤名称extractType(string): 提取类型text: 文本内容(默认)html: HTML 内容attribute: 属性值value: 表单值
attribute(string): 属性名,用于attribute类型regex(string): 正则表达式,用于提取特定内容saveTo(object): 捕获组映射filter(object): 元素过滤contains(string): 包含文本notContains(string): 不包含文本
multiple(boolean): 是否提取多个元素,默认falserequired(boolean): 是否必需,默认true
示例:
# 简单文本提取
- action: extract
name: "提取页面标题"
selector: "h1.title"
extractType: "text"
contextKey: "pageTitle"
# 提取属性
- action: extract
name: "提取头像URL"
selector: "img.avatar"
extractType: "attribute"
attribute: "src"
contextKey: "avatarUrl"
# 正则提取 + 捕获组
- action: extract
name: "提取配额信息"
selector: "p.quota"
extractType: "text"
regex: "(\\d+)\\s*/\\s*(\\d+)"
saveTo:
used: "$1"
total: "$2"
contextKey: "quotaInfo"
# 过滤 + 提取
- action: extract
name: "提取账单周期"
selector: "p.info"
filter:
contains: "Next billing"
regex: "(\\d+)\\s+days.*on\\s+([A-Za-z]+\\s+\\d+,\\s+\\d{4})"
saveTo:
days: "$1"
date: "$2"
contextKey: "billingInfo"
# 提取多个元素
- action: extract
name: "提取所有列表项"
selector: "li.item"
extractType: "text"
multiple: true
contextKey: "items"
# 可选提取(找不到不报错)
- action: extract
name: "提取错误消息"
selector: ".error-message"
contextKey: "errorMsg"
required: false
verify - 结果验证
用途:验证操作结果(如支付成功/失败),支持轮询检测多种条件
必需参数:
conditions(object): 验证条件success(array): 成功条件列表(任一满足即成功)failure(array): 失败条件列表(任一满足即失败)
可选参数:
name(string): 步骤名称timeout(number): 超时时间(毫秒),默认 10000pollInterval(number): 轮询间隔(毫秒),默认 500onSuccess(string): 成功时的行为continue: 继续执行(默认)return: 返回结果
onFailure(string): 失败时的行为throw: 抛出异常(默认,触发 retryBlock 重试)continue: 继续执行(忽略失败)return: 返回结果
onTimeout(string): 超时时的行为,默认throw
onFailure 行为详解:
| 值 | 行为 | 使用场景 |
|---|---|---|
throw |
抛出异常 | 在 retryBlock 内使用,失败时触发重试 |
continue |
继续执行 | 失败不影响流程,只记录结果 |
return |
返回结果 | 由后续步骤处理失败情况 |
重要:
- ✅ 在
retryBlock内使用onFailure: "throw"可触发重试 - ✅ 只有所有重试都失败,才会导致整个流程失败
- ❌ 单次验证失败不会终止流程(会自动重试)
支持的条件类型:
| 条件类型 | 说明 | 示例 |
|---|---|---|
urlContains |
URL 包含指定文本 | urlContains: "success" |
urlNotContains |
URL 不包含指定文本 | urlNotContains: "stripe.com" |
urlEquals |
URL 完全匹配 | urlEquals: "https://app.com/dashboard" |
elementExists |
元素存在 | elementExists: ".success-message" |
elementNotExists |
元素不存在 | elementNotExists: ".loading" |
elementVisible |
元素可见 | elementVisible: "#confirmation" |
elementHidden |
元素隐藏 | elementHidden: ".modal" |
textContains |
页面包含文本 | textContains: "Payment successful" |
textNotContains |
页面不包含文本 | textNotContains: "Error" |
elementTextContains |
元素包含文本 | {selector: ".status", text: "Active"} |
示例:
# 验证支付结果
- action: verify
name: "验证支付结果"
conditions:
success:
- urlNotContains: "checkout.stripe.com"
- elementExists: ".payment-success"
failure:
- textContains: "card was declined"
- textContains: "declined"
- elementExists: ".error-message"
timeout: 15000
pollInterval: 500
onFailure: "throw"
# 验证页面跳转
- action: verify
name: "验证登录成功"
conditions:
success:
- urlContains: "/dashboard"
- elementExists: ".user-profile"
failure:
- textContains: "Invalid credentials"
- elementExists: ".login-error"
timeout: 10000
onFailure: "throw"
# 验证元素状态
- action: verify
name: "验证加载完成"
conditions:
success:
- elementHidden: ".loading-spinner"
- elementVisible: ".content"
timeout: 30000
onTimeout: "continue"
# 复杂验证
- action: verify
name: "验证注册结果"
conditions:
success:
- urlEquals: "https://app.com/welcome"
failure:
- textContains: "Email already exists"
- textContains: "Username taken"
- elementExists: ".validation-error"
timeout: 10000
pollInterval: 1000
onSuccess: "return"
onFailure: "throw"
配合 retryBlock 使用:
- action: retryBlock
name: "支付流程"
maxRetries: 5
onRetryBefore:
- action: custom
handler: "regenerateCard"
steps:
- action: fillForm
fields:
cardNumber:
value: "{{card.number}}"
- action: custom
handler: "handleCaptcha"
- action: click
name: "提交支付"
selector:
- css: "#submit-payment"
# verify 检测到失败会抛异常,触发 retryBlock 重试
- action: verify
name: "验证支付结果"
conditions:
success:
- urlNotContains: "stripe.com"
failure:
- textContains: "declined"
- textContains: "card was declined"
timeout: 10000
pollInterval: 500
onFailure: "throw"
执行流程示例:
第1次尝试:
├─ 填写卡号: 4111111111111111
├─ 处理验证码
├─ 提交支付
└─ verify 检测
└─ 发现 "declined" ❌
└─ throw Error ⚡
└─ retryBlock 捕获 → 重试 1/5
第2次尝试:
├─ regenerateCard() 生成新卡: 4242424242424242
├─ 填写新卡号: 4242424242424242
├─ 处理验证码
├─ 提交支付
└─ verify 检测
└─ URL 不包含 "stripe.com" ✅
└─ 成功!继续下一步
总结:尝试了2次,第2次成功,流程继续
日志输出示例:
[Windsurf] [验证支付结果]
[windsurf] ✗ 验证失败: 页面包含文本: "card was declined"
[windsurf] ⚠ WARNING: 步骤失败,触发重试 (1/5)
[windsurf] 执行重试前钩子...
[windsurf] 重新生成银行卡...
[windsurf] ✓ 新卡号: 4242424242424242
[windsurf] 重新执行步骤...
[Windsurf] [填写银行卡信息]
[Windsurf] [验证支付结果]
[windsurf] ✓ 验证成功: URL 不包含 "stripe.com"
[windsurf] 支付流程完成!
retryBlock - 重试块
用途:将一组步骤作为整体进行重试
必需参数:
steps(array): 要重试的步骤数组
可选参数:
name(string): 重试块名称maxRetries(number): 最大重试次数,默认 3retryDelay(number): 重试间隔(毫秒),默认 2000onRetryBefore(array): 重试前执行的步骤onRetryAfter(array): 重试后执行的步骤
示例:
# 基本重试块
- action: retryBlock
name: "支付流程"
maxRetries: 5
retryDelay: 3000
steps:
- action: fillForm
name: "填写银行卡"
fields:
cardNumber:
find:
- css: '#cardNumber'
value: "{{card.number}}"
- action: click
name: "提交支付"
selector:
- css: '#submit-payment'
# 带重试钩子的重试块
- action: retryBlock
name: "注册流程(含验证码重试)"
maxRetries: 3
onRetryBefore:
# 重试前重新生成数据
- action: custom
handler: "regenerateAccount"
onRetryAfter:
# 重试后记录日志
- action: custom
handler: "logRetryAttempt"
steps:
- action: fillForm
name: "填写注册信息"
fields:
email:
find:
- css: '#email'
value: "{{account.email}}"
- action: custom
name: "处理验证码"
handler: "handleCaptcha"
custom - 自定义逻辑
用途:执行站点适配器中的自定义方法
必需参数:
handler(string): 适配器中的方法名
可选参数:
name(string): 步骤名称params(object): 传递给方法的参数optional(boolean): 是否可选,默认false
示例:
# 简单自定义调用
- action: custom
name: "处理验证码"
handler: "handleCaptcha"
# 带参数的自定义调用
- action: custom
name: "处理 Turnstile 验证"
handler: "handleTurnstile"
params:
timeout: 30000
maxRetries: 3
# 可选自定义调用
- action: custom
name: "获取订阅信息"
handler: "getSubscriptionInfo"
optional: true
# 重新生成数据
- action: custom
handler: "regenerateCard"
params:
country: "MO"
对应的适配器方法:
class MySiteAdapter extends SiteAdapter {
async handleCaptcha(params) {
// 处理验证码逻辑
return { success: true };
}
async handleTurnstile(params) {
const { timeout, maxRetries } = params;
// 处理 Turnstile 验证
return { success: true };
}
async regenerateCard(params) {
const { country } = params;
// 重新生成银行卡
this.context.data.card = this.cardGen.generate(country);
}
}
变量替换
框架支持在 YAML 中使用变量,格式为 {{path.to.variable}}
可用变量源
1. context.data - 运行时数据
- action: fillForm
fields:
email:
value: "{{account.email}}" # context.data.account.email
cardNumber:
value: "{{card.number}}" # context.data.card.number
2. config.site - 站点配置
- action: navigate
url: "{{site.url}}" # config.site.url
在适配器中设置变量
async beforeWorkflow() {
this.context.data = {
account: {
email: 'test@example.com',
password: 'SecurePass123',
firstName: 'John',
lastName: 'Doe'
},
card: {
number: '4111111111111111',
month: '12',
year: '25',
cvv: '123'
}
};
}
站点适配器开发
基本结构
const SiteAdapter = require('../core/site-adapter');
class MySiteAdapter extends SiteAdapter {
constructor(context) {
super(context, 'mysite'); // 'mysite' 对应 mysite.yaml
}
/**
* 工作流执行前 - 准备数据
*/
async beforeWorkflow() {
await super.beforeWorkflow(); // 清除浏览器状态
// 生成测试数据
this.context.data = {
account: this.generateAccount(),
card: this.generateCard()
};
}
/**
* 工作流执行后 - 清理或保存
*/
async afterWorkflow() {
await super.afterWorkflow();
// 保存结果等
}
/**
* 自定义方法 - 由 YAML 的 custom action 调用
*/
async handleCaptcha(params) {
// 实现验证码处理
return { success: true };
}
/**
* 重启钩子 - restart 策略调用
*/
async onRestart(options) {
// 返回需要重新执行的步骤名称
return [
'填写基本信息',
'设置密码'
];
}
}
module.exports = MySiteAdapter;
生命周期钩子
| 钩子 | 调用时机 | 用途 |
|---|---|---|
beforeWorkflow() |
工作流开始前 | 准备数据、清理状态 |
afterWorkflow() |
工作流完成后 | 保存结果、清理资源 |
onError(error) |
发生错误时 | 错误处理、截图 |
onRestart(options) |
restart 重试时 | 返回需要重新执行的步骤 |
重试策略
框架提供三种重试策略(在 executeRetryStrategy 中实现):
1. refresh - 刷新页面保持状态
params:
retryStrategy: 'refresh'
- 适用于刷新后保持当前页面状态的网站
- 只刷新页面,不重新填写
2. restart - 刷新后重新开始
params:
retryStrategy: 'restart'
- 适用于刷新后回到第一步的网站(如 Windsurf)
- 自动调用
onRestart()获取需要重新执行的步骤
3. wait - 延长等待时间
params:
retryStrategy: 'wait'
waitTime: 10000
- 不刷新页面,只等待更长时间
- 适用于加载慢的情况
框架提供的工具方法
// 日志
this.log('info', '消息');
this.log('success', '✓ 成功消息');
this.log('warn', '⚠ 警告消息');
this.log('error', '✗ 错误消息');
// 重新执行 YAML 步骤
await this.rerunSteps(['步骤名称1', '步骤名称2']);
await this.rerunSteps([0, 1, 2]); // 按索引
// 重试策略
await this.executeRetryStrategy('refresh', retryCount);
await this.executeRetryStrategy('restart', retryCount);
await this.executeRetryStrategy('wait', retryCount, { waitTime: 5000 });
最佳实践
1. 步骤命名清晰
# ❌ 不好
- action: click
selector:
- css: 'button'
# ✅ 好
- action: click
name: "点击 Continue 按钮(基本信息页)"
selector:
- css: 'button[type="submit"]'
2. 使用多个选择器备选
fields:
email:
find:
- css: '#email' # 首选
- css: 'input[name="email"]' # 备选1
- xpath: '//input[@type="email"]' # 备选2
3. 验证关键步骤
# 导航后验证
- action: navigate
url: "https://example.com/register"
verifyElements:
- '#username'
- '#email'
# 点击后验证
- action: click
name: "提交表单"
selector:
- css: '#submit'
verifyAfter:
appears:
- '#success-message'
4. 合理使用 optional
# 可能不存在的弹窗
- action: click
name: "关闭欢迎弹窗"
selector:
- css: '.welcome-modal .close'
optional: true
# 不影响主流程的数据提取
- action: extract
name: "提取备注信息"
selector: ".notes"
contextKey: "notes"
required: false
5. 模块化配置
# 将重复逻辑抽取为 retryBlock
- action: retryBlock
name: "支付流程"
maxRetries: 5
onRetryBefore:
- action: custom
handler: "regenerateCard"
steps:
# 复用的步骤组
- action: fillForm
...
- action: click
...
6. 适当使用 humanLike
# 重要表单使用人类行为模拟
- action: fillForm
name: "填写个人信息"
humanLike: true # 随机延迟、打字速度
fields:
firstName:
...
# 支付信息快速填写
- action: fillForm
name: "填写银行卡"
humanLike: false # 快速填写
fields:
cardNumber:
...
7. 使用 verify + retryBlock 实现智能重试
# ✅ 最佳实践:将需要重试的步骤放在 retryBlock 内
# hCaptcha 等验证码只处理一次(放在 retryBlock 外)
- action: custom
handler: "handleCaptcha"
# 支付提交和验证放在 retryBlock 内
- action: retryBlock
maxRetries: 5
onRetryBefore:
- action: custom
handler: "regenerateCard"
steps:
- action: fillForm
fields:
cardNumber:
value: "{{card.number}}"
- action: click
selector:
- css: "#submit"
# verify 失败会触发重试
- action: verify
conditions:
success:
- urlNotContains: "payment"
failure:
- textContains: "declined"
onFailure: "throw"
# ❌ 避免:把验证码放在 retryBlock 内
# 会导致每次重试都要处理验证码
关键点:
- ✅ 只重试必要的步骤(填表 + 提交 + 验证)
- ✅ 一次性步骤放在 retryBlock 外(验证码、页面导航)
- ✅ 用
verify检测结果,onFailure: "throw"触发重试 - ✅ 用
onRetryBefore重新生成数据(如新卡号)
8. 代码vs配置的选择
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 点击、填表、等待 | YAML | 简单、可维护 |
| 数据提取 | YAML (extract) + 处理(custom) | 提取配置化,处理逻辑化 |
| 结果验证 | YAML (verify) | 轮询检测、触发重试 |
| 验证码、人机验证 | custom | 复杂逻辑 |
| 条件判断、循环 | custom | YAML 不支持 |
| 数据库操作 | custom | 需要导入模块 |
完整示例
查看 src/automation-framework/configs/sites/windsurf.yaml 了解完整的真实案例。
常见问题
Q: 如何调试 YAML 配置?
A: 查看日志输出,每个步骤都会打印执行信息:
[Windsurf] [1/14] 打开注册页面
[Windsurf] 导航到: https://windsurf.com/account/register
[Windsurf] ✓ 页面加载完成
Q: 元素找不到怎么办?
A:
- 使用
verifyElements验证元素存在 - 增加
waitAfter等待时间 - 使用多个选择器备选
- 设置
optional: true跳过
Q: 如何处理动态内容?
A: 使用 wait action 等待元素出现:
- action: wait
type: element
find:
- css: '.dynamic-content'
timeout: 15000
Q: 变量替换不生效?
A: 检查:
- 变量路径是否正确(如
{{account.email}}) - 是否在
beforeWorkflow()中设置了this.context.data - 日志中查看变量值
Q: verify 失败会终止整个流程吗?
A: 不会!
- 在
retryBlock内使用onFailure: "throw",失败会触发重试 - 只有所有重试都失败,才会终止流程
- 单次失败只是触发重试,不影响整体流程
Q: 如何避免重试时重复处理验证码?
A: 将验证码处理放在 retryBlock 外面:
# ✅ 正确:验证码只处理一次
- action: custom
handler: "handleCaptcha"
- action: retryBlock
steps:
- 填写表单
- 提交
- 验证
# ❌ 错误:每次重试都处理验证码
- action: retryBlock
steps:
- action: custom
handler: "handleCaptcha"
- 填写表单
- 提交
Q: verify 的 timeout 和 retryBlock 的 maxRetries 有什么区别?
A:
- verify 的 timeout:单次验证的轮询时间(如检测支付结果15秒)
- retryBlock 的 maxRetries:失败后重试的次数(如支付失败重试5次)
- 总耗时 ≈
timeout × maxRetries(如 15秒 × 5次 = 75秒)
更新日志
- v1.0.0 - 基础框架
- navigate, fillForm, click, wait, custom
- v1.1.0 - 增强功能
- retryBlock 重试块
- 人类行为模拟
- 点击验证
- v1.2.0 - 数据提取
- extract action
- 正则提取
- 多元素提取
- v1.3.0 - 结果验证
- verify action
- 支持 10 种条件类型
- 轮询检测机制
- 配合 retryBlock 实现智能重试
框架持续演进中,欢迎贡献新功能! 🚀