# PRD — 自然语言用例智能定位功能

> **模块**: UI 自动化测试 — 智能元素定位
> **优先级**: P0
> **作者**: czj / Claude Code
> **创建日期**: 2026-08-05
> **最后修改**: 2026-08-05

---

## 一、需求背景

### 1.1 用户场景

用户在前端页面录入用例时，只需写自然语言步骤描述：

```
1. 访问登录页面
2. 输入用户名 admin@xty
3. 输入密码 Ubains@13579
4. 点击登录按钮
5. 点击功能中心
6. 点击信息发布
7. 点击新增按钮
```

系统自动访问被测系统，为每个步骤找到精确的定位元素，填充到用例中，使用例可直接执行。

### 1.2 当前问题

现有"获取定位"功能（`/api/element/locate-batch`）的工作方式：

```
自然语言步骤 → 提取页面所有元素 → Claude CLI 推断匹配 → 返回选择器
```

**问题**：

| # | 问题 | 示例 |
|---|------|------|
| 1 | AI 推断选择器不准确 | 返回 `.el-tabs` 但页面实际没有这个元素 |
| 2 | 只返回一个选择器，无备选 | 主选择器失效后用例直接失败 |
| 3 | 没有真实执行验证 | 选择器看起来对但点击不到 |
| 4 | 登录流程处理不完善 | 缺少勾选协议步骤导致无法登录 |

### 1.3 已验证的方案

在 2026-08-05 的测试中，通过 Playwright 脚本自动访问被测系统，自动执行操作并提取选择器，生成"信息发布-导航验证"用例（12 步），**执行 12/12 全部通过**。

验证结论：**Playwright 实际操作 + 提取选择器的方式是可靠的，不需要 AI 推断**。

---

## 二、需求目标

### 2.1 核心目标

用户录入自然语言步骤后，系统自动访问被测系统，**实际执行操作**并提取精确选择器，使生成的用例可直接执行通过。

### 2.2 与现有"获取定位"的区别

| 对比项 | 现有获取定位 | 智能定位（本需求） |
|--------|------------|-------------------|
| 方式 | 提取所有元素 → AI 匹配 | 实际执行操作 → 提取选择器 |
| 准确性 | AI 推断，不稳定 | 实际验证，高准确 |
| Claude CLI | 必须依赖 | 不需要 |
| 登录处理 | 简单登录 | 完整登录（含协议勾选） |
| 选择器数量 | 每步 1 个 | 每步多个候选 |
| 验证方式 | 无 | 实际执行验证 |

### 2.3 成功指标

| 指标 | 目标值 |
|------|--------|
| 定位选择器准确率 | ≥ 95% |
| 用例首次执行通过率 | ≥ 90% |
| 是否需要 Claude CLI | 不需要 |
| 单步骤定位延迟 | ≤ 3s |

---

## 三、功能需求

### FR-01: 自然语言步骤解析

将用户输入的自然语言步骤解析为可执行的操作：

| 自然语言 | 解析结果 |
|----------|----------|
| 访问登录页面 | `navigate` → `url: https://192.168.5.44` |
| 输入用户名 admin@xty | `fill` → `selector: ?, value: admin@xty` |
| 输入密码 Ubains@13579 | `fill` → `selector: ?, value: Ubains@13579` |
| 点击登录按钮 | `click` → `selector: ?` |
| 等待页面加载 | `wait` → `selector: ?, timeout: 10000` |
| 点击功能中心 | `click` → `selector: ?` |
| 点击信息发布 | `click` → `selector: ?` |

**关键点**：解析后 `selector` 为空，需要通过实际访问被测系统来填充。

**解析策略**：
- 关键词匹配动作类型：`输入`→fill，`点击`→click，`等待`→wait，`访问`→navigate，`选择`→select
- 提取操作值：`输入用户名 admin@xty` → value=`admin@xty`
- 提取目标元素描述：`点击登录按钮` → 目标=`登录按钮`

### FR-02: 自动访问被测系统并执行

系统使用 Playwright 自动访问被测系统，按步骤顺序执行操作，在执行过程中提取选择器。

**完整流程**：

