jsdom 测试体系全解析:web-platform-tests 与 Mocha 双框架的编写与运行指南

原创2026-09-30 20:21:41747 阅读
文章标签:后端测试网页爬虫

jsdom 测试体系全解析:web-platform-tests 与 Mocha 双框架的编写与运行指南

jsdom 是一个在 Node.js 中实现 DOM、HTML 等 Web 标准的 JavaScript 库,而它的测试体系直接服务于“与浏览器行为保持一致”这一核心使命。test/README.md 是理解这套测试体系的总入口:jsdom 同时使用两套测试框架——跨浏览器标准的 web-platform-tests(WPT) 与面向 jsdom 自身 API 的 Mocha。读完本文,你将掌握这两套框架的定位差异、测试文件的正确存放位置、从单条用例到全量套件的完整运行命令,以及如何利用 to-run.yaml、to-upstream-expectations.yaml 等配置文件管理“预期失败”的用例。

两套测试框架:WPT 与 Mocha 的分工

jsdom 项目采用两套测试框架,核心区别在于“测什么、从哪个角度测”:

框架 测试形态 运行环境 覆盖对象
web-platform-tests .html 文件,使用 testharness.js 报告结果 在 jsdom 创建的文档内部执行 Web 平台特性(DOM、HTML、CSSOM、Fetch、XHR 等标准行为)
Mocha .js 文件 在 Node.js 进程中以 Mocha 运行 jsdom 自身对外暴露的 API(如 JSDOM() 构造函数、options 参数等)

web-platform-tests:跨浏览器一致性测试

WPT 是浏览器厂商联合维护的跨浏览器测试库,jsdom 作为参与者,直接将这些 .html 测试文件在自己的实现内运行。这类测试与 jsdom 实现的具体 Web 平台类一一对应——例如 Attr、Node、Location、CSSStyleSheet 等类,其规格以 Web IDL 描述、实现位于 lib/jsdom/living/ 下对应的 *-impl.js,而 WPT 恰好提供了验证这些实现行为是否符合规范的最佳测试来源。

Mocha:jsdom API 的外向测试

Mocha 测试用于验证“只能在 jsdom API 层面完成、无法在真实浏览器中模拟”的场景。正如 test/api/README.md 所述,这类测试从“外部”视角测试 JSDOM 类对外呈现的公共 API(该 API 的权威文档是顶层 README),而非测试 jsdom 内部实现的 Web 平台特性。

新增测试放哪里:一条决定目录归属的核心原则

test/README.md 给出了一条非常明确的指引,值得所有贡献者牢记:

  • Web 平台特性的测试 → 放入 test/web-platform-tests/to-upstream/ 子目录,这应当是绝大多数新测试的归属地;
  • jsdom API 自身的测试 → 放入 test/api/ 子目录;
  • 其余目录一概忽略("Ignore all the rest")。

这条规则的背后逻辑很清晰:jsdom 的绝大多数功能工作都是在补齐 Web 平台标准,因此以 WPT 格式编写测试才是主流。只有像 JSDOM() 构造器选项、fromFile/fromURL 加载方式这类 jsdom 专属 API,才适合用 Mocha 在 test/api/ 中编写。

为什么叫 "to-upstream"?

test/web-platform-tests/to-upstream/ 的名字源于它的使命:当官方 WPT 仓库(web-platform-tests/wpt)中没有覆盖你正在修复的 bug 时,jsdom 仍以 WPT 格式编写测试放在这里,希望日后把这些测试**反向提交(upstream)**到 WPT 官方仓库,让所有浏览器共同受益。存放时需在 to-upstream/ 下新建与 WPT 官方仓库结构平行的子目录(如 FileAPI/blob/、css/cssom/、dom/nodes/)。

环境准备与全量运行

首次运行前的系统配置

按照 Contributing.md 的说明,运行测试前需要两步准备:

  1. npm install 安装全部依赖(当前项目版本为 30.0.1,依赖配置见 package.json);
  2. 按 WPT 官方文档配置本地系统环境,关键点是 hosts 文件必须包含 web-platform.test 等 WPT 专用域名——wpt-server.js 启动时会先执行 dns.lookup("web-platform.test") 进行探测,若解析失败会直接抛出错误并提示参考 WPT 的系统配置文档。

运行命令速查表

以下是本项目各测试子集的命令汇总(均来自 package.json 的 scripts 字段与 Contributing.md):

目标 命令
全量测试(含所有子集) npm test
运行子集前的必要准备 npm run pretest
全部 WPT(由 to-run.yaml 控制) npm run test:wpt
指定 WPT 目录(按名称过滤) npm run test:wpt -- --fgrep dom/events
全部 to-upstream WPT npm run test:tuwpt
指定 to-upstream WPT npm run test:tuwpt -- --fgrep domparsing
全部 jsdom API 测试 npm run test:api
单个 API 测试文件 npx mocha test/api/from-file.js
更新 WPT 到最新修订 npm run wpt:update(耗时较长,可能约 10 分钟)

