mcpField
D
diff

diff

github.com/adobe测试与质量318301 次安装1 天前更新
代码仓库

关于 diff

stardust:diff 是一项使用 Playwright 检查渲染原型与构建页面的技能。两种互补探测分别比较计算样式和 DOM 结构,并通过配置档案保持框架无关性。

主要功能

  1. 01视觉与结构对比探测
  2. 02分析计算样式和 DOM
  3. 03支持 EDS 与通用配置档案
  4. 04检查主内容及页面页眉页脚
  5. 05探测线上页面的裁切与内容存在性
  6. 06检查页眉交互一致性

适用场景

  1. 01验证 EDS 转换是否符合原型
  2. 02比较 Figma 导出稿与渲染构建页
  3. 03查找内容缺失或布局差异
  4. 04对照源页面检查重建页眉行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/adobe/skills --skill diff

安装后在 Claude Code 中输入/diff确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

scripts/
chrome-compare.mjs
chrome-explore.mjs
clip-probe.mjs
content-diff.mjs
content-inventory.mjs
content-presence.mjs
diff-profiles.mjs
image-signature.mjs
live-session.mjs
measure-live.mjs
unit-geometry.mjs
visual-diff.mjs
scripts/test/
chrome-compare.test.mjs
chrome-explore.test.mjs
chrome-fixtures.mjs
clip-probe.test.mjs
content-diff.test.mjs
content-presence.test.mjs
image-signature.test.mjs
live-session.test.mjs
unit-geometry.test.mjs
visual-diff.test.mjs

详细介绍

stardust:diff — 原型 ↔ 构建版对比

两个探测脚本,用于比较源原型与构建后的页面。它们发现的是不同类别的问题——两者都要运行;单独运行任何一个都可能误判为“看起来没问题”。

这两个探测脚本均与框架无关,使用 Playwright 通过计算样式 + DOM(而非像素)比较两个渲染后的 URL。所有特定技术栈的术语都放在配置文件中(--profile eds|generic);比较逻辑通用。

适用场景

  • 将原型转换为 EDS 后(stardust deploy 技能的第 10 步)——使用 --profile eds。
  • 检查两个渲染后的 URL 之间是否“构建结果与设计一致”(例如 Figma 导出与 React 构建版、旧页面与重建版)——使用 --profile generic。
  • 在转换/QA 工作流中用作验证关卡(参见 工作流用法)。

不适用于:没有 JS 装饰的单个静态文件(请使用构建/测试框架 URL,以便组件完成装饰——原始 .plain.html 没有可供分类的角色)。

两个探测脚本

探测脚本脚本能发现无法发现
像素 / 布局skills/diff/scripts/visual-diff.mjs图像拉伸(相对于源页面)、最大宽度换行丢失、空白渲染、表面色/底色反转、标题字号/字体差异、图像数量差异“文本正确,位置错误”;CTA 丢失(像素完整、颜色合理 → 不会标记)
结构内容 + 字体skills/diff/scripts/content-diff.mjs缺失 / 角色互换的标题·眉题·CTA、凭空添加/丢失的正文、渲染字体分支(宽度探测);丢失的 placeholder/aria-label/title 值、缺失/错误/位置移动的图标——默认检查主根节点以及 header/footer 框架根节点几何 / 布局回归

content-diff 会从每个根节点提取按顺序排列并按角色分类的清单(heading / eyebrow / cta+href / body):包括 --main 根节点以及默认包含的 header 和 footer 框架根节点;分类依据是计算样式 + 标签,因此原型 DOM 与构建版 DOM 能进行对称比较,然后按根节点逐一比较差异;每个根节点还会生成第二份清单,其中包含 placeholder / aria-label / title 值和图标。

运行

# Prereq 0: playwright importable from the project root (node -e "import('playwright')"), else
# npm i -D playwright pixelmatch pngjs cheerio --legacy-peer-deps
# (never --no-save; extract SKILL.md § Setup). Run the copied scripts from the project
# root: ESM resolves from the script.
# Copy the WHOLE skills/diff/scripts/ dir: content-diff imports its local diff-profiles.mjs
# AND content-inventory.mjs (deploy keeps its own synced copies for #93/#94).
# Prereq: a RENDERABLE source. Static → serve from its own dir (python3 -m http.server).
# The build URL must be the DECORATED page (live/preview or a local harness), not raw markup.
# ONE server, ONE port — probe before starting one (curl is always present, lsof is not):
curl -sI localhost:8791/ | head -1                   # 200/404 = something serves the port; no line = free
curl -sI localhost:8791/<prototype>.html | head -1   # 200 = it serves YOUR dir: reuse it
command -v lsof >/dev/null && lsof -nP -iTCP:8791 -sTCP:LISTEN   # optional: names the pid
# Nothing answered → start yours. A foreign server: never kill it; use a per-project port
# (the traps: ../deploy/SKILL.md § Step 10).
PROTO="http://localhost:8791/<prototype>.html"
BUILD="https://<branch>--<repo>--<owner>.aem.page/<path>"   # or http://localhost:3000/<harness>