```
1. 启动 Playwright 浏览器
2. 执行登录模板（自动登录）
3. 按步骤顺序：
   a. 根据自然语言描述，在当前页面查找匹配的元素
   b. 实际执行操作（点击/填写/等待）
   c. 提取精确选择器 + 候选选择器
   d. 截图
   e. 继续下一步
4. 关闭浏览器
5. 返回所有步骤的选择器
```

### FR-03: 元素查找策略

在每个步骤中，如何从自然语言描述找到页面上的目标元素：

**三级策略**（按优先级）：

#### 第一级：关键词直接匹配（最快，无需 AI）

```python
def find_element_by_keyword(page, description):
    """通过关键词在页面元素中直接匹配"""
    # 提取关键词
    keywords = extract_keywords(description)  # "登录按钮" → ["登录"]

    # 遍历页面所有可交互元素
    elements = page.locator('input, button, a, select, textarea, [role="button"]').all()

    for el in elements:
        # 匹配元素的文本内容
        text = el.inner_text().strip()
        if any(kw in text for kw in keywords):
            return el, extract_selectors(el)

        # 匹配 placeholder
        placeholder = el.get_attribute('placeholder') or ''
        if any(kw in placeholder for kw in keywords):
            return el, extract_selectors(el)

        # 匹配 aria-label
        aria = el.get_attribute('aria-label') or ''
        if any(kw in aria for kw in keywords):
            return el, extract_selectors(el)

    return None, None
```

**匹配规则**：

| 自然语言描述 | 关键词提取 | 匹配目标 | 匹配方式 |
|-------------|-----------|---------|---------|
| 输入用户名 | ["用户名", "手机号"] | placeholder | 包含匹配 |
| 输入密码 | ["密码"] | type=password | 精确匹配 |
| 输入验证码 | ["验证码", "图形"] | placeholder | 包含匹配 |
| 点击登录按钮 | ["登录"] | button 文本 | 包含匹配 |
| 点击功能中心 | ["功能中心"] | 文本/id | 包含匹配 |
| 点击信息发布 | ["信息发布"] | 文本 | 包含匹配 |
| 点击新增按钮 | ["新增", "添加"] | button 文本 | 包含匹配 |

#### 第二级：语义推断匹配

当关键词无法直接匹配时，根据操作类型推断目标元素：

| 操作 | 推断规则 |
|------|---------|
| `fill` + 有 value | 找输入框（input/textarea），优先匹配 placeholder 包含步骤描述关键词的 |
| `click` + 包含"按钮" | 找 button，优先匹配文本包含关键词的 |
| `click` + 包含"菜单"/"导航" | 找菜单项/链接 |
| `wait` + 包含"页面" | 找容器元素（.container, .el-main, [class*="nav"]） |
| `click` + 包含"抽屉"/"弹窗" | 找 .el-drawer, .el-dialog |

#### 第三级：页面快照 + 元素列表回退

如果前两级都没匹配到，提取页面所有可交互元素列表，按操作类型过滤后选择最可能的元素。

### FR-04: 多候选选择器

每个步骤提取多种选择器，执行时按优先级回退：

```json
{
  "order": 3,
  "name": "输入用户名",
  "action": "fill",
  "params": {
    "selector": "input[placeholder*=\"手机号\"]",
    "value": "admin@xty"
  },
  "selectors": {
    "primary": "input[placeholder*=\"手机号\"]",
    "candidates": [
      {"type": "css", "value": "input[placeholder*=\"手机号\"]", "confidence": 0.95},
      {"type": "css", "value": "#login-username", "confidence": 0.90},
      {"type": "xpath", "value": "//input[@placeholder[contains(.,'手机号')]]", "confidence": 0.80},
      {"type": "text", "value": "手机号/用户名/邮箱", "confidence": 0.70}
    ]
  },
  "element_info": {
    "tag": "INPUT",
    "type": "text",
    "placeholder": "手机号/用户名/邮箱",
    "text": "",
    "id": "",
    "className": "el-input__inner"
  }
}
```

### FR-05: 登录流程自动化

系统预置已验证的登录模板，自动处理登录（含协议勾选），用户无需手动配置登录步骤。

**预置登录模板（8 步，已验证通过）**：

