# 测试规范

**版本:** 2026-05-03
**适用范围:** CNC机床数据采集系统 — 前端管理后台 + 后端API

---

## 一、6条铁律

| # | 铁律 | 要求 |
|---|------|------|
| 1 | **接口先验证** | 改了任何API，立即用PowerShell调用，逐字段确认返回值与前端绑定一致 |
| 2 | **数据断言必须具体** | 禁止只检查 `toBeVisible()`，必须检查文本内容，排除空/`-`/`--`/`undefined`/`null` |
| 3 | **下拉框必须展开验证** | 展开→选项≥1→选择→确认显示值→点查询→确认筛选生效 |
| 4 | **表格必须检查每列** | 行数>0 + 第一行每列文本非空（日期、名称、数值、时间、状态逐列检查） |
| 5 | **卡片数值必须非空** | 每个卡片数值 ≠ 空 ≠ `-` ≠ `--` ≠ `undefined` ≠ `null`，允许为0时须注释说明 |
| 6 | **改完即验证** | 每改一处→编译→API验证→浏览器验证，禁止累积 |

---

## 二、验证层级

| 层级 | 执行时机 | 命令/方法 | 通过标准 |
|------|----------|-----------|----------|
| **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只是最基本的大门。

---

## 三、前后端接口契约验证

每次新增或修改API返回字段时必须执行：

| 检查项 | 通过标准 |
|--------|----------|
| DTO字段名 vs API实际返回JSON | 一致（考虑camelCase序列化：`TotalQuantity` → `totalQuantity`） |
| API返回字段名 vs 前端绑定 | 完全一致或兼容（`??` 回退） |
| 返回数据结构 | 前端取值路径正确（`res.data.items` vs `res.data`） |
| 空值处理 | 前端显示占位符（`-` 或 `暂无数据`），不白屏不报错 |
| 分页格式 | 数组 `[{...}]` vs 分页对象 `{items:[], total:0}` 前后端一致 |
| 枚举值 | 前端处理整数和字符串两种情况 |
| DateTime | 前端兼容 `"2026-05-03T00:00:00"` 和 `"2026-05-03"` |

---

## 四、Playwright E2E测试标准

> **强制要求：每次编写Playwright测试，必须逐项对照5.0维度总表。每个维度有测试用例或注释说明跳过原因。**

### 5.0 测试维度总表（每个页面必查）