# 1. PIXEL/layout
node skills/diff/scripts/visual-diff.mjs   "$PROTO" "$BUILD" --profile eds --sections ".hero"

# 2. STRUCTURAL content + type
node skills/diff/scripts/content-diff.mjs  "$PROTO" "$BUILD" --profile eds   # --json dumps both inventories

标志(两个工具通用):--profile eds|generic(默认 eds)、--width <px>(默认 1280)、 --main <selector[,selector…]>(内容根节点;content-diff 默认值由配置文件决定,并接受逗号分隔的列表——每个根节点都会单独列入清单并进行差异比较,每条发现都带有对应根节点 [root];visual-diff 接受一个选择器,默认 main),仅适用于 content-diff: --chrome / --no-chrome(默认开启——会将 header 和 footer 根节点与主根节点并列比较,这两个根节点分别解析为主根节点之外的第一个 <header>/[role=banner] 和 <footer>/[role=contentinfo];如果某一侧缺少相应根节点,则该侧在此处按空内容测量,根节点行会注明这一点),以及实时目标设置(共享引擎:scripts/live-session.mjs——每个上下文都会发送真实 Chrome UA 以及标准 Chrome 请求标头;仅凭 UA 仍会被 Akamai 级别的机器人管理拦截并返回 403):

  • --ua <string> — 覆盖 user agent(默认:真实 Chrome 桌面版 UA)。

  • --wait-until <state> — 覆盖 goto 等待条件。默认规则(scripts/live-session.mjs 中共享的 defaultWaitUntil),按每个 URL 侧分为三档:

    • localhost/127.0.0.1 → networkidle(本地原型 / 测试框架,不变);
    • EDS 构建 / 预览源站——主机名以 .aem.page、.aem.live、.hlx.page、 .hlx.live 结尾 → networkidle(它们会异步添加装饰内容,并且能可靠地达到 networkidle;在 domcontentloaded 时测量会读取装饰前的 DOM——在部署第 10 步中导致不稳定的 误报红色 / FONT FORK);
    • 所有其他实时 http(s) → domcontentloaded(带有分析信标的实时网站 永远无法达到 networkidle)。

    --wait-until 会覆盖以上三档设置。

  • --dismiss [sel,...] — 关闭两侧的覆盖层:Cookie 同意提示(点击关闭,不移除)以及定时显示的营销 / 新闻订阅弹窗,另可指定站点专属选择器;之后鼠标会停放在一旁。

  • --headed — 针对机器人管理站点的升级选项:以有头隐身模式运行真实 Chrome。

  • --locale <tag> — 固定 Accept-Language + 上下文区域设置(否则,进行地理位置重定向的站点每次运行时捕获到的区域设置可能不同)。

visual-diff 还支持:--out <dir>、--sections a,b(按区块截取屏幕截图)。

任一导航遇到机器人管理挑战 / 拦截插页时,都会大声报错并失败,退出码为 3——绝不会将其作为源站进行测量。可使用 --headed 升级处理;若仍被拦截,该站点需要 crawl.mjs 级别的采集,且无法在无头模式下运行此检查。

任一侧出现普通(非挑战)HTTP 错误——例如 404 构建侧,这在预览尚未传播到 aem.page 前很常见——不会导致致命错误:探测器会大声记录警告、测量错误页面,并由标志(BLANK RENDER / 内容不对称)传递信号,退出码为 0。这是探测器的提示性约定:0 = 已运行(标志仅供参考),1 = 探测器错误,3 = 机器人挑战。

