# PRD — 复杂用例通用执行机制：语义化用例执行

> **文档版本**: v1.0
> **创建日期**: 2026-08-18
> **作者**: Claude Code（czj 确认）
> **状态**: 待确认
> **关联文档**: `HANDOFF_UI自动化.md`（会话 9-20 全部补丁记录）

---

## 一、背景与问题

### 1.1 当前困境：复杂用例靠"打补丁"才能稳定

目标用例「会议管理-新建会议-czj录入」（21 步）经过 10+ 个会话、几十处补丁后才在服务器 5.60 稳定（API + UI 双链路 100% 通过）。但每处修复都是**针对特定页面/特定文本的特例**：

| 补丁位置 | 补丁内容 | 本质 |
|---------|---------|------|
| `element_mapping_service.py` `_exact_match`（L224-441） | 30+ 条加分规则（"预定会议"+20、"编辑"+12、"完成"+16……） | 在模糊文本评分上叠补丁，规则互相踩踏 |
| `playwright_executor.py` | `img:nth-of-type(2)` 编辑按钮、行级 checkbox、原选择器优先等特例 | 为特定 DOM 结构写死 |
| `elements_mapping.json` | 逐条补子页面条目 | 每个新页面都要人工探测 |

**结果**：每个新复杂用例（新建工单、预订会议室、新建会议）都要重新走"实测 DOM → 加映射条目 → 调加分 → 部署验证"的循环，问题无限重复。

### 1.2 根本原因：范式缺陷

当前范式是：**存储易碎 CSS 选择器 + 模糊文本评分匹配 + 无上下文 + 无页面状态**。

1. **选择器易碎**：录制/手写的 CSS 选择器依赖页面结构，动态渲染数据（会议室名称、参会人账号）无法写入静态选择器
2. **模糊评分不可靠**：30+ 条加分规则是在"文本相似度"上人工堆叠，无法根治"步骤文本 → 错误页面层级元素"的误配
3. **无执行期上下文**：UI 执行链路完全没有变量替换（`execution_service.py` 直接传 `case.to_dict()`），步骤之间无法传递数据
4. **无页面状态感知**：`page_url_service.recognize_page` 是"从静态步骤预测目标页"，不是"运行时读取当前浏览器在哪一页"；点击后不校验页面状态 → 存在"假通过"
5. **无数据自建/自清理**：测试数据依赖被测系统已有数据，残留会议污染下次执行

### 1.3 方案来源（前端协同）

前端开发人员将提供**页面元素 + 页面路由映射表**：
- 静态页面元素基本**唯一**（`data-key` / `data-id` / 稳定 class / 唯一文本）
- 主要难点是**动态渲染数据**（会议室名称、参会人账号等运行期值）

### 1.4 解决思路

**范式转变：从"记录选择器"到"记录业务意图"。**

用例步骤不再只存 CSS 选择器，而是存**业务意图**（`semantic` 目标：按钮[预定会议]、表格行[会议室=X]、勾选[参会人=admin]），执行期由解析器结合页面真实 DOM 现场定位；配合**上下文变量**（动态数据）、**页面状态验证**（防假通过）、**数据自建/自清理**（无残留）三大机制。

---

## 二、目标与非目标

### 2.1 目标

1. **建立语义化步骤模型**：步骤新增 `semantic`（语义目标）/`verify`（状态验证）/`run_on`（执行时机）字段，业务意图显式表达
2. **执行期现场定位**：语义目标 → 候选选择器 → DOM 验证 → 执行，复用现有全部成熟点击/填充回退策略
3. **上下文变量替换**：用例参数 + 前步 `save_as` 数据 + 内置生成器，UI 执行链路首次支持变量
4. **运行时页面身份感知**：页面注册表 + URL 模式 + DOM 指纹，语义目标带 `scope` 根治跨层级误配
5. **状态验证防假通过**：每步执行后校验预期页面状态，失败即真实失败
6. **数据自建/自清理**：`api_call` 步骤自建数据，`finally` 步骤无论成败都清理
7. **映射表降级为显式查找字典**：废弃 30+ 条模糊评分，前端映射表按"页面作用域 + 显式语义键"精确查找
8. **完全向后兼容**：现有 21 步用例不填新字段、不依赖新机制，必须持续通过