| # | 维度 | 必须断言 | 适用 | 禁止 |
|---|------|----------|------|------|
| 1 | 页面加载 | URL正确 + 关键元素可见 | 所有页面 | 只goto不等待 |
| 2 | 默认数据 | 打开即有数据（行数>0 / 卡片有值） | 有数据展示的页面 | 不检查数据 |
| 3 | 汇总卡片数值 | ≠ 空 ≠ `-` ≠ `--` ≠ `undefined` ≠ `null` | 有统计卡片的页面 | 只检查可见 |
| 3.1 | 图表有数据 | Canvas/SVG已渲染 + 有数据点/色块（非空白） | 有ECharts/Chart组件的页面 | 只检查容器可见 |
| 4 | 表格行数 | `.el-table__row` > 0 | 有表格的页面 | 只检查table可见 |
| 5 | 表格每列有值 | 第一行逐列文本非空 | 有表格的页面 | 只检查前2-3列 |
| 6 | 时间/数值列 | 有具体数值（非空非NULL） | 有时间/数值列的页面 | 跳过 |
| 7 | 状态标签 | 为已知中文文案（正常/离线/缺失/告警） | 有状态列的页面 | 不检查文本 |
| 8 | 下拉框展开 | 选项≥1 + 每项文本非空非占位 | 有下拉框的页面 | 只检查select可见 |
| 9 | 下拉框选择 | 选中→显示label→查询→筛选生效 | 有下拉框的页面 | 不验证筛选效果 |
| 10 | 筛选生效 | 选条件→查询→表格数据变化 | 有查询功能的页面 | 不验证变化 |
| 11 | 文本筛选 | 输入关键词→查询→结果每行包含关键词 | 有搜索框的页面 | 不验证匹配度 |
| 12 | 日期筛选 | 切换日期→查询→数据变化 | 有日期选择的页面 | 不验证效果 |
| 13 | 重置按钮 | 所有筛选恢复默认占位符 | 有重置按钮的页面 | 不验证清空 |
| 14 | 分页切换 | 第2页高亮 + 表格第一行数据变化 | 数据>1页 | 只检查组件可见 |
| 15 | 弹窗打开 | 可见 + 标题正确 + 表单有初始值 | 有弹窗的页面 | 不验证标题和内容 |
| 16 | 弹窗提交闭环 | 填写→提交→成功提示→列表刷新→数据变更 | 有写操作弹窗 | 只验证能打开 |
| 17 | API vs 页面对账 | 调API拿JSON→对比页面显示值 | 有统计卡片的页面 | 不对比API值 |
| 18 | 下拉选项值有效性 | 每项文本非空、value可选中 | 有下拉框的页面 | 不验证选项内容 |
| 19 | 空数据状态 | 不可能的条件→行数=0 + 不白屏不报错 | 有筛选的页面 | 不验证极端情况 |
| 20 | 错误处理 | 异常场景→友好提示不白屏 | 所有页面 | 不考虑异常 |
| 21 | 表格布局铺满 | 表格宽度=100%容器宽度，无大片右侧空白 | 有表格的页面 | 只检查表格可见 |
| 22 | 可点击链接跳转 | 表格/卡片中可点击字段→点击→URL跳转正确→目标页面加载成功+有数据 | 有链接/可点击字段的页面 | 不点击链接 |
| 23 | 操作按钮闭环 | 点击按钮→等待响应→验证页面状态变化（如停止→启动→状态切换、刷新→数据更新） | 有操作按钮的页面 | 只检查按钮可见 |

### 5.1 页面类型速查

| 页面类型 | 必须覆盖维度 |
|----------|-------------|
| 列表页（表格+筛选） | 1,2,4,5,8,9,10,13,14,19,21,22 |
| 列表页（表格+筛选+卡片） | 1,2,3,4,5,6,7,8,9,10,13,14,17,19,21,22 |
| 仪表盘/概览页 | 1,2,3,3.1,5,6,9,12,17,20,21,22,23 |
| 详情/表单页 | 1,15,16 |
| 所有页面 | 1,20 |

### 5.2 交互控件矩阵

| 控件 | 验证动作 | 通过标准 |
|------|----------|----------|
| 下拉框 | 展开→选择→查询 | 选项≥1，显示选中label，筛选生效 |
| 日期选择器 | 选择日期 | 输入框显示 `YYYY-MM-DD` |
| 查询按钮 | 点击 | 表格刷新 |
| 重置按钮 | 点击 | 所有筛选恢复默认 |
| 分页 | 切换页码 | 数据变化，页码高亮 |
| 弹窗-打开 | 点击触发 | 可见，标题正确，表单有初始值 |
| 弹窗-提交 | 填写→提交 | 成功提示，列表刷新 |
| 导出 | 点击 | 触发下载或提示 |
| 可点击链接 | 点击字段→等待跳转→验证URL+目标页数据 | URL正确，目标页面加载成功且有数据 |
| 操作按钮 | 点击→等待响应→验证状态变化 | 按钮文案/状态切换，数据刷新 |

### 5.3 数据展示矩阵

| 元素 | 验证标准 |
|------|----------|
| 汇总卡片 | 非空 ∧ ≠ `""` ∧ ≠ `"-"` ∧ ≠ `"--"` ∧ ≠ `"undefined"` ∧ ≠ `"null"` |
| 图表(ECharts等) | Canvas/SVG存在 + 非空白（有数据点/扇形/柱状） |
| 表格数据列 | 第一行每列有文本值 |
| 表格布局 | 表格撑满容器宽度，无大片右侧空白；至少一列不设固定width |
| 可点击字段 | 有点击样式（链接色/手型光标）的字段必须点击验证跳转：URL正确+目标页有数据 |
| 状态标签 | 已知中文文案 |
| 时间列 | 有值且格式正确 |
| 空状态 | 显示"暂无数据" |

