首页
/ Motrix MDXP Bridge 端到端验收测试指南:`motrix` CLI 与 Electron / Server 双运行时的配对与下载全链路验证

Motrix MDXP Bridge 端到端验收测试指南:`motrix` CLI 与 Electron / Server 双运行时的配对与下载全链路验证

2026-09-07 20:21:49作者:滕妙奇

本指南聚焦 Motrix 仓库 e2e/bridge 目录下的 MDXP bridge 端到端(E2E)验收测试体系,它用真实发布版 @motrix/cli(npm 包,仓库中以 devDependency 形式引入)驱动真实 Electron app 与 headless Node Server,完整验收 product 承诺的两段核心能力:device-code pairing(配对)download invocation(下载调用)。读完本文,你将掌握三条测试链路(Electron / Server / 远程浏览器 Extension)的构建、运行、环境变量与退出码语义,以及仓库是如何用同一套 E2E 同时守卫安全对抗探针与双仓协议契约的。

关联文档为 e2e/bridge/README.zh-CN.md,对应的测试实体位于 e2e/bridge 目录内;文中源码级细节均以该目录下真实 spec / driver 及根目录 package.json 的 scripts 为据。

Bridge E2E 概览:两条本地运行链路

e2e/bridge 提供 MDXP bridge 的端到端验收测试,覆盖产品承诺的两段核心能力——device-code pairingdownload invocation,并同时验证两种 runtime shell:

