|
|
|
@ -5,444 +5,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 一、历史问题复盘与根因分析
|
|
|
|
## 一、6条铁律
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 问题清单(实际发生)
|
|
|
|
| # | 铁律 | 要求 |
|
|
|
|
|
|
|
|
|---|------|------|
|
|
|
|
#### 前后端契约断裂(5起)
|
|
|
|
| 1 | **接口先验证** | 改了任何API,立即用PowerShell调用,逐字段确认返回值与前端绑定一致 |
|
|
|
|
|
|
|
|
| 2 | **数据断言必须具体** | 禁止只检查 `toBeVisible()`,必须检查文本内容,排除空/`-`/`--`/`undefined`/`null` |
|
|
|
|
| # | 问题 | 现象 | 教训 |
|
|
|
|
| 3 | **下拉框必须展开验证** | 展开→选项≥1→选择→确认显示值→点查询→确认筛选生效 |
|
|
|
|
|---|------|------|------|
|
|
|
|
| 4 | **表格必须检查每列** | 行数>0 + 第一行每列文本非空(日期、名称、数值、时间、状态逐列检查) |
|
|
|
|
| 1 | API返回 `{value, label}`,前端绑定 `{id, name}` | 车间/机床/工人下拉框全部无选项 | 前后端字段名必须对齐,不能各写各的 |
|
|
|
|
| 5 | **卡片数值必须非空** | 每个卡片数值 ≠ 空 ≠ `-` ≠ `--` ≠ `undefined` ≠ `null`,允许为0时须注释说明 |
|
|
|
|
| 2 | DTO新增 `activeMachineCount` 等字段,Service未填充值 | 汇总卡片(运行机床/切削总时/平均产量)显示空 | DTO加字段 ≠ 数据会自动填上 |
|
|
|
|
| 6 | **改完即验证** | 每改一处→编译→API验证→浏览器验证,禁止累积 |
|
|
|
|
| 3 | 采集状态API返回 `IsRunning`/`LastCollectTime`,前端期望 `status`/`uptimeSeconds` | 采集服务状态不显示 | 前后端命名风格(PascalCase vs camelCase)必须统一 |
|
|
|
|
|
|
|
|
| 4 | 车间API返回 `{data:{items:[...]}}` 分页格式 | 前端取不到选项列表 | 返回数据结构(数组 vs 分页对象)前后端必须确认 |
|
|
|
|
|
|
|
|
| 5 | 修正弹窗HTML模板缺失 | 弹窗打不开 | 前端组件完整性检查 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 测试验证流于形式(2起)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| # | 问题 | 现象 | 教训 |
|
|
|
|
|
|
|
|
|---|------|------|------|
|
|
|
|
|
|
|
|
| 6 | Playwright 37项测试全通过,但所有功能都不能用 | 断言只检查"元素存在",不检查"数据内容" | 必须验证数据值,不能只验证DOM结构 |
|
|
|
|
|
|
|
|
| 7 | 没有展开下拉框确认有选项 | 下拉框无数据但测试"通过" | 交互控件必须实际操作验证 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 后端NULL占位符未填充(2起)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| # | 问题 | 现象 | 教训 |
|
|
|
|
|
|
|
|
|---|------|------|------|
|
|
|
|
|
|
|
|
| 8 | SQL直接写 `NULL AS TotalRunTime, NULL AS TotalCuttingTime` | 表格运行时间/切削时间全为空 | NULL不报错、不崩溃,隐蔽性极强 |
|
|
|
|
|
|
|
|
| 9 | `DailySummaryResponse.MachineCount` 硬编码为0 | 运行机床数永远为0 | 禁止用占位值应付编译 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 配置/格式硬编码(3起)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| # | 问题 | 现象 | 教训 |
|
|
|
|
|
|
|
|
|---|------|------|------|
|
|
|
|
|
|
|
|
| 10 | Element Plus日期格式用 `yyyy-MM-dd`(错误),应为 `YYYY-MM-DD`(dayjs格式) | 日期选择器值无法解析 | 不同库的日期格式不同,必须查阅文档 |
|
|
|
|
|
|
|
|
| 11 | 采集服务serviceId为 `collector-service`,代码硬编码 `CncCollector` | 心跳匹配不上,采集状态显示未启动 | 关键配置项不能硬编码,必须与实际值一致 |
|
|
|
|
|
|
|
|
| 12 | 产量报表默认日期未设为今天 | 打开页面没有数据 | 页面初始化必须设置合理的默认值 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 开发流程缺陷(3起)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| # | 问题 | 现象 | 教训 |
|
|
|
|
|
|
|
|
|---|------|------|------|
|
|
|
|
|
|
|
|
| 13 | 编译通过 = "测试通过" | 功能全部不可用 | 编译通过 ≠ 功能正确 |
|
|
|
|
|
|
|
|
| 14 | 改完代码不发布到IIS就宣称完成 | 用户看到的是旧版本 | 每次改动必须发布 |
|
|
|
|
|
|
|
|
| 15 | Playwright只访问页面不操作控件 | 控件功能全不可用但测试"通过" | 必须模拟真实用户操作 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 四大根因
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 根因1:前后端接口契约断裂
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么反复出现:** 没有统一的接口契约文档。后端改字段名前端不知道,前端加绑定后端没实现。DTO只是C#类型定义,不等于运行时有数据。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**改进措施:**
|
|
|
|
|
|
|
|
- 每个API端点必须用PowerShell/脚本实际调用,确认返回的JSON字段名和结构
|
|
|
|
|
|
|
|
- 前端绑定字段名时,必须对照API实际返回值,不能凭假设
|
|
|
|
|
|
|
|
- 新增DTO字段后,必须追踪到Service层确认有填充逻辑
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 根因2:测试断言停留在DOM结构层面
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么反复出现:** Playwright的 `toBeVisible()` 只检查元素是否渲染,不检查内容。元素渲染了但内容是空的、NULL的、占位符的,测试照样通过。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**改进措施:**
|
|
|
|
|
|
|
|
- 断言必须检查具体文本内容(`toHaveText()`、`textContent()`)
|
|
|
|
|
|
|
|
- 数值断言必须排除空字符串、`-`、`--`、`undefined`、`null`、`0`(除非业务确认)
|
|
|
|
|
|
|
|
- 下拉框必须展开确认有选项,不能只检查select元素存在
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 根因3:NULL占位符滥用
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么反复出现:** 开发时用 `NULL AS 字段名` 占位,编译不报错、API返回200、不崩溃。NULL是"合法的空值",不会被任何自动检查发现。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**改进措施:**
|
|
|
|
|
|
|
|
- 禁止SQL中写 `NULL AS 字段名` 作为占位符
|
|
|
|
|
|
|
|
- 如果字段暂时无法计算,应抛出 NotImplementedException 而非返回NULL
|
|
|
|
|
|
|
|
- Code Review时重点检查SELECT列表中的NULL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 根因4:缺少强制验证门禁
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么反复出现:** 开发完成后没有"必须通过"的验证步骤。编译成功就认为完成了,没有实际操作验证。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**改进措施:**
|
|
|
|
|
|
|
|
- 建立4层验证流程(编译→API→浏览器→发布),每层必须通过才能进入下一层
|
|
|
|
|
|
|
|
- Playwright测试脚本覆盖所有交互控件和数据展示
|
|
|
|
|
|
|
|
- 每次改动后立即验证,不累积到"全部改完"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 二、测试原则(6条铁律)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 铁律1:接口先验证
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么:** 后端改了API返回格式,前端不知道就白改。先验证API再写前端,避免返工。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**怎么做:**
|
|
|
|
|
|
|
|
- 改了任何API端点,立即用PowerShell脚本调用
|
|
|
|
|
|
|
|
- 检查返回的JSON:字段名、字段值、数据结构、非空字段
|
|
|
|
|
|
|
|
- 把实际返回值和前端绑定对比,逐字段确认一致
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
|
|
# 登录获取token
|
|
|
|
|
|
|
|
$loginRes = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/login" -Method Post -ContentType "application/json" -Body '{"username":"admin","password":"admin123"}'
|
|
|
|
|
|
|
|
$token = $loginRes.data.token
|
|
|
|
|
|
|
|
$headers = @{ Authorization = "Bearer $token" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 调用目标API
|
|
|
|
|
|
|
|
$result = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/production/daily-summary?date=2026-05-03" -Headers $headers
|
|
|
|
|
|
|
|
$result.data | ConvertTo-Json # 逐字段检查
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 铁律2:数据断言必须具体
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么:** `toBeVisible()` 只能证明元素渲染了,不能证明数据正确。空值、占位符、错误值都能通过。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**怎么做:**
|
|
|
|
|
|
|
|
- 禁止只检查元素存在
|
|
|
|
|
|
|
|
- 必须检查元素的文本内容
|
|
|
|
|
|
|
|
- 数值必须是非空、非占位符的具体数字
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
|
|
// ❌ 错误:只检查元素存在
|
|
|
|
|
|
|
|
await expect(page.locator('.card-value')).toBeVisible()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 正确:检查具体数值
|
|
|
|
|
|
|
|
const value = await page.locator('.card-value').textContent()
|
|
|
|
|
|
|
|
expect(value).toBeTruthy()
|
|
|
|
|
|
|
|
expect(value).not.toBe('')
|
|
|
|
|
|
|
|
expect(value).not.toBe('-')
|
|
|
|
|
|
|
|
expect(value).not.toBe('--')
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 铁律3:下拉框必须展开验证
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么:** 下拉框渲染了不代表有选项。API返回空数组时select元素照样显示,只是展开后没有选项。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**怎么做:**
|
|
|
|
|
|
|
|
1. 点击下拉框展开
|
|
|
|
|
|
|
|
2. 等待下拉选项出现
|
|
|
|
|
|
|
|
3. 断言选项数量 >= 1
|
|
|
|
|
|
|
|
4. 选择一个选项
|
|
|
|
|
|
|
|
5. 确认输入框显示选中值
|
|
|
|
|
|
|
|
6. 点查询,确认筛选生效
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
|
|
// ❌ 错误:只检查select元素存在
|
|
|
|
|
|
|
|
await expect(page.locator('.el-select')).toBeVisible()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 正确:展开→确认有选项→选择→验证
|
|
|
|
|
|
|
|
await page.locator('.el-select').click() // 点击外层wrapper展开
|
|
|
|
|
|
|
|
const options = page.locator('.el-select-dropdown__item:visible')
|
|
|
|
|
|
|
|
await expect(options.first()).toBeVisible()
|
|
|
|
|
|
|
|
const count = await options.count()
|
|
|
|
|
|
|
|
expect(count).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
await options.first().click()
|
|
|
|
|
|
|
|
await page.getByRole('button', { name: '查询' }).click()
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 铁律4:表格必须检查数据列
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么:** 表格渲染了不代表有数据。后端返回空数组时表格只显示表头,但"表格可见"的断言照样通过。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**怎么做:**
|
|
|
|
|
|
|
|
- 检查表格有数据行(`.el-table__row` 数量 > 0)
|
|
|
|
|
|
|
|
- 取第一行,逐列检查文本内容不为空
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
|
|
const rows = page.locator('.el-table__body-wrapper .el-table__row')
|
|
|
|
|
|
|
|
const rowCount = await rows.count()
|
|
|
|
|
|
|
|
expect(rowCount).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查第一行的关键列
|
|
|
|
|
|
|
|
const firstRow = rows.first()
|
|
|
|
|
|
|
|
const cells = firstRow.locator('td .cell')
|
|
|
|
|
|
|
|
const dateText = await cells.nth(0).textContent()
|
|
|
|
|
|
|
|
expect(dateText).toBeTruthy()
|
|
|
|
|
|
|
|
expect(dateText).not.toBe('')
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 铁律5:卡片/统计必须检查数值
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么:** 卡片渲染了但数值是空、0、undefined、NaN,用户看到的是空白或错误。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**怎么做:**
|
|
|
|
|
|
|
|
- 每个卡片取数值部分
|
|
|
|
|
|
|
|
- 断言不为空字符串、不为 `-`、不为 `--`、不为 `undefined`、不为 `null`
|
|
|
|
|
|
|
|
- 如果业务上允许为0,需要明确注释说明
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 铁律6:改完即验证,不等批量
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**为什么:** 改了10处再一起验证,出问题时无法定位是哪一处改坏的。而且累积到后面容易漏验证。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**怎么做:**
|
|
|
|
|
|
|
|
- 每改一处 → 编译 → API验证 → 浏览器验证
|
|
|
|
|
|
|
|
- 全部通过后再改下一处
|
|
|
|
|
|
|
|
- 如果改动互相关联,最多合并2-3处一起验证
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 三、验证层级(4层)
|
|
|
|
## 二、验证层级
|
|
|
|
|
|
|
|
|
|
|
|
### L1 编译验证
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**执行时机:** 每次代码改动后
|
|
|
|
|
|
|
|
**命令:**
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
|
|
dotnet build src\CncWebApi\CncWebApi.csproj # 0错误
|
|
|
|
|
|
|
|
cd frontend && npm run build # 0错误
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
**通过标准:** 两个命令都返回退出码0
|
|
|
|
|
|
|
|
**注意:** 编译通过 ≠ 功能正确,这只是最基本的大门
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### L2 API验证
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**执行时机:** 改了后端代码后
|
|
|
|
|
|
|
|
**方法:** 用PowerShell调用改动过的API端点
|
|
|
|
|
|
|
|
**检查项:**
|
|
|
|
|
|
|
|
- HTTP状态码200
|
|
|
|
|
|
|
|
- 返回JSON的 `code` 字段为0
|
|
|
|
|
|
|
|
- 返回 `data` 不为null
|
|
|
|
|
|
|
|
- 关键字段值不为空/NULL
|
|
|
|
|
|
|
|
- 字段名和前端绑定一致
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**PowerShell模板:**
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
|
|
# 登录
|
|
|
|
|
|
|
|
$loginRes = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/login" -Method Post -ContentType "application/json" -Body '{"username":"admin","password":"admin123"}'
|
|
|
|
|
|
|
|
$token = $loginRes.data.token
|
|
|
|
|
|
|
|
$headers = @{ Authorization = "Bearer $token" }
|
|
|
|
|
|
|
|
$today = Get-Date -Format "yyyy-MM-dd"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 示例:验证产量汇总
|
|
|
|
|
|
|
|
$sum = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/production/daily-summary?date=$today" -Headers $headers
|
|
|
|
|
|
|
|
echo "总产量: $($sum.data.totalQuantity)" # 应有值
|
|
|
|
|
|
|
|
echo "运行机床: $($sum.data.activeMachineCount)" # 应 > 0
|
|
|
|
|
|
|
|
echo "切削总时: $($sum.data.totalCuttingTime)" # 应有值
|
|
|
|
|
|
|
|
echo "平均产量: $($sum.data.avgQuantityPerMachine)" # 应有值
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 示例:验证下拉选项
|
|
|
|
|
|
|
|
$ws = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/workshop/list" -Headers $headers
|
|
|
|
|
|
|
|
echo "车间数量: $($ws.data.items.Count)" # 应 > 0
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### L3 浏览器验证
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**执行时机:** 改了前端代码后、或需要验证用户交互时
|
|
|
|
|
|
|
|
**方法:** Playwright打开页面,实际操作每个交互控件
|
|
|
|
|
|
|
|
**检查项:** 见第五节"Playwright E2E测试断言标准"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**运行命令:**
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
|
|
cd frontend
|
|
|
|
|
|
|
|
npx playwright test e2e/smoke-iis.spec.ts --project=chromium
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### L4 发布验证
|
|
|
|
| 层级 | 执行时机 | 命令/方法 | 通过标准 |
|
|
|
|
|
|
|
|
|------|----------|-----------|----------|
|
|
|
|
|
|
|
|
| **L1 编译** | 每次代码改动后 | `dotnet build src\CncWebApi\CncWebApi.csproj` + `cd frontend && npm run build` | 两个命令退出码0 |
|
|
|
|
|
|
|
|
| **L2 API** | 改了后端后 | PowerShell调用改动过的API | HTTP 200 + `code=0` + `data`非null + 关键字段非空 |
|
|
|
|
|
|
|
|
| **L3 浏览器** | 改了前端后 | `cd frontend && npx playwright test e2e/smoke-iis.spec.ts --project=chromium` | 对照5.0维度总表逐项通过 |
|
|
|
|
|
|
|
|
| **L4 发布** | 交付前 | `appcmd recycle apppool "haoliang"` + 重复L2+L3 | 全部通过 + 局域网可访问 |
|
|
|
|
|
|
|
|
|
|
|
|
**执行时机:** 所有改动完成后、正式交付前
|
|
|
|
> 编译通过 ≠ 功能正确。L1只是最基本的大门。
|
|
|
|
**步骤:**
|
|
|
|
|
|
|
|
1. 回收AppPool:`appcmd recycle apppool "haoliang"`
|
|
|
|
|
|
|
|
2. 重复L2(API验证)
|
|
|
|
|
|
|
|
3. 重复L3(浏览器验证)
|
|
|
|
|
|
|
|
4. 确认 `http://192.168.1.202/admin/` 局域网可访问
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 四、前后端接口契约验证
|
|
|
|
## 三、前后端接口契约验证
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 字段名对齐检查
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
每次新增或修改API返回字段时,必须执行以下检查:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 检查项 | 方法 | 通过标准 |
|
|
|
|
每次新增或修改API返回字段时必须执行:
|
|
|
|
|--------|------|----------|
|
|
|
|
|
|
|
|
| 后端DTO字段名 vs API实际返回JSON | PowerShell调用API,查看返回值 | JSON key名与DTO属性名一致(考虑camelCase序列化) |
|
|
|
|
|
|
|
|
| API返回字段名 vs 前端绑定 | 对比Vue模板中的 `prop`/`:key`/`:label`/`:value` | 完全一致或兼容(支持 `??` 回退) |
|
|
|
|
|
|
|
|
| 返回数据结构 | 查看JSON嵌套层级 | 前端取值路径正确(`res.data.items` vs `res.data`) |
|
|
|
|
|
|
|
|
| 空值处理 | 构造空数据场景 | 前端显示合理的占位符(`-` 或 `暂无数据`),不报错 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 常见陷阱
|
|
|
|
| 检查项 | 通过标准 |
|
|
|
|
|
|
|
|
|--------|----------|
|
|
|
|
- **Newtonsoft.Json camelCase序列化**:C#的 `TotalQuantity` → JSON的 `totalQuantity`,前端绑定要对应
|
|
|
|
| DTO字段名 vs API实际返回JSON | 一致(考虑camelCase序列化:`TotalQuantity` → `totalQuantity`) |
|
|
|
|
- **分页格式**:部分API返回数组 `[{...}]`,部分返回分页对象 `{items:[], total:0}`,前端必须区分
|
|
|
|
| API返回字段名 vs 前端绑定 | 完全一致或兼容(`??` 回退) |
|
|
|
|
- **枚举值**:后端枚举可能序列化为整数或字符串,前端必须处理两种情况
|
|
|
|
| 返回数据结构 | 前端取值路径正确(`res.data.items` vs `res.data`) |
|
|
|
|
- **DateTime序列化**:可能为 `"2026-05-03T00:00:00"` 或 `"2026-05-03"`,前端日期解析要兼容
|
|
|
|
| 空值处理 | 前端显示占位符(`-` 或 `暂无数据`),不白屏不报错 |
|
|
|
|
|
|
|
|
| 分页格式 | 数组 `[{...}]` vs 分页对象 `{items:[], total:0}` 前后端一致 |
|
|
|
|
|
|
|
|
| 枚举值 | 前端处理整数和字符串两种情况 |
|
|
|
|
|
|
|
|
| DateTime | 前端兼容 `"2026-05-03T00:00:00"` 和 `"2026-05-03"` |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 五、Playwright E2E测试断言标准
|
|
|
|
## 四、Playwright E2E测试标准
|
|
|
|
|
|
|
|
|
|
|
|
> **强制要求:每次编写Playwright测试时,必须逐项对照以下维度清单。每个页面的每个维度都必须有对应测试用例覆盖,不允许遗漏。**
|
|
|
|
> **强制要求:每次编写Playwright测试,必须逐项对照5.0维度总表。每个维度有测试用例或注释说明跳过原因。**
|
|
|
|
|
|
|
|
|
|
|
|
### 5.0 测试维度总表(每个页面必查)
|
|
|
|
### 5.0 测试维度总表(每个页面必查)
|
|
|
|
|
|
|
|
|
|
|
|
以下20个维度是每个管理后台页面的**最低测试覆盖要求**。写任何页面的Playwright测试时,必须逐条确认是否有对应测试用例。如果某个维度不适用于当前页面(如该页面无下拉框),需在测试文件中用注释说明跳过原因。
|
|
|
|
| # | 维度 | 必须断言 | 适用 | 禁止 |
|
|
|
|
|
|
|
|
|---|------|----------|------|------|
|
|
|
|
| # | 维度 | 必须断言的内容 | 适用场景 | 禁止做法 |
|
|
|
|
| 1 | 页面加载 | URL正确 + 关键元素可见 | 所有页面 | 只goto不等待 |
|
|
|
|
|---|------|---------------|----------|----------|
|
|
|
|
| 2 | 默认数据 | 打开即有数据(行数>0 / 卡片有值) | 有数据展示的页面 | 不检查数据 |
|
|
|
|
| 1 | **页面加载** | URL正确 + 关键元素可见 | 所有页面 | 只goto不等待 |
|
|
|
|
| 3 | 汇总卡片数值 | ≠ 空 ≠ `-` ≠ `--` ≠ `undefined` ≠ `null` | 有统计卡片的页面 | 只检查可见 |
|
|
|
|
| 2 | **默认数据** | 页面打开即有数据(表格行数>0 / 卡片有值) | 有数据展示的页面 | 打开页面不检查数据 |
|
|
|
|
| 4 | 表格行数 | `.el-table__row` > 0 | 有表格的页面 | 只检查table可见 |
|
|
|
|
| 3 | **汇总卡片数值** | 每个卡片的数值 ≠ 空 ≠ `-` ≠ `--` ≠ `undefined` ≠ `null` | 有统计卡片的页面 | 只检查卡片可见不检查数值 |
|
|
|
|
| 5 | 表格每列有值 | 第一行逐列文本非空 | 有表格的页面 | 只检查前2-3列 |
|
|
|
|
| 4 | **表格行数** | `.el-table__row` 数量 > 0 | 有表格的页面 | 只检查table可见 |
|
|
|
|
| 6 | 时间/数值列 | 有具体数值(非空非NULL) | 有时间/数值列的页面 | 跳过 |
|
|
|
|
| 5 | **表格每列有值** | 第一行每列文本 ≠ 空(日期、名称、数值、时间、状态等逐列检查) | 有表格的页面 | 只检查前2-3列 |
|
|
|
|
| 7 | 状态标签 | 为已知中文文案(正常/离线/缺失/告警) | 有状态列的页面 | 不检查文本 |
|
|
|
|
| 6 | **运行时间/数值列** | 时间列有具体数值(非空非NULL) | 有时间/数值列的页面 | 跳过时间列验证 |
|
|
|
|
| 8 | 下拉框展开 | 选项≥1 + 每项文本非空非占位 | 有下拉框的页面 | 只检查select可见 |
|
|
|
|
| 7 | **状态标签** | 标签文本为已知中文文案(正常/离线/缺失/告警等) | 有状态列的页面 | 不检查状态文本内容 |
|
|
|
|
| 9 | 下拉框选择 | 选中→显示label→查询→筛选生效 | 有下拉框的页面 | 不验证筛选效果 |
|
|
|
|
| 8 | **下拉框展开** | 点击展开 → 选项数量 ≥ 1 → 每个选项文本非空非占位 | 有下拉框的页面 | 只检查select可见 |
|
|
|
|
| 10 | 筛选生效 | 选条件→查询→表格数据变化 | 有查询功能的页面 | 不验证变化 |
|
|
|
|
| 9 | **下拉框选择** | 选择一项 → 输入框显示选中label | 有下拉框的页面 | 选择后不验证显示值 |
|
|
|
|
| 11 | 文本筛选 | 输入关键词→查询→结果每行包含关键词 | 有搜索框的页面 | 不验证匹配度 |
|
|
|
|
| 10 | **筛选生效** | 选择筛选条件 → 点查询 → 表格数据变化 | 有查询功能的页面 | 不验证筛选后数据是否真的变了 |
|
|
|
|
| 12 | 日期筛选 | 切换日期→查询→数据变化 | 有日期选择的页面 | 不验证效果 |
|
|
|
|
| 11 | **文本筛选** | 输入关键词 → 点查询 → 结果每行包含关键词 | 有文本搜索框的页面 | 不验证搜索结果匹配度 |
|
|
|
|
| 13 | 重置按钮 | 所有筛选恢复默认占位符 | 有重置按钮的页面 | 不验证清空 |
|
|
|
|
| 12 | **日期筛选** | 切换日期范围 → 查询 → 数据正确变化 | 有日期选择的页面 | 不验证日期筛选效果 |
|
|
|
|
| 14 | 分页切换 | 第2页高亮 + 表格第一行数据变化 | 数据>1页 | 只检查组件可见 |
|
|
|
|
| 13 | **重置按钮** | 重置后所有筛选恢复默认占位符 | 有重置按钮的页面 | 不验证重置是否清空 |
|
|
|
|
| 15 | 弹窗打开 | 可见 + 标题正确 + 表单有初始值 | 有弹窗的页面 | 不验证标题和内容 |
|
|
|
|
| 14 | **分页切换** | 切到第2页 → 页码高亮 + 表格第一行数据变化 | 有分页且数据>1页 | 只检查分页组件可见 |
|
|
|
|
| 16 | 弹窗提交闭环 | 填写→提交→成功提示→列表刷新→数据变更 | 有写操作弹窗 | 只验证能打开 |
|
|
|
|
| 15 | **弹窗打开** | 点击触发 → 弹窗可见 + 标题正确 + 表单有初始值 | 有弹窗的页面 | 不验证弹窗标题和内容 |
|
|
|
|
| 17 | API vs 页面对账 | 调API拿JSON→对比页面显示值 | 有统计卡片的页面 | 不对比API值 |
|
|
|
|
| 16 | **弹窗提交闭环** | 填写表单 → 提交 → 成功提示 → 列表刷新 → 数据已变更 | 有写操作弹窗的页面 | 只验证弹窗能打开不验证提交 |
|
|
|
|
| 18 | 下拉选项值有效性 | 每项文本非空、value可选中 | 有下拉框的页面 | 不验证选项内容 |
|
|
|
|
| 17 | **API vs 页面对账** | 调API拿JSON → 对比页面卡片/表格显示值是否一致 | 有统计卡片的页面 | 不和API实际值对比 |
|
|
|
|
| 19 | 空数据状态 | 不可能的条件→行数=0 + 不白屏不报错 | 有筛选的页面 | 不验证极端情况 |
|
|
|
|
| 18 | **下拉框选项值有效性** | 每个选项的文本非空、value可选中 | 有下拉框的页面 | 不验证选项文本内容 |
|
|
|
|
| 20 | 错误处理 | 异常场景→友好提示不白屏 | 所有页面 | 不考虑异常 |
|
|
|
|
| 19 | **空数据状态** | 选不可能的条件 → 表格行数为0 + 无"白屏/报错" | 有筛选功能的页面 | 不验证极端情况 |
|
|
|
|
|
|
|
|
| 20 | **错误处理** | 异常场景(如网络断开)→ 显示友好提示不白屏 | 所有页面 | 不考虑异常场景 |
|
|
|
|
### 5.1 页面类型速查
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 维度适用性速查表
|
|
|
|
| 页面类型 | 必须覆盖维度 |
|
|
|
|
|
|
|
|
|----------|-------------|
|
|
|
|
不同类型的页面,必须覆盖的维度子集不同。写测试前先确认页面类型:
|
|
|
|
| 列表页(表格+筛选) | 1,2,4,5,8,9,10,13,14,19 |
|
|
|
|
|
|
|
|
| 列表页(表格+筛选+卡片) | 1,2,3,4,5,6,7,8,9,10,13,14,17,19 |
|
|
|
|
| 页面类型 | 必须覆盖的维度编号 |
|
|
|
|
| 仪表盘/概览页 | 1,2,3,20 |
|
|
|
|
|----------|-------------------|
|
|
|
|
| 详情/表单页 | 1,15,16 |
|
|
|
|
| **列表页(带表格+筛选)** | 1,2,4,5,8,9,10,13,14,19 |
|
|
|
|
| 所有页面 | 1,20 |
|
|
|
|
| **列表页(带统计卡片)** | 1,2,3,4,5,8,9,10,13,14,17,19 |
|
|
|
|
|
|
|
|
| **仪表盘/概览页** | 1,2,3 |
|
|
|
|
### 5.2 交互控件矩阵
|
|
|
|
| **详情/表单页** | 1,15,16 |
|
|
|
|
|
|
|
|
| **所有页面通用** | 1,20 |
|
|
|
|
| 控件 | 验证动作 | 通过标准 |
|
|
|
|
|
|
|
|
|------|----------|----------|
|
|
|
|
### 5.3 交互控件测试矩阵
|
|
|
|
| 下拉框 | 展开→选择→查询 | 选项≥1,显示选中label,筛选生效 |
|
|
|
|
|
|
|
|
| 日期选择器 | 选择日期 | 输入框显示 `YYYY-MM-DD` |
|
|
|
|
每个页面的每个交互控件,都必须按此矩阵验证:
|
|
|
|
| 查询按钮 | 点击 | 表格刷新 |
|
|
|
|
|
|
|
|
| 重置按钮 | 点击 | 所有筛选恢复默认 |
|
|
|
|
| 控件类型 | 验证动作 | 通过标准 | 禁止做法 |
|
|
|
|
| 分页 | 切换页码 | 数据变化,页码高亮 |
|
|
|
|
|----------|----------|----------|----------|
|
|
|
|
| 弹窗-打开 | 点击触发 | 可见,标题正确,表单有初始值 |
|
|
|
|
| **下拉框** | 1.点击展开 2.等待选项渲染 | 选项数量 ≥ 1 | 只检查select元素可见 |
|
|
|
|
| 弹窗-提交 | 填写→提交 | 成功提示,列表刷新 |
|
|
|
|
| **下拉框** | 选择某项 | 输入框文本变为选中项的label | 不验证选择后的显示值 |
|
|
|
|
| 导出 | 点击 | 触发下载或提示 |
|
|
|
|
| **下拉框** | 选择后点查询 | 表格数据变化(筛选生效) | 不验证筛选是否实际生效 |
|
|
|
|
|
|
|
|
| **日期选择器** | 打开选择日期 | 输入框显示 `YYYY-MM-DD` 格式 | 不验证日期格式 |
|
|
|
|
### 5.3 数据展示矩阵
|
|
|
|
| **查询按钮** | 点击 | 表格刷新(loading出现或数据变化) | 只检查按钮可见 |
|
|
|
|
|
|
|
|
| **重置按钮** | 点击 | 所有筛选恢复默认值 | 不验证筛选是否清空 |
|
|
|
|
| 元素 | 验证标准 |
|
|
|
|
| **分页** | 切换页码 | 表格数据变化,页码高亮正确 | 只检查分页组件可见 |
|
|
|
|
|------|----------|
|
|
|
|
| **弹窗-打开** | 点击触发按钮 | 弹窗可见,标题正确 | 不验证弹窗标题 |
|
|
|
|
| 汇总卡片 | 非空 ∧ ≠ `""` ∧ ≠ `"-"` ∧ ≠ `"--"` ∧ ≠ `"undefined"` ∧ ≠ `"null"` |
|
|
|
|
| **弹窗-表单** | 查看表单字段 | 各字段有合理初始值 | 不检查表单初始值 |
|
|
|
|
| 表格数据列 | 第一行每列有文本值 |
|
|
|
|
| **弹窗-提交** | 填写并提交 | 返回成功提示,列表刷新 | 不验证提交后的状态 |
|
|
|
|
| 状态标签 | 已知中文文案 |
|
|
|
|
| **导出** | 点击导出 | 触发下载或显示提示 | 不验证导出行为 |
|
|
|
|
| 时间列 | 有值且格式正确 |
|
|
|
|
|
|
|
|
| 空状态 | 显示"暂无数据" |
|
|
|
|
### 5.4 数据展示验证矩阵
|
|
|
|
|
|
|
|
|
|
|
|
### 5.4 断言示例
|
|
|
|
| 展示元素 | 验证标准 | 非法值(不通过) |
|
|
|
|
|
|
|
|
|----------|----------|------------------|
|
|
|
|
|
|
|
|
| **汇总卡片** | 每个数值:非空 ∧ ≠ `""` ∧ ≠ `"-"` ∧ ≠ `"--"` ∧ ≠ `"undefined"` ∧ ≠ `"null"` | 空字符串、`-`、`--`、`undefined`、`null` |
|
|
|
|
|
|
|
|
| **表格数据列** | 每列至少第一行有实际文本值 | 空单元格、`NULL` |
|
|
|
|
|
|
|
|
| **状态标签** | 显示正确的中文文案(正常/离线/缺失/告警等) | 空标签、英文文本 |
|
|
|
|
|
|
|
|
| **时间列** | 有值且格式正确(`YYYY-MM-DD` 或 `HH:mm:ss`) | 空值、格式错误 |
|
|
|
|
|
|
|
|
| **空状态** | 无数据时显示"暂无数据"或类似提示 | 空白页面、报错 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 5.5 断言红黑榜
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### ✅ 正确的断言(数据层面)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
```typescript
|
|
|
|
// 检查文本不为空且非占位符
|
|
|
|
// 数值断言(排除空/占位符)
|
|
|
|
const text = await locator.textContent()
|
|
|
|
const text = await locator.textContent()
|
|
|
|
expect(text).toBeTruthy()
|
|
|
|
expect(text).toBeTruthy()
|
|
|
|
expect(text!.trim()).not.toBe('')
|
|
|
|
expect(text!.trim()).not.toBe('')
|
|
|
|
expect(text!.trim()).not.toBe('-')
|
|
|
|
expect(text!.trim()).not.toBe('-')
|
|
|
|
expect(text!.trim()).not.toBe('--')
|
|
|
|
expect(text!.trim()).not.toBe('--')
|
|
|
|
|
|
|
|
|
|
|
|
// 检查下拉框有选项
|
|
|
|
// 下拉框断言(展开+选项)
|
|
|
|
const options = page.locator('.el-select-dropdown__item')
|
|
|
|
const options = page.locator('.el-select-dropdown__item')
|
|
|
|
await expect(options.first()).toBeVisible()
|
|
|
|
expect(await options.count()).toBeGreaterThan(0)
|
|
|
|
const count = await options.count()
|
|
|
|
|
|
|
|
expect(count).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查表格有数据行
|
|
|
|
// 表格断言(行数+列值)
|
|
|
|
const rows = page.locator('.el-table__body-wrapper .el-table__row')
|
|
|
|
const rows = page.locator('.el-table__body-wrapper .el-table__row')
|
|
|
|
expect(await rows.count()).toBeGreaterThan(0)
|
|
|
|
expect(await rows.count()).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查卡片数值
|
|
|
|
|
|
|
|
const cardValue = await page.locator('.card-value').textContent()
|
|
|
|
|
|
|
|
expect(parseFloat(cardValue!)).not.toBeNaN()
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### ❌ 错误的断言(结构层面,太宽松)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
|
|
// 这些断言无法发现数据为空的问题
|
|
|
|
|
|
|
|
await expect(page.locator('.card')).toBeVisible() // 卡片可见但值可能为空
|
|
|
|
|
|
|
|
await expect(page.locator('table')).toBeVisible() // 表格可见但可能没数据
|
|
|
|
|
|
|
|
await expect(page.locator('select')).toBeVisible() // 下拉框可见但可能没选项
|
|
|
|
|
|
|
|
await expect(page.locator('.el-dialog')).toBeVisible() // 弹窗可见但内容可能错误
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 六、API验证方法
|
|
|
|
## 五、API验证方法
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 PowerShell验证脚本模板
|
|
|
|
### 登录获取Token
|
|
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
```powershell
|
|
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 登录获取Token
|
|
|
|
|
|
|
|
$loginRes = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/login" -Method Post -ContentType "application/json" -Body '{"username":"admin","password":"admin123"}'
|
|
|
|
$loginRes = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/login" -Method Post -ContentType "application/json" -Body '{"username":"admin","password":"admin123"}'
|
|
|
|
$token = $loginRes.data.token
|
|
|
|
$token = $loginRes.data.token
|
|
|
|
$headers = @{ Authorization = "Bearer $token" }
|
|
|
|
$headers = @{ Authorization = "Bearer $token" }
|
|
|
|
$today = Get-Date -Format "yyyy-MM-dd"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# === 产量模块 ===
|
|
|
|
|
|
|
|
echo "--- 产量汇总 ---"
|
|
|
|
|
|
|
|
$sum = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/production/daily-summary?date=$today" -Headers $headers
|
|
|
|
|
|
|
|
echo "总产量: $($sum.data.totalQuantity)(应有值)"
|
|
|
|
|
|
|
|
echo "运行机床: $($sum.data.activeMachineCount)(应>0)"
|
|
|
|
|
|
|
|
echo "切削总时: $($sum.data.totalCuttingTime)(应有值)"
|
|
|
|
|
|
|
|
echo "平均产量: $($sum.data.avgQuantityPerMachine)(应有值)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
echo "--- 产量列表(前3条) ---"
|
|
|
|
|
|
|
|
$prod = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/production/daily?startDate=$today&endDate=$today&page=1&pageSize=3" -Headers $headers
|
|
|
|
|
|
|
|
echo "总数: $($prod.data.total)"
|
|
|
|
|
|
|
|
foreach ($item in $prod.data.items) {
|
|
|
|
|
|
|
|
echo " 机床=$($item.machineName) 产量=$($item.totalQuantity) 运行=$($item.totalRunTime)h 切削=$($item.totalCuttingTime)h"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# === 下拉选项 ===
|
|
|
|
|
|
|
|
echo "--- 车间选项 ---"
|
|
|
|
|
|
|
|
$ws = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/workshop/list" -Headers $headers
|
|
|
|
|
|
|
|
echo "数量: $($ws.data.items.Count)(应>0)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
echo "--- 机床选项 ---"
|
|
|
|
|
|
|
|
$mc = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/machine/list" -Headers $headers
|
|
|
|
|
|
|
|
echo "数量: $($mc.data.items.Count)(应>0)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
echo "--- 工人选项 ---"
|
|
|
|
|
|
|
|
$wr = Invoke-RestMethod -Uri "http://127.0.0.1/api/admin/worker/list" -Headers $headers
|
|
|
|
|
|
|
|
echo "数量: $($wr.data.items.Count)(应>0)"
|
|
|
|
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 非空字段验证清单
|
|
|
|
### 必须非空字段
|
|
|
|
|
|
|
|
|
|
|
|
每个API端点返回后,以下字段**必须非空**:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| API端点 | 必须非空字段 |
|
|
|
|
| API端点 | 必须非空字段 |
|
|
|
|
|----------|-------------|
|
|
|
|
|----------|-------------|
|
|
|
|
@ -456,48 +154,32 @@ echo "数量: $($wr.data.items.Count)(应>0)"
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 七、发布前Checklist(强制全部打勾)
|
|
|
|
## 六、发布前Checklist
|
|
|
|
|
|
|
|
|
|
|
|
每次交付前必须完成以下所有检查项:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 编译
|
|
|
|
|
|
|
|
- [ ] `dotnet build src\CncWebApi\CncWebApi.csproj` → 0错误
|
|
|
|
- [ ] `dotnet build src\CncWebApi\CncWebApi.csproj` → 0错误
|
|
|
|
- [ ] `cd frontend && npm run build` → 0错误
|
|
|
|
- [ ] `cd frontend && npm run build` → 0错误
|
|
|
|
|
|
|
|
|
|
|
|
### 发布
|
|
|
|
|
|
|
|
- [ ] 前端build输出已复制到 `src\CncWebApi\admin\`
|
|
|
|
- [ ] 前端build输出已复制到 `src\CncWebApi\admin\`
|
|
|
|
- [ ] 回收AppPool:`appcmd recycle apppool "haoliang"`
|
|
|
|
- [ ] 回收AppPool:`appcmd recycle apppool "haoliang"`
|
|
|
|
|
|
|
|
|
|
|
|
### API验证
|
|
|
|
|
|
|
|
- [ ] 改动过的API端点用PowerShell调用,返回值正确
|
|
|
|
- [ ] 改动过的API端点用PowerShell调用,返回值正确
|
|
|
|
- [ ] 新增/修改的字段有实际值(非NULL、非空)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 浏览器验证
|
|
|
|
|
|
|
|
- [ ] Playwright冒烟测试通过:`cd frontend && npx playwright test e2e/smoke-iis.spec.ts --project=chromium`
|
|
|
|
- [ ] Playwright冒烟测试通过:`cd frontend && npx playwright test e2e/smoke-iis.spec.ts --project=chromium`
|
|
|
|
- [ ] 下拉框展开有选项、选择后筛选生效
|
|
|
|
|
|
|
|
- [ ] 表格数据列有值
|
|
|
|
|
|
|
|
- [ ] 汇总卡片数值正确
|
|
|
|
|
|
|
|
- [ ] 弹窗能打开且有内容
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Git
|
|
|
|
|
|
|
|
- [ ] `git add` + `git commit -m "中文描述"` + `git push`
|
|
|
|
- [ ] `git add` + `git commit -m "中文描述"` + `git push`
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 八、反模式清单(禁止做法)
|
|
|
|
## 七、反模式清单
|
|
|
|
|
|
|
|
|
|
|
|
| # | 反模式 | 正确做法 |
|
|
|
|
| # | 禁止 | 必须改为 |
|
|
|
|
|---|--------|----------|
|
|
|
|
|---|------|----------|
|
|
|
|
| 1 | 编译通过 = 测试通过 | 编译通过只说明语法正确,必须验证功能 |
|
|
|
|
| 1 | 编译通过 = 测试通过 | 验证功能 |
|
|
|
|
| 2 | 只检查 `toBeVisible()` 不检查内容 | 必须检查文本值,排除空/占位符 |
|
|
|
|
| 2 | 只检查 `toBeVisible()` | 检查文本值,排除空/占位符 |
|
|
|
|
| 3 | SQL写 `NULL AS 字段名` 占位 | 禁止占位,要么实现要么抛异常 |
|
|
|
|
| 3 | SQL写 `NULL AS 字段名` 占位 | 实现或抛异常 |
|
|
|
|
| 4 | DTO加字段不填充值 | 加字段必须追踪到Service层实现填充 |
|
|
|
|
| 4 | DTO加字段不填充值 | 追踪到Service层实现填充 |
|
|
|
|
| 5 | 改完代码不发布到IIS | 每次改动必须发布后验证 |
|
|
|
|
| 5 | 改完代码不发布到IIS | 每次改动发布后验证 |
|
|
|
|
| 6 | Playwright不操作控件只看页面 | 必须模拟用户操作:点击、选择、输入 |
|
|
|
|
| 6 | Playwright不操作控件 | 模拟用户操作:点击、选择、输入 |
|
|
|
|
| 7 | 累积改动到"全部完成"再验证 | 改完一处立即验证 |
|
|
|
|
| 7 | 累积改动后批量验证 | 改完一处立即验证 |
|
|
|
|
| 8 | 前端凭假设绑定字段名 | 必须对照API实际返回值 |
|
|
|
|
| 8 | 前端凭假设绑定字段名 | 对照API实际返回值 |
|
|
|
|
| 9 | 硬编码配置值(serviceId、端口等) | 从配置文件读取或与实际部署保持一致 |
|
|
|
|
| 9 | 硬编码配置值 | 从配置文件读取 |
|
|
|
|
| 10 | 不检查Element Plus组件库的日期/格式要求 | 查阅文档确认格式(如 dayjs 的 `YYYY-MM-DD`) |
|
|
|
|
| 10 | 不检查组件库格式要求 | 查阅文档(如 dayjs `YYYY-MM-DD`) |
|
|
|
|
| 11 | Playwright测试不按维度表逐项覆盖 | 必须对照5.0节维度总表,每个维度有测试用例或注释跳过原因 |
|
|
|
|
| 11 | 测试不按维度表逐项覆盖 | 对照5.0维度总表 |
|
|
|
|
| 12 | 测试脚本只覆盖部分页面 | 每次改动涉及的页面都必须有测试覆盖 |
|
|
|
|
| 12 | 测试只覆盖部分页面 | 每个改动涉及的页面都要覆盖 |
|
|
|
|
| 13 | 新增测试维度后不更新维度总表 | 维度总表是强制标准,任何新发现的问题必须补充为新维度 |
|
|
|
|
| 13 | 新增维度后不更新维度总表 | 新发现的问题必须补充为新维度 |
|
|
|
|
|