按真实设备比例批量截图并比对界面布局
36.3/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-ui-labnpm 源 · dsh-ui-lab 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · vision
装得上吗
静态安装检查通过
未做真实安装
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 3 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-ui-lab @ 0.2.1
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/3 03:19:10
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 39 处,展示前 37 条)
- 读取本地文件src/cli.mjs:19
const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')) - 读取本地文件src/doctor.mjs:44
} else if (existsSync(executable)) { - 发起网络请求src/doctor.mjs:68
const response = await fetch(url, { redirect: 'manual', signal: controller.signal }) - 写入本地文件src/doctor.mjs:88
mkdirSync(dir, { recursive: true }) - 写入本地文件src/doctor.mjs:90
writeFileSync(probe, 'ok') - 删除本地文件src/doctor.mjs:91
rmSync(probe, { force: true }) - 读取本地文件src/profiles.mjs:113
text = readFileSync(filePath, 'utf8') - 读取本地文件src/playwright.mjs:37
if (existsSync(join(absolute, 'package.json'))) { - 读取本地文件src/playwright.mjs:54
if (!existsSync(anchor)) continue - 读取本地文件src/baseline.mjs:31
return createHash('sha256').update(readFileSync(path)).digest('hex') - 读取本地文件src/baseline.mjs:59
const current = readFileSync(paths.current).toString('base64') - 读取本地文件src/baseline.mjs:60
const reference = readFileSync(paths.reference).toString('base64') - 写入本地文件src/baseline.mjs:115
mkdirSync(baselineDir, { recursive: true }) - 读取本地文件src/baseline.mjs:118
if (entry.current === null || !existsSync(entry.current)) continue - 写入本地文件src/baseline.mjs:120
copyFileSync(entry.current, target) - 写入本地文件src/baseline.mjs:129
writeFileSync(join(baselineDir, 'baseline.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8') - 读取本地文件src/baseline.mjs:139
if (!existsSync(path)) return null - 读取本地文件src/baseline.mjs:141
const parsed = JSON.parse(readFileSync(path, 'utf8')) - 读取本地文件src/capture.mjs:74
const reuseState = surface === 'mobile' && fresh !== true && existsSync(statePath) - 写入本地文件src/capture.mjs:156
mkdirSync(outDir, { recursive: true }) - 读取本地文件src/capture.mjs:164
if (existsSync(paths.reference) === false) { - 读取本地文件src/inject.mjs:17
if (!existsSync(target)) throw new UsageError(`--inject path does not exist: ${input}`) - 读取本地文件src/inject.mjs:19
const stat = statSync(target) - 读取本地文件src/inject.mjs:22
if (!existsSync(manifestPath)) { - 读取本地文件src/inject.mjs:27
manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 读取本地文件src/inject.mjs:31
const cssPath = existsSync(join(target, 'mobile.css')) ? join(target, 'mobile.css') : null - 读取本地文件src/inject.mjs:32
const jsPath = existsSync(join(target, 'mobile.js')) ? join(target, 'mobile.js') : null - 发起网络请求src/pairing.mjs:57
response = await fetch(statusUrl, { signal: AbortSignal.timeout(timeoutMs) }) - 发起网络请求src/pairing.mjs:92
response = await fetch(new URL('/api/mobile-access/pairing/open', adminUrl), { - 写入本地文件src/pairing.mjs:130
mkdirSync(dirname(statePath), { recursive: true }) - 写入本地文件src/pairing.mjs:133
chmodSync(statePath, 0o600) - 写入本地文件src/report.mjs:187
mkdirSync(outDir, { recursive: true }) - 写入本地文件src/report.mjs:191
writeFileSync(htmlPath, renderReportHtml(summary), 'utf8') - 写入本地文件src/report.mjs:192
writeFileSync(jsonPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8') - 发起网络请求src/surfaces/mobile.mjs:50
const response = await fetch('/mobile-access/client-modules', { - 读取本地文件src/token.mjs:28
stats = statSync(filePath) - 读取本地文件src/token.mjs:49
token = readFileSync(filePath, 'utf8').trim()
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
为 DeepSeek Harness(DSH)打造的 UI 验证工具:在无头 Chromium 里按真实设备比例(6 种手机、4 种平板)加载 DSH 界面,提供截图、布局指标、矩阵报告和像素基线。
README
由 DeepSeek 最新模型翻译生成dsh-ui-lab
为 DSH 而生的 UI 验证工具。 把同一份 DeepSeek Harness 界面铺到一整面真实设备比例上,一眼看出它从哪一档开始坏——在 Linux / Windows 上跑,不用拿起手机。
一个 DSH 界面得同时扛住 360px 的安卓机、430px 的 iPhone、768px 的平板,以及中间所有档位。逐个手验又慢又不可复现。dsh-ui-lab 为每一种比例配好真实的 CSS 视口、设备像素比、触摸标志与 UA,加载真正的 DSH surface,输出截图与布局度量;一次跑一整组比例,还能生成矩阵报告——于是 UI 改动可以对比,而不是靠眼估。
它是度量仪器,不是生产测试套件:把它指向跑在本机或你自己局域网里的 DSH 实例即可。
早期开发中。CLI 与 JSON 结构已可用,1.0 前仍可能有小幅调整。
比例
这是这个工具的正题。
展开完整文档(README 剩余 1 节)
dsh-ui-lab
为 DSH 而生的 UI 验证工具。 把同一份 DeepSeek Harness 界面铺到一整面真实设备比例上,一眼看出它从哪一档开始坏——在 Linux / Windows 上跑,不用拿起手机。
一个 DSH 界面得同时扛住 360px 的安卓机、430px 的 iPhone、768px 的平板,以及中间所有档位。逐个手验又慢又不可复现。dsh-ui-lab 为每一种比例配好真实的 CSS 视口、设备像素比、触摸标志与 UA,加载真正的 DSH surface,输出截图与布局度量;一次跑一整组比例,还能生成矩阵报告——于是 UI 改动可以对比,而不是靠眼估。
它是度量仪器,不是生产测试套件:把它指向跑在本机或你自己局域网里的 DSH 实例即可。
早期开发中。CLI 与 JSON 结构已可用,1.0 前仍可能有小幅调整。
比例
这是这个工具的正题。当前内置十种比例,每一种都有自己的视口、DPR、触摸标志与 UA:
| 类别 | 机型 |
| 手机(6) | 360×800 安卓 · 375×667 iPhone SE · 390×844 iPhone 13/14/15 · 393×852 iPhone 15 Pro · 412×915 Pixel 7 · 430×932 iPhone 15 Pro Max |
| 平板(4) | 768×1024 iPad mini · 800×1280 Galaxy Tab · 820×1180 iPad Air 11 · 834×1194 iPad Pro 11 |
| 折叠屏 | 尚未提供 —— 类别已预留,不编造尺寸 |
要加自己的比例,改 profiles/profiles.json 即可;schema、UA 来源与读取器 API 见 docs/profiles.md。折叠屏是下一个里程碑:展开后的内屏和笔记本/平板不是同一个问题(姿态、铰链、分屏),值得单独处理,而不是先填一个猜出来的视口。
测的是面板,不是欢迎页
真正要看的界面大多藏在侧栏某个入口后面 —— 那是插件自己的面板。--open 先点开那一行,再测量与截图:
dsh-ui-lab shot --surface mobile --profile phone-390x844 --open Todos
dsh-ui-lab matrix --surface mobile --class tablet --open Todos --out .shots/todos
开启后结果里还会带上面板盒(宽、left…right、返回栏是否渲染)与面板内的触控目标 (多少个、最小多大、几个小于 44px)。这些列会进 report.html 与 summary.json,于是「这个面板在哪一档比例上坏」可以由矩阵直接回答。
抽屉收起时会挡住行;工具会先像人一样把它展开,标签仍然匹配不上时列出真实存在的标签,而不是静默失败。
两种 surface
DSH 面向手机尺寸的屏幕有两种不同的东西,dsh-ui-lab 都能打开:
| | dsh-mobile | stock DSH web |
| 是什么 | dsh-mobile 网关提供的专用移动页 | 手机/平板宽度下的 stock DSH web UI |
| 认证 | 一次性设备配对,缓存在 .cache/ | DSH web 启动 token |
| 验证什么 | 真实移动端 boot graph、.dshm-shell、mobile-layout.js、扩展运行时 | 桌面壳被压窄后的表现:重排、溢出、点击热区、抽屉 |
| 重点暴露 | 专用页是否挂载 | 横向溢出——取决于 build 与入口(960px fixture:390 下 961/960;一个真实 stock 实例:390/390) |
| 命令 | --surface mobile | --surface web(默认) |
横向溢出取决于 build 与入口,不是 DSH 的通用事实。本地 960px fixture 在 390 视口下是 scrollWidth 961 / body.clientWidth 960;一个真实 stock DSH web 实例(同一 --profile web、另一端口)在同宽度实测 390/390、overflowX: false。工具报告实际值——按数字判断,不要预设。原理见 docs/surfaces.md。
环境要求
- Linux 或 Windows(macOS 预期可用,但未纳入 CI)。
- Node.js >= 20。
- Playwright 及其 Chromium(npx playwright install chromium;Debian/Ubuntu 裸机可加 --with-deps 一并安装 Chromium 需要的系统库)。
- 一个 DSH 实例:
- stock web:DSH web UI,默认 http://127.0.0.1:3080。DSH 用 per-process 启动 token 保护它,见认证。
- dsh-mobile:桌面端开启 移动访问 → 局域网;网关地址由 admin status API 发现。
Windows 说明
Windows 上不需要任何 Linux 专属依赖:Playwright 的 Windows Chromium 自带运行时,npx playwright install chromium 即可。
- Playwright 解析顺序与 Linux 相同:--playwright / DSH_UI_LAB_PLAYWRIGHT → 本包的 node_modules → $DSH_HOME\profiles\web → $DSH_HOME\profiles\desktop。DSH 桌面安装通常已自带,无需另装。
- DSH_UI_LAB_WEB_TOKEN_FILE:mode 600 检查只属于 POSIX,Windows 上会跳过(改用 ACL)。token 文件放用户目录下(如 %USERPROFILE%\.dsh\web-token),按密钥对待,不需要 chmod。
- 路径用 Windows 原生写法;PowerShell 中含空格的路径要加引号,例如 --out "$env:USERPROFILE\dsh-shots"。相对路径在所有平台上都相对当前目录解析。
- npm test 与 npm run lint 直接可用;没有安装 Chromium 时浏览器用例会自动跳过。
安装
已发布到 npm registry:dsh-ui-lab。
npm install -g dsh-ui-lab
npx playwright install chromium
全局安装不会自动装上可选的 Playwright peer。可以让 CLI 复用 $DSH_HOME/profiles/web 自带的 Playwright(DSH 机器上通常如此),或者把它装在 CLI 旁边:
npm install -g playwright
npx playwright install chromium
通常全局 CLI 能直接解析到同级包;若不能:
export DSH_UI_LAB_PLAYWRIGHT="$(npm root -g)/playwright"
两种全局配法见 Troubleshooting。
不全局安装:
npx dsh-ui-lab list
npx playwright install chromium # 只需一次
从源码目录:
npm ci
npx playwright install chromium
npm link # 把 dsh-ui-lab 放进 PATH
Playwright 是可选 peer 依赖:list、--help 与单测不需要它,截图命令需要。
快速开始
0. 先看这台机器能不能跑(Node / Playwright / Chromium / DSH 可达性) dsh-ui-lab doctor
1. 看机型 dsh-ui-lab list dsh-ui-lab list --class phone --json
2. 截一个机型(默认 surface 是 stock web) dsh-ui-lab shot --surface web --profile phone-390x844 --out .shots --json
3. 截 dsh-mobile 专用页(首次配对,之后复用设备) dsh-ui-lab shot --surface mobile --profile phone-390x844 --out .shots/mobile
4. 跑矩阵并打开报告 dsh-ui-lab matrix --surface web --class phone --out .shots/web-phone → .shots/web-phone/report.html + summary.json
--profile 支持模糊名:--profile iphone 会报告歧义并列出候选,--profile "iphone se" 解析到 phone-375x667。
stock web 示例假设 DSH web token 已就绪(见认证);要打开专用页用 --surface mobile,或者用下面的本地 fixture 完全脱离 DSH 试跑。
矩阵也可以按 id 或分类收窄:
dsh-ui-lab matrix --surface mobile \
--profiles phone-390x844,tablet-768x1024 \
--out .shots/mobile-matrix --fresh
只测量、不写截图:
dsh-ui-lab measure --surface web --profile phone-390x844 --json
常用参数:
| 参数 | 作用 |
| --fresh | 丢弃缓存的移动端配对,重新配对 |
| --wait <ms> | 加载后额外等待时间(默认 2000) |
| --inject <path> | 截图前注入 CSS 文件或 dsh-mobile pack 目录 |
| --open | 点开标签匹配的侧栏行(例如某个插件入口),截它自己的面板而不是欢迎页 |
| --admin-url <url> | DSH 不在默认 loopback 端口时的移动配对 admin 地址 |
| --playwright <module> | 指定 Playwright 安装位置 |
没有 DSH 也能试
仓库自带一个复刻桌面壳的本地 fixture,web surface 可以完全不连服务器、不需要凭据地端到端跑通:
dsh-ui-lab shot --surface web --profile phone-390x844 \
--url "file://$PWD/test/fixtures/desktop.html" --out .shots/fixture --json
手机与平板宽度都会报 overflowX: true。浏览器测试(npm test)会用 node:http 提供同一个 fixture。
基线:这一档比例到底动没动
矩阵回答"现在长什么样",基线回答"跟上次比动了没有"。存一次可信的运行,之后每次跑都对着它比——比较是逐像素计数(带容差),所以一条抗锯齿边缘不会把每次都判成红的:
1. 存基线(在你认可的那次运行上做一次)
dsh-ui-lab matrix --surface mobile --class phone --open Todos \
--out .shots/run --save-baseline .baselines/todos
2. 之后每次对比,让退出码决定红绿
dsh-ui-lab matrix --surface mobile --class phone --open Todos \
--out .shots/run --baseline .baselines/todos
退出 0 = 在阈值内一致;退出 1 = 有变化或缺参考图
| 参数 | 作用 |
| --baseline <dir> | 与 <dir>/<surface>__<profile>.png 逐个比较;有变化或缺参考图时退出 1 |
| --save-baseline <dir> | 把本次截图存为基线并写 baseline.json(单张 shot 也支持) |
| --diff-threshold <pct> | 判定为"变化"的像素占比,默认 1% |
默认阈值不是 0,是有意的:活的 DSH 页面本来就有动的东西——相对时间("1 h ago")、闪烁光标、加载指示器。390×844@3x 的图上那是几千像素(约 0.1–0.6%)。带时钟的页面把阈值调高,想抓微小位移就调低(--diff-threshold 0.1)。
放进 CI
- run: npx dsh-ui-lab matrix --surface mobile --class phone --open Todos --out .shots --baseline .baselines/todos
非零退出即失败。只有在看过差异之后才用 --save-baseline 重存基线 —— 这道闸门就是这个意思。
认证
新浏览器没有 DSH web cookie,http://127.0.0.1:3080/ 会返回 401 dsh web authentication required。dsh web 启动时会打印一个带一次性、per-process 启动 token 的根 URL。把 token 交给工具的方式(优先级从高到低):
1. 把带 token 的 URL 作为 --url;
2. 导出 DSH_UI_LAB_WEB_TOKEN;
3. 把 token 单独放进 mode 600 的文件(POSIX 语义;Windows 上跳过 mode 检查、以 ACL 为准),用 DSH_UI_LAB_WEB_TOKEN_FILE 指向它。
工具用 token 换到签名 cookie 后继续访问干净 URL。token、CSRF cookie 与 storage state 不会出现在输出、报告或截图文件名里。dsh-mobile surface 不用这个 token,它走设备配对。
截图
下面的示例由仓库 fixture(test/fixtures/desktop.html)生成,不包含任何操作者的会话数据。两张都是 --surface web;桌面壳比视口宽,因此主栏被裁切——这正是要暴露的溢出。
| phone-390x844 · 390×844 @3× | tablet-768x1024 · 768×1024 @2× |
真实 dsh-mobile 截图会包含会话标题与账号头像,因此只在本地生成到 .shots/(已 gitignore),不入库。重新生成的命令见 docs/assets/README.md。
输出与度量
每次截图:
- <out>/<surface>__<profile>.png —— 按机型视口与 DPR 的截图(measure 不写 PNG);
- stdout —— 人类可读摘要,或 --json 的机器可读结果。
JSON 结果包含:
profile { id, label, category }
surface "web" | "mobile"
url 已清洗 URL(查询参数中的凭据已移除) metrics viewport, layoutViewport, visualViewport, devicePixelRatio, title,
documentElement { clientWidth, scrollWidth, clientHeight, scrollHeight },
body { clientWidth, scrollWidth },
overflowX, shell, dedicatedMobileFrontend
screenshot 绝对路径或 null injection 注入的 CSS/pack 元数据或 null consoleErrors 已清洗的 console/page 错误 durationMs 截图墙钟耗时
metrics.viewport 是模拟的 CSS 视口(390×844),layoutViewport 是 window.innerWidth 报告的值;移动模拟下溢出的页面会把后者放大,所以设备宽度看 viewport / visualViewport。
矩阵还会写:
- <out>/report.html —— 自包含,内联 CSS,缩略图按本地文件名引用,无外链;
- <out>/summary.json —— 每个机型一行:id、label、viewport、DPR、scrollWidth、body.clientWidth、overflowX、shell、耗时、状态、截图文件名与已清洗错误。
退出码
| 码 | 含义 |
| 0 | 成功(矩阵:没有失败行) |
| 1 | 运行/截图失败(导航、配对、认证、浏览器、矩阵有失败行) |
| 2 | 用法错误(未知命令/参数、非法值、缺失或歧义 profile) |
矩阵中单个机型失败不会中断,会逐条报告、照常写报告;只要有失败行就以 1 退出。
已知限制
以下属于环境属性,不是可以报的 bug:
- 安全区为 0。 Chromium 里 env(safe-area-inset-*) 解析为 0px,刘海、圆角、Home Indicator 都验不了。
- 没有 WebView。 Android System WebView 与 iOS WKWebView 在 UA、CSS 支持、缓存和视口行为上与 Chromium 有差异;这里能跑通,不等于 App 内 WebView 也稳。
- 没有真机手感。 拇指可达范围、点击热区舒适度、惯性滚动、双指缩放、触感、软键盘交互都不在范围内;visualViewport 不会像真机键盘那样收缩。
- dsh-mobile 0.8.0 未测。 工具在已安装的 dsh-mobile 0.7.1 上验证;0.8.0 已发布但尚未实测。
- TLS 不在验证范围。 dsh-mobile 网关使用自签证书,harness 有意忽略证书错误。
- 折叠屏未做。 category: "foldable" 仅预留,0 条数据,不编造尺寸;见 docs/profiles.md。
- 单页面、单视口。 多端同步、旋转中途、多窗口形态均不模拟。
- stock DSH web 需要启动 token。 裸 :3080 按设计返回 401,需按上文提供 token。
文档
- Surfaces —— 网关发现、配对、token、溢出。
- Profiles —— 机型表、读取 API、如何新增。
- Troubleshooting —— 认证、配对、溢出、TLS、Playwright。
- Contributing —— 环境、测试、提交规范、凭据。
- Changelog。
发布
发布由 .github/workflows/publish.yml 驱动:推一个与 package.json 版本一致的 tag,工作流会跑 lint 与单测,然后带 npm provenance 发布。
npm version patch --no-git-tag-version # 或手改 package.json
git commit -am "chore: 0.1.1"
git tag v0.1.1 && git push origin main v0.1.1
需要仓库 secret NPM_TOKEN(对 dsh-ui-lab 有读写的 granular token)。workflow_dispatch 默认先跑 npm publish --dry-run,不碰 registry 也能验一遍流水线。
许可证
MIT © 2026 LUgABeEEE。见 LICENSE。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
LUgABeEEE/dsh-ui-lab
按真实设备比例批量截图并比对界面布局
相关插件 · 视觉能力
智能体工作台Devin-AXIS/iPolloWork
企业级、本地优先的智能体工作台,面向个人和智能体团队——一个工作区,容纳多个智能体引擎;一套统一体系,管理插件和技能;支…
★ 7,359已汉化DSH
72
开源设计工作台nexu-io/open-design
OpenDesign:首个协作式 AI 设计智能体工作区
★ 105,514已汉化DSH
71.8
多智能体编码工作区spacering-net/codeg
多智能体协作的 AI 编程工作区:汇总 Claude Code、Codex、OpenCode、Pi、Grok Build 等来源的会话。支持桌面应用、自托管服务器或 Docker。
★ 4,289已汉化DSH
71.6
GPT 图像提示词库freestylefly/awesome-gpt-image-2
Prompt as Code | GPT Image 2 / 2.5 提示词与案例库,530+ 个案例、20+…
★ 34,534已汉化DSH
71.5
架构图生成器tt-a1i/archify
把你想理解、规划或分享的事,变成可以互动的可视化作品。
★ 92,675已汉化DSH
71.2
AI 用量桌面小部件Javis603/token-monitor
EN | 简 | 繁 | KO | JA | PT
★ 3,522已汉化DSH
71
这是你的仓库?认领条目