# 问题处理文档 - 新建会议用例执行失败（step 11 会议室勾选超时 / step 17 结束会议失败）

> **文档类型**: 问题处理文档
> **创建日期**: 2026-08-19
> **作者**: czj
> **优先级**: P0
> **状态**: ✅ 已修复并部署 5.60 生产，验收通过（连跑 3 次全绿 + 零残留）
> **受影响用例**: `case_13650e0406e64779b66e6fa5de35c24a`（新建会议 18 步）

---

## 一、问题描述

### 1.1 现象

对「新建会议」用例执行智能定位（smart-locate）验证达到 17/18 通过，再执行用例仍然失败，且连续多次（exec_2ca4e6c8、exec_c9da4587、exec_ed5f84b6）都失败在同一个步骤：

- **step 11「会议室选择：北京展厅会议室」**：`.el-table__row:has-text("北京展厅会议室") .el-checkbox` 超时（5s），重试 2 次后仍失败，整个执行耗时 254s。

失败日志（exec_ed5f84b6c42049d8ba2e8b73e1715a9b）：
```
[10] passed 会议名称输入：自动化新建会议 || ✓ 填充输入框: input[placeholder*="会议名称"] = 自动化新建会议
[11] failed 会议室选择：北京展厅会议室 || ✗ 无法点击元素（已尝试 1 个选择器）:
     ['.el-table__row:has-text("北京展厅会议室") .el-checkbox'] 最后错误: Timeout 5000ms exceeded.
```

另确认 **step 17「点击底部的【结束会议】按钮」** 在旧用例下也必然失败（见 2.5）。

### 1.2 复现步骤

1. 用例管理 > 对 `case_13650e04...` 执行智能定位 → 保存（17/18 通过，selector 已回填）
2. 执行该用例
3. step 7/8/9（功能中心/会议预约/新建会议）显示「⏭ 页面直达，跳过中间导航步骤」
4. step 10 填会议名称成功，step 11 会议室勾选超时失败

---

## 二、根因分析

### 2.1 页面直达优化跳过了中间导航步骤

`playwright_executor.py` Phase 4（页面识别 + URL 直达）：登录成功后识别到目标页 `create_meeting`，直接 `page.goto(url)` 到 `...#/meetingV3?...CreateMeeting`，并将 step 7/8/9 标记为 `skipped`（跳过中间导航点击）。此优化**予以保留**（走手动点击链的方案实测更脆弱，见「三、修复方案」决策说明）。

### 2.2 直达 URL 后表单 SPA 懒加载缓慢

| 方式 | 表格加载耗时 |
|------|------------|
| 手动点击链（功能中心→会议预约→新建） | ~2s |
| 直达 `goto(CreateMeeting URL)` | 12s-130s+ 不等（会议室表格懒加载，变量极大） |

step 10 填会议名称能成功，是因为 Playwright `fill` 会**自动等待元素出现**（最长 30s），撑过了懒加载期。

### 2.3 checkbox 位于隐藏列（is-hidden）

直达后页面 DOM 中北京展厅会议室行**存在**（`<tr class="el-table__row">`），但其选择复选框在 Element UI 的**隐藏选择列**里：

```html
<td class="el-table_1_column_1 el-table-column--selection is-hidden el-table__cell">
  <div class="cell"><label class="el-checkbox">
    <span class="el-checkbox__input"><span class="el-checkbox__inner"></span>
    <input type="checkbox" class="el-checkbox__original">
  </label></div>
</td>
```

`is-hidden` 使该列 checkbox **不可见**。Playwright 默认 `click` 等待元素可见可操作，隐藏 checkbox 永不满足 → 超时。**修复方式是执行器强制点击（force）绕过可见性检查 + 点击后校验勾选态**（防假通过），而不是依赖默认可见性检查。

### 2.4（核心）会议室表格仅列出「空闲/可预约」会议室——被占用即从列表消失

**实测确认**：创建页会议室表格**排除了当前被会议占用/预约的会议室**。当有残留会议占用「北京展厅会议室」时，该行从可选列表**消失**，step 11 选择器 `:has-text("北京展厅会议室")` 永远无法命中。

- 结束/取消占用会议 → 房间立即重新出现在列表第一行。
- 这解释了为何验收标准要求「连跑 2-3 次零残留」：每次执行的 step 17 结束会议释放房间，供下一次执行选择。

### 2.5（step 17 根因）step 12 切「预定会议」→ 会议「未开始」→ 无「结束会议」按钮

**实测确认会议生命周期**：

| 创建方式 | 会议状态 | 详情页操作按钮 |
|----------|----------|----------------|
| 立即开始（默认） | 进行中 | 延长会议 / **结束会议** |
| 预定会议 | 未开始 | 修改会议 / 取消会议 / 提前开始（**无结束会议**） |

