首页
/ LobeHub ux-audit 动态审计(L3):基于 agent-browser 与 CDP 插桩的用户旅程验证与性能量化

LobeHub ux-audit 动态审计(L3):基于 agent-browser 与 CDP 插桩的用户旅程验证与性能量化

2026-09-07 18:59:46作者:温玫谨Lighthearted

导读

本篇文章讲解 LobeHub 仓库中 .agents/skills/ux-audit/references/layer-3-dynamic.md 所定义的第三层动态审计(Layer 3 — Dynamic Audit):它在前两层(读代码、看渲染)的基础上,以自动化方式像真实用户一样驱动产品界面,专门捕获那些只在运行时才存在的状态(执行中、强制的错误/空态),验证"用户旅程"能否一步一步向前推进,并产出只有数字才能回答的指标——CLS、LCP、INP、长任务(long tasks)。读完你将掌握:如何用 agent-browser 通过 CDP 连接运行中的 Electron/Web 应用,如何编写并执行一条带证据采集的用户旅程,如何用 Network.emulateNetworkConditions 强制错误/空态,如何在页面加载前注入 Web Vitals 观察器并读出量化结果,以及如何把 L3 的结论融入 ux-audit 的共享报告。


一、为什么需要第三层:L1/L2 看不见的东西

LobeHub 的 ux-audit 技能把一次界面审计拆成三个互补的分层,全部定义在 ux-audit 技能入口 SKILL.md

分层 程序文件 做什么 能抓到的问题
L1 静态 layer-1-static.md 读代码 缺失的分支(空态/错误/重试)、草稿未持久化、结构性问题
L2 视觉 layer-2-visual.md 看渲染截图 真实视觉层级、间距/对比/对齐、截断/溢出、各状态的真实长相、响应式断点、明暗主题
L3 动态 layer-3-dynamic.md 用 acceptance 框架驱动真实用户旅程 + 插桩测量 执行中/锁定态、强制的错误/空态、步骤 N 是否通向 N+1、焦点/键盘、量化的 CLS/LCP/INP/长任务

一句话概括三者的分工:L1 读代码,L2 看渲染,L3 像用户一样驱动产品并测量它。这是唯一能够触达"只存在于运行期"状态的分层,是唯一验证旅程"步骤到步骤是否缝合"的分层,也是唯一能产出数字(CLS、LCP、INP、长任务)的分层——而截图和读代码都给不了这些数字。

ux-audit 的 SKILL.md 覆盖矩阵 明确规定了"哪个结论必须由哪个分层给出"的核心纪律:结论必须来自能看到它的分层,不能用代码去勾掉一个视觉/运行时的结论。据此,L3 独占或显著高于其他层的判定类型包括:

  • 执行中(in-progress)/锁定(locked)状态、强制错误/空态、能力受限(capability-gated)——只有 L3 能确认;
  • 旅程缝合(跨步骤的前进动量)——L1/L2 都只能给出"weak"级别的判断;
  • 焦点顺序 / 键盘可达性——只有 L3 能确认;
  • CLS / LCP / INP / 长任务数字——L1 完全不能,L2 只能定性;
  • 两个变体哪个"更好"(A/B 胜负)——本质是行为结果,必须由 L3(+ 数据分析)裁决,L1/L2 若做"机械对比"可以,但禁止宣布胜者。

什么时候需要跑 L3

  • L1 永远跑(廉价、离线、全覆盖的基线);
  • 当问题集中在布局、层级、渲染状态或响应式时加跑 L2
  • 当需要走一条旅程、强制 L1/L2 到不了的状态、或量化 CLS 等性能指标时,加跑 L3

每次审计只针对一个 surface,随着产品迭代按页面重复运行,这就是"持续审计"的含义。这也是为什么官方示例报告 home.md 在 L1 阶段会为尚未执行的 L3 单独预留一节,见 references/example/home.md 第 5 节


二、前置条件:先让 acceptance 框架的"环境 + 认证"变绿

L3 是整个审计体系里成本最高的一层——它需要一个运行中的环境。layer-3 文档明确要求:L3 假定 acceptance 框架的 Step 0(env + auth) 已经通过。