几点说明:

  • npm test 与子集的关系:npm test 执行 npm-run-all --continue-on-error test:*,会依次跑 test:api、test:to-port-to-wpts、test:tuwpt、test:wpt 全部子集。如果你只运行某个子集(而不是 npm test),请先执行 npm run pretest,它负责 npm run prepare(构建 IDL 生成的公共 API)与 npm run wpt:init(初始化 WPT 子模块)。
  • --fgrep 过滤:透传给底层 Mocha,按测试描述(describe 名)做子串匹配,例如 --fgrep dom/events 只运行 DOM 事件相关用例。
  • wpt:update 的实现:见 package.json,它先同步 WPT 子模块(git submodule update --init --recursive --remote),再在 test/web-platform-tests/tests 下调用 python wpt.py manifest 重新生成 wpt-manifest.json。

深入 web-platform-tests 运行机制

清单、配置文件与三份关键文件

WPT 子套件由 run-wpts.js 驱动,其运行链路可以拆解为:

  1. 读取 wpt-manifest.json 获得全部可用测试文件路径(清单由 wpt.py manifest 生成,工具函数在 wpt-manifest-utils.js);
  2. 用 expectations-utils.js 的 checkToRunFile() 解析 to-run.yaml,按目录分组得到“要运行哪些测试、各自预期结果是什么”;
  3. 启动本地 WPT 服务器(wpt-server.js 通过 python ./wpt.py serve --config ... 拉起,并轮询 HTTP/HTTPS/WS/WSS 四个端口就绪);
  4. 逐个运行测试文件并核对预期。

理解 to-run.yaml 的配置语法

to-run.yaml 是控制“jsdom 跑 WPT 官方仓库里哪些测试”的开关,其结构非常清晰:

DIR: FileAPI

