首页
/ Motrix 桥接端到端测试实战:基于真实 `motrix-cli` 的 MDXP 配对与下载验收指南

Motrix 桥接端到端测试实战:基于真实 `motrix-cli` 的 MDXP 配对与下载验收指南

2026-09-07 09:12:37作者:咎岭娴Homer

e2e/bridge/ 是 Motrix 项目中针对 MDXP 桥接层(Bridge) 的端到端(E2E)验收测试目录。它用真实发布的 @motrix/cli npm 包驱动真实的 Motrix 运行时,完整覆盖产品承诺的两大核心能力——设备码配对(device-code pairing)下载调用(download invocation)——并同时在 Electron 桌面壳与 headless Node 服务器壳两种运行时下验证。读完本文,你将掌握这套 E2E 体系的架构动机、两条测试腿(Electron 腿 / Server 腿)的构建与运行方法、远程浏览器扩展(Chromium + Firefox)威胁门禁与发布级 soak 测试的完整流程,以及测试代码中体现出的协议级安全细节。


一、这套 E2E 要验收什么:两条腿、两半承诺

MDXP 桥接是整个体系的枢纽:外部客户端(CLI、浏览器扩展)通过它向 Motrix 发起配对与下载请求。E2E 目录把验收拆成两条独立运行的"腿"(leg),分别对应产品承诺的两半:

测试文件 运行器 被测运行时
Electron e2e/bridge/cli-pair-and-download.spec.ts Playwright 打包后的 Electron 应用(dist/main/index.cjs
Server e2e/bridge/server-leg.mjs 独立 node 无头 Node 运行时(dist/server/index.mjs

两条腿都驱动真实的 CLI 二进制真实的 aria2,下载源是本地、限速、结果确定的 HTTP fixture(不依赖公网),并断言完整闭环:

pair → approve → token → download/addcompleted → 磁盘字节落盘 → watch SSE 流

除此之外还包含对抗性探测(adversarial probes):错误 token 应导致进程以 exit 4 退出、一次性 token 投递语义、配对拒绝路径,以及在 Server 腿上额外验证的 Spec 9 自审批绕过(self-approval-bypass)必须被关闭

为什么要分成两种形态? Electron 应用和 Node 服务器需要 互斥的 better-sqlite3 原生 ABI,无法共享同一个 node_modules。Electron 腿运行在标准 Playwright 套件里;Server 腿则是独立的 .mjs 驱动,跑在另一个按 Node ABI 构建的 git worktree 上。

从源码角度可以把测试覆盖归纳为两类可复现断言:配对协议正确性userCode 人类可读码、token-free 的 DTO、一次性 token、deny/expire 语义)与下载任务正确性(任务推进到 completed、落盘字节数与 fixture 一致、SSE 事件流包含 $/task/progress 与终止/统计事件)。


二、前置条件

在运行任一腿之前,需要满足:

  • 主 checkout 中至少执行过一次 pnpm install
  • aria2:默认自动使用 extra/<platform>/<arch>/aria2c 内置二进制(macOS/arm64 上无需任何额外操作);若想改用系统 aria2c,可用环境变量 MOTRIX_ARIA2_BIN 覆盖。
  • @motrix/cli CLI:由 pnpm install 自动安装(它是 devDependency)。E2E 从 node_modules 解析其内置 bin 入口,不再有仓库内的 CLI 构建步骤

从规范代码可以看到 CLI 入口的解析方式:在 cli-pair-and-download.spec.ts 中通过 createRequire(import.meta.url).resolve('@motrix/cli/dist/bin/motrix.js') 定位已发布的包入口;server-leg.mjs 采用完全一致的做法,确保两条腿测的是同一个真实发布产物。CLI 的默认下载引擎二进制解析路径见 server-leg.mjsMOTRIX_ARIA2_BIN ?? path.join(MAIN, 'extra', process.platform, process.arch, 'aria2c'),这就是"内置二进制自动被发现"逻辑的实现位置。


三、Electron 腿(Playwright)

1. 恢复 Electron ABI

主 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 强制重建原生模块。

2. 构建并运行

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

其中 build:electron 完整链条(见 package.json)为:build:legalscripts/ensure-native-abi.mjs electron → 依次执行 main / preload / worker / renderer 四个 Vite 构建。注意 ensure-native-abi.mjs electron 这一步会在构建前再次确认 ABI 正确,因此rebuild:for-electronbuild:electron 的顺序很重要

3. 测试运行特性说明

  • 每个测试都会启动真实的 Electron 应用,并通过 window.motrix.invoke('bridge:resolvePair', …) 批准配对——这正是界面里 PendingApprovalsSection(待审批列表)"Approve" 按钮实际调用的 IPC,因此测的是真实主进程 DeviceCodeService.approve → PairingService.issueToken 路径,而非测试桩。
  • Electron 会**有头(headed)**地打开窗口。桌面 macOS 会话下没有问题;Linux CI 上应使用 xvfb-run
  • pnpm test:e2e(完整套件)通过 *.spec.ts glob 也会收录该 spec。

4. 从 spec 源码读出的验收细节

cli-pair-and-download.spec.ts 里有几个值得注意的实现细节,可帮助你理解被测协议:

  • 端点发现waitForEndpoint() 会轮询 userDataDir/bridge/endpoint.json(最长 40 秒),解析出 { port, pid, localToken } 三要素——这是应用在 server.start() 后写下的桥接端点描述文件。
  • 人类可读码格式USER_CODE_RE = /^[A-Z2-9]{4}-[A-Z2-9]{4}$/,即 4-4 两组、去除易混淆字符(无 0/O、1/I/L)的大写字母数字码。测试断言 CLI 打印到 stderr 的 userCode 与配对收件箱(inbox)中出现的条目一致。
  • 配对请求的 token-free DTO:收件箱条目通过 bridge:listPendingPairRequests 查询获得,测试显式断言 DTO 不携带 token、不携带 deviceId 字段——令牌按构造永不暴露到渲染进程。
  • 配对弧线:CLI 侧 motrix pair --name e2e-cli--endpoint 指向 bridge 端口)会阻塞在轮询;测试在页面内用 bridge:resolvePairkind: 'cli'decision: 'allow')批准,CLI 收到 token 后以 exit 0 退出;随后 bridge:listPaired 应列出 e2e-cli,且收件箱不再包含该请求。
  • 下载验证:使用 startHttpFixture({ size: 2MB, throttleBytesPerSecond: 256KB/s }) 限速 fixture——测试注释说明"本地 1MB 传输完成太快,观测不到下载中状态",因此用限速 2MB 保证可观测地经过 Downloading 并产生多帧 $/task/progress。CLI 依次执行 --json add <url> --save-dir … --filename …、轮询 --json list 直至 completed、断言磁盘文件大小等于 fixture 大小、最后向 --json watch 进程发送 SIGINT,解析其 NDJSON 输出并断言事件流包含 $/task/progress 以及 $/task/completed$/stats
  • 对抗探测:错误 token 调 list → exit 4(stderr 含 AUTH 字样);对 /mdxp/pair/poll 重复轮询同一 requestId → 返回 { status: 'expired' }(一次性 token 语义);decision: 'deny' → 轮询得到 denied/expired 且无 token

与协议层对应,src/shared/protocol/bridge.ts 是这些 DTO 的规范定义源头:区分 cli(device-code,Spec 7b)与 extension 两类配对客户端,配对的渲染侧 DTO、审批决策参数均在此声明,并明确"token 永不暴露到 paired-client DTO"。想要更系统的协议背景可继续阅读 docs/bridge-pairing-protocol.md


四、Server 腿(独立驱动)

Server 腿不是 Playwright spec(.mjs 扩展名会被套件的 *.spec.ts glob 自动跳过),而是直接以 node 运行的无头驱动脚本。

1. 一次性准备:在兄弟 worktree 中构建 Node-ABI 服务器

目的:让主 checkout 保持 Electron ABI,而 worktree 单独承载 Node 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:让安装过程在 Node 环境下执行 better-sqlite3 自身的构建脚本(→ 生成 Node ABI),同时满足 pnpm 11 的依赖检查(deps-check),避免后续 build:server 静默重装并回退 ABI
  • MOTRIX_SKIP_ELECTRON_REBUILD=1:跳过 postinstall 里的 Electron 重建步骤。

2. 运行(可重复执行)

node e2e/bridge/server-leg.mjs