### 2.2 非目标

1. 不替换现有执行器——语义化是**新增的步骤表示能力**，老步骤走原路径
2. 不引入坐标定位（已论证不稳定，见 `_难点分析_智能定位功能策略可行性评估报告.md`）
3. 不做全局用例前置/后置（用 `run_on="finally"` 用例内清理，后续用 `depends_on` 扩展）
4. 不依赖每步 Claude 仲裁（规则优先，Claude 仅候选歧义时按需）
5. 不修改前端代码（使用前端已提供的 data 属性/稳定选择器）

---

## 三、总体架构

### 3.1 选择器优先级链（重构后）

```
execute_step 顶部（读 params 之后、Phase 3 映射注入之前）：
  1. step.semantic      结构化语义目标（新增，最高）
  2. params.selector/selectors/page_key+element_key（显式选择器，录制/手写）
  3. mapping v2 显式字典 lookup_by_key(page_id, element_key)（前端映射表，精确查找）
  4. keyword_matcher 规则解析（仅 legacy 自然语言步骤 + 配置开关）
  5. Claude 仲裁（仅候选歧义时）
```

解析结果统一**注入 `params["selectors"]`**，继续走现有 `_do_click`/`_do_fill`/`_do_check`/`_do_assert` 全部成熟回退策略（iframe/force/表格行/checkbox 状态校验），最大化复用与向后兼容。

### 3.2 架构图

```
┌────────────────────────────────────────────────────────────────┐
│                    Playwright 执行器                             │
│              playwright_executor.py                              │
│  execute_step: ctx.render(params) → semantic 解析 → 映射 v2 → 动作 │
└───────────────┬──────────────────────────────────┬─────────────┘
                │                                   │
    ┌───────────▼──────────┐          ┌─────────────▼───────────┐
    │  ExecutionContext    │          │  SemanticResolver       │
    │  ui_context.py       │          │  semantic_resolver.py   │
    │  {__CTX:key__} 渲染   │          │  目标→候选→DOM验证→注入链 │
    └──────────────────────┘          └─────────────┬───────────┘
                                                    │
        ┌──────────────────┬──────────────────┬─────▼──────────┐
        ▼                  ▼                  ▼                ▼
┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ 页面注册表    │  │ 映射表 v2    │  │ keyword_      │  │ Claude 仲裁   │
│ page_url_    │  │ 显式字典      │  │ matcher 规则  │  │ rank_candidates│
│ mapping.json │  │ lookup_by_key│  │ (legacy)     │  │ (按需)        │
└──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘
```

---

## 四、六大机制设计

### 4.1 语义目标数据模型（机制一）

**位置**：`StepDefinition` 新增**顶层可选字段** `semantic` / `verify` / `run_on`（不放 params——params 执行期被 Phase 3 改写污染；顶层字段 recorder `model_dump` 天然透传）。

**注意**：Pydantic v2 默认 `extra="ignore"`，前端若发 `semantic` 而 schema 未声明会被静默丢弃——schema 必须同步声明。

```json
{
  "order": 12,
  "name": "时间切换为：预定会议",
  "action": "click",
  "semantic": {
    "type": "tab",
    "text": "预定会议",
    "contains": null,
    "scope": "create_meeting",
    "role": "tab",
    "nth": 0,
    "container": { "type": "dialog", "selector": ".el-dialog", "title": "新建会议" },
    "save_as": null
  },
  "verify": {
    "type": "text_visible",
    "selector": ".time_btn .el-tabs__item.is-active",
    "text": "预定会议",
    "timeout": 5000,
    "must": true
  },
  "run_on": "always"
}
```

**`semantic.type`**（驱动候选生成策略）：`button` / `input` / `textarea` / `checkbox` / `row_checkbox` / `row` / `tab` / `link` / `menu` / `icon` / `text` / `dialog`。

**字段说明**：

