dsh-testhud
把自动测试的进展画在被测界面之上的浮层:每步的期待与实际,以及等待的人现在能不能动鼠标键盘。包内附带通用预编译二进制(arm64 + x86_64),无需 Xcode 命令行工具;浮层面板自身要求 macOS 14 及以上。
安装
dsh plugin --profile web add dsh-testhud
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-testhud


把自动测试的进展画在被测界面之上的浮层:置顶、鼠标穿透、不抢焦点,配一个任何 session 都能直接调的 test_hud 工具。
为什么要有它
agent 驱动 GUI、比对截图、批量渲染,一跑就是几分钟。这段时间里等的人什么都不知道:跑到第几步、这一步期待什么、是不是已经失败了,以及最要紧的那个问题——现在能不能动鼠标键盘了。
这块浮层就回答这些,画在屏幕上、不抢焦点:
- 在测什么:标题、测试对象、开始时间与已用时。
- 每一步的期待与实际:一行一条,✓ / ✗ / ⏳ / •。
- 常驻第一行的控制权提示:跑的时候「正在进行:先别动鼠标键盘」,结束「✓ 已结束,可以收回鼠标键盘的控制权了」。它是整块浮层里字重最重、也是唯一带底色的一行 —— 不能碰的时候是色板红
#FF3B30底配白字,可以接手了是色板绿#34C759底配黑字(字色由状态定,不按对比度挑,见「当前配色」)。 - 不碍事:鼠标穿透、置顶、不进 Dock 也不进 ⌘Tab,并且自动挑屏幕上最空的角,尽量少盖住被测界面。
- 位置可以手动调:抓住顶部那条色带拖即可。色带是面板上仅有的两处接鼠标的地方之一(它是把手),其余部分照旧穿透;内容溢出时内容区也能接鼠标,见「尺寸与溢出」。鼠标移到这两处会变成"张手"、按下去变"抓紧",松开还原 —— 光标形状本身就是"这儿能拖"的提示。
- 色带左端有三个圆点:红点关掉浮层、黄点折叠成只剩色带一条(再点一下回来)、绿点放大到可用区域上限 —— 高度撑满、宽度也撑满(再点回按内容自适应;只撑高度常常没效果,因为内容一多,自然高度本身就已经等于上限了)。颜色固定为色板红
#FF3B30/ 黄#FFCC00/ 绿#34C759,外面套一圈 1.0 点纯白描边 —— 红点与红色带现在是同一个色板色,描边是它唯一的辨识手段。
安装
dsh plugin --profile <profile> add github:yijiezhong/dsh-testhud # 直接从 GitHub 装 dsh plugin --profile <profile> add link:/path/to/dsh-testhud # 从本地 checkout 装 dsh plugin --profile <profile> add dsh-testhud # 从 npm 装(已发布 0.1.1)
Desktop 应用走同一条路(插件设置页),由插件管理器热挂载:新增的 bundle 立刻组合进来、宿主不重启。CLI 起的 profile 只在启动时组合 bundle 列表,如果你的 profile 不是 patchReload: live,装完重启一次。
bundle 必须真的列进 profile 的 dsh.profile.bundles —— dsh plugin add 会替你加;是否进了组合用 dsh --profile <profile> --dump-config 看有没有 # == dsh-testhud 层标记。之后该 profile 里的每个 session 都有 test_hud 工具,以及一小段"跑长测试要报进展"的系统提示约定。
@deepseek-ai/dsh-tools 声明为 peerDependency:dsh 把它解析到运行中安装的那一份(所以用的一定是宿主自己的 defineTool),并且当运行版本落在声明的范围之外时拒绝加载整个 bundle,而不是半挂上。node_modules 里那份只作 devDependency,供脱离宿主做独立测试用。
卸载:
dsh plugin --profile web remove dsh-testhud
agent 工具
action · 作用 · 参数
start · 开一次测试并拉起浮层 · title(必填)、target、anchor
step · 上报一步 · name(必填)、expect、actual、state = ok/fail/run/info
note · 底部补一行说明 · text
done · 写结论、结束这次测试 · failed(布尔)、conclusion
stop · 立刻关掉浮层 · —
浮层宽高都随内容自适应(见「尺寸与溢出」),只有步骤区滚动、头部固定;done 之后再停 12 秒才消失,够看清结论。
约定
只要在做自动化验证就必须有浮层:不许"只跑命令不起浮层"。连续几轮验证时,上一轮 done 之后、开始下一个动作之前先重新 start —— 等待的人要能随时看到"现在在测什么、能不能接手"。
收尾也一样重要:结束时 done,然后关掉为测试启动的那个 app(优雅退出,别强杀),并把浏览器 / DSH 界面切回前台 —— 不要被测 app 留在屏幕上挡着(用户明确要求)。
命令行
同一套逻辑、同一个进度文件,给 shell 脚本和 CI 用:
testhud start "Movable 工具栏布局自检" "Movable 主窗口" [anchor] testhud step "工具栏居中" "分组中心 == 窗口中心" "实测 962pt,居中" ok testhud note "剩下交给用户判断" testhud done ok "3 项检查全过" testhud stop testhud status
配置
在 profile 的 cordis.patch.yml 里按行 id 覆盖:
- id: testhud
config:
announceToAgent: true # 是否给每个 session 注入"长测试要报进展"的约定
defaultAnchor: auto # auto | center | left | right
topInset: 155 # 上边两个角从屏幕顶部往下让出多少点
停哪儿按这个顺序决定:
1. 不挡被测对象。面板拿三个位置去和前台应用的窗口比重叠面积,取最小的那个 —— 这一条压过下面所有规则。
2. 屏幕居中(在居中不挡任何东西的前提下)。
3. 否则退到侧边,先左后右。
纵向一律靠上:顶边距屏幕顶 topInset 点(默认 155),底边停在状态栏 / Dock 之上(Look.bottomInset,44 点)。横向不贴边(Look.sideInset,28 点),所以不会压住侧边栏或滚动条。锚点是 auto | center | left | right;旧配置里的 top-left / top-right 仍然认,映射到 left / right。
topInset 是因为浮层通常压在浏览器上:屏幕最上面那 ~150 点是别人的标签栏、地址栏、收藏栏,浮层贴在最上面会把这些盖住。默认 155 让浮层从浏览器 chrome 之下开始,与页面自己的头部齐平;填 0 则退化成"让开菜单栏后再留 14 点屏幕边距"。这个值从屏幕顶算起(定位用全屏 frame,不是 visibleFrame),传多少就落在多少。命令行可用环境变量 DSH_TESTHUD_TOP_INSET 覆盖。
尺寸与溢出
宽高都随内容自适应(HUD.layout()):
- 宽度按"不折行时最宽的一行" + 左右内边距(
Look.inset,16 点)算;下限 460 点(Look.minWidth),上限 = 屏幕可用宽度 − 左右各 28 点(Look.sideInset)。 - 高度按内容算,装得下就正好是内容高度;上限 = 从面板顶边(距屏幕顶
topInset,默认 155)到可见区底边、再让开 44 点(Look.bottomInset)的那一段,不再限制在屏高的 62%。黄点折叠到只剩色带,绿点直接撑到高度上限。
面板高度有上限,内容多了上面的部分会被推到可视区外。此时面板内容区会出现一个透明拖动命中层(ScrollHandle 窗口 + ScrollGrip 视图):按住往下拉能把上面的内容拉回来(自然滚动方向),松手后 0.28 秒 ease-out 平滑回到底部 —— 即"最后一行自动回到窗口最下方"。该命中层只在内容真被截掉时才启用(其余时候高度为 0),所以内容装得下时面板照旧完全鼠标穿透。鼠标移上去是"张手"、按下变"抓紧",松开还原。观测点:~/.dsh/dsh-testhud/scroll.json(含 scrollY / maxY / atBottom / panning / bouncing / cursor —— 最后一项是因为截图里没有鼠标指针,光标只能这样验)。
当前配色:只用 Apple 色卡的 22 色
面板里出现的每一个颜色都必须能在 Apple 色卡里找到 —— 色卡(Apple色板/1Apple配色色卡.key,本机位于 $HOME/PARA/8.Code/AIDoc/设计规范/)第 1 页那 22 色(九中性 + 三蓝 + 十功能色)。代码里唯一的取色来源是 hud/testhud.swift 的 ApplePalette,全部用 sRGB 构造(calibrated* 会被色彩空间转换改掉数值)。
变色龙 V2(2026-10-09):面板底纯透明 —— 不铺任何底色,也不显示背景快照,面板上只剩下文字与一圈细边框。这样被覆盖的内容原样可见,而浮层文字靠三件事在任何背景上都能被认出来(描边 + 阴影 + 颜色):
部位 · 浅色系(面板背后偏亮) · 深色系(面板背后偏暗)
主体背景 · 半透明(alphaFloor 0.50) · 同左
边框(1.0 点,Look.panelBorderWidth) · 纯黑 #000000 · 纯白 #FFFFFF
正文(标题 / 步骤名 / 结论 / 期待 / 实际,同一颜色) · 自适应选色,见下 · 同左
文字阴影 · 默认关闭(textShadowBlur = 0) · 同左
~~文字描边~~ · 默认关闭(strokeWidthPercent = 0) · 同左
步骤行首符号 · ✓ #34C759 / ✗ #FF3B30 / ⏳ #FF9500 / • 同正文色 · 同左
正文色是自适应选出来的(Theme.pickTextColor,2026-10-09)—— 按背景性质分四档,档内只在两个候选之间比一次对比度:
背景 · 选谁
本身有颜色(站点蓝、暖橙、深绿…) · 黑 / 白里对比更高的一极(有彩候选容易与背景同色系糊掉)
中性但亮度居中(纯中灰) · 黑 / 白(蓝字在中灰上只有 2.5:1)
中性偏亮 · 苹果蓝 #0071E3 与靛 #5856D6 中对比更高的
中性偏暗 · 亮蓝 #2997FF 与薄荷 #00C7BE 中对比更高的
实测八个背景(白底黑字 / 黑底白字 / 纯白 / 纯黑 / 中灰 / 站点蓝 / 暖橙 / 深绿)分别选出 靛 / 薄荷 / 靛 / 薄荷 / 黑 / 黑 / 黑 / 白,逐个用视觉确认可读。层次不再靠灰度,全部交给字重(同一字号、四档字重)。
阴影取"背景那一极"(浅色系白、深色系黑):它与背景融为一体,作用是把压在文字底下的背景笔画"推开"。描边默认关闭(strokeWidthPercent = 0)—— 三种方案实拍对比过:描边会让 18pt 的中文字形发胖、边缘发脏,而纯阴影既干净、可辨识度也不差;能力留着(设成负值即启用)。
色带与浅深档无关,按状态取色板红 / 绿:进行中 = 红 #FF3B30 + 白字、已结束 = 绿 #34C759 + 黑字;色带不透明度 0.75。三个圆点也取色板值(红 #FF3B30 / 黄 #FFCC00 / 绿 #34C759),配 1.0 点纯白描边。色带是面板上唯一仍不透明的一块。
行首符号用 ✓ / ✗ / ⏳ / • 而不是 ✅ / ❌:彩色 emoji 无视 foregroundColor,会画出自己那套色板外的颜色,只能换字符。同理,调用方(agent)写进步骤名 / 期待 / 实际的文字若自带 emoji,那些字形不受 ApplePalette 管。
**仍然"算出来"的只
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:3,最后更新 2026-10-21