Files
bmad-method-template/.claude/skills/d8d-dev/modes/visual-diff/visual-diff-html.md
zyh 39156e160f sync: d8d-dev 结构重构为 modes/phases/rules 子目录 + journey 子模式拆分
- d8d-dev: 扁平文件 → modes/(journey, validation, visual-diff), phases/, rules/ 子目录
- d8d-dev: journey-mode.md 拆分为 journey-index/definition/execution/validation 四文件
- d8d-skills-sync: 新增技能同步工具
- d8d-iterate: 新增需求迭代编排技能
2026-04-19 10:31:31 +00:00

21 KiB
Raw Blame History

原型对比验证模式Visual Diff Mode

定位:在回归验证和旅程验证之后,将实际页面实现与原型做逐页视觉对比,发现布局偏差、组件遗漏、交互方式差异等视觉问题。

两种对比方式(任选其一):

方式 来源 适用场景
Pencil MCP 直接版(推荐) .pen 设计源文件 .pen 文件时首选100% 设计稿保真度
HTML 原型版 prototype/pages/*.html .pen 文件或需要 HTML 交互时

Pencil MCP 直接版文档:visual-diff-mode-pencil.md

与其他模式的关系

  • 回归验证模式 → 检查功能点存在且正确(类比单元测试)
  • 旅程验证模式 → 检查用户流程完整性(类比 E2E 测试)
  • 原型对比验证模式 → 检查视觉还原度(类比视觉回归测试)

触发时机

  • 旅程验证模式完成后建议进入
  • 手动触发:/d8d-dev visual-diff/d8d-dev visual-diff epic-3
  • 流水线自动触发:每个 Epic 完成后(可配置)

前置条件

检查清单(任一不满足 → 报错并终止):
├── prototype/ 目录存在且含 pages/ 子目录
├── prototype/server.js 存在(原型预览服务)
├── pipeline-context.md 存在(含实际应用 URL 和凭据)
├── pm2 服务运行中(实际应用可访问)
└── 至少有一个 Epic 的 Story 已 verified

核心流程

Phase VD-0: 准备
  ├── 发现原型页面 → 建立页面清单
  ├── 读取 pipeline-context.md → 获取实际应用 URL
  ├── 启动原型服务 → node prototype/server.js (port 8080)
  └── 构建页面映射表(原型页面 ↔ 实际路由)

Phase VD-1: 逐页对比循环
  对每个映射页面:
  ├── A. 截取原型页面截图
  ├── B. 截取实际页面截图
  ├── C. UI 差异对比(三层对比法)
  └── D. 差异分级 + 记录

Phase VD-2: 汇总报告
  ├── 生成 visual-diff-report.md
  └── 输出差异统计和修复建议

Phase VD-0: 准备

0.1 发现原型页面

# 列出所有原型页面
find prototype/pages -name "*.html" | sort

从输出中提取页面清单,分类为 frontend 和 admin。

0.2 读取实际应用信息

读取 _bmad-output/implementation-artifacts/pipeline-context.md 获取:

  • 实际应用 URLAPI_URL, ADMIN_URL
  • 认证凭据
  • 已实现的路由

0.3 启动原型服务

# 检查 8080 端口是否已被占用
lsof -i :8080 || true

# 启动原型服务(后台运行)
cd prototype && node server.js &
# 验证服务可用
curl -sf --max-time 5 http://localhost:8080/ && echo "OK"

如果 8080 端口被占用,使用其他端口并记录。

0.4 构建页面映射表

自动映射规则

对每个原型页面 prototype/pages/{group}/{name}.html:
  1. 提取 group (app/admin) 和 name
  2. 在实际路由中查找匹配:
     - name=login → /login 或 /admin/login
     - name=dashboard → / 或 /dashboard
     - name=register → /register
     - 其他 → /{name} 或 /admin/{name}
  3. 验证实际路由可访问curl 检查非 404
  4. 不可访问的页面标记为 skipped

映射输出格式(记录到 pipeline-state.yaml

visual_diff_mapping:
  - prototype: "pages/app/login.html"
    actual_route: "/login"
    group: "frontend"
    status: "active"  # active / skipped / error

  - prototype: "pages/app/dashboard.html"
    actual_route: "/"
    group: "frontend"
    status: "active"

Phase VD-1: 逐页对比循环

对映射表中每个 status: active 的页面执行以下流程。

1.0 创建截图目录

mkdir -p tests/e2e/screenshots/visual-diff

1.A 截取原型页面截图

1. browser_navigate → http://localhost:8080/pages/{group}/{name}.html
2. browser_resize → 1440x900 (桌面端) 或 375x812 (移动端)
3. browser_wait_for → time=2 (等待页面渲染)
4. browser_take_screenshot(fullPage=false)
   → tests/e2e/screenshots/visual-diff/{name}-prototype-viewport.png
5. browser_take_screenshot(fullPage=true)
   → tests/e2e/screenshots/visual-diff/{name}-prototype-full.png

1.B 截取实际页面截图

1. browser_navigate → {实际 URL}{route}
2. browser_resize → 同上视口(必须与原型一致)
3. 处理认证:
   - 如果页面需要登录 → 从 pipeline-context.md 读取凭据
   - browser_navigate → 登录页 → 填写凭据 → 提交 → 等待跳转
   - 如果已登录(直接访问无跳转)→ 继续
4. browser_wait_for → time=3 (等待数据加载)
5. browser_take_screenshot(fullPage=false)
   → tests/e2e/screenshots/visual-diff/{name}-actual-viewport.png
6. browser_take_screenshot(fullPage=true)
   → tests/e2e/screenshots/visual-diff/{name}-actual-full.png

1.C 三层对比法

判断页面长度

// browser_evaluate
() => {
  return {
    pageHeight: document.documentElement.scrollHeight,
    viewportHeight: window.innerHeight,
    isLongPage: document.documentElement.scrollHeight > window.innerHeight * 1.5
  };
}

如果 isLongPage === false短页面

直接对比首屏:
  ui_diff_check(
    expected_image_source = "{name}-prototype-viewport.png",
    actual_image_source = "{name}-actual-viewport.png",
    prompt = "对比这两张截图:第一张是设计原型,第二张是实际实现。列出所有视觉差异,包括布局结构、组件排列、颜色、间距、字体、图标等。按严重程度排序。"
  )

如果 isLongPage === true长页面

执行三层对比法:

层 1首屏对比viewport 截图)

ui_diff_check(
  expected_image_source = "{name}-prototype-viewport.png",
  actual_image_source = "{name}-actual-viewport.png",
  prompt = "对比这两张首屏截图:第一张是设计原型,第二张是实际实现。首屏是最重要的视觉区域。列出所有差异,按严重程度排序。"
)

层 2全页面对比fullPage 截图)

ui_diff_check(
  expected_image_source = "{name}-prototype-full.png",
  actual_image_source = "{name}-actual-full.png",
  prompt = "对比这两张完整页面截图:第一张是设计原型,第二张是实际实现。注意页面可能很长,重点关注各区域的布局和组件差异。"
)

层 3差异区域精确定位仅当层 1 或层 2 发现差异时执行)

对原型和实际页面分别执行分段截图:
  1. 计算分段数量 = ceil(pageHeight / viewportHeight)
  2. 对每一段:
     a. browser_evaluate → window.scrollTo(0, segmentIndex * viewportHeight * 0.8)
        // 20% 重叠避免切割区域遗漏
     b. browser_wait_for → time=1
     c. browser_take_screenshot(fullPage=false)
        → {name}-prototype-section-{N}.png
        → {name}-actual-section-{N}.png
  3. 逐段 ui_diff_check 对比

原型页面也需要 scroll:原型是静态 HTML需要同样执行 scroll + 截图。

1.D 差异分级

每页对比完成后,将差异分级:

级别 标准 示例
🔴 严重 布局结构不同、关键组件缺失、交互方式不同 原型有两列布局实际一列、缺少侧边栏、用标签页代替下拉菜单
🟡 中等 颜色/间距偏差、图标不一致、文字样式差异、非核心组件缺失 卡片圆角不一致、间距差 8px+、图标风格不同
🟢 轻微 微小像素差异、动画效果差异、可接受的响应式适配差异 1-2px 偏移、hover 效果不同、字体渲染差异

1.E 即时写入 Context关键

每完成一个页面的 ui_diff_check 后,必须立即将结果写入 pipeline-context.md,防止上下文压缩导致结果丢失。

流程:
  1. ui_diff_check 返回差异结果
  2. 立即编辑 pipeline-context.md在 "原型对比验证结果" 部分更新该页面的差异摘要
  3. 格式:#### N/11 {页面名} — 严重程度 🔴/🟡/🟢/✅
  4. 列出关键差异(精简为 3-7 条)
  5. 继续下一个页面

为什么必须即时写入?

  • 原型对比流程涉及 11 个页面的截图+对比,对话上下文可能在中途被压缩
  • 如果不在每页完成后即时持久化,已完成的对比结果会丢失
  • pipeline-context.md 是上下文压缩后的恢复锚点

context 中的记录格式示例

## 原型对比验证结果 (Visual Diff)

### 全局问题(所有页面共通)
- 🔴 问题描述...

### 逐页结果

#### 1/11 Login — 严重 🔴
- 原型: pages/app/login.html → 实际: /login
- 🔴 差异1
- 🔴 差异2
- 🟡 差异3

#### 2/11 Register — 严重 🔴
- ...

差异记录格式pipeline-state.yaml

visual_diff_results:
  dashboard:
    prototype_page: "pages/app/dashboard.html"
    actual_route: "/"
    severity: moderate  # critical / moderate / minor / match
    viewport_compared: true
    fullpage_compared: true
    sections_compared: 3
    differences:
      - severity: critical
        location: "首屏左侧"
        description: "侧边栏缺失,原型有固定侧边栏导航"
        section: 1
      - severity: moderate
        location: "首屏中部"
        description: "上传区域卡片圆角:原型 16px实际 8px"
        section: 1
      - severity: minor
        location: "第2段"
        description: "卡片间距略宽:原型 16px实际 24px"
        section: 2

Phase VD-2: 汇总报告

2.1 生成报告

生成 tests/e2e/screenshots/visual-diff/visual-diff-report.md

# Visual Diff Report

生成时间: {timestamp}
对比页面数: {total_pages}

## Summary

| 级别 | 数量 |
|------|------|
| 🔴 严重 | {critical_count} |
| 🟡 中等 | {moderate_count} |
| 🟢 轻微 | {minor_count} |
| ✅ 匹配 | {match_count} |

## 严重差异(需修复)

### 🔴 {page_name}
- 原型截图: `visual-diff/{name}-prototype-viewport.png`
- 实际截图: `visual-diff/{name}-actual-viewport.png`
- 差异:
  1. {具体差异描述}
  2. {具体差异描述}
- 修复建议: {auto-fix 建议}

## 中等差异(建议修复)

### 🟡 {page_name}
...

## 轻微差异(可接受)

### 🟢 {page_name}
...

## 完全匹配

### ✅ {page_name}

2.2 更新 pipeline-state.yaml

visual_diff:
  status: completed  # completed / partial / failed
  total_pages: 11
  compared_pages: 10
  skipped_pages: 1
  critical_count: 0
  moderate_count: 3
  minor_count: 5
  match_count: 2
  report_path: "tests/e2e/screenshots/visual-diff/visual-diff-report.md"
  timestamp: "2026-04-08T10:30:00Z"

2.3 输出结论

═══════════════════════════════════════════
  Visual Diff Report 完成
═══════════════════════════════════════════
  总页面: 10 对比, 1 跳过
  🔴 严重: 0  → 无需修复
  🟡 中等: 3  → 建议修复
  🟢 轻微: 5  → 可接受
  ✅ 匹配: 2

  报告: tests/e2e/screenshots/visual-diff/visual-diff-report.md
═══════════════════════════════════════════

2.4 修复决策

VD-2 报告完成后:
  ├── 🔴 严重差异 > 0 → 自动进入 Phase VD-3 全量修复
  └── ✅ 无严重差异 → 流程结束

Phase VD-3: 全量自动修复

定位:在 Phase VD-2 发现严重差异后,自动修复所有视觉差异,使实际页面还原原型设计。以原型 HTML 为 Source of Truth重写 UI 层。

核心原则

  • 先全局后逐页:全局修复(主题、导航、品牌)先做,避免逐页修复被全局问题干扰
  • 保留后端逻辑:只改 UI 层React 组件 + CSS不动 API 路由和数据库
  • 以原型为基准:直接从原型 HTML 提取设计 token 和组件结构
  • 修复后必须验证:重新截图 + ui_diff_check 确认还原度
  • 全程零确认、全自动

3.1 全局修复(先修复影响所有页面的共性问题)

从 pipeline-context.md 的"原型对比验证结果"中提取全局问题,按优先级修复:

全局问题按优先级:
  P0: 主题系统(深色/浅色主题 — 影响 app 组页面)
  P0: 品牌标识(图标 + 标题 — 影响所有页面)
  P1: 管理后台导航结构(顶部水平导航 — 影响 admin 组页面)
  P1: 通用组件样式(按钮 padding/圆角、输入框样式)

3.1.1 主题修复

1. 读取原型 HTML如 pages/app/login.html提取
   - 页面背景色(如 #000000
   - 卡片/容器背景色(如 #1E1E1E
   - 文字颜色(如 #FFFFFF
   - 主色调(如 #FF8400
   - 边框颜色(如 #333333

2. 修改全局样式文件:
   - globals.css / tailwind.config.ts
   - 定义 CSS 变量或 Tailwind theme extend

3. 修改布局组件:
   - 背景色 class 对齐
   - 确保所有 app 组页面使用新主题

3.1.2 品牌标识修复

1. 从原型提取品牌元素:
   - 图标(如 FontAwesome fa-bolt 闪电图标)
   - 标题文字(如 "VividSpark"
   - 副标题文字(如页面特有描述)

2. 定位品牌组件文件,替换:
   - Logo 组件(图标 + 文字)
   - 页面标题区域

3.1.3 管理后台导航修复

1. 从原型 admin 页面提取导航结构:
   - 位置:顶部水平导航栏
   - 菜单项:用户管理、内容审核、平台配置、数据分析、系统设置
   - 右侧:管理员邮箱 + 登出按钮

2. 修改 admin 布局组件:
   - 从左侧边栏 → 顶部水平导航
   - 调整所有 admin 页面布局适配

3.1.4 全局修复完成后即时写入 context

编辑 pipeline-context.md在"原型对比验证结果"下添加:

### 全局修复记录
- ✅/⚠️ 主题修复:{结果摘要}
- ✅/⚠️ 品牌标识修复:{结果摘要}
- ✅/⚠️ 导航结构修复:{结果摘要}

3.2 逐页修复循环

对 Phase VD-2 报告中每个有差异的页面,执行以下流程:

对每个有差异的页面(按严重程度排序,🔴 优先):
  ├── Step A: 解析原型 HTML
  ├── Step B: 定位并重写实际源码
  ├── Step C: 重新截图验证
  └── 即时写入 context每页完成后

Step A: 解析原型 HTML

读取 prototype/pages/{group}/{name}.html提取

1. 设计 Token
   - 颜色:背景色、卡片色、文字色、主色调、边框色
   - 字体font-family、font-size、font-weight
   - 间距padding、margin、gap
   - 圆角border-radius
   - 阴影box-shadow

2. 组件列表:
   - 表单字段(类型、标签、占位符、图标)
   - 按钮(文字、颜色、大小、圆角)
   - 卡片(布局、内容结构)
   - 表格(列定义、操作按钮)
   - 特殊组件(标签页、进度条、图表区域)

3. 文案:
   - 页面标题、副标题
   - 占位符文字
   - 按钮文字
   - 链接文字

4. 布局结构:
   - grid/flex 布局
   - 列数/行数
   - 组件排列顺序

Step B: 定位并重写实际源码

1. 路由 → 源码文件映射:
   /login → src/app/(auth)/login/page.tsx
   /register → src/app/(auth)/register/page.tsx
   /dashboard → src/app/(creator)/dashboard/page.tsx或 src/app/page.tsx
   /publish → src/app/(creator)/publish/page.tsx
   /pricing → src/app/(creator)/pricing/page.tsx
   /settings → src/app/(creator)/settings/page.tsx
   /admin/login → src/app/admin/login/page.tsx
   /admin/users → src/app/admin/users/page.tsx
   /admin/moderation → src/app/admin/moderation/page.tsx
   /admin/analytics → src/app/admin/analytics/page.tsx
   /admin/platforms → src/app/admin/platforms/page.tsx
   /admin/settings → src/app/admin/settings/page.tsx

   通用组件:
   侧边栏 → src/components/layout/sidebar.tsx
   头部导航 → src/components/layout/header.tsx或 admin-header.tsx

2. 以原型为 Source of Truth对齐
   a. 组件结构:补全缺失组件(如 Remember me、FAQ、标签页、过滤器
   b. 布局结构:对齐网格列数、卡片 vs 列表等
   c. 样式 classTailwind class 对齐原型视觉
   d. 文案:品牌标题、占位符、按钮文字
   e. 数据展示:如原型用图表,引入 chart 库并实现

3. 关键约束:
   - 保留后端逻辑不变API 调用、数据处理)
   - 只修改 JSX 结构和 Tailwind class
   - 新增的 UI 组件使用项目已有的设计系统组件

Step C: 重新截图验证

1. 重跑 Phase VD-1 的截图流程:
   a. 截取修复后的实际页面截图
      → tests/e2e/screenshots/visual-diff/{name}-fixed-viewport.png
   b. ui_diff_check 对比(修复后 vs 原型)

2. 结果判定:
   - PASS无 🔴/🟡)→ 标记 ✅ fixed
   - FAIL → 调整修复策略,重试(最多 2 次)
   - 仍 FAIL → 标记 ⚠️ needs-manual-fix记录原因

3. 每页修复完成后即时写入 pipeline-context.md
   #### N/11 {页面名} — ✅ fixed / ⚠️ needs-manual-fix
   - 修复内容: {具体修复了什么}
   - 验证结果: {ui_diff_check 摘要}

3.3 修复后二次对比(全量验证)

全局 + 逐页修复完成后:
  1. 对所有 11 页重跑 Phase VD-1 截图 + ui_diff_check
  2. 生成修复前后对比:
     - 修复前: visual-diff/{name}-actual-viewport.png
     - 修复后: visual-diff/{name}-fixed-viewport.png
  3. 更新 visual-diff-report.md添加修复结果列
  4. 更新 pipeline-state.yaml visual_diff 部分

3.4 最终报告

visual_diff:
  status: completed  # completed / partial / failed
  total_pages: 11
  compared_pages: 11
  fixed_pages: 8      # 新增
  manual_fix_needed: 3  # 新增
  critical_count: 0    # 修复后的数值
  moderate_count: 1
  minor_count: 5
  match_count: 5
  report_path: "tests/e2e/screenshots/visual-diff/visual-diff-report.md"
  fix_report_path: "tests/e2e/screenshots/visual-diff/visual-diff-fix-report.md"
  timestamp: "2026-04-08T12:00:00Z"

输出结论:

═══════════════════════════════════════════════
  Visual Diff Report + 自动修复 完成
═══════════════════════════════════════════════
  总页面: 11 对比, 0 跳过
  ✅ 已修复: 8
  ⚠️ 需手动修复: 3见报告

  修复前: 🔴 11  🟡 0  🟢 0  ✅ 0
  修复后: 🔴 0   🟡 1  🟢 5  ✅ 5

  报告: tests/e2e/screenshots/visual-diff/visual-diff-report.md
  修复报告: tests/e2e/screenshots/visual-diff/visual-diff-fix-report.md
═══════════════════════════════════════════════

视口配置

目标 宽度 高度 用途
桌面端(管理后台) 1440 900 admin 组页面
桌面端(前端) 1440 900 app 组页面(如有移动端原型则用移动视口)
移动端 375 812 移动端原型(如有 mobile 目录)

错误处理

错误场景 处理方式
原型服务启动失败 检查端口占用,尝试其他端口,报告错误
实际页面无法访问 标记该页面为 error继续下一个
实际页面需要认证且登录失败 标记该页面为 error记录认证错误
ui_diff_check 超时 降级为仅 analyze_image 语义分析
截图文件过大 fullPage 截图限制最大 8000px 高度
原型页面无对应实际路由 标记为 skipped记录在报告中

注意事项

  • 视口必须一致:原型和实际页面使用完全相同的视口尺寸截图
  • 数据状态差异:原型是静态设计,实际页面有真实数据。对比时忽略因数据内容不同导致的差异(如文字内容、图片内容),只关注布局和组件差异
  • 认证页面login/register 页面不需要登录即可访问,直接截图对比
  • 原型静态特性:原型没有动画和过渡效果,实际页面可能有,这些差异标记为 🟢 轻微
  • 自动修复Phase VD-2 发现严重差异后自动进入 Phase VD-3 全量修复,全程零确认
  • 保留后端逻辑:修复只改 UI 层React 组件 + CSS不动 API 路由和数据库
  • 重试上限:每页最多修复重试 2 次,仍失败则标记为需手动修复