退出码语义:

  • 0 = 全部检查通过
  • 1 = 存在失败检查
  • 2 = 服务器构建缺失(需要重建 worktree)

3. 驱动代码里的验收步骤

server-leg.mjs 以编号区块组织检查,顺序即验收顺序:

  1. 双监听拓扑(dual-listener):以 PORT(web 控制面)与 MOTRIX_MDXP_HOST/MOTRIX_MDXP_PORT(MDXP 桥接)两个独立端口启动 dist/server/index.mjs,等待 GET /healthz 就绪、断言 MDXP 桥写下 endpoint.jsonportlocalToken)、并验证 MDXP 桥能应答 GET /nonce。服务器通过 MOTRIX_OPERATOR_TOKEN 注入操作者 token。
  2. Spec 9 默认拒绝(deny-by-default):匿名访问 /rpc/query/bridge:listPendingPairRequests/api/tasks/pause-all 都应得到 401。
  3. 操作者认证POST /rpc/auth/login(携带正确 token)→ 200 + Set-Cookie: mtx_op=;随后用 Authorization: Bearer <operator token> 调用 /rpc/query 应返回 200 与数组。
  4. 引擎就绪(宽容模式):通过 query:getEngineStatus 轮询至 state === 'ready';若超时不直接判死(下载轮询环节会暴露真正的引擎故障)。
  5. 调用(invocation):用 CLI add 真实下载本地限速 fixture,轮询至 completed,断言落盘字节数与 fixture 完全一致;对 watch 进程发 SIGINT,断言其以 0 退出且流式输出中包含 $/task/progress 与终止/统计事件。
  6. 设备码配对(操作者门禁审批):CLI pair --name srv-cli 发起请求 → 操作者收件箱查询到该请求(断言 userCode 格式且 DTO 无 token/deviceId)→ 先以匿名调用 bridge:resolvePair 断言 401(自审批绕过必须关闭)→ 再以操作者 Bearer 调用获批 → CLI exit 0,bridge:listPaired 中出现 srv-cli
  7. 对抗性:错误桥接 token 调 list → exit 4。

脚本还通过 mkdtemp 为每次运行创建独立数据目录与 HOME(cliEnv() 注入 HOME/XDG_CONFIG_HOME),确保 CLI 的凭据落盘不影响真实用户目录。


五、远程浏览器扩展腿(Chromium + Firefox)

这一道门禁在本地 HTTPS/WSS 反向代理之后启动真实的 Server MBP1 运行时,并用生产构建的扩展分别在两种浏览器中驱动。覆盖场景包括:全新配对、按权威域(authority-scoped)的同意授权、敏感 Header/Cookie 剥离、浏览器重启重连、Server 重启重连、持久化吊销(durable revoke)、分别经 /bridge 反代前缀与代理根路径重新配对。另有独立 Chromium 用例:单个持久化配置文件中同时配对两台独立 Server,证明同意授权不会在两者间泄漏,且下载提交始终遵循选中的权威域。Firefox 断言还额外要求无票据远程身份保持 unverified

1. 构建扩展并运行

先在扩展仓库构建两种变体,再从 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

pnpm test:e2e:remote-extension 的完整语义(见 package.json)是先跑 pnpm check:remote-extension-threats,再以专用配置文件 e2e/bridge/remote-extension.playwright.config.ts 运行 Playwright——该配置 workers: 1fullyParallel: falseretries: 0,只匹配 remote-extension(?:-firefox)?-wss.spec.ts 两个 spec。

2. 威胁证据门禁(T01–T29)

Playwright 启动前,命令会先校验 e2e/bridge/remote-extension-threat-evidence.jsonT01–T29 每一个威胁都必须至少保留一个映射到的测试文件与测试名,跨两个仓库(Motrix 与 extension)共同构成证据链。扩展仓库位置默认从 MOTRIX_EXTENSION_BUILD 推断;仅当构建位于其仓库目录之外时才需显式设置 MOTRIX_EXTENSION_REPO

以下任一情况都会使门禁失败:被删除/改名的测试、缺失的威胁条目、不安全的路径、或证据文件是符号链接。该 JSON 是跨仓库契约的可审计清单,例如 T02 的"拒绝结构上伪造的 authority"同时映射到 extension 仓库的 bridge-route.test.ts 与 Motrix 仓库 src/server/bridge/remote-extension-wss.integration.test.ts 中的过期证书拒绝用例。

