提交 4b690a98 authored 作者: 陈泽健's avatar 陈泽健

feat(cases): 新增21个会议管理测试用例及问题处理文档

- 新增 create_meeting_cases_v2.py 用例创建脚本(第二批次)
- 数据库新增21个会议管理用例(会议列表操作/模板/日程/会议室/预定数据)
- 新增协议弹窗确定按钮点击无效的问题处理与执行计划文档
- 更新 HANDOFF.md 交接文档
- 删除运维集控旧文件
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 6c8b339c
# 执行计划 - 修复协议弹窗确定按钮点击无效
> **文档类型**: 执行计划文档
> **创建日期**: 2026-07-17
> **作者**: czj
> **关联文档**: `_问题处理_协议弹窗确定按钮点击无效.md`
> **状态**: 待执行
---
## 一、改动总览
| # | 改动项 | 文件/范围 | 改动量 | 说明 |
|---|--------|-----------|--------|------|
| 1 | 等待协议弹窗出现 | `playwright_executor.py` do_login() | ~5 行 | 用 wait_for_selector 等待 .el-dialog 可见 |
| 2 | 增加复选框勾选 | `playwright_executor.py` do_login() | ~8 行 | 点击确定前先勾选协议复选框 |
| 3 | 多选择器 + 多点击方式 | `playwright_executor.py` do_login() | ~50 行 | 6 个选择器回退 + 3 种点击方式回退 |
| 4 | 等待弹窗消失 | `playwright_executor.py` do_login() | ~5 行 | 点击后等待 .el-dialog hidden |
| 5 | 调整等待时间 | `playwright_executor.py` do_login() | ~1 行 | 弹窗后等待从 5000ms 调整为 2000ms |
**总计**:1 个代码文件改动,约 70 行新增/修改。
---
## 二、详细执行步骤
### Step 1:修改 do_login() 协议弹窗处理逻辑
**文件**`backend/app/executors/playwright_executor.py`
#### 1.1 替换协议弹窗处理代码
**修改前**(第 261-270 行):
```python
# 处理协议弹窗
dialogs = self._page.query_selector_all('.el-dialog button, [role="dialog"] button')
for db in dialogs:
text = (db.inner_text() or '').strip()
if ('确定' in text or '同意' in text) and db.is_visible():
db.click()
logger.debug(f"点击协议确认: {text}")
break
self._page.wait_for_timeout(5000)
```
**修改后**
```python
# === 等待协议弹窗出现(最多等待 10 秒) ===
try:
self._page.wait_for_selector('.el-dialog:visible', timeout=10000)
logger.debug("协议弹窗已出现")
except Exception as e:
logger.warning(f"等待协议弹窗超时(可能无弹窗): {e}")
# === 勾选协议复选框(如有) ===
checkbox_selectors = [
'.el-dialog .el-checkbox__original',
'.el-dialog input[type="checkbox"]',
'.el-checkbox__original',
]
for cb_sel in checkbox_selectors:
try:
cb = self._page.query_selector(cb_sel)
if cb and cb.is_visible():
try:
if not cb.is_checked():
cb.click()
logger.debug(f"勾选协议复选框: {cb_sel}")
self._page.wait_for_timeout(500)
except Exception as e:
logger.debug(f"复选框操作失败(可能不需要): {e}")
break
except Exception:
continue
# === 多选择器 + 多种点击方式回退 ===
confirm_selectors = [
".el-dialog .el-dialog__body button:has-text('确定')",
".el-dialog button.el-button--primary:has-text('确定')",
".el-dialog button:has-text('确定')",
".el-dialog button:has-text('同意')",
".el-dialog button:has-text('阅读并同意')",
"[role='dialog'] button:has-text('确定')",
]
clicked = False
for sel in confirm_selectors:
try:
btn = self._page.query_selector(sel)
if btn and btn.is_visible():
# 方式1:普通点击
try:
btn.click(timeout=5000)
logger.debug(f"协议确认-普通点击成功: {sel}")
clicked = True
break
except Exception as e1:
logger.debug(f"协议确认-普通点击失败: {e1}")
# 方式2:dispatch_event
try:
btn.dispatch_event('click')
logger.debug(f"协议确认-dispatch_event成功: {sel}")
clicked = True
break
except Exception as e2:
logger.debug(f"协议确认-dispatch_event失败: {e2}")
# 方式3:JS click
try:
self._page.evaluate('(el) => el.click()', btn)
logger.debug(f"协议确认-JS click成功: {sel}")
clicked = True
break
except Exception as e3:
logger.debug(f"协议确认-JS click失败: {e3}")
except Exception as e:
logger.debug(f"选择器 {sel} 查询失败: {e}")
if not clicked:
logger.warning("所有协议弹窗点击方式均失败,尝试最后手段:键盘 Enter")
try:
self._page.keyboard.press('Enter')
logger.debug("协议确认-键盘 Enter")
except Exception as e:
logger.warning(f"键盘 Enter 也失败: {e}")
# === 等待弹窗消失 ===
try:
self._page.wait_for_selector('.el-dialog', state='hidden', timeout=5000)
logger.debug("协议弹窗已关闭")
except Exception as e:
logger.debug(f"等待弹窗关闭超时(可能已关闭): {e}")
self._page.wait_for_timeout(2000)
```
**改动说明**
1. **等待弹窗出现**:用 `wait_for_selector('.el-dialog:visible')` 显式等待弹窗可见,替代固定 3 秒等待
2. **勾选复选框**:点击"确定"前先检查并勾选协议复选框,解决可能的前端校验
3. **6 级选择器回退**:从最精确到最宽松,逐步尝试
4. **3 种点击方式回退**:普通 click → dispatch_event → JS evaluate click
5. **键盘 Enter 兜底**:所有点击方式失败后尝试键盘回车
6. **等待弹窗消失**:点击后确认弹窗关闭,而非固定等待 5 秒
7. **缩短后置等待**:从 5000ms 改为 2000ms(弹窗关闭后无需长等)
### Step 2:同步更新用例步骤中的协议弹窗选择器
**文件**`backend/scripts/create_login_cases_full.py`
#### 2.1 更新 AGREE_STEP 选择器
**修改前**(第 200-204 行):
```python
AGREE_STEP = {
"name": "确认同意协议弹窗",
"action": "click",
"params": {"selector": 'button:has-text("确定")'},
}
```
**修改后**
```python
AGREE_STEP = {
"name": "确认同意协议弹窗",
"action": "click",
"params": {
"selectors": [
".el-dialog button.el-button--primary:has-text('确定')",
".el-dialog button:has-text('确定')",
".el-dialog button:has-text('同意')",
".el-dialog button:has-text('阅读并同意')",
"button:has-text('确定')",
]
},
}
```
**改动说明**
- 将单选择器 `selector` 改为多选择器列表 `selectors`,利用执行器的多选择器回退机制
- 优先匹配弹窗内的按钮,最后回退到无容器限定的选择器
### Step 3:验证修复效果
1. 在 8001 端口启动后端
2. 执行"会议管理"模块用例
3. 检查执行日志,确认协议弹窗处理正常
4. 验证 6/6 用例全部通过
---
## 三、验证步骤
### 3.1 代码验证
| 步骤 | 操作 | 预期结果 |
|------|------|----------|
| 1 | grep "wait_for_selector.*el-dialog" playwright_executor.py | 有匹配(等待弹窗逻辑) |
| 2 | grep "confirm_selectors" playwright_executor.py | 有匹配(多选择器列表) |
| 3 | grep "dispatch_event" playwright_executor.py | 有匹配(点击方式回退) |
| 4 | grep "el-checkbox" playwright_executor.py | 有匹配(复选框勾选) |
### 3.2 功能验证
| 步骤 | 操作 | 预期结果 |
|------|------|----------|
| 1 | 启动后端 + 前端 | 服务正常,8001/health 返回 healthy |
| 2 | 执行"会议管理"模块 6 用例 | 登录成功,6/6 全部 passed |
| 3 | 查看首用例步骤日志 | 显示"协议弹窗已出现"+"协议确认-xxx点击成功"+"协议弹窗已关闭" |
| 4 | 执行其他模块用例 | 登录步骤正常,用例可执行 |
### 3.3 回归验证
| 步骤 | 操作 | 预期结果 |
|------|------|----------|
| 1 | 执行"数据分析"模块用例 | 登录正常,用例执行无异常 |
| 2 | 执行"运维集控"模块用例 | 登录正常,用例执行无异常 |
| 3 | 批量执行全部 29 个用例 | 登录流程稳定,无卡死 |
---
## 四、风险评估
| 风险 | 概率 | 影响 | 缓解 |
|------|------|------|------|
| 协议弹窗结构变化导致选择器失效 | 低 | 登录失败 | 6 级选择器回退覆盖多种弹窗结构 |
| 复选框不存在导致异常 | 低 | 无影响 | 查询失败时 continue 跳过 |
| wait_for_selector 超时(无弹窗场景) | 低 | 额外等待 10 秒 | 超时后 warning 继续执行,不中断流程 |
| 多种点击方式均失败 | 低 | 登录失败 | 键盘 Enter 兜底 + 日志告警 |
| 修改影响其他弹窗处理逻辑 | 低 | 其他弹窗异常 | 仅修改 do_login() 中的协议弹窗处理,不影响其他动作 |
---
*本文档由 Claude Code 生成,遵循项目执行计划文档规范。*
\ No newline at end of file
# 问题处理文档 - 协议弹窗确定按钮点击无效
> **文档类型**: 问题处理文档
> **创建日期**: 2026-07-17
> **作者**: czj
> **优先级**: P0
> **状态**: 待修复
---
## 一、问题描述
### 1.1 现象
批量执行测试用例时,登录流程无法完成。具体表现为:
1. 执行器访问登录页,填写账号密码验证码,点击登录按钮
2. 协议弹窗正常弹出,"确定"按钮可见
3. Playwright 尝试点击"确定"按钮,但页面无响应
4. 登录流程卡住,后续所有用例因无法登录而失败
### 1.2 复现步骤
1. 启动后端服务 `uvicorn app.main:app --port 8001`
2. 启动前端服务 `npm run dev`
3. 访问 http://localhost:3000/cases/ui
4. 执行任意需要登录的用例(如"会议管理"模块用例)
5. 观察执行日志,协议弹窗步骤失败
### 1.3 已验证的排查
| 尝试方式 | 结果 |
|----------|------|
| Playwright `click()` | 无效 |
| `click(force=True)` 强制点击 | 无效 |
| JS `element.click()` 通过 evaluate | 无效 |
| 键盘 `Enter` | 无效 |
| 手动在浏览器中登录 | 正常 |
| 被测系统服务状态 | 正常 |
### 1.4 影响范围
- 所有需要登录的测试用例均无法执行
- 登录用例本身无法通过
- 自动登录 (`auto_login=True`) 失败
- 手动登录步骤模式也失败
---
## 二、根因分析
### 2.1 协议弹窗选择器问题
#### 2.1.1 登录页存在多个"确定"按钮
根据 `backend/element_selectors.json` 实测数据,登录页有 **2 个"确定"按钮**
```json
{"text": "确定", "class": ["el-button", "el-button--primary"], "type": "button"},
{"text": "确定", "class": ["el-button", "el-button--primary"], "type": "button"},
{"text": "阅读并同意", "class": ["el-button", "el-button--primary"], "type": "button"}
```
用例步骤中使用的选择器 `button:has-text("确定")` 会匹配到第一个"确定"按钮,但这不一定是协议弹窗中的那个。
#### 2.1.2 选择器策略不一致
| 位置 | 选择器 | 问题 |
|------|--------|------|
| `do_login()` 第 262 行 | `.el-dialog button, [role="dialog"] button` | 先找容器再找按钮,但可能匹配到弹窗外层容器中的其他按钮 |
| 用例步骤 | `button:has-text("确定")` | 无容器限定,可能匹配到页面上非弹窗的"确定"按钮 |
| `selector_mapper.py` confirm_agreement | `.el-dialog button:has-text('确定')` | 精确,但用例数据中未使用 `page_key/element_key` 引用 |
### 2.2 协议弹窗无显式等待
`do_login()` 中的代码(第 259-268 行):
```python
self._page.wait_for_timeout(3000) # 仅等待 3 秒
# 处理协议弹窗
dialogs = self._page.query_selector_all('.el-dialog button, [role="dialog"] button')
for db in dialogs:
text = (db.inner_text() or '').strip()
if ('确定' in text or '同意' in text) and db.is_visible():
db.click()
logger.debug(f"点击协议确认: {text}")
break
```
**问题**
- 没有用 `wait_for_selector` 等待弹窗出现
- 如果弹窗加载慢于 3 秒,会找不到按钮
- 如果弹窗动画未完成,`is_visible()` 可能返回 True 但点击无效
### 2.3 点击可能被事件拦截
被测系统可能使用了以下技术导致 Playwright 点击被拦截:
1. **事件委托** - Vue/Element UI 可能通过父元素监听事件
2. **pointer-events** - CSS 可能临时禁用了指针事件
3. **点击验证** - 前端可能检测点击来源,非真实用户点击则忽略
4. **事件冒泡阻止** - 弹窗可能阻止了事件冒泡
### 2.4 弹窗可能需要特定交互顺序
协议弹窗可能需要:
- 先勾选"我已阅读协议"复选框
- 再点击"确定"按钮
根据 `element_selectors.json`,登录页有一个 checkbox:
```json
{"type": "checkbox", "placeholder": "", "class": ["el-checkbox__original"]}
```
如果 checkbox 未勾选,"确定"按钮点击可能被前端拦截。
---
## 三、修复方案
### 3.1 方案:等待弹窗 + 多选择器回退 + 多种点击方式(推荐)
#### 改动点
| # | 改动 | 位置 | 说明 |
|---|------|------|------|
| 1 | 等待协议弹窗出现 | `do_login()` | 用 `wait_for_selector` 等待 `.el-dialog` 可见 |
| 2 | 使用精确选择器 | `do_login()` | 改用 `.el-dialog .el-dialog__body button:has-text('确定')` |
| 3 | 多种点击方式回退 | `do_login()` | 先 `click()`,失败则 `dispatch_event('click')`,再失败则 JS `click()` |
| 4 | 增加复选框勾选 | `do_login()` | 点击"确定"前先勾选协议复选框(如有) |
| 5 | 增加等待弹窗消失 | `do_login()` | 点击后等待弹窗消失,确认关闭成功 |
#### 详细改动
**修改 `do_login()` 中协议弹窗处理部分**
修改前(第 261-270 行):
```python
# 处理协议弹窗
dialogs = self._page.query_selector_all('.el-dialog button, [role="dialog"] button')
for db in dialogs:
text = (db.inner_text() or '').strip()
if ('确定' in text or '同意' in text) and db.is_visible():
db.click()
logger.debug(f"点击协议确认: {text}")
break
self._page.wait_for_timeout(5000)
```
修改后:
```python
# 等待协议弹窗出现(最多等待 10 秒)
try:
self._page.wait_for_selector('.el-dialog:visible', timeout=10000)
logger.debug("协议弹窗已出现")
except Exception as e:
logger.warning(f"等待协议弹窗超时(可能无弹窗): {e}")
# 勾选协议复选框(如有)
checkbox = self._page.query_selector('.el-dialog .el-checkbox__original, .el-dialog input[type="checkbox"]')
if checkbox and checkbox.is_visible():
try:
if not checkbox.is_checked():
checkbox.click()
logger.debug("勾选协议复选框")
except Exception as e:
logger.debug(f"复选框操作失败(可能不需要): {e}")
# 多选择器策略 + 多种点击方式
selectors = [
".el-dialog .el-dialog__body button:has-text('确定')",
".el-dialog button.el-button--primary:has-text('确定')",
".el-dialog button:has-text('确定')",
".el-dialog button:has-text('同意')",
".el-dialog button:has-text('阅读并同意')",
"[role='dialog'] button:has-text('确定')",
]
clicked = False
for sel in selectors:
try:
btn = self._page.query_selector(sel)
if btn and btn.is_visible():
# 方式1:普通点击
try:
btn.click(timeout=5000)
logger.debug(f"点击协议确认(普通点击): {sel}")
clicked = True
break
except Exception as e1:
logger.debug(f"普通点击失败: {e1}")
# 方式2:dispatch_event
try:
btn.dispatch_event('click')
logger.debug(f"点击协议确认(dispatch_event): {sel}")
clicked = True
break
except Exception as e2:
logger.debug(f"dispatch_event 失败: {e2}")
# 方式3:JS click
try:
self._page.evaluate('(el) => el.click()', btn)
logger.debug(f"点击协议确认(JS click): {sel}")
clicked = True
break
except Exception as e3:
logger.debug(f"JS click 失败: {e3}")
continue
except Exception as e:
logger.debug(f"选择器 {sel} 失败: {e}")
continue
if not clicked:
logger.warning("所有协议弹窗点击方式均失败")
# 等待弹窗消失
try:
self._page.wait_for_selector('.el-dialog', state='hidden', timeout=5000)
logger.debug("协议弹窗已关闭")
except Exception as e:
logger.debug(f"等待弹窗关闭超时(可能已关闭): {e}")
self._page.wait_for_timeout(2000)
```
### 3.2 不采用的方案
| 方案 | 原因 |
|------|------|
| 增加 `click(force=True)` | 已验证无效,force 只跳过可操作性检查,不绕过事件拦截 |
| 增加等待时间到 10 秒 | 治标不治本,弹窗 3 秒内必现,问题在点击方式 |
| 放弃协议弹窗处理 | 登录流程必须完成,否则所有用例无法执行 |
---
## 四、验收标准
| 测试项 | 预期结果 |
|--------|----------|
| 协议弹窗等待 | `wait_for_selector('.el-dialog:visible')` 成功 |
| 协议弹窗点击 | 日志显示"点击协议确认",无"失败"日志 |
| 协议弹窗关闭 | 日志显示"协议弹窗已关闭" |
| 登录成功判断 | `_is_logged_in = True`,日志显示"自动登录成功" |
| 会议管理 6 用例执行 | 6/6 全部 passed |
| 其他模块用例执行 | 登录步骤正常,用例可执行 |
---
## 五、相关文件
- `backend/app/executors/playwright_executor.py` — 执行器核心(do_login 方法)
- `backend/app/utils/selector_mapper.py` — 选择器映射
- `backend/element_selectors.json` — 登录页元素实测数据
- `backend/scripts/create_login_cases_full.py` — 登录用例生成脚本
---
*本文档由 Claude Code 生成,遵循项目问题处理文档规范。*
\ No newline at end of file
此差异已折叠。
此差异已折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论