提交 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
# HANDOFF — 会话交接文档 # HANDOFF — 会话交接文档
> **生成时间**: 2026-07-20 > **生成时间**: 2026-07-21
> **当前分支**: `platform-auto-test` > **当前分支**: `platform-auto-test`
> **最近提交**: `b0d1f92a` fix(execution): 退出登录超时修复与执行跳转优化 > **最近提交**: `6c8b339c` feat(executor): 新增3种断言类型+协议弹窗修复
> **状态**: ✅ 协议弹窗修复已验证,会议创建完整流程已实现,共56个用例 > **状态**: ✅ 新增21个会议管理用例,总用例数达77个
--- ---
...@@ -21,14 +21,6 @@ ...@@ -21,14 +21,6 @@
| 前端 | Vue 3 + Element Plus + Vite 5 + TypeScript + ECharts | | 前端 | Vue 3 + Element Plus + Vite 5 + TypeScript + ECharts |
| 被测系统 | https://192.168.5.44 (admin@xty / Ubains@13579 / 验证码 csba) | | 被测系统 | https://192.168.5.44 (admin@xty / Ubains@13579 / 验证码 csba) |
### 被测系统关键特性
被测系统是 **SPA + hash 路由**,主页是"常用功能"卡片式布局:
- 登录页 URL: `https://192.168.5.44/#/platform?platform=%2Fplatform%2F%23%2Flogin`
- 主页 URL: `https://192.168.5.44/#/platform?platform=%2Fplatform%2F%23%2FHome`
- 菜单元素: `<div class="block"><p>菜单名</p></div>`,主页有 29 个 `.block`
- 已登录时访问 `#/login` 会被重定向回主页
### 目录结构(关键路径) ### 目录结构(关键路径)
``` ```
...@@ -37,7 +29,9 @@ platform-auto-test/ ...@@ -37,7 +29,9 @@ platform-auto-test/
│ ├── main.py # FastAPI 入口(Windows ProactorEventLoop) │ ├── main.py # FastAPI 入口(Windows ProactorEventLoop)
│ ├── executors/playwright_executor.py # 执行引擎(13种动作+11种断言) │ ├── executors/playwright_executor.py # 执行引擎(13种动作+11种断言)
│ ├── services/execution_service.py # 执行调度 │ ├── services/execution_service.py # 执行调度
│ └── test_protocol_dialog.py # 协议弹窗测试脚本 │ └── scripts/
│ ├── create_login_cases_full.py # 基础用例创建脚本
│ └── create_meeting_cases_v2.py # 新增会议用例脚本
├── frontend/src/ ├── frontend/src/
│ ├── views/Cases.vue # 用例管理 │ ├── views/Cases.vue # 用例管理
│ ├── views/Execution.vue # 执行中心 │ ├── views/Execution.vue # 执行中心
...@@ -52,96 +46,102 @@ platform-auto-test/ ...@@ -52,96 +46,102 @@ platform-auto-test/
## 二、本次任务(已完成) ## 二、本次任务(已完成)
### 任务:补充可执行的模块用例 + 会议创建完整流程 ### 任务:补充会议相关的可执行测试用例
**核心成果**: **核心成果**:
1. ✅ 协议弹窗修复验证通过 1. ✅ 新增 21 个会议管理测试用例
2. ✅ 执行引擎新增 3 种断言类型(element_exists、element_count_min、url_contains) 2. ✅ 会议管理模块从 29 个用例增加到 50 个
3. ✅ 新增 7 个模块用例(会议列表操作、会议室筛选等) 3. ✅ 总用例数从 56 个增加到 77 个
4. ✅ 新增 6 个会议创建相关用例 4. ✅ 创建用例生成脚本 `create_meeting_cases_v2.py`
5. ✅ 新增预约会议创建 + 修改验证用例
6. ✅ 共计 56 个测试用例
--- ---
## 三、已完成事项(本次会话) ## 三、已完成事项(本次会话)
### 3.1 协议弹窗修复验证 ✅ ### 3.1 用例创建脚本开发 ✅
- 重启后端服务
- 运行 `test_protocol_dialog.py` 脚本
- 登录成功,URL 跳转到主页
### 3.2 执行引擎断言类型扩展
修改文件:`backend/app/executors/playwright_executor.py` 创建文件:`backend/scripts/create_meeting_cases_v2.py`
新增断言类型: ### 3.2 新增用例汇总(21个)
- `element_exists` — 验证元素存在于 DOM(不要求可见)
- `element_count_min` — 验证元素数量 >= expected
- `url_contains` — 验证 URL 包含指定字符串
### 3.3 新增用例汇总 **Phase 1: 会议列表操作类(6个)**
| 用例名称 | 优先级 | 描述 |
|----------|--------|------|
| 会议列表-查看详情验证 | high | 点击操作菜单"查看详情",验证弹窗显示会议基本信息 |
| 会议列表-结束会议验证 | high | 点击操作菜单"结束会议",验证确认弹窗并取消 |
| 会议列表-延长会议验证 | high | 点击操作菜单"延长会议",验证延长时间弹窗 |
| 会议列表-取消预约会议验证 | high | 对预约会议执行取消操作 |
| 会议列表-修改预约会议验证 | high | 对预约会议执行修改操作并验证 |
| 会议列表-不同状态操作验证 | medium | 验证不同状态会议的操作菜单差异 |
**Phase 2: 会议模板功能(4个)**
| 用例名称 | 优先级 | 描述 |
|----------|--------|------|
| 会议模板-新增模板验证 | high | 点击新增模板,验证弹窗打开 |
| 会议模板-编辑模板验证 | medium | 编辑已有模板,验证编辑弹窗 |
| 会议模板-删除模板验证 | medium | 删除单个模板,验证删除确认弹窗 |
| 会议模板-批量删除验证 | medium | 验证批量删除按钮存在且可点击 |
**模块用例(7个)**: **Phase 3: 个人日程功能(3个)**
| 用例名称 | 优先级 | 状态 | | 用例名称 | 优先级 | 描述 |
|----------|--------|------| |----------|--------|------|
| 会议列表-数据查询验证 | high | ✅ | | 个人日程-会议卡片验证 | medium | 验证进行中会议卡片显示正确信息 |
| 会议室列表-查看详情 | medium | ✅ | | 个人日程-创建会议入口 | medium | 从个人日程页面点击"创建"进入新建会议 |
| 新建会议-表单验证 | high | ✅ | | 个人日程-日期切换验证 | low | 验证日期选择器切换日期功能 |
| 预定数据-筛选功能验证 | medium | ✅ |
| 会议统计-图表展示验证 | medium | ✅ | **Phase 4: 会议室列表功能(3个)**
| 运维统计-登录验证 | high | ✅ | | 用例名称 | 优先级 | 描述 |
| 管理看板-图表展示验证 | medium | ✅ |
**会议创建用例(6个)**:
| 用例名称 | 优先级 | 状态 |
|----------|--------|------| |----------|--------|------|
| 新建会议-会议室选择验证 | high | ✅ | | 会议室列表-筛选功能验证 | high | 使用容纳人数、日期、名称筛选会议室 |
| 新建会议-时长选择验证 | medium | ✅ | | 会议室列表-预约会议验证 | high | 从会议室列表发起会议预约 |
| 会议列表-搜索功能验证 | high | ✅ | | 会议室列表-预览详情验证 | medium | 点击预览按钮查看会议室详情 |
| 会议列表-表格列验证 | medium | ✅ |
| 会议室列表-搜索功能 | medium | ✅ | **Phase 5: 预定数据+会议类型(5个)**
| 会议列表-操作菜单验证 | high | ✅ | | 用例名称 | 优先级 | 描述 |
**完整流程用例(2个)**:
| 用例名称 | 优先级 | 状态 |
|----------|--------|------| |----------|--------|------|
| 新建会议-完整创建流程(立即开始) | high | ✅ | | 预定数据-多条件筛选验证 | high | 使用多个下拉选择框组合筛选 |
| 新建会议-预约时间创建(预定会议) | high | ✅ | | 新建会议-视讯会议验证 | medium | 创建视讯会议类型会议 |
| 新建会议-普通会议验证 | medium | 创建普通会议类型会议 |
| 新建会议-周期会议验证 | medium | 创建周期会议类型会议 |
| 新建会议-时间模式切换验证 | high | 验证立即开始和预定会议两种时间模式切换 |
### 3.4 当前用例统计 ### 3.3 当前用例统计
| 模块 | 用例数 | | 模块 | 用例数 |
|------|--------| |------|--------|
| 登录模块 | 5 | | 会议管理 | 50 |
| 会议管理 | 29 |
| 数据分析 | 14 | | 数据分析 | 14 |
| 运维管理 | 4 | | 登录模块 | 5 |
| 管理看板 | 4 | | 管理看板 | 4 |
| **总计** | **56** | | 运维管理 | 4 |
| **总计** | **77** |
--- ---
## 四、关键发现(会议系统) ## 四、关键发现(被测系统)
### 4.1 会议类型选择 ### 4.1 会议列表操作菜单
- **本地会议 / 视讯会议 / 普通会议** — 会议类型 radio 选项 - **more 按钮**: `.more`
- **立即开始 / 预定会议** — 在时间选择区域(`.time_btn`),控制会议是否立即生效 - **弹出菜单项**: `.el-popover li:has-text('查看详情')`
- **注意事项**: 需要用 JS 点击才能稳定触发
### 4.2 操作菜单差异 ### 4.2 操作菜单差异
| 会议状态 | 可用操作 | | 会议状态 | 可用操作 |
|----------|----------| |----------|----------|
| 立即开始 | 延长会议、结束会议、查看详情 | | 进行中(立即开始) | 查看详情、结束会议、延长会议 |
| 预定会议 | 修改会议、取消会议、查看详情 | | 预定状态 | 查看详情、修改会议、取消会议 |
### 4.3 会议类型选项
- 本地会议 / 视讯会议 / 普通会议 / 周期会议
- 选择器: `.el-radio:has-text('视讯会议')`
### 4.3 操作菜单交互 ### 4.4 时间模式
- 操作列有一个"三个点"按钮(`.more` - 立即开始 / 预定会议
- 点击后弹出 popover 菜单 - 选择器: `.time_btn:has-text('预定会议')`
- 需要用 JS 点击才能稳定触发
--- ---
...@@ -149,11 +149,11 @@ platform-auto-test/ ...@@ -149,11 +149,11 @@ platform-auto-test/
| 优先级 | 待办 | 说明 | | 优先级 | 待办 | 说明 |
|--------|------|------| |--------|------|------|
| **P0** | 提交本次代码变更 | 新增断言类型 + 56 个用例 | | **P0** | 提交本次代码变更 | 新增21个用例 + 用例创建脚本 |
| **P1** | Settings.vue 功能实现 | Phase 5 剩余 P0 | | **P1** | 验证新增用例可执行性 | 通过前端执行中心批量运行 |
| **P1** | Pinia 状态管理 + 单元测试 | Phase 5 剩余 P0 | | **P2** | 根据执行结果调整选择器 | 部分用例可能需要选择器优化 |
| **P2** | 补充会议修改/取消完整用例 | 基于预约会议操作 | | **P2** | Settings.vue 功能实现 | Phase 5 剩余 P0 |
| **P2** | main.py 跨平台兼容 | Linux 部署铺路 | | **P3** | 补充其他模块深层用例 | 数据分析、运维管理等 |
--- ---
...@@ -170,17 +170,18 @@ platform-auto-test/ ...@@ -170,17 +170,18 @@ platform-auto-test/
| 7 | NOT NULL constraint error_message | nullable=False 但成功为None | nullable=True | | 7 | NOT NULL constraint error_message | nullable=False 但成功为None | nullable=True |
| 8 | curl 发中文 JSON 失败 | Windows bash 编码 | 用 Python httpx/sqlite3 直查 | | 8 | curl 发中文 JSON 失败 | Windows bash 编码 | 用 Python httpx/sqlite3 直查 |
| 9 | 用例执行顺序乱 | run_execution 查 case_results 无 ORDER BY | JOIN TestCase ORDER BY module_id, order, created_at | | 9 | 用例执行顺序乱 | run_execution 查 case_results 无 ORDER BY | JOIN TestCase ORDER BY module_id, order, created_at |
| 10 | navigate 超时 30s | networkidle 在 SPA 永不空闲 | 用 domcontentloaded + timeout=15000 + 异常捕获 | | 10 | navigate 超时 30s | networkidle 在 SPA 永不空闲 | 用 domcontentloaded + timeout=15000 |
| 11 | 已登录访问 #/login 被重定向 | SPA hash 路由已登录态拦截 | 退出登录用 reset_login_state 清 cookies | | 11 | 已登录访问 #/login 被重定向 | SPA hash 路由已登录态拦截 | 退出登录用 reset_login_state 清 cookies |
| 12 | auto_login 用例重复登录 | 首用例手动登录后 _is_logged_in 未同步 | _detect_login_state 检测主页 .block 设标记 | | 12 | auto_login 用例重复登录 | 首用例手动登录后 _is_logged_in 未同步 | _detect_login_state 检测主页 .block 设标记 |
| 13 | 菜单 text= 选择器失败 | 被测系统菜单是 .block>p 卡片 | 用 `.block:has-text("菜单名")` | | 13 | 菜单 text= 选择器失败 | 被测系统菜单是 .block>p 卡片 | 用 `.block:has-text("菜单名")` |
| 14 | 后端代码改了不生效 | 僵尸进程占端口,新进程绑定失败 | 换端口或彻底释放端口;勿用 --reload | | 14 | 后端代码改了不生效 | 僵尸进程占端口 | 换端口或彻底释放端口;勿用 --reload |
| 15 | _is_logged_in 判断不准 | 子页面无 .block,不设 True | 仅在主页设 True,不主动设 False | | 15 | _is_logged_in 判断不准 | 子页面无 .block,不设 True | 仅在主页设 True,不主动设 False |
| 16 | 协议弹窗点击无效 | 选择器不精确 + 无显式等待 + 点击被拦截 | 6 级选择器回退 + 3 种点击方式 + 等待弹窗出现/消失 | | 16 | 协议弹窗点击无效 | 选择器不精确 + 无显式等待 | 6 级选择器回退 + 3 种点击方式 + 等待弹窗出现/消失 |
| 17 | 不支持的断言类型 | 用例使用了未实现的断言类型 | 扩展 _do_assert 方法 | | 17 | 不支持的断言类型 | 用例使用了未实现的断言类型 | 扩展 _do_assert 方法(已新增 element_exists、element_count_min、url_contains) |
| 18 | wait 步骤缺少选择器 | wait 步骤只有 timeout 无 selector | wait 步骤必须提供 selector 参数 | | 18 | wait 步骤缺少选择器 | wait 步骤只有 timeout 无 selector | wait 步骤必须提供 selector 参数 |
| 19 | checkbox 点击超时 | Element UI checkbox 需要特殊处理 | 使用 `force: True` 或 JS 点击 `.el-checkbox__inner` | | 19 | checkbox 点击超时 | Element UI checkbox 需要特殊处理 | 使用 `force: True` 或 JS 点击 `.el-checkbox__inner` |
| 20 | 预约会议创建失败 | 时间选择器在"预定会议"模式下结构变化 | 点击"预定会议"后等待页面更新再选时间 | | 20 | 预约会议创建失败 | 时间选择器在"预定会议"模式下结构变化 | 点击"预定会议"后等待页面更新再选时间 |
| 21 | 操作菜单点击失败 | .more 按钮点击后弹出的是顶部菜单 | 需要用 JS 点击,等待 .el-popover 出现 |
--- ---
...@@ -216,9 +217,9 @@ PYTHONIOENCODING=utf-8 python test_protocol_dialog.py ...@@ -216,9 +217,9 @@ PYTHONIOENCODING=utf-8 python test_protocol_dialog.py
## 八、未提交变更 ## 八、未提交变更
本次会话有以下未提交变更: 本次会话有以下未提交变更:
- `backend/app/executors/playwright_executor.py` — 新增 3 种断言类型 - `backend/scripts/create_meeting_cases_v2.py` — 新增用例创建脚本(重要)
- `backend/data/test_platform.db` — 新增测试用例数据 - `backend/data/test_platform.db` — 新增 21 个测试用例数据
- 多个临时探索脚本(已清理 - 多个临时探索脚本(建议不提交
--- ---
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称:create_meeting_cases_v2.py
模块描述:补充会议管理模块可执行测试用例(第二批次)
基于对被测系统的实际探索,补充以下功能点:
1. 会议列表操作:查看详情、结束会议、延长会议、取消预约、修改预约
2. 会议模板功能:新增、编辑、删除、批量删除
3. 个人日程功能:会议卡片、创建入口、日期切换
4. 会议室列表功能:筛选、预约、预览详情
5. 预定数据功能:多条件筛选
6. 会议类型特定:视讯会议、普通会议、周期会议
作者:czj
创建日期:2026-07-20
"""
import asyncio
import httpx
import sys
BASE_URL = "http://localhost:8001"
# 被测系统真实信息
TARGET_URL = "https://192.168.5.44"
USERNAME = "admin@xty"
PASSWORD = "Ubains@13579"
CAPTCHA = "csba"
# 会议管理模块ID(已存在)
MEETING_MODULE_ID = "module_e80a5d08b48d4c67a16cb86156320f25"
# 通用配置
DEFAULT_CONFIG = {
"timeout": 120000,
"screenshot": True,
"auto_login": True,
"step_retry_count": 2,
}
async def create_cases():
"""创建所有补充用例"""
async with httpx.AsyncClient(timeout=60) as client:
created_count = 0
# ==================== Phase 1: 会议列表操作类用例 ====================
print("=" * 60)
print("【Phase 1】会议列表操作类用例")
print("=" * 60)
phase1_cases = build_phase1_cases()
for case in phase1_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
if r.status_code == 201:
print(f" ✅ {case['name']}")
created_count += 1
else:
print(f" ❌ {case['name']}: {r.status_code} {r.text[:100]}")
# ==================== Phase 2: 会议模板功能用例 ====================
print("\n" + "=" * 60)
print("【Phase 2】会议模板功能用例")
print("=" * 60)
phase2_cases = build_phase2_cases()
for case in phase2_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
if r.status_code == 201:
print(f" ✅ {case['name']}")
created_count += 1
else:
print(f" ❌ {case['name']}: {r.status_code} {r.text[:100]}")
# ==================== Phase 3: 个人日程功能用例 ====================
print("\n" + "=" * 60)
print("【Phase 3】个人日程功能用例")
print("=" * 60)
phase3_cases = build_phase3_cases()
for case in phase3_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
if r.status_code == 201:
print(f" ✅ {case['name']}")
created_count += 1
else:
print(f" ❌ {case['name']}: {r.status_code} {r.text[:100]}")
# ==================== Phase 4: 会议室列表功能用例 ====================
print("\n" + "=" * 60)
print("【Phase 4】会议室列表功能用例")
print("=" * 60)
phase4_cases = build_phase4_cases()
for case in phase4_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
if r.status_code == 201:
print(f" ✅ {case['name']}")
created_count += 1
else:
print(f" ❌ {case['name']}: {r.status_code} {r.text[:100]}")
# ==================== Phase 5: 预定数据+会议类型用例 ====================
print("\n" + "=" * 60)
print("【Phase 5】预定数据+会议类型用例")
print("=" * 60)
phase5_cases = build_phase5_cases()
for case in phase5_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
if r.status_code == 201:
print(f" ✅ {case['name']}")
created_count += 1
else:
print(f" ❌ {case['name']}: {r.status_code} {r.text[:100]}")
# ==================== 汇总 ====================
print("\n" + "=" * 60)
print("【汇总】")
print("=" * 60)
print(f" 新增用例: {created_count} 个")
stats = await client.get(f"{BASE_URL}/api/stats/overview")
s = stats.json()
print(f" 当前总用例: {s['cases']['total']} 个")
def nav_home_step(order):
"""导航到主页步骤"""
return {
"order": order,
"name": "点击进入主页",
"action": "navigate",
"params": {"url": f"{TARGET_URL}/#/platform?platform=%2Fplatform%2F%23%2FHome"},
}
def build_phase1_cases():
"""Phase 1: 会议列表操作类用例"""
cases = []
# 用例1: 会议列表-查看详情验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议列表-查看详情验证",
"description": "点击操作菜单'查看详情',验证弹窗显示会议基本信息",
"priority": "high",
"tags": ["会议列表", "操作菜单", "详情"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议列表", "action": "click",
"params": {"selector": ".block:has-text('会议列表')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "验证有会议数据", "action": "assert",
"params": {"type": "element_count_min", "selector": ".el-table__row", "expected": "1"}},
{"order": 5, "name": "点击操作菜单按钮", "action": "click",
"params": {"selector": ".more"}},
{"order": 6, "name": "等待菜单弹出", "action": "wait",
"params": {"selector": ".el-popover:visible", "timeout": 5000}},
{"order": 7, "name": "点击查看详情", "action": "click",
"params": {"selectors": [
".el-popover li:has-text('查看详情')",
".el-popover .themeUl_li li:first-child"
]}},
{"order": 8, "name": "等待详情弹窗", "action": "wait",
"params": {"selector": ".el-dialog:visible", "timeout": 5000}},
{"order": 9, "name": "验证弹窗标题", "action": "assert",
"params": {"type": "contains", "selector": ".el-dialog__title", "expected": "会议基本信息"}},
{"order": 10, "name": "验证弹窗内容包含会议名称", "action": "assert",
"params": {"type": "contains", "selector": ".el-dialog__body", "expected": "会议名称"}},
{"order": 11, "name": "关闭弹窗", "action": "click",
"params": {"selector": ".el-dialog__headerbtn"}},
],
"config": DEFAULT_CONFIG,
})
# 用例2: 会议列表-结束会议验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议列表-结束会议验证",
"description": "点击操作菜单'结束会议',验证确认弹窗并取消",
"priority": "high",
"tags": ["会议列表", "操作菜单", "结束会议"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议列表", "action": "click",
"params": {"selector": ".block:has-text('会议列表')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "点击操作菜单按钮", "action": "click",
"params": {"selector": ".more"}},
{"order": 5, "name": "等待菜单弹出", "action": "wait",
"params": {"selector": ".el-popover:visible", "timeout": 5000}},
{"order": 6, "name": "点击结束会议", "action": "click",
"params": {"selectors": [
".el-popover li:has-text('结束会议')",
".el-popover .themeUl_li li:nth-child(4)"
]}},
{"order": 7, "name": "等待确认弹窗", "action": "wait",
"params": {"selector": ".el-message-box:visible", "timeout": 5000}},
{"order": 8, "name": "验证确认提示", "action": "assert",
"params": {"type": "contains", "selector": ".el-message-box__message", "expected": "结束"}},
{"order": 9, "name": "点击取消", "action": "click",
"params": {"selector": ".el-message-box button:has-text('取 消')"}},
],
"config": DEFAULT_CONFIG,
})
# 用例3: 会议列表-延长会议验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议列表-延长会议验证",
"description": "点击操作菜单'延长会议',验证延长时间弹窗",
"priority": "high",
"tags": ["会议列表", "操作菜单", "延长会议"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议列表", "action": "click",
"params": {"selector": ".block:has-text('会议列表')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "点击操作菜单按钮", "action": "click",
"params": {"selector": ".more"}},
{"order": 5, "name": "等待菜单弹出", "action": "wait",
"params": {"selector": ".el-popover:visible", "timeout": 5000}},
{"order": 6, "name": "点击延长会议", "action": "click",
"params": {"selectors": [
".el-popover li:has-text('延长会议')",
".el-popover .themeUl_li li:nth-child(5)"
]}},
{"order": 7, "name": "等待延长弹窗", "action": "wait",
"params": {"selector": ".el-dialog:visible, .el-message-box:visible", "timeout": 5000}},
{"order": 8, "name": "验证延长提示", "action": "assert",
"params": {"type": "contains", "selector": ".el-dialog__body, .el-message-box__message", "expected": "时间"}},
{"order": 9, "name": "取消操作", "action": "click",
"params": {"selectors": [
".el-dialog button:has-text('取消')",
".el-message-box button:has-text('取 消')"
]}},
],
"config": DEFAULT_CONFIG,
})
# 用例4: 会议列表-取消预约会议验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议列表-取消预约会议验证",
"description": "搜索预约状态的会议,执行取消操作",
"priority": "high",
"tags": ["会议列表", "操作菜单", "取消会议"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议列表", "action": "click",
"params": {"selector": ".block:has-text('会议列表')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "搜索预约会议", "action": "fill",
"params": {"selector": "input[placeholder='请输入会议名称']", "value": "预约"}},
{"order": 5, "name": "等待搜索结果", "action": "wait",
"params": {"selector": ".el-table", "timeout": 5000}},
{"order": 6, "name": "验证有预约会议", "action": "assert",
"params": {"type": "element_count_min", "selector": ".el-table__row", "expected": "1"}},
{"order": 7, "name": "点击操作菜单按钮", "action": "click",
"params": {"selector": ".more"}},
{"order": 8, "name": "等待菜单弹出", "action": "wait",
"params": {"selector": ".el-popover:visible", "timeout": 5000}},
{"order": 9, "name": "点击取消会议", "action": "click",
"params": {"selectors": [
".el-popover li:has-text('取消会议')",
".el-popover .themeUl_li li:nth-child(3)"
]}},
{"order": 10, "name": "等待确认弹窗", "action": "wait",
"params": {"selector": ".el-message-box:visible", "timeout": 5000}},
{"order": 11, "name": "点击取消按钮", "action": "click",
"params": {"selector": ".el-message-box button:has-text('取 消')"}},
],
"config": DEFAULT_CONFIG,
})
# 用例5: 会议列表-修改预约会议验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议列表-修改预约会议验证",
"description": "搜索预约状态的会议,验证修改功能可用",
"priority": "high",
"tags": ["会议列表", "操作菜单", "修改会议"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议列表", "action": "click",
"params": {"selector": ".block:has-text('会议列表')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "搜索预约会议", "action": "fill",
"params": {"selector": "input[placeholder='请输入会议名称']", "value": "预约"}},
{"order": 5, "name": "等待搜索结果", "action": "wait",
"params": {"selector": ".el-table", "timeout": 5000}},
{"order": 6, "name": "点击操作菜单按钮", "action": "click",
"params": {"selector": ".more"}},
{"order": 7, "name": "等待菜单弹出", "action": "wait",
"params": {"selector": ".el-popover:visible", "timeout": 5000}},
{"order": 8, "name": "验证有修改会议选项", "action": "assert",
"params": {"type": "element_exists", "selector": ".el-popover li:has-text('修改会议')"}},
{"order": 9, "name": "点击修改会议", "action": "click",
"params": {"selectors": [
".el-popover li:has-text('修改会议')",
".el-popover .themeUl_li li:nth-child(2)"
]}},
{"order": 10, "name": "等待修改弹窗", "action": "wait",
"params": {"selector": ".el-dialog:visible", "timeout": 5000}},
{"order": 11, "name": "关闭弹窗", "action": "click",
"params": {"selector": ".el-dialog__headerbtn"}},
],
"config": DEFAULT_CONFIG,
})
# 用例6: 会议列表-不同状态操作验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议列表-不同状态操作验证",
"description": "验证不同状态会议的操作菜单选项差异",
"priority": "medium",
"tags": ["会议列表", "操作菜单", "状态差异"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议列表", "action": "click",
"params": {"selector": ".block:has-text('会议列表')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "点击操作菜单按钮", "action": "click",
"params": {"selector": ".more"}},
{"order": 5, "name": "等待菜单弹出", "action": "wait",
"params": {"selector": ".el-popover:visible", "timeout": 5000}},
{"order": 6, "name": "验证有查看详情选项", "action": "assert",
"params": {"type": "element_exists", "selector": ".el-popover li:has-text('查看详情')"}},
{"order": 7, "name": "点击页面空白处关闭菜单", "action": "click",
"params": {"selector": "body"}},
{"order": 8, "name": "等待菜单关闭", "action": "wait",
"params": {"selector": ".el-popover", "timeout": 2000}},
],
"config": DEFAULT_CONFIG,
})
return cases
def build_phase2_cases():
"""Phase 2: 会议模板功能用例"""
cases = []
# 用例7: 会议模板-新增模板验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议模板-新增模板验证",
"description": "点击新增模板,验证弹窗打开",
"priority": "high",
"tags": ["会议模板", "新增", "模板管理"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议模板", "action": "click",
"params": {"selector": ".block:has-text('会议模板')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "点击新增模板按钮", "action": "click",
"params": {"selector": "button:has-text('新增模板')"}},
{"order": 5, "name": "等待新增弹窗", "action": "wait",
"params": {"selector": ".el-dialog:visible", "timeout": 5000}},
{"order": 6, "name": "验证弹窗标题", "action": "assert",
"params": {"type": "contains", "selector": ".el-dialog__title", "expected": "模板"}},
{"order": 7, "name": "验证有模板名称输入框", "action": "assert",
"params": {"type": "element_exists", "selector": ".el-dialog input"}},
{"order": 8, "name": "关闭弹窗", "action": "click",
"params": {"selector": ".el-dialog__headerbtn"}},
],
"config": DEFAULT_CONFIG,
})
# 用例8: 会议模板-编辑模板验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议模板-编辑模板验证",
"description": "编辑已有模板,验证编辑弹窗",
"priority": "medium",
"tags": ["会议模板", "编辑", "模板管理"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议模板", "action": "click",
"params": {"selector": ".block:has-text('会议模板')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "验证有模板数据", "action": "assert",
"params": {"type": "element_count_min", "selector": ".el-table__row", "expected": "1"}},
{"order": 5, "name": "点击编辑按钮", "action": "click",
"params": {"selectors": [
".el-table__row:first-child button:has-text('编辑')",
".el-table__row:first-child .el-button--text"
]}},
{"order": 6, "name": "等待编辑弹窗", "action": "wait",
"params": {"selector": ".el-dialog:visible", "timeout": 5000}},
{"order": 7, "name": "验证弹窗标题", "action": "assert",
"params": {"type": "contains", "selector": ".el-dialog__title", "expected": "模板"}},
{"order": 8, "name": "关闭弹窗", "action": "click",
"params": {"selector": ".el-dialog__headerbtn"}},
],
"config": DEFAULT_CONFIG,
})
# 用例9: 会议模板-删除模板验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议模板-删除模板验证",
"description": "删除单个模板,验证删除确认弹窗",
"priority": "medium",
"tags": ["会议模板", "删除", "模板管理"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议模板", "action": "click",
"params": {"selector": ".block:has-text('会议模板')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "验证有模板数据", "action": "assert",
"params": {"type": "element_count_min", "selector": ".el-table__row", "expected": "1"}},
{"order": 5, "name": "点击删除按钮", "action": "click",
"params": {"selectors": [
".el-table__row:first-child button:has-text('删除')",
".el-table__row:first-child .el-button--danger"
]}},
{"order": 6, "name": "等待确认弹窗", "action": "wait",
"params": {"selector": ".el-message-box:visible", "timeout": 5000}},
{"order": 7, "name": "点击取消", "action": "click",
"params": {"selector": ".el-message-box button:has-text('取 消')"}},
],
"config": DEFAULT_CONFIG,
})
# 用例10: 会议模板-批量删除验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议模板-批量删除验证",
"description": "验证批量删除按钮存在且可点击",
"priority": "medium",
"tags": ["会议模板", "批量删除", "模板管理"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议模板", "action": "click",
"params": {"selector": ".block:has-text('会议模板')"}},
{"order": 3, "name": "等待表格加载", "action": "wait",
"params": {"selector": ".el-table", "timeout": 10000}},
{"order": 4, "name": "验证批量删除按钮存在", "action": "assert",
"params": {"type": "element_exists", "selector": "button:has-text('批量删除')"}},
{"order": 5, "name": "点击批量删除按钮", "action": "click",
"params": {"selector": "button:has-text('批量删除')"}},
{"order": 6, "name": "等待提示弹窗", "action": "wait",
"params": {"selector": ".el-message-box:visible, .el-message:visible", "timeout": 5000}},
{"order": 7, "name": "点击取消或关闭", "action": "click",
"params": {"selectors": [
".el-message-box button:has-text('取 消')",
".el-message-box__headerbtn"
]}},
],
"config": DEFAULT_CONFIG,
})
return cases
def build_phase3_cases():
"""Phase 3: 个人日程功能用例"""
cases = []
# 用例11: 个人日程-会议卡片验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "个人日程-会议卡片验证",
"description": "验证进行中会议卡片显示正确信息",
"priority": "medium",
"tags": ["个人日程", "会议卡片", "日程管理"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入个人日程", "action": "click",
"params": {"selector": ".block:has-text('个人日程')"}},
{"order": 3, "name": "等待页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}},
{"order": 4, "name": "验证有会议信息", "action": "assert",
"params": {"type": "contains", "selector": "body", "expected": "会议"}},
{"order": 5, "name": "验证有会议时间信息", "action": "assert",
"params": {"type": "contains", "selector": "body", "expected": "时间"}},
],
"config": DEFAULT_CONFIG,
})
# 用例12: 个人日程-创建会议入口
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "个人日程-创建会议入口",
"description": "从个人日程页面点击创建进入新建会议",
"priority": "medium",
"tags": ["个人日程", "创建会议", "快捷入口"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入个人日程", "action": "click",
"params": {"selector": ".block:has-text('个人日程')"}},
{"order": 3, "name": "等待页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}},
{"order": 4, "name": "点击创建按钮", "action": "click",
"params": {"selector": "button:has-text('创建')"}},
{"order": 5, "name": "等待跳转", "action": "wait",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "timeout": 10000}},
{"order": 6, "name": "验证进入新建会议页面", "action": "assert",
"params": {"type": "element_exists", "selector": "input[placeholder='会议名称/会议议题']"}},
],
"config": DEFAULT_CONFIG,
})
# 用例13: 个人日程-日期切换验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "个人日程-日期切换验证",
"description": "验证日期选择器切换日期功能",
"priority": "low",
"tags": ["个人日程", "日期选择", "日程管理"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入个人日程", "action": "click",
"params": {"selector": ".block:has-text('个人日程')"}},
{"order": 3, "name": "等待页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}},
{"order": 4, "name": "验证日期选择器存在", "action": "assert",
"params": {"type": "element_exists", "selector": ".el-date-editor"}},
],
"config": DEFAULT_CONFIG,
})
return cases
def build_phase4_cases():
"""Phase 4: 会议室列表功能用例"""
cases = []
# 用例14: 会议室列表-筛选功能验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议室列表-筛选功能验证",
"description": "使用容纳人数、日期、名称筛选会议室",
"priority": "high",
"tags": ["会议室列表", "筛选", "查询"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议室列表", "action": "click",
"params": {"selector": ".block:has-text('会议室列表')"}},
{"order": 3, "name": "等待页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}},
{"order": 4, "name": "验证有筛选输入框", "action": "assert",
"params": {"type": "element_count_min", "selector": "input.el-input__inner", "expected": "2"}},
{"order": 5, "name": "填写会议室名称", "action": "fill",
"params": {"selector": "input[placeholder='会议室名称']", "value": "会议室"}},
{"order": 6, "name": "点击确定筛选", "action": "click",
"params": {"selector": "button:has-text('确定')"}},
{"order": 7, "name": "等待筛选结果", "action": "wait",
"params": {"selector": "body", "timeout": 5000}},
],
"config": DEFAULT_CONFIG,
})
# 用例15: 会议室列表-预约会议验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议室列表-预约会议验证",
"description": "从会议室列表发起会议预约",
"priority": "high",
"tags": ["会议室列表", "预约会议", "快捷入口"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议室列表", "action": "click",
"params": {"selector": ".block:has-text('会议室列表')"}},
{"order": 3, "name": "等待页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}},
{"order": 4, "name": "点击新建会议按钮", "action": "click",
"params": {"selector": "button:has-text('新建会议')"}},
{"order": 5, "name": "等待跳转到新建会议", "action": "wait",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "timeout": 10000}},
{"order": 6, "name": "验证进入新建会议页面", "action": "assert",
"params": {"type": "element_exists", "selector": "input[placeholder='会议名称/会议议题']"}},
],
"config": DEFAULT_CONFIG,
})
# 用例16: 会议室列表-预览详情验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "会议室列表-预览详情验证",
"description": "点击预览按钮查看会议室详情",
"priority": "medium",
"tags": ["会议室列表", "预览", "详情"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入会议室列表", "action": "click",
"params": {"selector": ".block:has-text('会议室列表')"}},
{"order": 3, "name": "等待页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}},
{"order": 4, "name": "验证有预览按钮", "action": "assert",
"params": {"type": "element_exists", "selector": "button:has-text('预览')"}},
],
"config": DEFAULT_CONFIG,
})
return cases
def build_phase5_cases():
"""Phase 5: 预定数据+会议类型用例"""
cases = []
# 用例17: 预定数据-多条件筛选验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "预定数据-多条件筛选验证",
"description": "使用多个下拉选择框组合筛选",
"priority": "high",
"tags": ["预定数据", "筛选", "数据分析"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入预定数据", "action": "click",
"params": {"selector": ".block:has-text('预定数据')"}},
{"order": 3, "name": "等待页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}},
{"order": 4, "name": "验证有多个筛选下拉框", "action": "assert",
"params": {"type": "element_count_min", "selector": ".el-select", "expected": "5"}},
],
"config": DEFAULT_CONFIG,
})
# 用例18: 新建会议-视讯会议验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "新建会议-视讯会议验证",
"description": "创建视讯会议类型会议",
"priority": "medium",
"tags": ["新建会议", "会议类型", "视讯会议"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入新建会议", "action": "click",
"params": {"selector": ".block:has-text('新建会议')"}},
{"order": 3, "name": "等待表单加载", "action": "wait",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "timeout": 10000}},
{"order": 4, "name": "填写会议名称", "action": "fill",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "value": "视讯会议测试用例"}},
{"order": 5, "name": "选择视讯会议类型", "action": "click",
"params": {"selector": ".el-radio:has-text('视讯会议')"}},
{"order": 6, "name": "验证视讯会议被选中", "action": "assert",
"params": {"type": "element_exists", "selector": ".el-radio:has-text('视讯会议').is-checked, .el-radio:has-text('视讯会议') input:checked"}},
],
"config": DEFAULT_CONFIG,
})
# 用例19: 新建会议-普通会议验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "新建会议-普通会议验证",
"description": "创建普通会议类型会议",
"priority": "medium",
"tags": ["新建会议", "会议类型", "普通会议"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入新建会议", "action": "click",
"params": {"selector": ".block:has-text('新建会议')"}},
{"order": 3, "name": "等待表单加载", "action": "wait",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "timeout": 10000}},
{"order": 4, "name": "填写会议名称", "action": "fill",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "value": "普通会议测试用例"}},
{"order": 5, "name": "选择普通会议类型", "action": "click",
"params": {"selector": ".el-radio:has-text('普通会议')"}},
{"order": 6, "name": "验证普通会议被选中", "action": "assert",
"params": {"type": "element_exists", "selector": ".el-radio:has-text('普通会议')"}},
],
"config": DEFAULT_CONFIG,
})
# 用例20: 新建会议-周期会议验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "新建会议-周期会议验证",
"description": "创建周期会议类型会议",
"priority": "medium",
"tags": ["新建会议", "会议类型", "周期会议"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入新建会议", "action": "click",
"params": {"selector": ".block:has-text('新建会议')"}},
{"order": 3, "name": "等待表单加载", "action": "wait",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "timeout": 10000}},
{"order": 4, "name": "填写会议名称", "action": "fill",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "value": "周期会议测试用例"}},
{"order": 5, "name": "选择周期会议类型", "action": "click",
"params": {"selector": ".el-radio:has-text('周期会议')"}},
{"order": 6, "name": "验证周期会议被选中", "action": "assert",
"params": {"type": "element_exists", "selector": ".el-radio:has-text('周期会议')"}},
],
"config": DEFAULT_CONFIG,
})
# 用例21: 新建会议-时间模式切换验证
cases.append({
"module_id": MEETING_MODULE_ID,
"name": "新建会议-时间模式切换验证",
"description": "验证立即开始和预定会议两种时间模式切换",
"priority": "high",
"tags": ["新建会议", "时间模式", "预约"],
"steps": [
nav_home_step(1),
{"order": 2, "name": "点击进入新建会议", "action": "click",
"params": {"selector": ".block:has-text('新建会议')"}},
{"order": 3, "name": "等待表单加载", "action": "wait",
"params": {"selector": "input[placeholder='会议名称/会议议题']", "timeout": 10000}},
{"order": 4, "name": "验证立即开始按钮存在", "action": "assert",
"params": {"type": "element_exists", "selector": ".time_btn:has-text('立即开始'), [class*=time]:has-text('立即开始')"}},
{"order": 5, "name": "验证预定会议按钮存在", "action": "assert",
"params": {"type": "element_exists", "selector": ".time_btn:has-text('预定会议'), [class*=time]:has-text('预定会议')"}},
{"order": 6, "name": "点击预定会议", "action": "click",
"params": {"selectors": [
".time_btn:has-text('预定会议')",
"[class*=time]:has-text('预定会议')"
]}},
{"order": 7, "name": "等待时间选择器出现", "action": "wait",
"params": {"selector": "input[placeholder='选择时间']", "timeout": 5000}},
],
"config": DEFAULT_CONFIG,
})
return cases
if __name__ == "__main__":
asyncio.run(create_cases())
print("\n🎉 会议管理模块补充用例创建完成!")
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论