3. 可选的可执行文件覆盖与浏览器注意事项

  • 可选覆盖项:MOTRIX_CHROMIUM_EXECUTABLEMOTRIX_FIREFOX_EXECUTABLE
  • Firefox 运行器使用 WebDriver BiDi 标准的临时扩展安装命令。
  • 浏览器证书绕过仅局限于本地测试 profile;另设独立的 WSS 集成套件在不做任何绕过的前提下证明可信 CA 成功以及未知 CA、过期、错误主机三种拒绝场景。
  • Chromium 请使用 Playwright 匹配的 Chrome-for-Testing / Chromium 构建。部分 stable Google Chrome 版本会忽略自动化加载解包扩展的 flag,导致等待扩展 service worker 时超时;即便显式指定可执行文件,也必须指向支持该测试模式的构建。

e2e/bridge/remote-extension-wss.spec.ts 源码可以读到浏览器如何被驱动:chromium.launchPersistentContext 携带 --disable-extensions-except / --load-extension 指向生产构建目录,通过 --host-resolver-rules=MAP <public-host> 127.0.0.1 把测试域名解析到本机反代,并以 --ignore-certificate-errors-spki-list=<SPKI pin> 仅对测试证书放行(SPKI 由 PEM 证书实时计算);随后等待扩展 service worker、打开 options.html 的 Integration 页签,通过"Pair"对话框输入配对码完成真实用户级配对交互。

4. 锁定兼容仓库版本(pinning)

浏览器扩展 harness 是一份跨仓库契约。不要在当前工作树改动尚未提交时记录 HEAD。正确流程是:

  1. 先在两个仓库各自创建一个实现提交;
  2. e2e/bridge/remote-extension-compatibility.example.json 复制为 remote-extension-compatibility.json,并把两处占位符替换为完整的 40 位小写 commit SHA(示例中分别对应 motrix-extensionmotrix-app 两个仓库的实现提交);
  3. 从 Motrix 侧校验锁定的结果:
pnpm check:remote-extension-compatibility \
  --manifest e2e/bridge/remote-extension-compatibility.json \
  --motrix-repo . \
  --extension-repo /absolute/path/to/motrix-extension

校验器会拒绝:占位符、过短或大写 SHA、协议漂移、浏览器用例少于 5 个、来自错误仓库的 commit,以及不是对应 checkout HEAD 祖先的任何 pin。校验通过后把 manifest 放进后续的 Motrix commit 中——这避免了自引用式的 Motrix SHA,使"实现 + 测试"的精确配对可审查。清单内还记录了 protocol: "MDXP-over-MBP1"e2e.browserCases: 5 与对应命令,供发布追溯。

5. Beta soak(稳定性浸泡)

soak 运行器会重复同一套受威胁门禁约束的五用例套件,任一次重复失败即整体失败。默认 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 门禁

6. Release soak(发布门禁,证据产出包装器)

在兼容 SHA manifest 提交后,使用产出证据的包装器:

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 等于其 pin、Motrix 在其实现 pin 之后除兼容 manifest 提交外无其他改动。完成这些源预检后,它会在锁定 checkout 上重新构建两种扩展变体、拒绝位于预期仓库目录之外或符号链接的构建路径,并对全新产物做哈希。随后记录明确的浏览器版本与 OS 细节,写出 evidence.json 与完整 Playwright playwright-report.json

  • 报告必须解析出恰好 100 个通过的浏览器用例,且无顶层或用例级错误;
  • 浏览器运行失败 → 归档为 failed;
  • 退出码为 0 但 JSON 报告缺失或非法 → 归档为 incomplete,仍然判门禁失败
  • 证据目录不得预先存在,防止后续运行覆盖更早的记录。

六、Server 腿维护

环境变量覆盖(均可选)

变量 默认值 含义
MOTRIX_SERVER_DIR ../motrix-turbo-srv node-ABI 构建目录
MOTRIX_E2E_WEB_PORT 8090 web / 操作者控制面端口
MOTRIX_E2E_MDXP_PORT 16801 MDXP 桥接端口
MOTRIX_ARIA2_BIN 内置二进制 aria2c 可执行文件