acceptance 框架的运行流程定义在 .agents/acceptance/PROCESS.md,其核心阶段分为环境解析、依赖安装、启动服务与注入登录态:

  1. 先解析环境:从项目自己的环境解析器读取端口与 base URL,绝不使用硬编码端口表;解析值与正在运行的 dev server 不一致时,先修复环境再继续。
  2. 处理依赖:根目录安装并不覆盖 apps/desktopapps/cli 等独立应用,运行涉及哪个独立应用就要在哪个应用内单独安装。
  3. 启动完整环境:包含该功能依赖的每一个服务——请求会被分发到的队列、缓存、对象存储都是硬前置条件,而不是可选优化项。
  4. 注入认证而非驱动登录流程:通过种子会话、cookie/状态恢复或 CLI 签发的 token 直接注入登录态,绝不驱动交互式登录/OAuth 流程——那会劫持用户的浏览器会话。
  5. 屏幕录制预检(仅对 OS 级采集):用 .agents/acceptance/scripts/check-screen-recording.sh 把关权限与屏幕状态;而 CDP 采集(agent-browser screenshotcdp-screenshot.sh 等)不受影响。

Layer 3 文档提到的每一条 CDP 操作之所以"也能在云端的 headless 环境、xvfb-run 下工作",正是因为整个证据链基于 CDP 渲染而非 OS 级屏幕捕获。相关命令(端口、服务、认证、surface、探针)的统一清单见 .agents/acceptance/PROJECT.md


三、驱动器:通过 CDP 使用 agent-browser

L3 的驱动器是 agent-browser over CDP(Chrome DevTools Protocol),即连接到正在运行的应用(Electron 或 Web)并对其进行操控与取证。其核心命令与在审计中的用途如下:

命令 在一次审计中的用途
agent-browser --cdp 9222 snapshot -i 抓取 accessibility/DOM 树 + 可交互元素(查找控件、断言存在性)
agent-browser --cdp 9222 screenshot 在每一步采集渲染证据(喂给 L2 检查)
agent-browser --cdp 9222 eval "<js>" 插桩——注入 web-vitals 观察器、读取状态、强制条件
type / click(见 acceptance 框架的 surfaces/ 驱动用户旅程
scripts/record-gif.sh 基于时间线的证据(流式输出、布局跳动)

组合使用时的典型节奏是:snapshot -i 确认控件可交互并断言存在 → click/type 推进旅程 → screenshot 在每个状态点留证 → eval 注入观察器或读取运行期状态。

一条硬约束:响应式扫描不要靠缩放 Electron 窗口

layer-3 文档特别强调:缩放 Electron 窗口会触发一次完整的 SPA 重载。因此凡是涉及"响应式 / 多视口"的扫描,都必须针对 web Chrome over CDP 来做,而不是通过拉伸 Electron 窗口来模拟。这条约束让 L3 在桌面端之外拥有了干净的移动视口取证途径。

时间型证据:record-gif.sh

type/click/screenshot 都是静态帧取证,而当断言对象是"随时间变化的行为"——流式输出、计时器跳动、加载状态、动画——时需要 GIF。仓库中的 .agents/acceptance/scripts/record-gif.sh 负责把 CDP 帧序列合成为 GIF,用于嵌入测试报告:

# 用法:record-gif.sh <output.gif> <duration_seconds> [fps]
./record-gif.sh "$DIR/assets/case2-tray-running.gif" 12 2 &
GIF_PID=$!
# ... 触发流式/动画行为 ...
wait $GIF_PID

它的可配置环境变量包括:AB_TARGET="--cdp 9222"(Electron,默认;遵循 CDP_PORT)、AB_TARGET="--session your-session"(Web agent-browser 会话)以及可选的 GIF_WIDTH(输出宽度,默认保持源分辨率)。脚本依赖 ffmpeg,由于每帧截图本身有约 0.3~0.5 秒延迟,实际可用帧率在 1~2 fps 这个现实区间。值得注意的工程细节:它对每一帧单独生成调色板并关闭抖动(palettegen=stats_mode=single:max_colors=256:reserve_transparent=0 + paletteuse=new=1:dither=none),因为一个全局 256 色调色板会把细微的中性骨架变成彩色噪点。这是"云兼容"的原因所在——帧来自 CDP 渲染,不涉及 OS 级录屏权限。


四、旅程模板(Journey Template):按有序步骤走并逐步取证

审计的单元是 User Journey——把某个 surface 的用户旅程定义成有序步骤,逐一定位每一层并驱动它们,在每个步骤采集证据、对"前进动量"做断言。下面以 home → task 为例说明模板长什么样:

  1. 着陆在该 surfacesnapshot + screenshot;断言主控件是否可聚焦(focusable)。
  2. 执行核心动作(输入并发送) → 先捕获执行中(in-progress)状态(是否展示?是否锁定输入?),再捕获完成(done)状态;断言它是否向前引导(步骤 2 有没有暴露进入步骤 3 的入口)。(对应 ux 规范 Act §3.1)
  3. 强制错误路径(方法见下一节)→ 断言出现"失败 + 重试",且已输入内容被保留。(对应 ux 规范 §4.2 / §2.1)
  4. 强制空路径 → 断言出现的是精心设计的空态页面,而不是空白页或卡死状态。

每一步都要记录:snapshotscreenshot,以及对照期望状态的 pass/fail。凡是"没有前进路径的步骤、死掉的 spinner(永久加载)、或输入被清空"的步骤,都构成一条审计发现(finding)。

这套模板把抽象规范变成了可判定断言:某一步没有前路、某个 spinner 永不结束、某次发送把输入框抹掉了——这三个就是动态层最典型的三类问题。


五、状态强制手册(State-Forcing Cookbook)

L1 读代码时只能"推断"某个状态的分支是否存在,L2 只能截到"当前已经渲染出来"的状态。而 L3 的价值在于把那些代码里存在、但正常路径走不到的状态真正制造出来

  • 错误 / 离线(error/offline)——用 CDP 的 Network.emulateNetworkConditions {offline:true} 模拟离线(或按路由屏蔽特定请求),然后触发那次 fetch,观察失败 + 重试 UI 是否出现。这正是**当面(live)抓出"永久骨架屏(permanent skeleton)"**的手段:一个只在 fetch 成功后才置位的 init 标志,会让失败请求表现为"永远加载"。
  • 慢速 / 保持加载(slow / hold-loading)——用 CDP 限速(如 slow 3G)把骨架屏"冻住"足够久,方便截图与目测判断(L2 职责),同时观察它是否最终会超时
  • 空(empty)——用一个全新 / 空账号,或清空相关 store/表,渲染出首次运行的空态。
  • 能力受限(capability-gated)——选中一个不具备某能力的模型,断言出现软性警告(对应 ux 规范 §4.2 capability),并且在切回具备能力的模型后警告消失。

把这些状态照进 L1 的检查点就形成了完整闭环:L1 在代码里发现 isInit 只在成功时置位并标记"疑似永久骨架",L3 用离线强制在真实运行中确认它。


六、性能与 CLS 插桩:把"卡"变成数字

截图无法量化抖动(jank),因此 L3 通过 eval 注入观察器:在导航/触发之前注入,等 surface 稳定后再读取累积值。

CLS(Cumulative Layout Shift,累计布局偏移)——L1/L2 都测不了的那一项:

// 1) 在加载/触发之前注入:agent-browser --cdp 9222 eval "<这段代码>"
window.__cls = 0;
new PerformanceObserver((l) => {
  for (const e of l.getEntries()) if (!e.hadRecentInput) window.__cls += e.value;
}).observe({ type: 'layout-shift', buffered: true });
// 2) 等 surface 稳定之后读取:agent-browser --cdp 9222 eval "window.__cls"

其余指标采用同样的形态:observe({ type: 'largest-contentful-paint' }) 得到 LCPobserve({ type: 'longtask' }) 观察主线程阻塞;INP 则在页面带有 web-vitals 库时通过它来测。

为什么要精确地在"加载→内容替换"之后读取?因为骨架屏与真实内容的高度不匹配(skeleton-height mismatch,对应 ux 规范 Feedback §4.1)恰恰是在这个时间窗口显形——加载切换的瞬间正是布局偏移最容易爆发的时刻。L2 只能靠前后对比截图"定性"地展示一次跳动,并把症状转交给 L3;L3 负责把数字测出来。这与 layer-2-visual.md 的收尾建议完全对齐:CLS 的量化一定归属 L3。

核心 Web Vitals 判定阈值(报告时同时给出数字与结论):

  • CLS:≤ 0.1 为良好(good)· ≤ 0.25 为需改进(needs-work)· 再高为差(poor)
  • LCP:≤ 2.5s 为良好
  • INP:≤ 200ms 为良好

报告时给出数字 判定,并且指出发生偏移的块(point at the block that shifted)——没有定位的量值对修复没有帮助。


七、诚实的边界(Honest Limits)

L3 不是万能层,文档明确列出它的四类限制:

  1. 依赖运行环境 + 认证通过(acceptance PROCESS Step 2),比 L1/L2 更慢、更容易 flaky——一条失败的步骤要先重跑一次再采信,避免把偶发抖动当成产品缺陷。
  2. 并非所有状态都容易强制:某些错误路径需要路由级 mock 或 fixtures 才能制造出来。
  3. 这里截到的截图仍然需要一轮 L2 视觉审查——引用前必须先"读图"核实(这正是 SKILL.md 中"证据而非感觉"这一基本规则的延伸:被引用但你没看过的截图只是 vibe,不是证据)。
  4. 只做 L3 独有的事——旅程、强制状态与指标;不要在这里重新推导 L1/L2 已能给出的结论。

官方报告 example/home.md 的第 5 节就是这条原则的正面示范:一篇 L1-only 的审计把若干待确认结论显式标注为 "pending L2/L3",并规划出 L3 具体要做什么——"强制简报离线以当面对证永久骨架屏(确认 gap ①)""驱动发送旅程确认 in-progress/locked 状态与前进引导(确认 gap ④)""注入 layout-shift 观察器量化 home 的 CLS 数字并给出结论"。代码里骨架覆盖看起来很全,但只有指标能证明它没有跳。


八、输出贡献:把动态证据汇入共享报告

一次 journey 级别的 L3 输出贡献包含三部分,全部汇入 ux-audit 的共享报告格式(其完整工作示例与报告骨架见 references/example/task-detail.mdreferences/example/home.md):

  1. 逐步 pass/fail——每条 journey 带 snapshot/screenshot 证据;
  2. 强制状态发现(forced-state findings)——错误/空/加载态中,那些 L1 只是"推断"、现在被当面确认的问题(如永久骨架屏、死 spinner);
  3. 性能表——CLS / LCP / INP / 长任务,逐项给出数值与判定结论。

落入共享报告后,这些结论会汇入 ux-audit 统一的严重性分级与"落地(land)"流程:具体缺陷修复或建单、可泛化缺口回灌ux 技能清单(补强规则及 ❌ 示例)、优秀案例作为 ✅ 示例落回清单,审计报告本身则存档为下一次运行的模板。这也是整个 ux-audit 技能"封闭循环"的一环——ux 是被审计的基准,而审计是让 ux 清单不断变得更敏锐的机制。


九、与相邻层协作的要点小结

  • L1 ↔ L3:L1 找出"疑似"运行时缺陷(缺错误分支、无重试、草稿未持久化、init 只在成功时置位),L3 负责把它们从代码里"请"到真机上确认。
  • L2 ↔ L3:L2 提供"看起来如何"的定性判断(层级、空态是否像真页面、CLS 症状),L3 提供"数值是多少"的量化结论;L3 截到的每一张图仍需 L2 的视觉复核。
  • 评测基准:L3 每一步断言对应的规范条目来自 ux 技能(如 Act §3.1、Feedback §4.1/§4.2、Read §1.1、Edit §2.1)与 Tidwell 模式目录 pattern-catalog.md

对追求可重复、基于证据而非感觉的 UI 审计而言,L3 的独特价值在于它给"用户体验"补上了其他两层永远拿不出的东西:真实旅程的缝合性、被强制出来的异常态、以及可以放进报告的 CLS/LCP/INP 数字。把这些数字与文档中的核心 Web Vitals 阈值对齐并定位到偏移块,一份动态审计就不再是主观点评,而是可以回归、可以对比、可以驱动修复的工程证据。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391