Blob-methods-from-detached-frame.html: [timeout, Unknown]
blob/Blob-constructor.any.html:
  "Blob interface object": [fail, function is not instanceof Function]
  "Passing a Float16Array as element of the blobParts array should work.": [fail-node:<24, Float16Array not implemented]
  • DIR: <路径>:启用某个目录下的全部测试,路径对应 WPT 官方仓库中的目录(如 css/cssom、FileAPI)。相邻两个 DIR: 之间用 --- 分隔;
  • 目录下紧跟的条目:对单个测试文件或子测试设置“预期”。形如 [原因, 说明],第一项是预期类型,第二项是人类可读的原因注释;
  • glob 与批量匹配:支持 **、* 通配(如 BlobURL/**: [fail-slow, Unknown]),也支持 !(a|b) 排除语法(如 animations/!(border-bottom-width-composition|...).html),还支持 "old-tests/css3-modsel-{113..114}.xml" 这样的花括号区间展开;
  • 文件级 vs 子测试级:条目值为数组时表示整个文件的预期;条目值为对象时,键是 testharness 子测试名,表示文件内部分子测试的预期;
  • 硬性格式约束:expectations-utils.js 会做校验——DIR 路径不允许以 / 开头或结尾、目录必须真实存在于子模块中、期望模式必须按字典序排列、fail/fail-slow/timeout 等必须是合法取值,否则直接抛错。

预期值(expectation)的完整枚举

expectations-utils.js 中定义了完整的合法预期类型:

预期值 含义
fail 预期失败(运行并断言失败成立)
fail-slow 预期失败且运行缓慢(直接跳过)
timeout 预期超时(跳过)
flaky 不稳定测试(跳过)
needs-canvas 需要 canvas 依赖(未安装则跳过)
fail-with-canvas 安装了 canvas 时预期失败(未安装则跳过)
pass-slow 通过但缓慢(跳过)
fail-node:<semver范围> 在指定 Node 版本范围内预期失败(如 fail-node:<24 表示 Node 24 以下)
skip-node:<semver范围> 在指定 Node 版本范围内跳过(仅文件级可用)

结合 expectations-utils.js 的 resolveReason() 可以看到运行时的裁决逻辑:fail-slow、pass-slow、timeout、flaky 一律转为 skip;needs-canvas 与 fail-with-canvas 取决于本机是否加载了 canvas(utils.js 导出 Canvas,未安装则为空);fail-node: 前缀则用 semver 与当前 process.versions.node 比对。

新增/启用一个 WPT 用例的标准流程

假设你在 WPT 官方仓库中找到了覆盖某个特性的测试文件,Contributing.md 给出的流程是:

  1. 在 to-run.yaml 中找到覆盖该目录的 DIR: 段;
  2. 删除(或修改)其中禁用该测试的期望行;
  3. 若没有对应 DIR: 段,则新增一个段并设置正确的预期。

to-upstream:本地 WPT 用例与真实浏览器验证

运行机制

run-tuwpts.js 与 WPT 主套件略有不同:由于 to-upstream/ 文件数量少,它每次运行都重新生成 manifest(而不是复用 wpt-manifest.json),并且会用 python ./wpt.py lint 对本地测试做格式检查,lint 失败则直接以非零状态退出。预期文件是 to-upstream-expectations.yaml,同样由 checkToUpstreamExpectations() 加载校验。

写了一个 jsdom 跑不通但浏览器能通过的测试怎么办

这是 WPT 贡献中常见的场景:Contributing.md 建议——仍然值得提交,把它放到 to-upstream/ 对应目录下,然后在 to-upstream-expectations.yaml 中为该测试添加一行“预期失败”,并附上记录进度的 issue 链接,供后续 jsdom 贡献者持续跟进。

用真实浏览器验证 to-upstream 测试

npm run test:tuwpt:browser

该命令(对应 scripts/verify-tuwpts-in-browser.js)会启动本地 WPT 服务器、打开默认浏览器、运行全部 to-upstream 测试并打印摘要。支持以下参数:

# 只跑 domparsing 相关,且只打印失败与摘要
npm run test:tuwpt:browser -- --fgrep domparsing --reporter min

# 用 Chromium 无头模式自动验证
npm run test:tuwpt:browser -- --browser=chromium --browser-arg=--headless --fgrep domparsing --reporter min

几个值得注意的行为细节:

  • --browser:指定 Chrome/Chromium 可执行文件实现全自动验证。运行器使用临时 profile(允许弹窗),测完自动关闭浏览器并删除 profile;每个测试在全新的顶层窗口中运行,且有独立控制器,即使测试发生导航或超时也能继续推进;
  • --reporter min:仅打印失败与最终摘要;
  • --browser-arg:可重复传入浏览器启动参数,例如无头模式 --headless。运行器不会主动禁用浏览器沙箱,若环境需要请显式加 --browser-arg=--no-sandbox;
  • 若显式传入 --browser-arg=--user-data-dir=...,则会覆盖临时 profile,此时运行器不会删除该目录;
  • 测试失败或浏览器启动出错都会导致非零退出状态。

Mocha API 测试的编写与运行

目录结构与定位

test/api/ 存放 jsdom API 的 Mocha 测试,从仓库现状看,它涵盖了 basics.js、options.js、options-run-scripts.js、cookies.js、encoding.js、fragment.js、from-file.js、from-url.js、from-outside.js、methods.js、resources*.js、virtual-console.js、jsdom-errors.js、css-parsing-errors.js、file-url-functionality.js 等主题,另有 helpers/(resources.js、servers.js)与 fixtures/(各类 HTML/XML/编码测试夹具)。这些文件分别对应 JSDOM 构造器的各种选项(如 resources、runScripts、url、cookieJar、virtualConsole、beforeParse 等)。

编写一个新 API 测试

Contributing.md 的指引是:在 test/api/ 下新增文件并遵循周边既有约定。需要注意,当前版本的 package.json 中 test:api 直接执行 mocha test/api/(按目录扫描),因此新增文件放入该目录即可被自动发现。

运行方式

# 全部 API 测试
npm run test:api

# 单个文件(以 from-file.js 为例)
npx mocha test/api/from-file.js

遗留的旧式测试:to-port-to-wpts

test/to-port-to-wpts/ 是历史遗留目录:其中的测试使用 Mocha 编写,但内容本应是 WPT 格式,jsdom 保留它们仅为覆盖率,直到被逐步转换。npm test 会通过 test:to-port-to-wpts(依次运行该目录及其 level1/level2/level3 子目录)把这些旧测试一并跑完——这正是 test/README.md 里“运行全量测试会包含旧测试”这一说法的来源。写新测试时不要往这里添加内容。

结语:一套服务于“与浏览器一致”的测试组合拳

从 test/README.md 出发可以看到,jsdom 测试体系的组织哲学非常一致:能测 Web 平台行为的,一律以 WPT 格式编写并尽量回馈官方仓库;只有 jsdom 专属 API 才用 Mocha 在 test/api/ 中测试。理解 to-run.yaml 与 to-upstream-expectations.yaml 的预期语法,就能读懂“哪些用例已启用、哪些已知失败及其原因”,这也是判断一个功能实现状态的最直接窗口。写测试前,建议同时阅读 Contributing.md 中关于 WPT 编写、to-run 配置与浏览器验证的完整说明,以及 test/web-platform-tests/expectations-utils.js 中预期值的权威定义,确保你的新用例既符合规范,也能被这套双框架体系正确接纳与长期维护。

登录后查看全文
jsdom