| 字段 | 说明 |
|------|------|
| `text` | 主匹配文本（渲染后用于映射表精确键 + DOM 文本/placeholder/aria-label 匹配） |
| `contains` | 行/单元格次级过滤（如表格行必须含 `admin@xty12`） |
| `scope` | 页面注册表 id，只在正确页面内定位（根治跨层级误配） |
| `role` | 语义角色（映射 Playwright `get_by_role`） |
| `container` | 父容器约束 `{type: dialog/drawer/table/tab/card, selector, title}` → 生成组合选择器 |
| `nth` | 同命中取第 N 个（如 `.delimg img:nth-of-type(2)`） |
| `save_as` | 把定位到的元素文本/值写入上下文 |

**与现有 `selectors` 的关系**：`semantic` 存在时解析器产出选择器链注入 `params["selectors"]`，二者不互斥；显式 `selectors` 始终是兜底链。

### 4.2 上下文系统与变量替换（机制二）

**位置**：新增 `backend/app/executors/ui_context.py`；**复用 `TemplateResolver`**（PATTERN `\{__([A-Za-z_+0-9:]+?)__\}` 允许冒号）。

**占位符语法**（与现有 `{__XXX__}` 兼容）：

| 占位符 | 说明 | 来源 |
|--------|------|------|
| `{__NOW__}` `{__NOW_+2_H__}` `{__RANDOM_NAME_8__}` `{__UUID__}` `{__RANDOM_INT_1_100__}` | 内置生成器 | 复用 `TemplateResolver` |
| `{__CTX:meetingName__}` | 引用上下文变量 | 新增 `CTX:` token handler |
| `{__CTX:meetingName__}的议题` | 占位符可嵌任意字符串 | 递归字符串替换 |

**`ExecutionContext`**：
- `values: Dict[str, Any]`，初始化来源：`case.parameters`（`{"meetingName":"自动化-{__RANDOM_NAME_8__}", "room":"北京展厅会议室", ...}`，`execute_case` 入口按序渲染一次）；缺省内置 `{__CTX:caseName__}` / `{__CTX:caseId__}`
- `render(value)`：递归 dict/list/str；字符串先跑内置生成器，再替换 `{__CTX:key__}`；未解析占位符保留原文 + 日志警告
- `set(key, value)` / `get(key)`

**渲染点**：
- `execute_step` 顶部整步渲染（url/value/selector/expected/semantic.text/verify.text）
- `_do_api_call` 内对 api_case 的 body/url/headers 再渲染
- **渲染后不写回 step**，只作用于 params 局部副本——防重复渲染放大 `{__NOW__}` 类值

**防污染**：`run_all_cases_sync`（execution_service.py L306-376）在专用线程复用一个 executor 跑多用例——`execute_case` 入口必须**每用例重置 ctx**。

### 4.3 执行期定位解析器（机制三，核心）

**位置**：新增 `backend/app/services/semantic_resolver.py`。

**核心流程**：

```
resolve_step(page, step, scope, ctx):
  if step.get("semantic"):           return resolve_semantic(page, step["semantic"], scope, ctx)
  if has_explicit_locator(step):     return None                        # 用原样 params
  if config.USE_KEYWORD_MATCHER:     return resolve_by_keyword(page, step, scope, ctx)
  return None
```

**resolve_semantic（语义目标 → 选择器链）**：

1. **文本渲染**：`text = ctx.render(target.text)`
2. **Scope 校验**：`target.scope` 与当前页面身份不符 → 若已登录且注册表有该页 URL → 直达导航；否则抛 `ScopeMismatch`（杜绝跨层级误配）
3. **映射表 v2 精确键**：`mapping.lookup_by_key(target.scope, target.text or element_key)` → 命中直接返回选择器链
4. **规则候选**：`keywords = extract_keywords(text)`；按 `type` 映射动作；`match_element_by_keywords(page, keywords, action)`；无命中走 `find_element_by_semantic`
   - **性能关键**：`match_element_by_keywords` 全页扫会枚举上千元素——scope 已知时把 `base_selector` 收缩到 scope 容器内，既提速又防跨容器误配
5. **DOM 过滤**：按 `contains` / `container` / `nth` 过滤候选
6. **歧义仲裁（Claude 按需）**：仅当过滤后候选 **>1** 且规则无法用 `container`/`nth` 定夺时，`get_candidate_details` → `claude_service.rank_candidates`；`confirmed=false` 或失败回退第 0 个
7. **产出选择器链**（注入 `params["selectors"]`）：语义定位器（`role:/text:/placeholder:/label:/testid:`）→ DOM 派生 CSS → 表格行回退（`.el-table__row:has-text(...)`，与 `_do_click` 策略 5/6 一致）
8. **save_as**：定位后提取元素文本/input_value 写入 ctx

