🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

多端界面验证台 LUgABeEEE/dsh-ui-lab

DeepSeek Harnessv0.2.1
LUgABeEEE3 天前有提交已提供中文文档扫描:含敏感能力

按真实设备比例批量截图并比对界面布局

36.3/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-ui-lab
npm 源 · 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
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 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。
预览分享卡片长什么样 ↓LUgABeEEE/dsh-ui-lab
分享 · DeepSeek Harness Hub
LUgABeEEE/dsh-ui-lab

按真实设备比例批量截图并比对界面布局

这是你的仓库?认领条目

回到顶部