Merge pull request 'sync: enhance visual-diff-mode with icon check rules' (#39) from sync/d8d-skills-20260415-025644 into master
Reviewed-on: http://gitea.d8d.fun/d8dfun/bmad-method-template/pulls/39
This commit is contained in:
@@ -363,13 +363,13 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
```
|
||||
触发即时使用数据操作后:
|
||||
↓
|
||||
1. browser_snapshot → 如果 Toast 包含关键数据 → 磄即记录
|
||||
1. browser_snapshot → 如果 Toast 包含关键数据 → 立即记录
|
||||
↓ (Toast 已消失)
|
||||
2. browser_console_messages(level="info") → 查找 console.log 记录
|
||||
↓ (无 console.log → FAIL #14 → AUTO-FIX: 添加 console.log)
|
||||
↓ (有 console.log → 从中提取数据)
|
||||
↓ (console 也无有用数据)
|
||||
3. browser_network_requests(static=false, requestBody=false, requestHeaders=false) → 从 API 卶应提取
|
||||
3. browser_network_requests(static=false, requestBody=false, requestHeaders=false) → 从 API 响应提取
|
||||
↓ (network 也未捕获)
|
||||
4. 回退: curl/API 直接查询(如重新请求 send-code)
|
||||
```
|
||||
@@ -454,7 +454,7 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
- [ ] 截图文件名**必须**包含 `.png` 扩展名(`journey-epic-N-M-step-S.png`)
|
||||
- [ ] 截图**必须**保存到 `tests/e2e/screenshots/` 目录,不能保存到当前工作目录
|
||||
- [ ] **每个截图文件必须是真正的 PNG 图片** → `file <path>` 检测输出必须包含 "PNG image data"
|
||||
- [ ] **强制:每截一张图立刻检查** → `browser_take_screenshot` 完成后立即执行 `file` 检测,失败立刻重截
|
||||
- [ ] **强制:每截一张图立刻检查** → `browser_take_screenshot` 完成后**立刻**执行 `file` 检测格式,失败立刻重截
|
||||
- [ ] 每个用户步骤完成 → `browser_take_screenshot` + `file` 格式验证 + `analyze_image` → 视觉验证关卡
|
||||
- [ ] 旅程最终完成 → `browser_take_screenshot` 最终截图 + `browser_network_requests` 资源检查
|
||||
- [ ] 违反任意一条 → 中断补全,不能继续
|
||||
@@ -510,8 +510,8 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
result: "pass|fail"
|
||||
- check: "all_issues_resolved"
|
||||
result: "pass|fail"
|
||||
executed_at: "{ISO timestamp}"
|
||||
note: "{备注}"
|
||||
- check: "file_format_validation"
|
||||
result: "pass|fail"
|
||||
```
|
||||
c. 步骤级 GATE 检查(6 项,任一 FAIL → 阻断):
|
||||
├── G1: screenshot_consistency — screenshot_path 文件存在
|
||||
@@ -537,7 +537,7 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
- 步骤 4(修复阶段)仅处理功能断言 FAIL,不处理视觉验证 FAIL
|
||||
f. 如涉及数据变化 → browser_evaluate 或 curl 验证数据(✅ 允许 curl 补充)
|
||||
|
||||
6. ⛔ **旅程步骤原型/Pencil 视觉对比验证**(⛔ 强制 — 针对操作时产生的动态状态)
|
||||
6. ⛔ **旅程步骤原型/Pencil 视觉对比验证**(⛔ 强制 — 针对操作时产生的动态状态):
|
||||
目的: 捕获并验证操作时才会出现的视觉状态(hover、选中、弹窗、展开、加载中、错误提示等),这些状态在静态原型对比中无法覆盖
|
||||
|
||||
触发条件(满足任一即执行):
|
||||
@@ -562,8 +562,8 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
**Pencil MCP 模式(优先)**:
|
||||
- 读取 pipeline-context.md 获取设计文件路径(如 VividSpark1.2.pen)
|
||||
- mcp__pencil__batch_get → 搜索与当前状态匹配的帧
|
||||
- 搜索关键词: "{Page}-{State}" 如 "Dashboard-Modal", "Login-Error", "Upload-Loading"
|
||||
- 或按 frame 类型和名称模式匹配
|
||||
- 搜索关键词: "{Page}-{State}" 如 "Dashboard-Modal", "Login-Error"
|
||||
- 或按 frame 类型和名称模式匹配
|
||||
- 如找到匹配帧 → mcp__pencil__export_nodes 导出设计截图
|
||||
|
||||
**HTML 原型模式(回退)**:
|
||||
@@ -632,13 +632,13 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
- ✅ 无差异 → 标记状态正常
|
||||
|
||||
f. 自动修复与重测(如触发):
|
||||
- AUTO-FIX 触发后:
|
||||
1. 定位相关源码文件(根据差异类型确定)
|
||||
2. 执行修复(如调整 CSS、修改组件样式等)
|
||||
3. 重新截图: browser_take_screenshot
|
||||
4. 重新对比: ui_diff_check
|
||||
5. 修复成功 → 标记 auto_fix_result: "PASS"
|
||||
6. 修复失败 → 标记 auto_fix_result: "FAIL",记录原因
|
||||
- AUTO-FIX 触发后:
|
||||
1. 定位相关源码文件(根据差异类型确定)
|
||||
2. 执行修复(如调整 CSS、修改组件样式等)
|
||||
3. 重新截图: browser_take_screenshot
|
||||
4. 重新对比: ui_diff_check
|
||||
5. 修复成功 → 标记 auto_fix_result: "PASS"
|
||||
6. 修复失败 → 标记 auto_fix_result: "FAIL",记录原因
|
||||
|
||||
6. ⛔ **源码 SVG data URI 扫描**(视觉验证关卡内 — 每次 snapshot 后强制执行):
|
||||
g. ⛔ **约束一致性检查**(每个用户步骤首次执行时 — R7 执行时强制检测):
|
||||
@@ -746,8 +746,7 @@ user_journeys:
|
||||
| 维度 | 回归验证模式 | 旅程验证模式 |
|
||||
|------|------------|------------|
|
||||
| 粒度 | 逐 Story | 跨 Story/Epic |
|
||||
| 目标 | 验证功能点存在且正确 | 验证用户能完成目标 |
|
||||
| 类比 | 单元测试 | 集成测试/E2E |
|
||||
| 目标 | 验证功能点存在 | 验证用户能完成目标 |
|
||||
| 入口 | 直接 navigate 到目标页面 | 从自然入口开始 |
|
||||
|
||||
- 两种模式**独立运行**,互不依赖
|
||||
|
||||
@@ -175,6 +175,15 @@ mkdir -p tests/e2e/screenshots/visual-diff-pencil
|
||||
- 便于独立追踪每个页面的验证进度
|
||||
- 避免单个 agent 上下文过大导致污染或遗漏
|
||||
|
||||
### 分批并行策略
|
||||
|
||||
**Anthropic API 配额限制**: 单次 5 小时窗口有请求次数限额,一次性启动全部 agent 容易触发 `429 AccountQuotaExceeded`。
|
||||
|
||||
**推荐执行策略**:
|
||||
- 每次启动 **5 个页面** 验证
|
||||
- 等待全部完成后再启动下一批
|
||||
- 如果遇到 429 配额超限,**提示用户更换 API key 或等待配额重置** 后继续(重置时间在错误提示中有说明)
|
||||
|
||||
### 0.8 启动逐个任务验证
|
||||
|
||||
对**每个任务**启动一个**独立的 agent**,按照 Phase PVD-1 流程执行验证。独立 agent 避免上下文污染,执行更精准。
|
||||
@@ -184,15 +193,15 @@ mkdir -p tests/e2e/screenshots/visual-diff-pencil
|
||||
## Phase PVD-1: 逐页门禁对比循环
|
||||
|
||||
⛔ **核心原则:逐页执行,每页必须通过门禁才能继续下一页。禁止全量截图后批量对比。**
|
||||
**最佳实践:每个页面启动一个独立 agent,避免上下文污染,执行更精准。**
|
||||
**最佳实践:每个页面启动一个独立 agent,分批并行执行,避免上下文污染和 Playwright MCP 配额超限,执行更精准。**
|
||||
|
||||
对映射表中每个 `status: active` 的页面,依次执行以下完整循环(每个页面由独立 agent 处理):
|
||||
对映射表中每个 `status: active` 的页面,**分批启动**独立 agent 依次执行以下完整循环(每个页面由独立 agent 处理):
|
||||
|
||||
```
|
||||
PVD-1 PER-PAGE LOOP:
|
||||
A. 导出设计截图
|
||||
B. 截取实际页面
|
||||
C. AI 视觉对比
|
||||
C. 结构校验 + AI 视觉对比
|
||||
D. 差异分级
|
||||
E. PAGE GATE-CHECK ⛔
|
||||
├── 🟢 通过 → G. 记录 → NEXT PAGE
|
||||
@@ -221,13 +230,36 @@ PVD-1 PER-PAGE LOOP:
|
||||
|
||||
```
|
||||
1. browser_resize → 375x812(移动端)或 1440x900(管理后台)
|
||||
2. browser_navigate → {实际 URL}{route}
|
||||
3. 处理认证(如需要)
|
||||
2. 处理认证(如需要)→ 见下文
|
||||
3. browser_navigate → {实际 URL}{route}
|
||||
4. browser_wait_for → time=3
|
||||
5. browser_take_screenshot(fullPage=false)
|
||||
→ tests/e2e/screenshots/visual-diff-pencil/{name}-actual.png
|
||||
```
|
||||
|
||||
### 认证处理(需要登录的页面)
|
||||
|
||||
大多数功能页面需要登录才能访问,未登录会自动跳转到登录页。正确流程:
|
||||
|
||||
1. 如果已有测试 token,在 `browser_navigate` **之前** 注入 token 到 `localStorage`:
|
||||
```javascript
|
||||
await page.evaluate((token, userInfo) => {
|
||||
localStorage.setItem('token', token);
|
||||
localStorage.setItem('user', JSON.stringify(userInfo));
|
||||
}, token, userInfo);
|
||||
```
|
||||
2. 再导航到目标 URL
|
||||
3. 如果需要组织者权限,确保测试账号已通过局头认证(状态 `APPROVED`)
|
||||
|
||||
### 常见问题排查
|
||||
|
||||
| 问题现象 | 可能原因 | 解决方案 |
|
||||
|------|------|---------|
|
||||
| 截图黑屏 / 内容空白 | 缺少必要 URL 参数(如 `eventId`),页面无法获取数据 | 从 API 获取有效测试 ID,访问时带上查询参数 `?id=xxx` |
|
||||
| 始终显示登录页(即使应该已登录) | 缺少 token 或 token 无效 | 重新登录获取有效 token 注入 `localStorage` |
|
||||
| API 返回 401 即使有 token | **服务端环境变量 `JWT_SECRET` 与本地不匹配** | 修改 `api/.env` 后执行 `pm2 restart api` 重启加载 |
|
||||
| 页面白屏 / JavaScript 报错 | API 返回 `null` 时代码直接访问属性 | 添加可选链操作符 `?.` 做防御性编程 |
|
||||
|
||||
### 1.C 结构校验 + AI 视觉对比
|
||||
|
||||
**分两步:先做结构校验,再做视觉对比**
|
||||
@@ -243,7 +275,9 @@ PVD-1 PER-PAGE LOOP:
|
||||
for each child in node.children:
|
||||
collectNamedNodes(child, path + node.name + ".", resultList)
|
||||
|
||||
结果包含所有层级:topBar, topBar.topLeft, topBar.topRight, tabSection, tabSection.tabLeft, etc.
|
||||
**强制要求**: **任何层级**只要节点有 name 属性,都必须提取并检查。**不允许只检查顶层不检查深层**。
|
||||
|
||||
结果包含所有层级:topBar, topBar.topLeft, topBar.topRight, tabSection, tabSection.tabLeft, barCard1, barCard1.ratingBadge, barCard1.activitySnippet 等等(深度嵌套没问题,全部提取)。
|
||||
示例: topBar.topLeft / tabSection.tabLeft / availableCard / amountInput / ...
|
||||
3. **检查全局配置关联**: 如果设计帧包含 name=bottomNav/BottomNav 且是 ref 组件
|
||||
- 读取 app/mini-program/src/pages.json → 获取当前 tabBar 配置
|
||||
@@ -252,9 +286,11 @@ PVD-1 PER-PAGE LOOP:
|
||||
- 数量不匹配 → critical_count += 1(底部导航结构错误是严重差异)
|
||||
4. 读取实际页面源码(app/mini-program/src/pages/{page}/index.vue),双向检查每个必需组件:
|
||||
- **必需组件检查**: 按路径节点整个区块缺失 → 直接判定为 🔴 严重差异 → critical_count += 1
|
||||
**强制规则**: 无论层级多深,**只要设计节点有 name 就必须存在**,缺失必须计数
|
||||
- **多余组件检查**: 实际页面存在设计稿中没有的顶层区块 → 🟡 中等差异 → moderate_count += 1
|
||||
- **组件类型一致性检查**: 设计是 `icon_font` 但实现用纯文本字符 → 🟡 中等差异 → moderate_count += 1
|
||||
- **文字样式一致性检查**: **任意设计节点 type=text 且有 name** → 检查 fill 颜色 + fontSize + fontWeight 与实际CSS不符 → 🟡 中等差异 → moderate_count += 1
|
||||
- **布局对齐检查**: 设计节点声明 `justifyContent: space_between` → 检查实际CSS是否正确设置 `display: flex; justify-content: space-between;` → 如果右侧元素未靠右对齐 → 🟡 中等差异 → moderate_count += 1
|
||||
5. 结构校验通过后,使用 Read 工具分别查看设计截图和实际截图,AI 综合分析视觉差异:
|
||||
- 布局结构(网格/弹性/定位、对齐方式)
|
||||
- 组件排列(导航栏、卡片、按钮、图标)
|
||||
@@ -265,6 +301,85 @@ PVD-1 PER-PAGE LOOP:
|
||||
- 设计规范(是否违反 Neon Pulse 原则:如存在不必要的 1px 边框)
|
||||
```
|
||||
|
||||
### 图标检查特别规则(@egoist/tailwindcss-icons)
|
||||
|
||||
**设计要求**:设计节点 `type=icon_font` 必须用 tailwindcss-icons 实现,禁止使用 emoji 或纯文本占位。
|
||||
|
||||
**正确 HTML 结构:**
|
||||
```vue
|
||||
<!-- ✅ 正确:容器嵌套独立图标元素 -->
|
||||
<view class="action-icon pink">
|
||||
<view class="i-lucide-flame"></view>
|
||||
</view>
|
||||
|
||||
<!-- ❌ 错误:图标类直接放在容器 -->
|
||||
<view class="action-icon pink i-lucide-flame"></view>
|
||||
```
|
||||
|
||||
**正确 CSS 规则:**
|
||||
```css
|
||||
/* 必须添加确保SVG继承颜色 */
|
||||
.action-icon > view svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: currentColor;
|
||||
}
|
||||
```
|
||||
|
||||
**图标命名规则:**
|
||||
|
||||
项目 tailwind.config.js 已启用三个图标集合:
|
||||
```js
|
||||
collections: getIconCollections(["mdi", "lucide", "material-symbols"]);
|
||||
```
|
||||
|
||||
| 图标库 | 类名前缀 | 适用场景 |
|
||||
|---------|----------|----------|
|
||||
| Material Symbols | `i-material-symbols-*` | 直接使用设计中的原始名称,不需要转换 |
|
||||
| Material Design Icons | `i-mdi-*` | 常用 material 图标 |
|
||||
| Lucide | `i-lucide-*` | 设计中 Material Symbols 不存在时使用 |
|
||||
|
||||
**Material Symbols → Lucide 名称映射表**(当设计使用 Material Symbols 但需要用 Lucide 替代时参考):
|
||||
|
||||
| Material 名称 | Lucide 对应名称 | 示例场景 |
|
||||
|---------------|-----------------|------------|
|
||||
| `local_fire_department` | `flame` | 热门推荐/火焰 |
|
||||
| `near_me` | `navigation` | 附近/定位 |
|
||||
| `military_tech` | `trophy` | 排行/奖杯 |
|
||||
| `wine_bar` | `wine` | 酒吧/酒杯 |
|
||||
| `location_on` | `map-pin` | 位置 |
|
||||
| `star` | `star` | 评分/星星 |
|
||||
| `chevron_right` | `chevron-right` | 向右箭头 |
|
||||
|
||||
**差异定级规则:**
|
||||
- 结构错误(图标类在容器上)→ 🟡 中等差异 `moderate_count += 1`
|
||||
- 缺少 `fill: currentColor` → 🟡 中等差异 `moderate_count += 1`
|
||||
- 图标名称不匹配(按上表)→ 🟡 中等差异 `moderate_count += 1`
|
||||
- **使用 emoji 替代图标** → 🔴 严重差异 `critical_count += 1` **(微信小程序不兼容emoji,强制修复)**
|
||||
|
||||
### 布局对齐检查
|
||||
|
||||
对于设计节点使用 `justifyContent: space_between` 的水平排列容器:
|
||||
- 检查实际CSS是否正确设置 `display: flex; justify-content: space-between; align-items: flex-start;`
|
||||
- 如果左侧元素挤在一起,右侧元素没有靠右对齐 → 🟡 中等差异 `moderate_count += 1`
|
||||
|
||||
对于设计节点声明的对齐方式:
|
||||
- 实际实现对齐方式不符 → 🟡 中等差异 `moderate_count += 1`
|
||||
|
||||
### 防御性编程提醒
|
||||
|
||||
当 API 数据可能返回 `null` 时(未登录/加载失败),确保代码使用**可选链操作符 `?.`**访问属性避免渲染崩溃:
|
||||
|
||||
```vue
|
||||
<!-- 正确 -->
|
||||
<template>{{ earnings?.totalAmount }}</template>
|
||||
|
||||
<!-- 错误 -> 页面渲染崩溃 -->
|
||||
<template>{{ earnings.totalAmount }}</template>
|
||||
```
|
||||
|
||||
如果发现源码缺少可选链,在 AUTO-FIX 阶段应当添加。
|
||||
|
||||
**优势**:
|
||||
- 利用设计文件本身的结构信息,比纯视觉分析更可靠
|
||||
- **递归检查所有层级** → 确保不会漏检顶层存在但内部子区块缺失(如 topBar 存在但 topLeft 缺失)
|
||||
@@ -329,6 +444,10 @@ FIX LOOP (最多 3 轮):
|
||||
F2. 修改代码(CSS/组件/布局/颜色)
|
||||
- 使用 Edit 工具修改对应文件
|
||||
- 参照设计稿的 Neon Pulse 设计规范
|
||||
- **图标问题修复优先顺序**:
|
||||
1. 修正结构 → 将图标类从容器移出,嵌套独立 `<view>`
|
||||
2. 修正名称 → 按 Material → Lucide 映射表修改正确名称
|
||||
3. 添加CSS → 确保 `svg { fill: currentColor; }` 让颜色正确继承
|
||||
F3. 重启服务(如修改了后端或需要刷新构建)
|
||||
- Bash: pm2 restart mini-h5(移动端)或 pm2 restart admin(管理后台)
|
||||
F4. 重截实际页面
|
||||
@@ -367,6 +486,22 @@ visual_diff_pencil:
|
||||
|
||||
---
|
||||
|
||||
## 复查阶段(必须)
|
||||
|
||||
> **第一轮 PVD-1 验证完成成后,必须启动一轮专门的复查阶段**
|
||||
|
||||
原因:
|
||||
- 初始验证时,很多需要登录/参数的页面因未登录/缺少参数导致截图不正确,但代码对齐检查已通过
|
||||
- 必须逐个页面确认**截图内容实际正确**
|
||||
- 只有**所有页面截图都正确**,整个验证任务才算完成
|
||||
|
||||
操作:
|
||||
1. 为每个 `status: active` 页面创建独立的**复查 Task**
|
||||
2. 逐个检查:截图文件存在 → 内容显示正确目标页面 → 布局与设计对齐
|
||||
3. 如果不正确:修复问题(注入token/添加URL参数/修复代码bug)→ 重新截图
|
||||
|
||||
---
|
||||
|
||||
## Phase PVD-2: 汇总报告
|
||||
|
||||
### 2.1 生成报告
|
||||
|
||||
Reference in New Issue
Block a user