**resolve_by_keyword（legacy 自然语言步骤过渡路径）**：`extract_keywords` → `match_element_by_keywords` → 歧义走 Claude → 注入。这是把 `keyword_matcher.py`（现在完全未接入执行器）真正 wire 到执行链。

**wire 点**：`execute_step` 中 `params = step.get("params", {})`（L1066）之后、Phase 3 映射注入（L1073）之前调用，命中则 `params["selectors"] = chain`。

### 4.4 页面注册表（机制四：运行时页面身份感知）

**位置**：`page_url_mapping.json` 从"1 页静态识别"升级为"页面注册表"；`page_url_service.py` 新增 `recognize_current_page`。

```json
{
  "pages": [
    { "id": "create_meeting", "name": "新建会议",
      "url_patterns": [".*meetingV3.*CreateMeeting.*"],
      "fingerprint": [
        { "selector": ".el-dialog__title", "text": "新建会议", "min_count": 1 },
        { "selector": ".topic_item input", "min_count": 1 }
      ],
      "skip_steps": { ... } },
    { "id": "login", "name": "登录页",
      "url_patterns": [".*#/login.*", ".*platform%2Flogin.*"],
      "fingerprint": [ { "selector": "input[placeholder*=\"手机号\"]", "min_count": 1 } ] },
    { "id": "home", "name": "主页",
      "url_patterns": [".*Home.*"],
      "fingerprint": [ { "selector": ".block", "min_count": 1 } ] },
    { "id": "meeting_detail", "name": "会议详情",
      "url_patterns": [".*MeetingDetail.*"],
      "fingerprint": [ { "selector": ".buttom_btn", "min_count": 1 } ] }
  ]
}
```

**`recognize_current_page(page)`**：
- `url_patterns` 逐一 `re.search(page.url)` 计分（URL 先经 `_replace_base_url` 归一化）
- URL 命中 ≤1 页直接返回；0 或歧义时跑 `fingerprint`：`page.query_selector(sel)` 且（text 非空时）`text_content()==text` 计数 ≥ `min_count`
- 返回 `(scope_id, confidence)`，全无命中返回 `None`

**运行时**：登录成功/导航后更新 `self._current_scope`；语义目标带 `scope` 时只有 `current_scope == scope`（或可导航）才放行解析。

### 4.5 状态验证（机制五：防假通过）

**位置**：`execute_step` 动作执行后、记录结果前。

**`verify` 字段**（顶层可选）：

| verify.type | 含义 | 复用 |
|------------|------|------|
| `text_visible` / `text_hidden` | 文本出现/消失 | `_do_assert` contains |
| `element_visible` / `element_hidden` | 元素可见/隐藏 | `_do_assert` visible/hidden |
| `url_contains` | URL 包含 | `_do_assert` url_contains |
| `dialog` / `drawer` | 弹窗/抽屉出现 | `_do_assert` element_exists |
| `value` | 输入值相等 | `_do_assert` value |

- `verify.must` 缺省 `false`：验证失败只 warn 不 raise；`true` 则步骤真实失败
- `expected` 是"人类可读期望"，`verify` 是"机器可校验状态"；`config.enforce_verification`（默认 false）开启时，`expected` 非空且无 `verify` 的 click/fill 步骤走默认校验

### 4.6 数据自建/自清理（机制六：api_call + finally）

**`api_call` 步骤**（新 action）：

```json
{
  "order": 1, "name": "创建会议数据", "action": "api_call",
  "params": {
    "api_case": {
      "name": "创建会议API",
      "steps": {
        "method": "POST", "url": "/api/meeting/create",
        "body": { "name": "{__CTX:meetingName__}", "roomId": "r1" },
        "assertions": [ { "type": "status_code", "operator": "equals", "value": 200 } ]
      }
    },
    "save_as": { "meetingId": "$.data.id" }
  }
}
```

