# 脚本录制经验总结

> 生成时间：2026-08-05
> 作者：czj / Claude Code

---

## 一、核心流程

```
编写录制脚本 → 运行并调试 → 记录有效选择器 → 通过API创建用例 → 验证执行
```

### 关键步骤

1. **启动浏览器**：必须忽略 SSL 证书（被测系统用自签名证书）
2. **登录流程**：需要处理协议复选框、验证码、跳转等待
3. **导航流程**：功能中心 → 抽屉菜单 → 目标页面
4. **记录步骤**：每一步操作记录 action、selector、value
5. **创建用例**：通过 API 写入数据库
6. **验证执行**：调用 `/api/executions/{id}/run` 触发执行

---

## 二、踩坑记录

### 2.1 登录相关

| # | 问题 | 原因 | 解决方案 |
|---|------|------|---------|
| 1 | 验证码输入框选错 | 登录页有 6 个输入框（账号登录/短信登录两套） | 用 `input[placeholder*="图"]` 精确匹配图形验证码 |
| 2 | 登录一直停在 login 页面 | 必须先勾选协议复选框 | 添加 `.el-checkbox` 点击步骤 |
| 3 | 登录跳转超时 | 微前端架构加载慢（7-8秒） | `wait` 的 timeout 设 15000ms |
| 4 | `wait` 操作报错 | 执行引擎要求 wait 必须有 selector | 修改为 `wait` 带选择器：`[class*='nav'], .container, .el-main` |

### 2.2 导航相关

| # | 问题 | 原因 | 解决方案 |
|---|------|------|---------|
| 5 | 功能中心 `.home_nav_left` 找不到 | 该 class 在当前页面不存在 | 使用 XPath：`//*[@id="Home"]/div[1]/div[1]` |
| 6 | 功能抽屉菜单项点击 | 抽屉中的菜单项是文本 | 使用组合选择器：`.el-drawer >> text="信息发布"` |

### 2.3 执行引擎相关

| # | 问题 | 原因 | 解决方案 |
|---|------|------|---------|
| 7 | 执行一直卡在 pending | 创建执行后需要调用 `/run` 端点 | POST `/api/executions/{id}/run` |
| 8 | 步骤结果为空 | `wait` 操作只传了 timeout 没有 selector | wait 必须传 selector |

---

## 三、有效选择器汇总

### 3.1 登录页面

| 元素 | 选择器 | 类型 |
|------|--------|------|
| 用户名输入框 | `input[placeholder*="手机号"]` | CSS |
| 密码输入框 | `input[type="password"]` | CSS |
| 图形验证码输入框 | `input[placeholder*="图"]` | CSS |
| 协议复选框 | `.el-checkbox` | CSS |
| 登录按钮 | `button:has-text("登录")` | CSS+文本 |

### 3.2 首页

| 元素 | 选择器 | 类型 |
|------|--------|------|
| 功能中心图标 | `//*[@id="Home"]/div[1]/div[1]` | XPath |
| 导航区域 | `[class*='nav'], .container, .el-main` | CSS |

### 3.3 功能抽屉

| 元素 | 选择器 | 类型 |
|------|--------|------|
| 抽屉容器 | `.el-drawer` | CSS |
| 菜单项 | `.el-drawer >> text="信息发布"` | CSS+文本 |

---

## 四、录制脚本模板

