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

feat(test): 新增测试用例数据与批量创建脚本

- 新增 backend/data/test_platform.db,包含 5 个模块 + 25 个可执行测试用例
  (基于实测被测系统 https://192.168.5.44 页面元素构建)
- 新增 backend/scripts/create_login_cases_full.py,基于真实选择器批量创建
  登录模块及测试用例的辅助脚本
- 更新 HANDOFF.md,同步最新提交记录、项目结构与会话工作内容
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 f5003c87
# HANDOFF — 会话交接文档 # HANDOFF — 会话交接文档
> **生成时间**: 2026-07-13 > **生成时间**: 2026-07-14
> **当前分支**: `platform-auto-test` > **当前分支**: `platform-auto-test`
> **最近提交**: `3c4cff2` — feat(test): 新增测试用例依赖管理和批量操作功能 > **最近提交**: `f5003c8` — feat(executor): 新增 SelectorMapper 选择器增强与步骤级实时推送
--- ---
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
| 层级 | 技术 | 版本 | | 层级 | 技术 | 版本 |
|------|------|------| |------|------|------|
| 后端 | FastAPI + SQLAlchemy + SQLite | Python 3.x | | 后端 | FastAPI + SQLAlchemy + SQLite | Python 3.10 |
| 前端 | Vue 3 + Element Plus + Vite 5 + TypeScript | Node 18+ | | 前端 | Vue 3 + Element Plus + Vite 5 + TypeScript | Node 18+ |
| 浏览器自动化 | Playwright (Chromium) | 1.40.x | | 浏览器自动化 | Playwright (Chromium) | 1.40.x |
| 实时通信 | WebSocket (FastAPI 内置) | — | | 实时通信 | WebSocket (FastAPI 内置) | — |
...@@ -27,27 +27,28 @@ ...@@ -27,27 +27,28 @@
``` ```
platform-auto-test/ platform-auto-test/
├── backend/app/ ├── backend/app/
│ ├── main.py # FastAPI 入口 │ ├── main.py # FastAPI 入口(已配 ProactorEventLoop)
│ ├── executors/playwright_executor.py # 执行引擎核心 │ ├── executors/playwright_executor.py # 执行引擎核心(同步 API)
│ ├── services/ # 业务逻辑层 │ ├── services/execution_service.py # 执行调度(线程池 + 步骤回调)
│ ├── routers/ # API 路由层 │ ├── utils/selector_mapper.py # 选择器映射器(多选器回退)
│ ├── models/ # 数据模型(含新增的依赖模型) │ ├── routers/ # 10 个路由模块
│ └── scripts/ # 辅助脚本(15个) │ ├── models/ # 5 个 ORM 模型
│ ├── schemas/ # Pydantic 校验
│ └── scripts/ # 辅助脚本(含 create_login_cases_full.py)
├── frontend/src/ ├── frontend/src/
│ ├── views/ # 7 个页面组件 │ ├── views/ # 7 个页面组件
│ └── api/ # API 调用层 │ ├── api/ # API 调用层
│ ├── types/case.ts # 用例类型(含 selectors/pageKey 等新字段)
│ └── utils/ # request.ts + websocket.ts
├── Docs/PRD/自动化测试平台/ # 所有需求/计划/进度文档 ├── Docs/PRD/自动化测试平台/ # 所有需求/计划/进度文档
│ ├── _PRD_平台自动化测试可视化系统需求文档.md ├── README.md # 项目说明文档(本次新增)
│ ├── _PRD_平台自动化测试可视化系统需求文档_计划执行.md ├── CLAUDE.md # Claude Code 工作指导(本次新增)
│ ├── 当前进度记录.md └── .claude/skills/ # Claude Code 技能
│ ├── 测试结果汇总报告.md ├── CreateCMD/ # 打开 CMD 窗口
│ └── ... (其他 PRD 文档) ├── GitCommit/ # 代码提交辅助
└── .claude/skills/ # Claude Code 技能 ├── Handoff/ # 生成交接文档
├── CreateCMD/ # 打开 CMD 窗口 ├── prd-plan/ # PRD → 执行计划
├── GitCommit/ # 代码提交辅助 └── prd-code/ # 执行计划 → 代码
├── Handoff/ # 生成交接文档(本技能)
├── prd-plan/ # PRD → 执行计划
└── prd-code/ # 执行计划 → 代码
``` ```
--- ---
...@@ -56,9 +57,12 @@ platform-auto-test/ ...@@ -56,9 +57,12 @@ platform-auto-test/
本次会话主要做了以下工作: 本次会话主要做了以下工作:
1. **修复了 `CreateCMD` skill 的路径配置**:将 bat 文件中的 `E:\nodejs\claude.cmd` 更新为 `C:\nvm4w\nodejs\claude.cmd``cd` 目录从 `E:\GithubData\ubains-module-test\platform-auto-test` 更新为 `C:\PycharmData\ubains-module-test\platform-auto-test` 1. **生成了 README.md 和 CLAUDE.md**:完整的项目说明文档和 Claude Code 工作指导文档
2. **新增了 `Handoff` skill**:用于在会话结束时自动生成 HANDOFF.md 交接文档。 2. **执行了 GitCommit**:提交了 16 个文件(选择器增强 + 步骤级实时推送 + 项目文档 + PRD 文档),commit `f5003c8`,已推送到远程
3. **生成了本 HANDOFF.md** 3. **发现数据库为空**:新初始化的 `data/test_platform.db` 中模块 0、用例 0
4. **实际访问被测系统**:用 Playwright 访问 https://192.168.5.44 登录页面,抓取真实元素选择器
5. **批量创建测试用例**:基于实测选择器创建 5 个模块 + 25 个可执行用例
6. **验证可执行性**:执行了 2 个核心用例,全部通过(登录页面访问 16.7s、完整登录流程 169.2s)
--- ---
...@@ -67,16 +71,15 @@ platform-auto-test/ ...@@ -67,16 +71,15 @@ platform-auto-test/
| 里程碑 | 状态 | 说明 | | 里程碑 | 状态 | 说明 |
|--------|------|------| |--------|------|------|
| Phase 1-4 全部完成 | ✅ | 基础框架、核心功能、执行与可视化、分析与报告 | | Phase 1-4 全部完成 | ✅ | 基础框架、核心功能、执行与可视化、分析与报告 |
| Phase 5 约 30% | 🔄 | 已完成为 Excel 导入、执行引擎调通、数据库锁修复 | | Phase 5 约 30% | 🔄 | 已完成 Excel 导入、执行引擎调通、数据库锁修复 |
| 26 个测试用例全部通过 | ✅ | 含 7 高优先级 + 12 中优先级 + 7 低优先级 | | SelectorMapper 选择器增强 | ✅ | page_key + element_key 多选器回退解析 |
| 深层交互测试(5 个) | ✅ | 表单填写、按钮点击、断言验证 | | 步骤级 WebSocket 实时推送 | ✅ | 线程安全广播,前端实时步骤时间线 |
| WebSocket 实时推送 | ✅ | 毫秒级延迟,6 种消息类型 | | 单步重试机制 | ✅ | step_retry_count 配置支持 |
| 报告生成和导出 | ✅ | HTML 报告,支持预览/导出/下载 | | README.md / CLAUDE.md | ✅ | 完整项目文档 |
| 数据清理功能 | ✅ | 截图清理、孤儿清理、数据库优化 |
| 选择器优化 | ✅ | 多选器回退策略,19 个用例更新 |
| CI/CD 集成 | ✅ | GitHub Actions 工作流 |
| 测试用例依赖管理 | ✅ | 新增依赖模型、批量操作、拓扑排序 |
| GitCommit skill 完善 | ✅ | 三重门控、分支检查、推送确认 | | GitCommit skill 完善 | ✅ | 三重门控、分支检查、推送确认 |
| Handoff skill | ✅ | 自动生成交接文档 |
| **实测选择器 + 25 个可执行用例** | ✅ | **本次新增**:5 模块 25 用例,2 个已验证通过 |
| 26 个旧测试用例(上次会话) | ⚠️ | 旧数据库已丢失,需重新创建或从旧数据恢复 |
--- ---
...@@ -84,8 +87,6 @@ platform-auto-test/ ...@@ -84,8 +87,6 @@ platform-auto-test/
### 当前无严重阻塞项 ### 当前无严重阻塞项
Phase 5 剩余任务尚未启动,但无技术阻断:
| 待办 | 优先级 | 说明 | | 待办 | 优先级 | 说明 |
|------|--------|------| |------|--------|------|
| 系统配置页面 (Settings.vue) | P0 | 占位页面,需实现功能 | | 系统配置页面 (Settings.vue) | P0 | 占位页面,需实现功能 |
...@@ -96,12 +97,14 @@ Phase 5 剩余任务尚未启动,但无技术阻断: ...@@ -96,12 +97,14 @@ Phase 5 剩余任务尚未启动,但无技术阻断:
| 前端布局抽取 | P1 | 独立 Layout 组件 + 侧边栏折叠 | | 前端布局抽取 | P1 | 独立 Layout 组件 + 侧边栏折叠 |
| 定时任务 | P2 | Celery/APScheduler | | 定时任务 | P2 | Celery/APScheduler |
| Docker 部署 | P2 | Docker Compose 配置 | | Docker 部署 | P2 | Docker Compose 配置 |
| **补充更多深层交互用例** | P1 | 当前 25 个用例以页面访问为主,需增加表单填写、搜索筛选等深层交互 |
### 已知风险 ### 已知风险
- **SQLite 并发瓶颈**当前使用 SQLite,多用户并发场景下可能出现 `database is locked`,远期需迁移 PostgreSQL - **SQLite 并发瓶颈**:多用户并发场景下可能出现 `database is locked`,远期需迁移 PostgreSQL
- **Windows 子进程限制**:Playwright 在 Windows asyncio 下无法正常启动,当前用同步 + 线程池规避,非完美方案 - **Windows 子进程限制**:Playwright 在 Windows asyncio 下无法正常启动,当前用同步 + 线程池规避
- **无用户权限系统**:当前无认证/授权机制,仅限内网使用 - **无用户权限系统**:当前无认证/授权机制,仅限内网使用
- **数据库易丢失**:SQLite 文件在 `data/` 目录(gitignored),换机器或清理后数据全丢,需考虑数据持久化方案
--- ---
...@@ -109,72 +112,118 @@ Phase 5 剩余任务尚未启动,但无技术阻断: ...@@ -109,72 +112,118 @@ Phase 5 剩余任务尚未启动,但无技术阻断:
### 坑 1:Playwright 在 Windows 上必须用同步 API ### 坑 1:Playwright 在 Windows 上必须用同步 API
- **现象**`NotImplementedError` (SelectorEventLoop 不支持子进程)`It looks like you are using Playwright Sync API inside the asyncio loop` - **现象**`NotImplementedError` (SelectorEventLoop 不支持子进程)
- **根因**:Windows 默认 `SelectorEventLoop` 不支持子进程管道`ProactorEventLoop` 虽支持但 Playwright 仍会检测 asyncio 环境 - **根因**:Windows 默认 `SelectorEventLoop` 不支持子进程管道
- **正确做法** - **正确做法**
1. `main.py` 设置 `asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())` 1. `main.py` 设置 `asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())`
2. 使用 `sync_playwright`(同步 API)而非 `async_playwright` 2. 使用 `sync_playwright`(同步 API)
3. 通过 `loop.run_in_executor()` 在线程池中执行 3. 通过 `loop.run_in_executor()` 在线程池中执行
4. 每个测试用例创建独立的 `PlaywrightExecutor` 实例 4. 每个测试用例创建独立的 `PlaywrightExecutor` 实例
- **不要**:尝试用 `asyncio.create_task``threading``multiprocessing` 在 Windows 上实现异步执行——都已试过,全部失败 - **不要**:尝试用 `asyncio.create_task``threading``multiprocessing` 在 Windows 上实现异步执行
### 坑 2:每个用例必须用独立的执行器实例 ### 坑 2:每个用例必须用独立的执行器实例
- **现象**:第二次执行时 `execute_case()` 调用 `start()` 失败 - **现象**:第二次执行时 `execute_case()` 调用 `start()` 失败
- **根因**:共享执行器在 `stop()` 后将 `_is_running` 设为 `False`,下次 `start()` 在 asyncio 环境中失败
- **正确做法**:每次执行都 `PlaywrightExecutor(...)` 新实例 - **正确做法**:每次执行都 `PlaywrightExecutor(...)` 新实例
### 坑 3:前端 baseURL 不能带 `/api` ### 坑 3:前端 baseURL 不能带 `/api`
- **现象**:请求路径变成 `/api/api/cases` 导致 404 - **现象**:请求路径变成 `/api/api/cases` 导致 404
- **根因**`baseURL: '/api'` 与路由路径 `/api/cases` 叠加
- **正确做法**`baseURL` 设为空字符串,路由路径写完整 `/api/...` - **正确做法**`baseURL` 设为空字符串,路由路径写完整 `/api/...`
### 坑 4:SQLite 并发写锁 ### 坑 4:SQLite 并发写锁
- **现象**`SQLite database is locked` - **现象**`SQLite database is locked`
- **正确做法**`check_same_thread=False` + `pool_pre_ping=True`,仍需注意同步执行期间的并发写入 - **正确做法**`check_same_thread=False` + `pool_pre_ping=True`
### 坑 5:bash 中 `start` 不是 Windows 原生 cmd ### 坑 5:bash 中 `start` 不是 Windows 原生 cmd
- **现象**:CMD 窗口打开、cd 成功但 claude 不执行 - **现象**:CMD 窗口打开但 claude 不执行
- **根因**:bash 里的 `start` 解析为 `/usr/bin/start`(MSYS 封装),会损坏 `&&` 复合命令的引号
- **正确做法**`MSYS_NO_PATHCONV=1 cmd.exe /c start "Title" cmd /k "<bat 路径>"` - **正确做法**`MSYS_NO_PATHCONV=1 cmd.exe /c start "Title" cmd /k "<bat 路径>"`
### 坑 6:`claude` 命令不在 Windows PATH ### 坑 6:`claude` 命令不在 Windows PATH
- **现象**:新开的 cmd 窗口敲 `claude` 报"不是内部或外部命令" - **正确做法**:用 `which claude` 找出完整路径,补 `.cmd` 后缀
- **正确做法**:用 `which claude` 找出完整路径,补 `.cmd` 后缀,用完整路径调用
### 坑 7:前端 axios 超时 ### 坑 7:前端 axios 超时
- 默认 30s 超时,但 Playwright 执行可能需要几分钟 - 默认 30s 太短,Playwright 执行可能需要几分钟
- **正确做法**:超时设为 5 分钟(300000ms) - **正确做法**:超时设为 5 分钟(300000ms)
### 坑 8:curl 发送中文 JSON 到 FastAPI 失败
- **现象**`curl -d '{"name":"登录模块"}'``There was an error parsing the body`
- **根因**:Windows bash 下 curl 发送中文编码问题
- **正确做法**:用 Python httpx/requests 发送,或确保 curl 使用 UTF-8 编码
### 坑 9:Playwright 浏览器 JS evaluate 中正则表达式转义
- **现象**`SyntaxError: Invalid regular expression: missing /`
- **根因**:Python 字符串中的 `\n` 等转义字符传入 JS evaluate 时被二次解析
- **正确做法**:避免在 `page.evaluate()` 中使用含反斜杠的字符串,改用 Playwright 原生 API
--- ---
## 六、重要文档索引 ## 六、被测系统实测选择器(重要参考)
| 文档 | 路径 | 说明 | ### 登录页面 (https://192.168.5.44)
|------|------|------|
| PRD 主文档 | `Docs/PRD/自动化测试平台/_PRD_平台自动化测试可视化系统需求文档.md` | 项目需求定义 | | 元素 | 实测选择器 | 备注 |
| 执行计划 | `Docs/PRD/自动化测试平台/_PRD_平台自动化测试可视化系统需求文档_计划执行.md` | 分阶段执行计划 | |------|-----------|------|
| 当前进度记录 | `Docs/PRD/自动化测试平台/当前进度记录.md` | 含 26 个测试用例结果、关键决策 | | 账号输入框 | `input[placeholder*="手机号"]` | placeholder="手机号/用户名/邮箱" |
| 进度报告 | `Docs/PRD/自动化测试平台/_开发进度报告_20260709.md` | 含完整 API 清单、Phase 5 待办 | | 密码输入框 | `input[placeholder*="密码"]` | type=password |
| 测试结果汇总 | `Docs/PRD/自动化测试平台/测试结果汇总报告.md` | 执行结果详细数据 | | 图形验证码 | `input[placeholder*="图形验证"]` | 固定值 csba |
| 登录模块优化 | `Docs/PRD/自动化测试平台/_PRD_需求文档_登录模块测试优化.md` | 最新 PRD 需求 | | 手机号输入框 | `input[placeholder="手机号"]` | 短信登录 tab |
| 登录模块执行计划 | `Docs/PRD/自动化测试平台/_执行计划_登录模块测试优化.md` | 对应的执行计划 | | 邮箱输入框 | `input[placeholder="邮箱"]` | 邮箱登录 tab |
| 用例管理完成报告 | `Docs/PRD/自动化测试平台/测试用例管理优化-第一阶段完成报告.md` | 依赖管理功能完成报告 | | 登录按钮 | `button.login_btn` | class 含 login_btn + el-button--primary |
| 密码登录 tab | `text=密码登录` | |
| 短信登录 tab | `text=短信登录` | |
| 邮箱登录 tab | `text=邮箱登录` | |
| 同意协议弹窗 | `button:has-text("确定")` | 登录后弹出 |
| 登录成功标志 | `text=admin@xty` | 用户名显示在页面右上角 |
| 登录成功 URL | `#/Home` | URL 包含 Home |
### 登录后首页功能入口
常用功能区包含 23 个功能入口:新建会议、会议模板、个人日程、会议室列表、会议列表、预定数据、使用数据、管理看板、故障数据、运维数据、运维统计、巡检报表、会议统计、通知统计、会议服务统计、会议室统计、Welink会议统计、历史记录-WeLink、SMC3.0会议统计、历史记录-SMC3.0、会议概览、会议室概览、置顶功能
--- ---
## 七、启动指南 ## 七、当前平台数据
| 指标 | 数值 |
|------|------|
| 模块总数 | 5 |
| 用例总数 | 25 |
| 执行总数 | 2 |
| 通过率 | 100% (2/2) |
### 模块分布
| 模块 | 用例数 | 优先级 |
|------|--------|--------|
| 登录模块 | 5 | 高 |
| 会议管理 | 6 | 高 |
| 数据分析 | 8 | 中 |
| 运维管理 | 3 | 中 |
| 管理看板 | 3 | 高 |
### 已验证通过的用例
| 用例 | 步骤数 | 耗时 | 结果 |
|------|--------|------|------|
| 登录-页面访问验证 | 5 | 16.7s | ✅ 全部 passed |
| 登录-完整登录流程 | 8 | 169.2s | ✅ 全部 passed |
---
## 八、启动指南
```bash ```bash
# 后端(端口 8001) # 后端(端口 8001)
cd backend cd backend
pip install -r requirements.txt pip install -r requirements.txt
playwright install chromium playwright install chromium # 首次需安装浏览器
uvicorn app.main:app --reload --port 8001 uvicorn app.main:app --reload --port 8001
# 前端(端口 3000,代理指向 8001) # 前端(端口 3000,代理指向 8001)
...@@ -192,25 +241,40 @@ npm run dev ...@@ -192,25 +241,40 @@ npm run dev
- 登录用户: admin@xty / Ubains@13579 - 登录用户: admin@xty / Ubains@13579
- 验证码: csba(固定值) - 验证码: csba(固定值)
**批量创建用例脚本**
```bash
cd backend
python scripts/create_login_cases_full.py
```
--- ---
## 八、本次会话详细信息 ## 九、重要文档索引
### 会话时间 | 文档 | 路径 | 说明 |
2026-07-13 ~ 2026-07-13 |------|------|------|
| README.md | `README.md` | 项目说明文档(本次新增) |
| CLAUDE.md | `CLAUDE.md` | Claude Code 工作指导(本次新增) |
| PRD 主文档 | `Docs/PRD/自动化测试平台/_PRD_平台自动化测试可视化系统需求文档.md` | 项目需求定义 |
| 执行计划 | `Docs/PRD/自动化测试平台/_PRD_平台自动化测试可视化系统需求文档_计划执行.md` | 分阶段执行计划 |
| 当前进度记录 | `Docs/PRD/自动化测试平台/当前进度记录.md` | 含 26 个旧用例结果、关键决策 |
| 进度报告 | `Docs/PRD/自动化测试平台/_开发进度报告_20260709.md` | 含完整 API 清单、Phase 5 待办 |
| 登录模块优化 PRD | `Docs/PRD/自动化测试平台/_PRD_需求文档_登录模块测试优化.md` | 登录用例优化方案 |
| 执行器准确率优化 PRD | `Docs/PRD/自动化测试平台/_PRD_需求文档_执行器准确率优化.md` | 选择器增强方案 |
| 用例创建脚本 | `backend/scripts/create_login_cases_full.py` | 基于实测选择器的批量用例创建 |
### 执行的操作 ---
1. 使用 `/CreateCMD 3` 打开 3 个 CMD 窗口运行 Claude
2. 修复 `CreateCMD` skill 的路径配置(从 `E:\` 更新为 `C:\`
3. 创建 `Handoff` skill
4. 生成本 HANDOFF.md
### 未提交的变更 ## 十、未提交的变更
- `.claude/skills/CreateCMD/_run_claude.bat` — 路径配置修改
- `pc自动化测试/` 目录 — 新出现的 untracked 目录
### 远程仓库 | 文件 | 说明 | 是否应提交 |
- `origin`: http://git.ubainsyun.com/bing/ubains-module-test.git |------|------|-----------|
| `backend/scripts/create_login_cases_full.py` | 用例创建脚本 | ✅ 应提交 |
| `backend/login_page.png` | 登录页截图(调试用) | ❌ 不应提交 |
| `backend/data/` | SQLite 数据库 + 截图 + 报告 | ❌ gitignored |
| `frontend/package-lock.json` | npm 锁文件 | ✅ 应提交 |
| `frontend/node_modules/` | npm 依赖 | ❌ gitignored |
| `pc自动化测试/` | 非项目源码 | ❌ 不应提交 |
--- ---
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称:create_login_cases_full.py
模块描述:基于真实访问被测系统获取的页面元素,批量创建登录模块及可执行测试用例
被测系统:统一管理平台 https://192.168.5.44
登录页面元素(实测):
- 账号输入框: input[placeholder*="手机号"] (placeholder="手机号/用户名/邮箱")
- 密码输入框: input[placeholder*="密码"] (type=password)
- 图形验证码: input[placeholder*="图形验证"]
- 登录按钮: button.login_btn (class="el-button login_input login_btn el-button--primary")
- 同意协议弹窗: 点击"确定"按钮
- 登录方式切换: 密码登录 / 短信登录 / 邮箱登录 (.login-tab)
- 登录成功标志: URL 跳转到 #/Home,显示用户名 admin@xty12
作者:czj
创建日期:2026-07-13
"""
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"
async def create_all():
async with httpx.AsyncClient(timeout=30) as client:
# ==================== 1. 创建登录模块 ====================
print("=" * 60)
print("【1】创建登录模块")
print("=" * 60)
r = await client.post(f"{BASE_URL}/api/modules", json={
"name": "登录模块",
"description": "统一管理平台登录功能测试:页面访问、正常登录、异常登录、登录方式切换",
"icon": "lock",
})
if r.status_code == 201:
login_module_id = r.json()["id"]
print(f" ✅ 登录模块创建成功: {login_module_id}")
else:
# 可能已存在,查询获取
r2 = await client.get(f"{BASE_URL}/api/modules?skip=0&limit=100")
for m in r2.json().get("items", []):
if m["name"] == "登录模块":
login_module_id = m["id"]
print(f" ℹ️ 登录模块已存在: {login_module_id}")
break
# ==================== 2. 登录模块用例 ====================
login_cases = build_login_cases(login_module_id)
for case in login_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
if r.status_code == 201:
print(f" ✅ {case['name']}")
else:
print(f" ❌ {case['name']}: {r.status_code} {r.text[:150]}")
# ==================== 3. 创建会议管理模块 ====================
print("\n" + "=" * 60)
print("【3】创建会议管理模块")
print("=" * 60)
meeting_module_id = await create_module(client, "会议管理", "会议预约、模板、列表等会议相关功能", "calendar")
meeting_cases = build_meeting_cases(meeting_module_id)
for case in meeting_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
status = "✅" if r.status_code == 201 else "❌"
print(f" {status} {case['name']}")
# ==================== 4. 创建数据分析模块 ====================
print("\n" + "=" * 60)
print("【4】创建数据分析模块")
print("=" * 60)
data_module_id = await create_module(client, "数据分析", "使用数据、故障数据、会议统计等数据看板", "data-line")
data_cases = build_data_cases(data_module_id)
for case in data_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
status = "✅" if r.status_code == 201 else "❌"
print(f" {status} {case['name']}")
# ==================== 5. 创建运维管理模块 ====================
print("\n" + "=" * 60)
print("【5】创建运维管理模块")
print("=" * 60)
ops_module_id = await create_module(client, "运维管理", "运维数据、巡检报表、设备管理", "setting")
ops_cases = build_ops_cases(ops_module_id)
for case in ops_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
status = "✅" if r.status_code == 201 else "❌"
print(f" {status} {case['name']}")
# ==================== 6. 创建管理看板模块 ====================
print("\n" + "=" * 60)
print("【6】创建管理看板模块")
print("=" * 60)
board_module_id = await create_module(client, "管理看板", "管理看板、会议室概览、通知统计", "data-board")
board_cases = build_board_cases(board_module_id)
for case in board_cases:
r = await client.post(f"{BASE_URL}/api/cases", json=case)
status = "✅" if r.status_code == 201 else "❌"
print(f" {status} {case['name']}")
# ==================== 汇总 ====================
print("\n" + "=" * 60)
print("【汇总】")
print("=" * 60)
stats = await client.get(f"{BASE_URL}/api/stats/overview")
s = stats.json()
print(f" 模块总数: {s['modules']['total']}")
print(f" 用例总数: {s['cases']['total']}")
async def create_module(client, name, description, icon):
r = await client.post(f"{BASE_URL}/api/modules", json={
"name": name, "description": description, "icon": icon
})
if r.status_code == 201:
print(f" ✅ {name}模块创建成功")
return r.json()["id"]
else:
r2 = await client.get(f"{BASE_URL}/api/modules?skip=0&limit=100")
for m in r2.json().get("items", []):
if m["name"] == name:
print(f" ℹ️ {name}模块已存在")
return m["id"]
def nav_step(order, name):
"""标准导航步骤:打开登录页 + 登录 + 同意协议 + 等待首页"""
return {
"order": order,
"name": name,
"action": "navigate",
"params": {"url": TARGET_URL},
}
def login_steps(start_order=1):
"""登录前置步骤序列,返回步骤列表"""
return [
{
"order": start_order,
"name": "打开登录页面",
"action": "navigate",
"params": {"url": TARGET_URL},
},
{
"order": start_order + 1,
"name": "等待登录表单加载",
"action": "wait",
"params": {"selector": 'input[placeholder*="手机号"]', "timeout": 15000},
},
{
"order": start_order + 2,
"name": "输入账号",
"action": "fill",
"params": {"selector": 'input[placeholder*="手机号"]', "value": USERNAME},
},
{
"order": start_order + 3,
"name": "输入密码",
"action": "fill",
"params": {"selector": 'input[placeholder*="密码"]', "value": PASSWORD},
},
{
"order": start_order + 4,
"name": "输入图形验证码",
"action": "fill",
"params": {"selector": 'input[placeholder*="图形验证"]', "value": CAPTCHA},
},
{
"order": start_order + 5,
"name": "点击登录按钮",
"action": "click",
"params": {"selector": "button.login_btn"},
},
]
LOGIN_CONFIG = {
"timeout": 120000,
"screenshot": True,
"auto_login": False,
"step_retry_count": 2,
}
# 登录后需要同意协议弹窗的步骤
AGREE_STEP = {
"name": "确认同意协议弹窗",
"action": "click",
"params": {"selector": 'button:has-text("确定")'},
}
def build_login_cases(module_id):
"""登录模块用例"""
cases = []
# 用例1:登录-页面访问验证
cases.append({
"module_id": module_id,
"name": "登录-页面访问验证",
"description": "验证登录页面可正常访问,标题、表单元素正确显示",
"priority": "high",
"tags": ["页面访问", "冒烟测试"],
"steps": [
nav_step(1, "打开登录页面"),
{"order": 2, "name": "验证页面标题", "action": "assert",
"params": {"type": "title", "expected": "统一管理平台"}},
{"order": 3, "name": "等待账号输入框", "action": "wait",
"params": {"selector": 'input[placeholder*="手机号"]', "timeout": 10000}},
{"order": 4, "name": "验证密码输入框存在", "action": "assert",
"params": {"selector": 'input[type="password"]', "type": "visible"}},
{"order": 5, "name": "验证登录按钮存在", "action": "assert",
"params": {"selector": "button.login_btn", "type": "visible"}},
],
"config": {"timeout": 60000, "screenshot": True, "auto_login": False},
})
# 用例2:登录-完整登录流程
steps = login_steps(1)
steps.append({"order": 7, "name": "确认同意协议弹窗", "action": "click",
"params": {"selector": 'button:has-text("确定")'}})
steps.append({"order": 8, "name": "等待登录成功跳转", "action": "wait",
"params": {"selector": "text=admin@xty", "timeout": 15000}})
cases.append({
"module_id": module_id,
"name": "登录-完整登录流程",
"description": "完整的密码登录流程:输入账号密码、图形验证码、点击登录、确认协议、验证登录成功",
"priority": "high",
"tags": ["核心流程", "登录", "P0"],
"steps": steps,
"config": LOGIN_CONFIG,
})
# 用例3:登录-空字段提交验证
cases.append({
"module_id": module_id,
"name": "登录-空账号密码验证",
"description": "验证账号密码为空时点击登录,系统应提示错误,不应跳转",
"priority": "medium",
"tags": ["异常场景"],
"steps": [
nav_step(1, "打开登录页面"),
{"order": 2, "name": "等待表单加载", "action": "wait",
"params": {"selector": 'input[placeholder*="手机号"]', "timeout": 10000}},
{"order": 3, "name": "直接点击登录按钮(不填写)", "action": "click",
"params": {"selector": "button.login_btn"}},
{"order": 4, "name": "验证仍停留在登录页", "action": "assert",
"params": {"selector": 'input[placeholder*="手机号"]', "type": "visible"}},
],
"config": {"timeout": 60000, "screenshot": True, "auto_login": False},
})
# 用例4:登录-错误密码验证
steps = login_steps(1)
# 修正密码为错误密码(覆盖步骤4)
steps[3]["params"]["value"] = "WrongPassword123"
steps.append({"order": 7, "name": "等待错误提示出现", "action": "wait",
"params": {"selector": ".el-message, .el-message-box", "timeout": 10000}})
cases.append({
"module_id": module_id,
"name": "登录-错误密码验证",
"description": "验证使用错误密码登录时,系统应给出错误提示,不进入系统",
"priority": "medium",
"tags": ["异常场景", "安全验证"],
"steps": steps,
"config": {"timeout": 120000, "screenshot": True, "auto_login": False},
})
# 用例5:登录-登录方式切换验证
cases.append({
"module_id": module_id,
"name": "登录-登录方式切换验证",
"description": "验证密码登录/短信登录/邮箱登录三种方式可以正常切换",
"priority": "low",
"tags": ["UI验证"],
"steps": [
nav_step(1, "打开登录页面"),
{"order": 2, "name": "等待表单加载", "action": "wait",
"params": {"selector": 'input[placeholder*="手机号"]', "timeout": 10000}},
{"order": 3, "name": "切换到短信登录", "action": "click",
"params": {"selector": 'text=短信登录'}},
{"order": 4, "name": "验证手机号输入框出现", "action": "wait",
"params": {"selector": 'input[placeholder="手机号"]', "timeout": 5000}},
{"order": 5, "name": "切换到邮箱登录", "action": "click",
"params": {"selector": 'text=邮箱登录'}},
{"order": 6, "name": "验证邮箱输入框出现", "action": "wait",
"params": {"selector": 'input[placeholder="邮箱"]', "timeout": 5000}},
{"order": 7, "name": "切换回密码登录", "action": "click",
"params": {"selector": 'text=密码登录'}},
{"order": 8, "name": "验证密码登录表单恢复", "action": "assert",
"params": {"selector": 'input[placeholder*="手机号"]', "type": "visible"}},
],
"config": {"timeout": 60000, "screenshot": True, "auto_login": False},
})
return cases
def build_meeting_cases(module_id):
"""会议管理模块用例(登录后访问各功能页)"""
pages = [
("新建会议", "新建会议页面访问验证", "验证新建会议页面可正常打开"),
("会议模板", "会议模板页面访问验证", "验证会议模板页面可正常打开"),
("个人日程", "个人日程页面访问验证", "验证个人日程页面可正常打开"),
("会议室列表", "会议室列表页面访问验证", "验证会议室列表页面可正常打开"),
("会议列表", "会议列表页面访问验证", "验证会议列表页面可正常打开"),
("预定数据", "预定数据页面访问验证", "验证预定数据页面可正常打开"),
]
cases = []
for page_name, case_name, desc in pages:
steps = login_steps(1)
steps.append({"order": 7, "name": "确认同意协议弹窗", "action": "click",
"params": {"selector": 'button:has-text("确定")'}})
steps.append({"order": 8, "name": "等待进入首页", "action": "wait",
"params": {"selector": "text=admin@xty", "timeout": 15000}})
steps.append({"order": 9, "name": f"点击进入{page_name}", "action": "click",
"params": {"selector": f"text={page_name}"}})
steps.append({"order": 10, "name": f"验证{page_name}页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}})
cases.append({
"module_id": module_id,
"name": case_name,
"description": desc,
"priority": "high",
"tags": ["页面访问", page_name],
"steps": steps,
"config": LOGIN_CONFIG,
})
return cases
def build_data_cases(module_id):
"""数据分析模块用例"""
pages = [
"使用数据", "故障数据", "会议统计", "通知统计",
"会议服务统计", "会议室统计", "Welink会议统计", "SMC3.0会议统计",
]
cases = []
for page_name in pages:
steps = login_steps(1)
steps.append({"order": 7, "name": "确认同意协议弹窗", "action": "click",
"params": {"selector": 'button:has-text("确定")'}})
steps.append({"order": 8, "name": "等待进入首页", "action": "wait",
"params": {"selector": "text=admin@xty", "timeout": 15000}})
steps.append({"order": 9, "name": f"点击进入{page_name}", "action": "click",
"params": {"selector": f"text={page_name}"}})
steps.append({"order": 10, "name": f"验证{page_name}页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}})
cases.append({
"module_id": module_id,
"name": f"{page_name}-页面访问验证",
"description": f"验证{page_name}数据页面可正常打开并显示",
"priority": "medium",
"tags": ["数据分析", page_name],
"steps": steps,
"config": LOGIN_CONFIG,
})
return cases
def build_ops_cases(module_id):
"""运维管理模块用例"""
pages = ["运维数据", "运维统计", "巡检报表"]
cases = []
for page_name in pages:
steps = login_steps(1)
steps.append({"order": 7, "name": "确认同意协议弹窗", "action": "click",
"params": {"selector": 'button:has-text("确定")'}})
steps.append({"order": 8, "name": "等待进入首页", "action": "wait",
"params": {"selector": "text=admin@xty", "timeout": 15000}})
steps.append({"order": 9, "name": f"点击进入{page_name}", "action": "click",
"params": {"selector": f"text={page_name}"}})
steps.append({"order": 10, "name": f"验证{page_name}页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}})
cases.append({
"module_id": module_id,
"name": f"{page_name}-页面访问验证",
"description": f"验证{page_name}页面可正常打开",
"priority": "medium",
"tags": ["运维管理", page_name],
"steps": steps,
"config": LOGIN_CONFIG,
})
return cases
def build_board_cases(module_id):
"""管理看板模块用例"""
pages = ["管理看板", "会议概览", "会议室概览"]
cases = []
for page_name in pages:
steps = login_steps(1)
steps.append({"order": 7, "name": "确认同意协议弹窗", "action": "click",
"params": {"selector": 'button:has-text("确定")'}})
steps.append({"order": 8, "name": "等待进入首页", "action": "wait",
"params": {"selector": "text=admin@xty", "timeout": 15000}})
steps.append({"order": 9, "name": f"点击进入{page_name}", "action": "click",
"params": {"selector": f"text={page_name}"}})
steps.append({"order": 10, "name": f"验证{page_name}页面加载", "action": "wait",
"params": {"selector": "body", "timeout": 10000}})
cases.append({
"module_id": module_id,
"name": f"{page_name}-页面访问验证",
"description": f"验证{page_name}页面可正常打开",
"priority": "high",
"tags": ["管理看板", page_name],
"steps": steps,
"config": LOGIN_CONFIG,
})
return cases
if __name__ == "__main__":
asyncio.run(create_all())
print("\n🎉 全部用例创建完成!")
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论