- **复用 `ApiTestExecutor`**（`api_test_executor.py` L375）：构造 client_config（base_url=`settings.TARGET_URL`），`start()` → `execute_case(api_case)` → `stop()`。同步、不依赖浏览器、无 Windows/Playwright 事件循环冲突，可在 UI 执行线程内直接调用
- `save_as` 用 `AssertionEngine._extract_json_path`（L318）从响应体取路径值写 ctx
- `ApiCaseResult.status == "failed"` → raise（步骤真实失败）

**`run_on` 语义**（顶层可选，默认 `"always"`）：

| 值 | 行为 |
|----|------|
| `always` | 正常步骤 |
| `finally` | 无论前面成败都执行；失败不掩盖主结果（主结果 passed 而 finally 失败 → 用例 failed 并标注"清理步骤失败"） |
| `on_failure` / `on_success` | 条件执行（预留） |

**execute_case 改动**：
- 主循环遇失败时，"剩余步骤标记跳过"只跳过 `run_on=="always"` 与 `"on_success"`；`"finally"`/`"on_failure"` 不跳过
- 外层 `finally` 调 `_run_finally_steps`（异常路径也保证执行）

---

## 五、映射表 v2 格式规范（前端开发填写）

**目标**：`elements_mapping.json` 从"231 条扁平 + 模糊评分"升级为"页面作用域 + 显式语义键 + 选择器链"。

**关键变化**：
1. 元素**嵌套在 page 下**，键即显式语义键（`lookup_by_key(page_id, key)` 纯 dict 查找，无评分）
2. 每元素是**有序选择器链**（`selectors` 数组，高优先级在前）
3. `type`/`role` 供语义目标类型映射
4. `shared_elements` 处理登录页/全局弹窗按钮等跨页元素
5. 旧 v1 扁平 `elements` 保留加载，legacy 键仍可用

```json
{
  "version": "2.0",
  "updated_at": "2026-08-18",
  "source": "前端开发人员提供：页面元素 + 页面路由映射表",
  "pages": [
    {
      "id": "create_meeting",
      "name": "新建会议",
      "url": "https://.../#/meetingV3?...#/CreateMeeting",
      "url_patterns": [".*meetingV3.*CreateMeeting.*"],
      "fingerprint": [
        { "selector": ".el-dialog__title", "text": "新建会议", "min_count": 1 },
        { "selector": ".topic_item input", "min_count": 1 }
      ],
      "elements": {
        "预定会议": {
          "selectors": [".time_btn .btns:has-text(\"预定会议\")", "button:has-text(\"预定会议\")"],
          "type": "tab", "role": "tab",
          "description": "时间切换 Tab：预定会议"
        },
        "会议名称输入框": {
          "selectors": ["input[placeholder*=\"会议名称\"]", ".topic_item input"],
          "type": "input",
          "description": "创建会议页会议名称输入框"
        },
        "会议室选择": {
          "selectors": [".el-table__row:has-text(\"北京展厅会议室\") .el-checkbox", ".el-checkbox:has-text(\"北京展厅会议室\")"],
          "type": "row_checkbox",
          "description": "右侧会议室表格行勾选"
        },
        "编辑按钮": {
          "selectors": [".room .delimg", ".room .delimg img"],
          "type": "icon",
          "description": "左侧会议室框编辑图标（切换参会人配置模式）"
        },
        "参会人勾选": {
          "selectors": [".el-table__row:has-text(\"admin@xty12\") .el-checkbox", ".el-checkbox:has-text(\"admin@xty12\")"],
          "type": "row_checkbox"
        },
        "确定创建按钮": { "selectors": ["button:has-text(\"确定创建\")"], "type": "button" },
        "取消会议按钮": { "selectors": [".buttom_btn button:has-text(\"取消会议\")"], "type": "button" }
      }
    }
  ],
  "shared_elements": {
    "登录页-账号输入框": { "selectors": ["input[placeholder*=\"手机号\"]"], "type": "input" },
    "登录页-密码输入框": { "selectors": ["input[type=\"password\"]"], "type": "input" },
    "登录按钮": { "selectors": ["button:has-text(\"登录\")"], "type": "button" },
    "全局确定按钮": { "selectors": [".el-message-box button.el-button--primary:has-text(\"确定\")"], "type": "button" }
  }
}
```