已发布源站探测(#125)

content-diff 会将原型与其构建版本进行比对;但针对一个 LIVE 电商源站时,它对每个节点的判定都是错误的,而像素门禁却让一个所有卡片都被裁切的页面通过了(../replica/reference/source-fidelity-gate.md § 所有页面的已发布源站门禁)。三个探测会提出可核验的问题;它们都在同一个无窗口的真实 Chrome 层级中加载两侧,并以相同方式等待页面稳定(scripts/measure-live.mjs)。

ORIGIN="https://www.example.com/<path>"; SERVED="https://main--repo--owner.aem.live/<path>"
node stardust/scripts/diff/clip-probe.mjs "$SERVED" [--json clip.json]                       # D1: exit 2 on cut / hidden text or controls
node stardust/scripts/diff/content-presence.mjs "$ORIGIN" "$SERVED" [--variable "<selO>=<selE>"]  # D2: exit 2 on MISSING/HIDDEN link or heading
node stardust/scripts/diff/unit-geometry.mjs "$ORIGIN" "$SERVED" --unit "<selO>=<selE>" --n 2      # D3: exit 2 on an element off / hidden / missing

解读:🔴 裁切组会指出裁切元素(固定高度且设置了 overflow: hidden 的框)——修复该区块的 CSS,绝不要改内容。隐藏链接 = 存在于 DOM 中但被裁切(CSS);缺失链接 = 未提供(编码器);控件状态 = 同一控件的另一个值;数量…… = 会随会话变化的区域,请目视确认。源站侧遇到 HTTP ≥ 400 时会明确失败(退出码 4),遇到机器人验证时也会明确失败(退出码 3)。注意事项:无限滚动的源站会在等待稳定期间持续加载——将该区域标记为 --variable;没有 <main> 的在线源站会将整页与整页进行比较(范围行会说明这一点),除非使用 --chrome,否则页眉/页脚交由 Chrome 裁切门禁处理;数量短语和“阅读更多”启发式使用英文词表(--count-words、--more-words)。

页眉契约(#132)

上述每项探测都只会检查静止状态下的页眉(指针停放,动画冻结),所以即使所有菜单都不对,它们仍会通过;已发布的运行版本中,模板代码将悬停式大型菜单做成了点击下拉菜单,将搜索和购物车做成普通链接,还将逐级展开抽屉做成了静态列表。现在会从源站记录一次页眉行为,并将构建版本与之比较:

node stardust/scripts/diff/chrome-explore.mjs "$ORIGIN" stardust/chrome/header-contract.json   # 1440 + 390
node stardust/scripts/diff/chrome-explore.mjs "$SERVED" stardust/chrome/header-build.json
node stardust/scripts/diff/chrome-compare.mjs stardust/chrome/header-contract.json stardust/chrome/header-build.json \
  --profile replica --json stardust/chrome/header-parity.json    # functional under redesign and reskin

探索器会悬停(桌面端)、点击并按键操作每个页眉控件,然后记录每种打开状态(链接、标题、裁切、aria-expanded、焦点、滚动锁定、动画、关闭路径);嵌套层级受 --max-states 400 --max-depth 4 限制;还会记录搜索输入提示和提交行为,以及滚动后的页眉;其摘要行会估算工作量。比较器会在缺少控件或状态(包括将悬停重建为点击)、面板链接、键盘操作、关闭路径或滚动锁定不符时以 2 退出;replica 还会检查打开状态的裁切图(2 %)、动画(max(80 ms, 25 %)和滚动状态。只有站点所有者可以清除一项发现:编辑 stardust/chrome/header-decisions.json({ decisions: [{ finding, decision, reason, by, at }] }),绝不能由 Agent 签署。它会在提取时通过 --prep 运行(即契约),也会在基础框架外壳和部署第 10 步中运行;交接前会在在线主机上运行(done-check header_parity),并在 qa 中运行。裁切图保存在 stardust/.work/chrome/。

阅读 content-diff

  • 🔴 缺少 CTA / 标题 / 眉题 — 确实有内容丢失。修复。眉题缺失通常是分段时遗漏,因为眉题位于标题之前;CTA 缺失意味着组件根本没有渲染链接。这些情况正是像素探针无法检测到的。
  • 🔴 角色互换 — 同一文本出现在不同角色下(正文被渲染为眉题,眉题被并入导语)。修复组件的节点分段。同样的外观下,正文/眉题 → 标题属于可接受的角色升级(根级别行和 --json 中显示为 accepted,不算问题);标题 → 正文/眉题属于 🟡 角色降级。视觉上隐藏的(sr-only)文本丢失属于 🟡 缺少 SR-ONLY 文本;sr-only 链接丢失仍属于 🔴 缺少 CTA。
  • 🔴 缺少占位符、缺少图标、交互元素上的图标不一致(输入框、按钮、链接,或位于其中)——例如,本地化搜索占位符丢失、分享链接缺少图标、语言链接上的旗帜错误、图标框为空。修复。交互元素之外的同类问题属于 🟡。aria-label 丢失属于任何位置都无内容丢失的 🟡(未通过门槛)。
  • 图标按标识或相同像素配对:重新托管的文件(SAME IMAGE)或用 <img> 绘制的 svg(SAME ICON)都可接受。布局尺寸为 0×0 的图标不算。
  • 🟡 缺少标题 — 始终属于 🟡:多数辅助技术不会读取标题提示,设计上通常也会将其省略。🟡 图标移位 — 锚点通过后,在另一个锚点处配对到相同图标(通常是无文本的 <button> 宿主,锚定为 button#n):确认其位置。🟡 图标类型(未比较像素,例如隐藏图标)以及所有额外项。
  • 每条问题行都会标明其根节点——🔴 MISSING PLACEHOLDER [header]: …——报告则会为每个根节点输出一个区块(root "main"、root "header" 等;某一侧缺少的根节点在该侧按空内容衡量,且行中会注明是哪一侧)。
  • 🟡 缺少正文 / 额外项 — 正文内容丢失,或构建版本中出现源内容没有的文案。通常是占位内容被改写为实际文案。确认是否符合预期;不要盲目“修复”。
  • 🟠 字体分叉 — 匹配的文本行渲染字体 FACE 不同(宽度探测,不使用 document.fonts.check)。source X→sys 表示原型指定了字体 X,但从未加载,因此回退到系统字体——此时构建版本自行托管预期的回退字体是正确做法,不是错误。所有分叉行会归并为一条建议。
  • 已知限制——节点粒度的 JOIN/SPLIT 会被判定为 🔴(#87)。 当源版本将一个文本段渲染为 N 个相邻节点,而构建版本将相同文本渲染为一个节点(或反过来——例如,三个事实标签与一个合并的标签 span),差异比较目前会将这一非缺陷报告为缺少 + 角色互换 + 额外项。在合并匹配功能推出之前(源节点是构建版本同一区域节点的子串时,会给出 🟡 JOIN/SPLIT 建议),如果发现 🔴 的文本拼接后恰好与某条额外项的文本一致,请先核实再将其视为内容丢失——确认属合理情况即为通过。

通过标准: 视觉红旗全部不存在或均有合理解释,并且 content-diff 中 0 个 🔴 且 0 个无内容丢失的 🟡(../replica/reference/source-fidelity-gate.md § 通过标准第 1 项)。每次修复后都要重新运行两项检查。

配置

skills/diff/scripts/diff-profiles.mjs 中保存了这些配置。配置提供源/目标标签、各标记对应的修复提示、字体差异阈值、默认内容根选择器以及眉题分类阈值。引擎不包含任何技术栈专属字符串。

  • eds(默认)——Edge Delivery / DA 修复用语,以及 stardust deploy 技能的问题编号。
  • generic ——适用于任何技术栈的通用源/构建用语。

要添加配置,请复制 diff-profiles.mjs 中的 generic 并编辑 hints。

共享引擎 + 循环内的同类工具

结构探针的分类器 + 差异比较器位于 skills/diff/scripts/content-inventory.mjs (deploy 会导入其同步副本)。它们使用同一套检测工具,作为 stardust deploy 技能的两个关卡: section-schema.mjs(编码前的 ENCODE/DECODE 契约,deploy #93)和 block-roundtrip.mjs (循环内的逐块关卡,deploy #94——同一份清单差异比较器,在编写时针对本地 decorate() 测试框架逐块运行, 无需 DA,并通过退出代码设关)。转换过程中运行循环内关卡;部署后最终验证时,运行本技能的两个探针。如果这里首次发现缺陷,而循环内关卡已通过 = 交付流水线在传输过程中重塑了内容——修复该块的扁平化结构回退逻辑,而不是修改编写内容。

工作流用法

在验证阶段调用两个脚本,并根据输出设关,就像 deploy 的 Validate 阶段对已提供服务且已装饰的构建所做的那样(本地测试框架或分支预览):修复 visual-diff 的 STRETCHED/FLUSH-LEFT/SURFACE-GROUND/GAP/HEADING FACE 标记(除非有合理理由——字体替换会显示为一行 HEADING FACE ×N),并处理每一项 content-diff 🔴;确认 🟡/🟠,重复检查,直到两者都通过。

评论

支持 Markdown