# 自动化框架使用教程

## 目录
- [框架概述](#框架概述)
- [快速开始](#快速开始)
- [YAML 配置结构](#yaml-配置结构)
- [内置 Actions](#内置-actions)
  - [navigate - 页面导航](#navigate---页面导航)
  - [fillForm - 表单填写](#fillform---表单填写)
  - [click - 点击操作](#click---点击操作)
  - [wait - 等待](#wait---等待)
  - [extract - 数据提取](#extract---数据提取)
  - [verify - 结果验证](#verify---结果验证)
  - [retryBlock - 重试块](#retryblock---重试块)
  - [custom - 自定义逻辑](#custom---自定义逻辑)
- [变量替换](#变量替换)
- [站点适配器开发](#站点适配器开发)
- [最佳实践](#最佳实践)

---

## 框架概述

这是一个**配置驱动**的浏览器自动化框架，基于 Puppeteer 构建。核心理念：

- ✅ **90% 配置，10% 代码** - 大部分操作通过 YAML 配置
- ✅ **声明式编程** - 只需描述"做什么"，不需要"怎么做"
- ✅ **可复用** - 一次配置，多处使用
- ✅ **易维护** - 修改配置即可调整流程

---

## 快速开始

### 1. 创建站点配置文件

在 `src/automation-framework/configs/sites/` 创建 `mysite.yaml`:

```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`:

```javascript
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. 运行

```javascript
const AutomationFactory = require('./src/automation-framework');

const factory = new AutomationFactory({
  logger: console,
  adspowerUserId: 'your-profile-id'
});

await factory.execute('mysite');
```

---

## YAML 配置结构

### 基本结构

```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): 导航后等待时间（毫秒）

**示例**：

```yaml
# 基本导航
- 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`

**示例**：

```yaml
# 基本表单填写
- 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`

**选择器格式**：
```yaml
selector:
  - css: '#submit-btn'
  - xpath: '//button[@id="submit"]'
  - text: 'Continue'
  - name: 'submit'
```

**示例**：

```yaml
# 简单点击
- 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

**示例**：

```yaml
# 固定延迟
- 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): 是否提取多个元素，默认 `false`
- `required` (boolean): 是否必需，默认 `true`

**示例**：

```yaml
# 简单文本提取
- 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): 超时时间（毫秒），默认 10000
- `pollInterval` (number): 轮询间隔（毫秒），默认 500
- `onSuccess` (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"}` |

**示例**：

```yaml
# 验证支付结果
- 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 使用**：

```yaml
- 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次成功，流程继续
```

**日志输出示例**：

```log
[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): 最大重试次数，默认 3
- `retryDelay` (number): 重试间隔（毫秒），默认 2000
- `onRetryBefore` (array): 重试前执行的步骤
- `onRetryAfter` (array): 重试后执行的步骤

**示例**：

```yaml
# 基本重试块
- 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`

**示例**：

```yaml
# 简单自定义调用
- 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"
```

**对应的适配器方法**：

```javascript
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** - 运行时数据

```yaml
- action: fillForm
  fields:
    email:
      value: "{{account.email}}"      # context.data.account.email
    
    cardNumber:
      value: "{{card.number}}"         # context.data.card.number
```

**2. config.site** - 站点配置

```yaml
- action: navigate
  url: "{{site.url}}"                  # config.site.url
```

### 在适配器中设置变量

```javascript
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'
    }
  };
}
```

---

## 站点适配器开发

### 基本结构

```javascript
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 - 刷新页面保持状态**
```yaml
params:
  retryStrategy: 'refresh'
```
- 适用于刷新后保持当前页面状态的网站
- 只刷新页面，不重新填写

**2. restart - 刷新后重新开始**
```yaml
params:
  retryStrategy: 'restart'
```
- 适用于刷新后回到第一步的网站（如 Windsurf）
- 自动调用 `onRestart()` 获取需要重新执行的步骤

**3. wait - 延长等待时间**
```yaml
params:
  retryStrategy: 'wait'
  waitTime: 10000
```
- 不刷新页面，只等待更长时间
- 适用于加载慢的情况

### 框架提供的工具方法

```javascript
// 日志
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. 步骤命名清晰

```yaml
# ❌ 不好
- action: click
  selector:
    - css: 'button'

# ✅ 好
- action: click
  name: "点击 Continue 按钮（基本信息页）"
  selector:
    - css: 'button[type="submit"]'
```

### 2. 使用多个选择器备选

```yaml
fields:
  email:
    find:
      - css: '#email'           # 首选
      - css: 'input[name="email"]'  # 备选1
      - xpath: '//input[@type="email"]'  # 备选2
```

### 3. 验证关键步骤

```yaml
# 导航后验证
- action: navigate
  url: "https://example.com/register"
  verifyElements:
    - '#username'
    - '#email'

# 点击后验证
- action: click
  name: "提交表单"
  selector:
    - css: '#submit'
  verifyAfter:
    appears:
      - '#success-message'
```

### 4. 合理使用 optional

```yaml
# 可能不存在的弹窗
- action: click
  name: "关闭欢迎弹窗"
  selector:
    - css: '.welcome-modal .close'
  optional: true

# 不影响主流程的数据提取
- action: extract
  name: "提取备注信息"
  selector: ".notes"
  contextKey: "notes"
  required: false
```

### 5. 模块化配置

```yaml
# 将重复逻辑抽取为 retryBlock
- action: retryBlock
  name: "支付流程"
  maxRetries: 5
  onRetryBefore:
    - action: custom
      handler: "regenerateCard"
  steps:
    # 复用的步骤组
    - action: fillForm
      ...
    - action: click
      ...
```

### 6. 适当使用 humanLike

```yaml
# 重要表单使用人类行为模拟
- action: fillForm
  name: "填写个人信息"
  humanLike: true  # 随机延迟、打字速度
  fields:
    firstName:
      ...

# 支付信息快速填写
- action: fillForm
  name: "填写银行卡"
  humanLike: false  # 快速填写
  fields:
    cardNumber:
      ...
```

### 7. 使用 verify + retryBlock 实现智能重试

```yaml
# ✅ 最佳实践：将需要重试的步骤放在 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: 
1. 使用 `verifyElements` 验证元素存在
2. 增加 `waitAfter` 等待时间
3. 使用多个选择器备选
4. 设置 `optional: true` 跳过

### Q: 如何处理动态内容？

A: 使用 `wait` action 等待元素出现：
```yaml
- action: wait
  type: element
  find:
    - css: '.dynamic-content'
  timeout: 15000
```

### Q: 变量替换不生效？

A: 检查：
1. 变量路径是否正确（如 `{{account.email}}`）
2. 是否在 `beforeWorkflow()` 中设置了 `this.context.data`
3. 日志中查看变量值

### Q: verify 失败会终止整个流程吗？

A: **不会！** 
- 在 `retryBlock` 内使用 `onFailure: "throw"`，失败会触发重试
- 只有所有重试都失败，才会终止流程
- 单次失败只是触发重试，不影响整体流程

### Q: 如何避免重试时重复处理验证码？

A: 将验证码处理放在 `retryBlock` 外面：
```yaml
# ✅ 正确：验证码只处理一次
- 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 实现智能重试

---

**框架持续演进中，欢迎贡献新功能！** 🚀