**前端开发交付要求**：
- 每个业务页面：`id`（如 `create_meeting`）+ `url_patterns`（进入该页的 URL 正则）+ `fingerprint`（页面特征元素选择器）+ `elements`（页面内全部可交互元素）
- 每个元素：`selectors` 数组（2-3 个回退）+ `type`（button/input/tab/row_checkbox/icon 等）
- 跨页元素（登录、全局弹窗按钮）放 `shared_elements`
- **动态数据不写进映射表**：会议室名称、参会人账号等运行期值由语义目标 `text`/`contains` 携带（渲染自上下文），映射表只放静态结构

---

## 六、分阶段实施计划

| 阶段 | 内容 | 关键文件 | 验证 |
|------|------|---------|------|
| **1** | 数据模型 + 上下文骨架：`semantic`/`verify`/`run_on` 字段、`parameters` 暴露、`ui_context.py`、执行器 ctx 渲染 | `schemas/test_case.py`、`executors/ui_context.py`、`executors/playwright_executor.py`、`frontend/src/types/case.ts` | 渲染器单测 + 21 步用例回归 |
| **2** | 页面注册表 + 运行时页面身份：`url_patterns`+`fingerprint`、`recognize_current_page` | `page_url_mapping.json`、`page_url_service.py` | debug 接口返回识别页，行为零变化 |
| **3** | 语义解析器接入（核心）：`semantic_resolver.py`、keyword_matcher wire、Claude 按需 | `services/semantic_resolver.py`、`playwright_executor.py`、`keyword_matcher.py`、`claude_service.py` | `/api/debug/resolve` 真实页面返回解析链 |
| **4** | 状态验证 + api_call + finally | `playwright_executor.py`（`_verify_step_state`/`_do_api_call`/`_run_finally_steps`） | 新用例连跑两次断言无残留 |
| **5** | 映射表 v2 + 废弃模糊评分 | `elements_mapping.json`、`element_mapping_service.py`（`lookup_by_key`） | 全量回归对比，回归只加显式条目 |
| **6** | 前端支持 + 语义化重写示范 | `CaseStepEditor.vue`、`Recorder.vue`、`stepParser.ts`、`types/case.ts`、新建语义化用例 | 新旧两版用例都通过 |

---

## 七、配置项

| 配置 | 默认 | 说明 |
|------|------|------|
| `SEMANTIC_ENABLED` | `true` | 总开关：关闭后执行器完全走旧范式 |
| `USE_KEYWORD_MATCHER` | `false` | legacy 自然语言步骤的 keyword_matcher 回退 |
| `USE_FUZZY_MAPPING` | `false` | 映射表 v1 模糊评分（阶段 5 后关停，可回切） |
| `ENFORCE_VERIFICATION` | `false` | `expected` 非空且无 `verify` 时强制默认校验 |

---

## 八、验收标准

### 8.1 功能验收

| 场景 | 预期 |
|------|------|
| 语义目标步骤执行 | 解析器命中，复用成熟点击/填充策略，步骤通过 |
| 动态数据 | `{__CTX:meetingName__}` 渲染为同一值，创建/断言/清理引用一致 |
| 跨层级误配 | `scope` 不符时报 `ScopeMismatch` 或直达导航，不再误配到错误页面元素 |
| 假通过 | `verify.must=true` 步骤状态验证失败 → 真实 failed |
| 残留数据 | `finally` 步骤无论成败都执行，连跑两次无残留会议 |
| 老用例回归 | 现有 21 步用例全程通过（不填新字段） |

### 8.2 精度验收

| 指标 | 当前 | 目标 |
|------|------|------|
| 复杂用例从编写到稳定 | 10+ 会话 / 几十处补丁 | 语义化步骤一次编写基本稳定 |
| 映射表命中 | 模糊评分误配风险 | 显式键精确查找 100% |
| 动态数据定位 | 无机制，靠打补丁 | 上下文 + 行级文本定位 |

### 8.3 回退验证