(上述默认值均可在 server-leg.mjs 顶部常量中直接看到,环境变量读不到时才回落到内置二进制路径。)

拉取新代码后刷新 worktree

cd ../motrix-turbo-srv
git fetch && git checkout --detach origin/main      # 或被测分支
pnpm build:server                                   # 依赖有变化时先重跑上面的 install
cd -

清理

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

七、Gotchas(易踩的坑)

  • CLI 自动发现是 darwin 硬编码的:指向 ~/Library/Application Support/Motrix/bridge/endpoint.json——它只能找到 Electron 桥接,永远找不到服务器。因此 Server 腿总是显式传 --endpoint http://127.0.0.1:<mdxp-port> --token <localToken>。(这与测试 spec 中通过 waitForEndpoint() 轮询 userDataDir/bridge/endpoint.json 获得 port + localToken 的逻辑互相印证:桌面端把端点元数据落盘、CLI 从固定位置读取。)
  • 配对批准是刻意保留的人工步骤(没有无头自动批准)。测试驱动的是真实的批准面:Electron 上通过 bridge:resolvePair IPC,Server 上通过操作者门禁的 POST /rpc/command/bridge:resolvePair
  • remote-extension-wss.spec.tsremote-extension-firefox-wss.spec.ts 才是当前活跃的浏览器扩展 WSS 生命周期门禁;较早的 pair-and-submitreceiver-directrevoke 等文件仍是狭窄的占位场景,不作为覆盖证明使用。同目录的 e2e/bridge/remote-extension-harness.ts 是共享的运行时/代理启动 harness,两个 WSS spec 都从它复用 startRuntimestartTlsProxypendingPairingCode 等基础设施。

八、把测试跑起来之前,先读懂全局

这套 E2E 的价值在于"每一层都是真实的":

  • 客户端真实——被测 CLI 是发布到 npm 的 @motrix/cli,测试通过 createRequirenode_modules 解析其打包入口(见 cli-pair-and-download.spec.ts 顶部注释),而非仓库内联的桩;
  • 下载真实——本地限速 HTTP fixture 保证 aria2 实际完成一次可观测的限速下载,磁盘字节大小被精确断言;
  • 批准面真实——Electron 上走的是 PendingApprovalsSection "Approve" 按钮同款 IPC bridge:resolvePair,Server 上走的是操作者 Bearer 门禁的 RPC,渲染 DTO 与传输层 DTO 均 token-free(src/shared/protocol/bridge.ts 是这些结构的权威声明处);
  • 运行时真实且双 ABI 隔离——同一 checkout 无法同时承载 Electron 与 Node 两种 better-sqlite3 ABI,所以 Electron 腿吃主 checkout、Server 腿吃兄弟 worktree,二者由 package.json 中的 rebuild:for-electronbuild:electronbuild:servertest:e2e* 脚本矩阵串起。

运行入口速查:

目标 命令
Electron 腿(配对 + 下载全流程) pnpm build:electron && pnpm exec playwright test e2e/bridge/cli-pair-and-download.spec.ts
Server 腿(无头 Node,双监听 + 操作者门禁) 先建 ../motrix-turbo-srv worktree 并 pnpm build:server,再 node e2e/bridge/server-leg.mjs
浏览器扩展门禁 扩展仓库先 build:chromium/build:firefox,再设 MOTRIX_EXTENSION_BUILD/MOTRIX_FIREFOX_EXTENSION_BUILDpnpm test:e2e:remote-extension
Beta soak / Release soak pnpm test:e2e:remote-extension:soak / pnpm test:e2e:remote-extension:release-soak(发布模式需先提交并校验兼容 SHA manifest)
兼容 pin 校验 pnpm check:remote-extension-compatibility --manifest … --motrix-repo . --extension-repo …

此外,e2e/bridge/README.zh-CN.md 是本篇指南的中文对照版本;想深入了解 MDXP 配对协议的完整规范可继续阅读 docs/bridge-pairing-protocol.md(含协议向量示例)。把上面任一腿跑绿,就意味着 Motrix 的"设备码配对 + 下载调用"承诺在对应运行时上经过了从 CLI 输入到磁盘字节、再到 SSE 事件流的全链路真实验证。

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