```python
from playwright.sync_api import sync_playwright
import json, time

steps = []

def add_step(order, name, action, params, expected=""):
    step = {
        "order": order, "name": name, "action": action,
        "params": params, "expected": expected, "actual": "",
        "selectors": None, "page_key": None, "element_key": None,
        "force": None, "wait_after": None,
        "locator_type": None, "locator_value": None
    }
    steps.append(step)

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False,
        args=['--ignore-certificate-errors']
    )
    context = browser.new_context(ignore_https_errors=True)
    page = context.new_page()

    # 1. 登录
    page.goto('https://192.168.5.44/', wait_until='networkidle')
    add_step(1, "访问登录页面", "navigate", {"url": "https://192.168.5.44/"})

    page.wait_for_selector('input[placeholder*="手机号"]', timeout=10000)
    add_step(2, "等待登录表单加载", "wait", {"selector": 'input[placeholder*="手机号"]', "timeout": 10000})

    page.fill('input[placeholder*="手机号"]', 'admin@xty')
    add_step(3, "输入用户名", "fill", {"selector": 'input[placeholder*="手机号"]', "value": "admin@xty"})

    page.fill('input[type="password"]', 'Ubains@13579')
    add_step(4, "输入密码", "fill", {"selector": 'input[type="password"]', "value": "Ubains@13579"})

    page.fill('input[placeholder*="图"]', 'csba')
    add_step(5, "输入验证码", "fill", {"selector": 'input[placeholder*="图"]', "value": "csba"})

    page.locator('.el-checkbox').click()
    add_step(6, "勾选协议复选框", "click", {"selector": '.el-checkbox'})

    page.click('button:has-text("登录")')
    add_step(7, "点击登录按钮", "click", {"selector": 'button:has-text("登录")'})

    # 等待登录跳转
    for i in range(15):
        time.sleep(1)
        if 'login' not in page.url:
            break
    add_step(8, "等待登录跳转完成", "wait", {"selector": "[class*='nav'], .container, .el-main", "timeout": 15000})

    # 2. 导航到目标模块
    page.click('//*[@id="Home"]/div[1]/div[1]')
    add_step(9, "点击功能中心图标", "click", {"selector": '//*[@id="Home"]/div[1]/div[1]'})

    page.wait_for_selector('.el-drawer', timeout=5000)
    add_step(10, "等待功能抽屉打开", "wait", {"selector": '.el-drawer', "timeout": 5000})

    page.click('.el-drawer >> text="信息发布"')
    add_step(11, "点击信息发布菜单", "click", {"selector": '.el-drawer >> text="信息发布"'})

    # 3. 在目标页面操作...
    # page.wait_for_selector('.el-table', timeout=10000)
    # add_step(12, "等待页面加载", "wait", {"selector": '.el-table', "timeout": 10000})

    browser.close()

# 保存步骤
with open('recorded_steps.json', 'w', encoding='utf-8') as f:
    json.dump(steps, f, ensure_ascii=False, indent=2)
```

---

## 五、与前端"获取定位"功能的对比

### 5.1 当前"获取定位"功能

- **入口**：用例管理页面 → 点击"获取定位"按钮
- **流程**：打开配置弹窗 → 调用后端 `/api/element/locate-batch` → 后端通过 Claude CLI 分析页面元素
- **局限**：
  - 需要手动填写步骤描述
  - 依赖 Claude CLI 进行元素定位（可能不准确）
  - 无法自动导航到目标页面（需要手动配置菜单）

### 5.2 脚本录制方式的优势

- **直接操作**：在浏览器中真实操作，所见即所得
- **选择器精确**：通过 Playwright 的 `page.fill()`、`page.click()` 等方法自动获取选择器
- **完整流程**：从登录到导航到操作，全流程录制

### 5.3 可应用的方向

1. **录制器增强**：将脚本录制模式集成到前端的"用例录制"功能
2. **智能选择器提取**：在录制过程中自动提取元素的所有可用选择器（CSS、XPath、文本）
3. **选择器回退策略**：录制时同时记录多个候选选择器，执行时按优先级回退
4. **登录流程模板化**：将登录步骤固化为模板，新用例自动包含

---

## 六、已验证的用例

| 用例名称 | 用例ID | 模块 | 步骤数 | 执行结果 |
|----------|--------|------|--------|---------|
| 信息发布-导航验证 | case_a6e96c9a38934456901a8a40b9786bf2 | 信息发布 | 12 | ✅ 全部通过 |

---

*本文档由 Claude Code 于 2026-08-05 生成，记录脚本录制经验总结。*