| 场景 | 预期 |
|------|------|
| `SEMANTIC_ENABLED=false` | 执行器走旧范式，行为与改造前一致 |
| 语义目标无法解析 | 回退到显式 `selectors` 兜底链 |
| Claude 调用失败/超时 | 回退第 0 个候选，不阻塞执行 |
| 页面识别不到 | `recognize_current_page` 返回 None，零行为变化 |

---

## 九、风险评估

| 风险 | 影响 | 缓解 |
|------|------|------|
| 语义目标 schema 未同步声明被丢弃 | 静默丢字段 | schema 同步声明，recorder 测试透传 |
| 渲染误伤老用例 | 老用例回归 | 渲染仅在含 `__` 时生效；每阶段 flag 可关 |
| 解析器注入坏选择器 | 执行失败 | 仅 `semantic` 存在才启用；显式 selectors 兜底；debug 接口预检 |
| 关停模糊评分致回归 | 老用例失败 | `USE_FUZZY_MAPPING` 可回切；回归只加显式条目 |
| finally 抛错掩盖主结果 | 结果误判 | finally 失败不覆盖主状态，仅标注 |
| 前端映射表延迟提供 | 阶段 5 受阻 | 阶段 1-4 不依赖映射表；v1 表继续兜底 |

---

## 十、「新建会议」语义化重写示范

用例级参数：

```json
"parameters": {
  "meetingName": "自动化-{__RANDOM_NAME_8__}",
  "room": "北京展厅会议室",
  "participant": "admin@xty12"
}
```

| 原步骤 | 语义化改写 |
|--------|-----------|
| 12 时间切换为：预定会议 | `semantic{type:tab, text:预定会议, scope:create_meeting}` + `verify{text_visible, text:预定会议}` |
| 13 会议议题输入 | `semantic{type:input, text:议题, scope:create_meeting}`，`params.value="{__CTX:meetingName__}的议题"` |
| 14 编辑按钮 | `semantic{type:icon, text:编辑, scope:create_meeting, container:{type:card, selector:".room"}}` + `verify{element_visible, selector:".room .user_list"}` |
| 15 勾选参会人 | `semantic{type:row_checkbox, text:"{__CTX:participant__}", contains:"{__CTX:participant__}", scope:create_meeting, container:{type:table, selector:".el-table"}}` |
| 17 确定创建 | `semantic{type:button, text:确定创建, scope:create_meeting, container:{type:dialog}}` + `verify{dialog, text:创建成功}` |
| 20 取消会议 | `semantic{type:button, text:取消会议, scope:meeting_detail, container:{type:footer, selector:".buttom_btn"}}` + verify 弹窗 |
| 21 二次确认确定 | `semantic{type:button, text:确定, scope:meeting_detail, container:{type:dialog, title:取消会议}}` + verify 取消成功 |
| 新增 22 finally 清理 | `action:api_call, run_on:finally`，查询 `{__CTX:meetingName__}` 断言无残留 |

**动态数据处理**：步骤 10 会议名称、步骤 13 议题、步骤 15 参会人、finally 清理全部引用同一 `{__CTX:...__}` 上下文，保证创建/断言/清理一致；动态数据不写映射表，由语义目标 text/contains 携带。

---

## 十一、参考资料

- `HANDOFF_UI自动化.md` — 会话 9-20 全部补丁记录（本 PRD 的问题来源）
- `backend/app/executors/playwright_executor.py` — 执行引擎（execute_step L1031、_do_click L1308、_do_assert L1898）
- `backend/app/services/element_mapping_service.py` — 元素映射服务（_exact_match L224 待废弃）
- `backend/app/services/keyword_matcher.py` — 语义→元素管线（未接入执行器）
- `backend/app/services/page_url_service.py` + `backend/app/data/page_url_mapping.json` — 页面识别
- `backend/app/executors/template_resolver.py` — 变量模板解析器（复用）
- `backend/app/executors/api_test_executor.py` — 接口测试执行器（api_call 复用）
- `backend/app/schemas/test_case.py` — StepDefinition（L23-57 待扩展）
- `_PRD_元素映射表定位方案_前端key-value键值_提升定位稳定性.md` — 映射表 v1 PRD
- `_难点分析_智能定位功能策略可行性评估报告.md` — 方案可行性评估

---

*本文档待用户确认后进入执行计划阶段（/prd-plan）。*