| 步骤 | 名称 | 操作 | 选择器 | 值 |
|------|------|------|--------|-----|
| 1 | 访问登录页面 | navigate | - | https://192.168.5.44 |
| 2 | 等待登录表单加载 | wait | `input[placeholder*="手机号"]` | timeout:10000 |
| 3 | 输入用户名 | fill | `input[placeholder*="手机号"]` | admin@xty |
| 4 | 输入密码 | fill | `input[type="password"]` | Ubains@13579 |
| 5 | 输入验证码 | fill | `input[placeholder*="图"]` | csba |
| 6 | 勾选协议复选框 | click | `.el-checkbox` | - |
| 7 | 点击登录按钮 | click | `button:has-text("登录")` | - |
| 8 | 等待登录跳转完成 | wait | `[class*='nav'], .container, .el-main` | timeout:15000 |

**功能中心导航模板（3 步，已验证通过）**：

| 步骤 | 名称 | 操作 | 选择器 |
|------|------|------|--------|
| 9 | 点击功能中心图标 | click | `//*[@id="Home"]/div[1]/div[1]` |
| 10 | 等待功能抽屉打开 | wait | `.el-drawer` |
| 11 | 点击目标菜单 | click | `.el-drawer >> text="${菜单名}"` |

### FR-06: 定位结果展示与编辑

定位完成后，前端展示每个步骤的定位结果：

- ✅ 定位成功：显示主选择器 + 置信度 + 截图
- ⚠️ 定位不确定：显示候选选择器列表，用户可选择
- ❌ 定位失败：标记失败，用户可手动填写选择器

用户可编辑选择器后重新验证。

---

## 四、API 设计

### 4.1 智能定位 API

#### POST /api/element/smart-locate

**请求体**：

```json
{
  "steps": [
    {"order": 1, "name": "访问登录页面", "action": "navigate", "params": {"url": "https://192.168.5.44"}},
    {"order": 2, "name": "输入用户名", "action": "fill", "params": {"value": "admin@xty"}},
    {"order": 3, "name": "输入密码", "action": "fill", "params": {"value": "Ubains@13579"}},
    {"order": 4, "name": "点击登录按钮", "action": "click"},
    {"order": 5, "name": "点击功能中心", "action": "click"},
    {"order": 6, "name": "点击信息发布", "action": "click"},
    {"order": 7, "name": "点击新增按钮", "action": "click"}
  ],
  "auto_login": true,
  "navigate_menu": "信息发布",
  "page_url": "https://192.168.5.44"
}
```

**响应**：

```json
{
  "success": true,
  "total_steps": 7,
  "located_steps": 7,
  "results": [
    {
      "order": 1,
      "name": "访问登录页面",
      "success": true,
      "action": "navigate",
      "params": {"url": "https://192.168.5.44"},
      "selectors": {
        "primary": null,
        "candidates": []
      },
      "screenshot": "base64..."
    },
    {
      "order": 2,
      "name": "输入用户名",
      "success": true,
      "action": "fill",
      "params": {
        "selector": "input[placeholder*=\"手机号\"]",
        "value": "admin@xty"
      },
      "selectors": {
        "primary": "input[placeholder*=\"手机号\"]",
        "candidates": [
          {"type": "css", "value": "input[placeholder*=\"手机号\"]", "confidence": 0.95},
          {"type": "xpath", "value": "//input[contains(@placeholder,'手机号')]", "confidence": 0.80}
        ]
      },
      "element_info": {
        "tag": "INPUT",
        "placeholder": "手机号/用户名/邮箱",
        "type": "text"
      },
      "screenshot": "base64..."
    }
  ],
  "message": "成功定位 7/7 个步骤"
}
```

### 4.2 登录模板 API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/login-templates` | 获取所有登录模板 |
| POST | `/api/login-templates` | 创建登录模板 |
| PUT | `/api/login-templates/{id}` | 更新登录模板 |
| DELETE | `/api/login-templates/{id}` | 删除登录模板 |

---

## 五、核心算法

### 5.1 整体流程