链路 文件 Runner 被测 shell
Electron e2e/bridge/cli-pair-and-download.spec.ts Playwright 打包后的 Electron app(dist/main/index.cjs
Server e2e/bridge/server-leg.mjs standalone node headless Node shell(dist/server/index.mjs

两条链路都会驱动真实 CLI binary真实 aria2 下载(使用本地、限速、确定性的 HTTP fixture,不访问公网),并断言完整流程:

pair → approve → token → download/add → completed → 落盘字节 → watch SSE

测试还包含对抗性探针:错误 token → 退出码 4、token 仅交付一次、deny(拒绝)路径,以及 Server 链路上的 Spec 9 self-approval-bypass(匿名自批准绕过必须被关闭)。

为什么要拆成两种形式?

Electron app 与 Node server 需要相反的 better-sqlite3 native ABI,不能共用同一份 node_modules。因此:

  • Electron 链路放在标准 Playwright suite 中(.spec.ts,会被 *.spec.ts glob 捕获);
  • Server 链路是一个独立的 .mjs driver,运行在单独的 node-ABI build(一个 git worktree)上——server-leg.mjs.mjs 结尾正是为了让 Playwright suite 的 *.spec.ts glob 将其跳过,从文件命名上就隔离了 ABI 语境。

前置条件

  • 主 checkout 中至少运行过一次 pnpm install
  • aria2:默认会自动使用 extra/<platform>/<arch>/aria2c 中的 bundled binary(macOS/arm64 不需要额外配置)。如需使用系统 aria2c,可通过 MOTRIX_ARIA2_BIN 覆盖。
  • @motrix/cli CLI:由 pnpm install 自动安装(它是 devDependency)。E2E 从 node_modules 解析其打包后的 bin——不再有 in-tree 构建 CLI 的步骤。

从仓库看,这条「CLI 外置化」的策略在源码中有明确落点:package.jsondevDependencies 声明 "@motrix/cli": "^0.5.0";而在 cli-pair-and-download.spec.tsserver-leg.mjs 中,均通过 createRequire(import.meta.url).resolve('@motrix/cli/dist/bin/motrix.js')node_modules 解析出真实发布产物的入口文件来 spawn。也就是说,测试驱动的不是仓库内源码构建的「替身」,而是消费者视角下真正会被 pnpm i 装进来的发布版 CLI——这本身就是对 CLI 打包产物可用性的一层验收。

Electron 链路(Playwright)

主 checkout 必须携带 Electron 版本的 better-sqlite3 ABI(这是 pnpm install / pnpm start 后的默认状态)。如果你之前在这个 checkout 中运行过 pnpm start:server,请先恢复:

pnpm run rebuild:for-electron

该脚本在 package.json 中定义为 electron-rebuild --force --only better-sqlite3,作用是强制将 better-sqlite3 重新编译到 Electron 运行时所需的 ABI(与 rebuild:for-node = pnpm rebuild better-sqlite3 正好相反,两者分别对应两个 runtime shell)。

构建并运行:

pnpm build:electron                       # dist/{main,preload,renderer,worker}
pnpm exec playwright test e2e/bridge/cli-pair-and-download.spec.ts

说明:

  • 这个 spec 会在每个 test 中启动真实 Electron app,并通过 window.motrix.invoke('bridge:resolvePair', …) approve pairing;这正是 PendingApprovalsSection 中 "Approve" 按钮调用的 IPC(spec 头注释明确指出该通道与 renderer 的 pair-resolve.ts 集成、进而走真实主进程 DeviceCodeService.approve → PairingService.issueToken 路径字节级一致)。
  • 运行期间 Electron 会打开 headed window。在桌面 macOS session 中可以直接运行;Linux CI 中请使用 xvfb-run
  • pnpm test:e2e(完整 suite)也会通过 *.spec.ts glob 自动包含这个 spec。

spec 内部结构与对抗性探针(源码层展开)

cli-pair-and-download.spec.ts 共包含 5 个 test,分别验证:

  1. device-code pair:用 hermetic credentials 目录(HOME/XDG_CONFIG_HOME 指向 userDataDir 内 cli-home,避免写真实 home)spawn motrix --endpoint <bridge> pair --name e2e-cli;随后通过 bridge:listPendingPairRequests 轮询 inbox,断言请求出现在 token-free 的 DTO 中(userCode 匹配 /^[A-Z2-9]{4}-[A-Z2-9]{4}$/,且不携带 token/deviceId),CLI stderr 打印了相同 human code;再经 bridge:resolvePair{kind:'cli', requestId, decision:'allow'})批准,CLI 轮询拿到 token 后以退出码 0 收尾,bridge:listPaired 中新增该 client,inbox 中请求消失。
  2. invocation:先启动 --json watchadd 一个本地限速 fixture(2 MiB,256 KiB/s 节流,保证能观测到 Downloading 阶段并产生多条 $/task/progress SSE 帧),轮询 list 直到 completed,校验落盘字节数与 fixture size 完全一致,最后对 watch 进程发送 SIGINT 并断言其 stdout 的 NDJSON 帧中出现了 $/task/progress$/task/completed$/stats
  3. adversarial: wrong bearer token:携带伪造 token 调用 list,断言退出码为 4 且 stderr 匹配 /auth/——即「AUTH」类失败的错误码约定。
  4. adversarial: one-time token delivery:直接向 /mdxp/pair/request 发 JSON POST 造出请求,批准后首次 /mdxp/pair/poll 返回 approved + token,重放同一 poll 必须返回 { status: 'expired' }——token 只交付一次。
  5. adversarial: deny:批准路径改为 decision:'deny',poll 返回 denied/expired 且不含 token。

此外 spec 还内置了两个测试基建,值得复用理解:waitForEndpoint(userDataDir) 会轮询 app 启动后写入的 bridge/endpoint.json(含 portpidlocalToken);bridgeInvoke(page, channel, params) 把 channel+params 通过 page.evaluate 的参数序列化传进浏览器上下文,在页面内仅引用自己的 arg 调用 window.motrix.invoke——与 renderer transport 使用同一个调用面。

Server 链路(standalone driver)

一次性准备:在相邻 worktree 中构建 node-ABI server

这样可以让主 checkout 保持 Electron ABI,而 worktree 持有 Node ABI——这是应对「Electron 与 Node 需要相反 better-sqlite3 ABI」这一约束的标准做法。

git worktree add --detach ../motrix-turbo-srv HEAD
cd ../motrix-turbo-srv
MOTRIX_SKIP_ELECTRON_REBUILD=1 pnpm --config.dangerouslyAllowAllBuilds=true install
pnpm build:server                         # dist/server + dist/renderer-web
cd -

--config.dangerouslyAllowAllBuilds=true 会允许 install 运行 better-sqlite3 自己的 Node build script(→ Node ABI),同时满足 pnpm 11 的 deps-check,避免 build:server 静默重新 install 并把 ABI 切回去。MOTRIX_SKIP_ELECTRON_REBUILD=1 会跳过 postinstall 中的 Electron rebuild。根目录 package.jsonstart:server = MOTRIX_SKIP_ELECTRON_REBUILD=1 node dist/server/index.mjs 也是同一套心智:跑 server 前先跳过 Electron rebuild 以保住 Node ABI。

运行(可反复执行)

node e2e/bridge/server-leg.mjs

退出码:0 = 全部检查通过,1 = 某项检查失败,2 = server build 缺失(需要重新构建 worktree)。

driver 内部验收清单(源码层展开)

server-leg.mjs 是一个按编号分段的验收 driver,实测它 spawn 真实 dist/server/index.mjs(headless、带 MDXP bridge + Spec 9 operator-auth gate),随后用真实 motrix CLI 打它:

  1. dual-listener topology:web control plane 的 /healthz 返回 {ok:true};bridge 在 MOTRIX_MDXP_PORT 端口写出 endpoint.json(含 localToken),并应答 GET /nonce(200)。
  2. Spec 9 deny-by-default:匿名 POST /rpc/query/bridge:listPendingPairRequests 与匿名 POST /api/tasks/pause-all 都必须 401。
  3. Operator authenticationPOST /rpc/auth/login(body {token})返回 200 且 Set-Cookiemtx_op=;随后用 Authorization: Bearer op-<hex> 访问 /rpc/query 放行并返回数组。
  4. Engine readiness:容忍式地等待 query:getEngineStatus 达到 state === 'ready'
  5. INVOCATIONwatch --jsonadd 一个 2 MiB / 256 KiB/s 限速 fixture,轮询至 completed,校验磁盘字节数,SIGINT 结束 watch 并断言 SSE 事件流出现 $/task/progress 与终态事件。
  6. PAIRING(operator-gated approval):spawn motrix pair --name srv-cli,在 operator inbox 看到 token-free 的请求后,先探测匿名 bridge:resolvePair 必须 401(self-approval bypass 已关闭),再由 operator 用 Bearer 批准,CLI 退出 0,bridge:listPaired 收录该 client。
  7. Adversarial:错误 bridge token → CLI 退出码 4。

driver 内部自带一个 check(name, cond, detail) 计数器和失败时的 server log tail 兜底输出,方便 CI 排障。

远程浏览器 Extension 链路(Chromium + Firefox)

该门禁会把真实 Server MBP1 runtime 放在本地 HTTPS/WSS 反向代理之后,并在两个浏览器中驱动生产 Extension 构建。测试分别经过 /bridge 代理前缀和代理根路径,覆盖 fresh pair、按 authority 隔离的 consent、敏感 header/Cookie 剥离、浏览器重启重连、Server 重启重连、durable revoke 和 re-pair。独立 Chromium 场景还会在同一持久 profile 中保留两个 Server 的配对,证明 consent 不会跨 Server 泄漏,且提交始终跟随当前选中的 authority。Firefox 还必须断言无 NM ticket 的远程身份保持为 unverified

先在 Extension checkout 构建两个产物,再从 Motrix 运行:

pnpm --filter @motrix/extension build:chromium
pnpm --filter @motrix/extension build:firefox

MOTRIX_EXTENSION_BUILD=/absolute/path/to/motrix-extension/packages/ext/dist/chromium \
MOTRIX_FIREFOX_EXTENSION_BUILD=/absolute/path/to/motrix-extension/packages/ext/dist/firefox \
pnpm test:e2e:remote-extension

Playwright 启动前,该命令会先验证 e2e/bridge/remote-extension-threat-evidence.json:T01–T29 每项威胁都必须继续绑定至少一个双仓测试文件和测试标题。Extension checkout 默认从 MOTRIX_EXTENSION_BUILD 推导;只有构建产物不在 checkout 内时才需要显式设置 MOTRIX_EXTENSION_REPO。删除或重命名测试、缺少威胁 ID、不安全路径或符号链接证据都会使门禁失败。

可用 MOTRIX_CHROMIUM_EXECUTABLEMOTRIX_FIREFOX_EXECUTABLE 覆盖浏览器路径。Firefox runner 使用 WebDriver BiDi 标准临时扩展安装命令。证书绕过仅存在于本地测试 profile;独立 WSS integration suite 会在不绕过验证的情况下证明受信 CA 成功,以及 unknown CA、过期证书和 hostname mismatch 必须失败。Chromium 应使用与 Playwright 匹配的 Chrome-for-Testing/Chromium 构建。部分正式版 Google Chrome 会忽略自动加载 unpacked Extension 的参数,最终超时等待 service worker;即便显式设置可执行文件,也必须指向支持该测试模式的浏览器构建。

测试运行时与浏览器启动参数(源码层展开)

远程链路由 e2e/bridge/remote-extension.playwright.config.ts 约束:testDir: '.'testMatch 只匹配 remote-extension(?:-firefox)?-wss\.spec\.ts$workers: 1retries: 0,整体 timeout: 120_000。从 e2e/bridge/remote-extension-wss.spec.ts 可以看到 Chromium 以 launchPersistentContext 启动,携带 --disable-extensions-except / --load-extension、用 --host-resolver-rules=MAP motrix.test 127.0.0.1 把伪域名解析回本机,并通过 --ignore-certificate-errors-spki-list=<sha256 spki pin> 仅对测试自签证书放行;harness 侧(e2e/bridge/remote-extension-harness.ts)则通过 bootstrapBridgeForServer 把真实 bridge 暴露在 wss://motrix.test:<port>/bridge 与代理根路径之后。

固定双仓兼容版本

浏览器 harness 是跨仓协议契约。两端实现改动提交之前,不得把当前 working tree 的旧 HEAD 写成兼容证据。先分别创建 Extension 与 Motrix 实现提交,再把 e2e/bridge/remote-extension-compatibility.example.json 复制为 remote-extension-compatibility.json,并将两个占位符替换为对应实现提交的完整 40 位小写 SHA(manifest 的 extension.commitmotrix.commit 字段,schema 中还声明了 protocol: "MDXP-over-MBP1"browserCases: 5 与执行命令)。然后在 Motrix 仓库执行:

pnpm check:remote-extension-compatibility \
  --manifest e2e/bridge/remote-extension-compatibility.json \
  --motrix-repo . \
  --extension-repo /absolute/path/to/motrix-extension

验证器会拒绝占位符、短 SHA、大写 SHA、协议漂移、少于五个浏览器场景、来自错误仓库的提交,以及不是对应 checkout 当前 HEAD 祖先的提交。验证通过后,用一个更晚的 Motrix 提交记录该清单;这样既避免 Motrix SHA 自引用,也能让审阅者精确复现兼容组合。

Beta soak 与发布门禁

Soak runner 会重复执行同一套带威胁前置门禁的五场景测试;任意一次失败都会令命令失败。默认执行 20 轮、共 100 个浏览器场景,并限制最多 100 轮,避免环境配置错误产生无界任务:

MOTRIX_CHROMIUM_EXECUTABLE=/path/to/chrome-for-testing \
MOTRIX_EXTENSION_BUILD=/absolute/path/to/motrix-extension/packages/ext/dist/chromium \
MOTRIX_FIREFOX_EXTENSION_BUILD=/absolute/path/to/motrix-extension/packages/ext/dist/firefox \
MOTRIX_REMOTE_EXTENSION_SOAK_REPEATS=20 \
pnpm test:e2e:remote-extension:soak

必须归档完整输出,以及 OS、浏览器版本、两端实现 SHA、轮数、起止时间和所有代理/网络故障注入记录。一次普通 E2E 全绿只是回归证据,不能代替 beta soak 门禁。

固定兼容 SHA 清单提交后,发布门禁必须改用自动生成证据的包装命令:

MOTRIX_CHROMIUM_EXECUTABLE=/path/to/chrome-for-testing \
MOTRIX_FIREFOX_EXECUTABLE=/path/to/firefox \
MOTRIX_EXTENSION_REPO=/absolute/path/to/motrix-extension \
MOTRIX_EXTENSION_BUILD=/absolute/path/to/motrix-extension/packages/ext/dist/chromium \
MOTRIX_FIREFOX_EXTENSION_BUILD=/absolute/path/to/motrix-extension/packages/ext/dist/firefox \
MOTRIX_REMOTE_EXTENSION_SOAK_EVIDENCE_DIR=/absolute/archive/remote-extension-soak \
MOTRIX_REMOTE_EXTENSION_SOAK_FAULTS=none \
pnpm test:e2e:remote-extension:release-soak

发布模式强制恰好 20 轮、两个仓库均干净、Extension HEAD 与固定 SHA 完全一致,且 Motrix 实现 SHA 之后只能新增兼容清单。源码预检通过后,它会从固定 Extension checkout 重新构建两个浏览器版本,拒绝仓库外或符号链接构建目录,并计算新产物哈希。它会记录显式浏览器版本与 OS 信息,生成 evidence.json 和完整的 Playwright playwright-report.json;报告必须能解析出恰好 100 个通过场景,且不得包含顶层或场景错误。浏览器失败会归档为 failed;即使进程返回 0,只要 JSON 报告缺失或无效也会归档为 incomplete 并使门禁失败。证据目录必须是新目录,后续运行不能覆盖先前记录。

Server 链路维护

环境变量覆盖(全部可选)

变量 默认值 含义
MOTRIX_SERVER_DIR ../motrix-turbo-srv node-ABI build 目录
MOTRIX_E2E_WEB_PORT 8090 web / operator control-plane 端口
MOTRIX_E2E_MDXP_PORT 16801 MDXP bridge 端口
MOTRIX_ARIA2_BIN bundled aria2c binary

这些覆盖在 server-leg.mjs 中都有同名的默认值兜底实现:MOTRIX_ARIA2_BIN 缺省时解析到主 checkout 的 extra/<platform>/<arch>/aria2c;Server 进程还会把 PORTMOTRIX_MDXP_HOST/PORTMOTRIX_DATA_DIRMOTRIX_OPERATOR_TOKENMOTRIX_PUBLIC_URLMOTRIX_RENDERER_DIR 注入运行环境——其中 operator token 会在运行时用 crypto.randomBytes 现生成,保证每次运行的匿名 401 探针都是真实鉴权结果。

拉取新代码后刷新 worktree

cd ../motrix-turbo-srv
git fetch && git checkout --detach origin/main      # 或被测 branch
pnpm build:server                                   # 如果 deps 变化,重新运行上面的 install
cd -

清理

git worktree remove --force ../motrix-turbo-srv

注意事项(设计边界)

  • CLI auto-discovery 在 darwin 上是 hardcoded~/Library/Application Support/Motrix/bridge/endpoint.json。它只能找到 Electron bridge,永远不会找到 server。因此 Server 链路始终显式传入 --endpoint http://127.0.0.1:<mdxp-port> --token <localToken>
  • Pairing approval 是刻意保留的人工步骤(没有 headless auto-approve)。测试驱动的是真实 approval surface:Electron 上的 bridge:resolvePair IPC,以及 Server 上由 operator gate 保护的 POST /rpc/command/bridge:resolvePair。这正是上面 Electron 与 Server 两条链路分别用 window.motrix.invoke 与 operator Bearer 去批准配对的根本原因——批准面就是产品实际暴露给用户的那个面。
  • e2e/bridge/remote-extension-wss.spec.tsremote-extension-firefox-wss.spec.ts 是当前启用的 browser-extension WSS 生命周期门禁。旧的 pair-and-submitreceiver-directrevoke 仍是窄范围 placeholder,不能作为覆盖证据。

综合来看,这套 e2e/bridge 体系的价值在于:它把「CLI 能否与宿主配对并驱动真实下载」这一最容易在跨仓、跨 ABI、跨浏览器场景中回归的承诺,固化成三条相互补充的本地可复现门禁——Electron 链路守卫桌面 app 的真实 IPC 批准面,Server 链路守卫 operator 鉴权与 deny-by-default,远程浏览器链路则守卫双仓协议契约与威胁模型(T01–T29)的持续绑定,任何一侧的实现漂移都会在合并前被拦截。

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

项目优选

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