### 5.4 断言示例

```typescript
// 数值断言（排除空/占位符）
const text = await locator.textContent()
expect(text).toBeTruthy()
expect(text!.trim()).not.toBe('')
expect(text!.trim()).not.toBe('-')
expect(text!.trim()).not.toBe('--')

// 下拉框断言（展开+选项）
const options = page.locator('.el-select-dropdown__item')
expect(await options.count()).toBeGreaterThan(0)

// 表格断言（行数+列值）
const rows = page.locator('.el-table__body-wrapper .el-table__row')
expect(await rows.count()).toBeGreaterThan(0)
```

---

## 五、API验证方法

### 登录获取Token

```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" }
```

### 必须非空字段

| API端点 | 必须非空字段 |
|----------|-------------|
| `/admin/production/daily-summary` | `totalQuantity`, `activeMachineCount`, `totalCuttingTime`, `avgQuantityPerMachine` |
| `/admin/production/daily` | `items[].machineName`, `items[].totalQuantity`, `items[].date` |
| `/admin/workshop/list` | `items[].value`, `items[].label` |
| `/admin/machine/list` | `items[].value`, `items[].label` |
| `/admin/worker/list` | `items[].value`, `items[].label` |
| `/admin/dashboard/statistics` | 各统计卡片字段 |
| `/admin/collector/status` | `status` |

---

## 六、发布前Checklist

- [ ] `dotnet build src\CncWebApi\CncWebApi.csproj` → 0错误
- [ ] `cd frontend && npm run build` → 0错误
- [ ] 前端build输出已复制到 `src\CncWebApi\admin\`
- [ ] 回收AppPool：`appcmd recycle apppool "haoliang"`
- [ ] 改动过的API端点用PowerShell调用，返回值正确
- [ ] Playwright冒烟测试通过：`cd frontend && npx playwright test e2e/smoke-iis.spec.ts --project=chromium`
- [ ] `git add` + `git commit -m "中文描述"` + `git push`

---

## 七、反模式清单

| # | 禁止 | 必须改为 |
|---|------|----------|
| 1 | 编译通过 = 测试通过 | 验证功能 |
| 2 | 只检查 `toBeVisible()` | 检查文本值，排除空/占位符 |
| 3 | SQL写 `NULL AS 字段名` 占位 | 实现或抛异常 |
| 4 | DTO加字段不填充值 | 追踪到Service层实现填充 |
| 5 | 改完代码不发布到IIS | 每次改动发布后验证 |
| 6 | Playwright不操作控件 | 模拟用户操作：点击、选择、输入 |
| 7 | 累积改动后批量验证 | 改完一处立即验证 |
| 8 | 前端凭假设绑定字段名 | 对照API实际返回值 |
| 9 | 硬编码配置值 | 从配置文件读取 |
| 10 | 不检查组件库格式要求 | 查阅文档（如 dayjs `YYYY-MM-DD`） |
| 11 | 测试不按维度表逐项覆盖 | 对照5.0维度总表 |
| 12 | 测试只覆盖部分页面 | 每个改动涉及的页面都要覆盖 |
| 13 | 新增维度后不更新维度总表 | 新发现的问题必须补充为新维度 |
| 14 | 图表只检查容器DOM存在 | 验证Canvas/SVG有实际渲染内容（数据点/色块） |
| 15 | 表格所有列设固定width导致右侧空白 | 至少一列不设width让其自动填充剩余空间 |
| 16 | 不点击表格中的可跳转链接 | 必须点击链接→验证跳转URL+目标页面有数据 |
| 17 | 只检查按钮可见，不验证点击后效果 | 必须点击按钮→验证页面状态/数据是否按预期变化 |