```python
def smart_locate(steps, auto_login=True, navigate_menu=""):
    """智能定位主流程"""
    # 1. 启动浏览器
    executor = PlaywrightExecutor()
    executor.start()

    # 2. 自动登录（使用预置模板）
    if auto_login:
        executor.do_login()  # 执行登录模板8步

    # 3. 导航到目标菜单
    if navigate_menu:
        navigate_to_menu(executor, navigate_menu)  # 功能中心3步

    # 4. 逐步骤定位
    results = []
    for step in steps:
        result = locate_step(executor, step)
        results.append(result)

        # 如果定位成功，实际执行操作（确保下一步在正确的页面状态）
        if result["success"]:
            execute_step(executor, result)

    # 5. 关闭浏览器
    executor.stop()

    return results
```

### 5.2 单步骤定位

```python
def locate_step(executor, step):
    """为单个步骤定位元素"""
    action = step["action"]
    name = step["name"]

    # navigate 类型不需要定位
    if action == "navigate":
        return {"success": True, "selectors": None}

    # 提取关键词
    keywords = extract_keywords(name)

    # 第一级：关键词直接匹配
    element, selectors = find_element_by_keyword(executor.page, keywords, action)

    if element:
        return {
            "success": True,
            "selectors": selectors,
            "element_info": extract_element_info(element)
        }

    # 第二级：语义推断
    element, selectors = find_element_by_semantic(executor.page, keywords, action)

    if element:
        return {
            "success": True,
            "selectors": selectors,
            "element_info": extract_element_info(element)
        }

    # 第三级：回退
    return {"success": False, "message": f"未找到匹配元素: {name}"}
```

### 5.3 关键词提取

```python
# 动作词表
ACTION_WORDS = ["点击", "输入", "选择", "等待", "访问", "勾选", "填写", "按下", "切换"]

# 元素类型词表
ELEMENT_WORDS = ["按钮", "输入框", "下拉", "菜单", "链接", "复选框", "表格", "标签", "图标"]

def extract_keywords(description):
    """从自然语言描述中提取关键词"""
    # 去除动作词
    text = description
    for word in ACTION_WORDS + ELEMENT_WORDS:
        text = text.replace(word, "")

    # 分词（按空格、标点分割）
    words = [w.strip() for w in re.split(r'[\s，,、]', text) if w.strip()]

    return words
```

---

## 六、与现有"获取定位"的关系

### 6.1 替代关系

本功能**替代**现有的 `/api/element/locate-batch` 接口，不再需要 Claude CLI。

### 6.2 复用部分

| 组件 | 处理方式 |
|------|---------|
| `PlaywrightExecutor` | 复用，扩展 `do_login()` 和 `navigate_to_menu()` |
| `extract_interactive_elements()` | 复用，用于元素列表提取 |
| 前端配置弹窗 | 改造，去掉 Claude CLI 相关配置 |
| WebSocket 推送 | 复用，实时推送定位进度 |

### 6.3 不再需要

| 组件 | 原因 |
|------|------|
| `call_claude_batch_via_ssh()` | 不再依赖 Claude CLI |
| SSH 连接逻辑 | 不再需要连接宿主机 |
| AI 置信度判断 | 用实际执行验证代替 |

---

## 七、非功能需求

| # | 需求 | 说明 |
|---|------|------|
| 1 | 定位过程互斥 | 同一时间只能有一个定位任务运行 |
| 2 | 超时保护 | 单次定位最长 5 分钟 |
| 3 | 浏览器资源清理 | 定位完成后自动关闭浏览器 |
| 4 | 兼容现有用例格式 | 生成的步骤格式与现有用例完全兼容 |

---

## 八、约束与风险

| # | 风险 | 缓解措施 |
|---|------|----------|
| 1 | 关键词提取不准 | 多级匹配策略 + 语义推断 + 回退 |
| 2 | 微前端 shadowRoot 内元素 | 遍历 shadowRoot + micro-app 容器 |
| 3 | iframe 内元素 | 注入脚本到 iframe contentDocument |
| 4 | Windows 同步 API 约束 | 使用 sync_playwright + 线程池 |
| 5 | 页面动态加载 | 每步操作后等待 + 截图验证 |

---

*本文档由 Claude Code 于 2026-08-05 创建。*