预定会议**到点也不会自动开始**。旧用例 step 12 点击「预定会议」tab → 会议创建后为「未开始」→ 详情页没有「结束会议」→ **step 17 必然失败**。

### 2.6（step 12 选择器坑）`.time_btn > div:first-child .btns` 零匹配

第一次改 step 12 为保持「立即开始」时用了 `.time_btn > div:first-child .btns`，实测超时。DOM 结构为：

```html
<div class="time_btn">
  <div class="btns button_active">立即开始</div>   <!-- first-child 就是 .btns 自身 -->
  <div><div class="btns">预定会议</div></div>       <!-- last-child 是包裹 div，内含 .btns 子孙 -->
</div>
```

`.time_btn > div:first-child .btns` 要求 first-child 的 `.btns` **子孙**，但 first-child 自身就是 `.btns`，无子孙 → **零匹配**。原 `.time_btn > div:last-child .btns` 能命中是因为 last-child 是包裹 div 且内含 `.btns`。修正为 `.time_btn > div:first-child`（直接命中「立即开始」tab div 自身）。

---

## 三、修复方案（已实施）

### 3.1 决策：保留页面直达 + 增强执行器（否决"禁用直达走点击链"）

曾计划禁用 create_meeting 页面直达、改走手动点击链，但实测**手动路径在会议室被占用时同样无该行可选**，且点击链在会议列表/新建页切换时不稳定。最终采用：

1. **保留页面直达**（`page_url_mapping.json` 不改回，语义化 scope 识别不受影响）；
2. **增强执行器策略 5**（`backend/app/executors/playwright_executor.py`）：
   - **行等待改轮询**：`wait_for_selector(row, attached, 30s)` → `while` 轮询 `query_selector_all(row).length > 0`，最长 **150s**（覆盖会议室表格 12s-130s+ 的懒加载）；
   - **checkbox 点击改循环 + 校验**：`force` 点击 → `_checkbox_is_checked()` 校验 → 失败再 `JS el.click()` 兜底 → 每 2s 重试，最长 **40s**；
   - 保留原策略 3（点击整行）兜底。
3. **修复 step 17**：用例 step 12 由「时间切换为：预定会议」改为「时间保持为：立即开始」，选择器 `.time_btn > div:last-child .btns` → `.time_btn > div:first-child`（修正零匹配坑）。保持立即开始 → 会议为「进行中」→ 详情页有「结束会议」→ step 17 可点。

### 3.2 改动文件清单

| 文件 | 改动 |
|------|------|
| `backend/app/executors/playwright_executor.py` | 策略 5 行等待轮询 + checkbox 循环 force 点击 + 校验防假通过 |
| `backend/scripts/fix_step12.py` | 新脚本：API 更新用例 step 12（保持立即开始 + 修正选择器） |
| `backend/scripts/run_case_verify.py` | 验证脚本（触发执行 + 拉结果） |
| `backend/scripts/check_residue.py` | 残留会议检查脚本 |
| `backend/scripts/deploy_fix_560.py` | 一键部署 5.60（scp + fix + 重启 + 验收） |

---

## 四、验收结果（已完成）

### 4.1 本地（backend，SQLite）

连跑 3 次 `run_case_verify.py --case case_862b...`：**18/18 全绿**（含 step 11 勾选、step 17 结束会议），3 次 skipped 均为 7/8/9（页面直达预期跳过），残留会议 **0**。

### 4.2 生产 5.60（容器，MySQL）

- 已部署执行器 + 用例修复，重启容器 `plat-auto-test-app`；
- 目标用例 `case_13650e0406e64779b66e6fa5de35c24a` 连跑 3 次（exec_c8c35813、exec_6bdb170c、exec_c0f5fffc）：
  - MySQL `case_results` 三条均 `passed`；
  - 每次 18 步，step 7/8/9 SKIP（页面直达）、step 11/12/17/18 全部 PASS；
  - 容器内残留检查：**残留会议「自动化新建会议」数量 0**。

---

## 五、验收标准（对照）

| 测试项 | 预期结果 | 实际 |
|--------|----------|------|
| 执行「新建会议」用例 | step 7/8/9 跳过（页面直达）、step 10/11 通过 | ✅ 本地 + 生产均通过 |
| step 11 勾选北京展厅会议室 | 通过（懒加载轮询 + force 勾选） | ✅ 通过 |
| step 12 保持立即开始 | 通过 | ✅ 通过 |
| step 17 结束会议 | 通过（立即开始→进行中→有结束会议） | ✅ 通过 |
| 全 18 步 | 全部通过 | ✅ 本地 3 次 + 生产 3 次 |
| 连续执行 2-3 次 | 全绿、零残留 | ✅ 残留 0 |

---

*本文档由 Claude Code 生成，遵循项目问题处理文档规范。*
