Ladybird 测试体系实战指南:从 test-web 四类测试到 Sanitizer 与 WPT 全流程
【免费下载链接】ladybirdTruly independent web browser项目地址: https://gitcode.com/GitHub_Trending/la/ladybird
本篇指南基于 Ladybird 仓库的官方测试文档 Documentation/Testing.md 展开,并结合 Tests/LibWeb/test-web 测试运行器、Meta/ladybird.py 与 CMakePresets.json 的源码实现进行纵深讲解。读完后你将能够:独立跑通 Ladybird 的全部单测与 LibWeb 网页测试,复现 CI 的 Sanitizer 构建与失败场景,编写 Text / Layout / Ref / Screenshot 四类 LibWeb 测试并正确完成 rebaseline,以及用 Meta/WPT.sh 运行、对比与导入 Web Platform Tests。
测试体系总览:Tests/ 目录与每库一目录
Ladybird 的测试统一放在 Tests/ 目录下,每个被测试的库对应一个子目录,例如 Tests/AK 测试 AK 基础库,Tests/LibCore 测试 LibCore,Tests/LibJS 测试 JavaScript 引擎(含 1400 多个 JS 测试文件),Tests/LibWeb 则是网页引擎测试的核心阵地。
文档明确要求:为 LibWeb 新增的每个特性或 bug 修复,都应在Tests/LibWeb中配套一个测试,并按特性选择 Text、Layout、Ref 或 Screenshot 四种类型之一;而针对内部 C++ 代码的测试则以独立的TestFoo.cpp文件形式放在Tests/LibWeb下。这一点可以从源码结构直接得到印证——Tests/LibWeb 目录中既有Text/、Layout/、Ref/、Screenshot/四个按类型划分的测试根目录,也有一批 C++ 单元测试文件,如 TestHTMLTokenizer.cpp、TestCSSPixels.cpp、TestContentBlocker.cpp、TestStructuredSerializeCorpus.cpp 等。
运行测试的三种方式
提示:若要复现 CI 上的失败,应参考后文的 Sanitizer 构建运行 一节。
文档给出的第一种(也是最简单的方式)是使用仓库自带的 Meta/ladybird.py 脚本。它的test子命令先执行 configure 与 build,再运行测试。从源码看(Meta/ladybird.py 的test_main函数),该脚本实际做的事情就是调用 ctest:
test_args = [ "ctest", "--preset", preset, "--output-on-failure", "--test-dir", str(build_dir), ] if pattern: test_args.extend(["-R", pattern])即:Meta/ladybird.py test运行所有测试;Meta/ladybird.py test LibWeb通过 ctest 的-R正则过滤只跑名称匹配LibWeb的测试。preset 默认取环境变量BUILD_PRESET,否则为Release,构建目录随之映射到Build/release(见 Meta/ladybird.py 的known_presets表:Debug →Build/debug、Sanitizer →Build/sanitizers等)。
LibWeb 网页测试是如何注册为 CMake 测试的?在 Tests/LibWeb/test-web/CMakeLists.txt 中可以看到,test-web可执行文件被注册为名为LibWeb的 ctest 测试,并固定附带--python-executable与--per-test-timeout 120 -v -v参数:
if (BUILD_TESTING) add_test( NAME LibWeb COMMAND $<TARGET_FILE:test-web> --python-executable ${Python3_EXECUTABLE} --per-test-timeout 120 -v -v ) ... set_tests_properties(LibWeb PROPERTIES ENVIRONMENT LADYBIRD_SOURCE_DIR=${LADYBIRD_SOURCE_DIR} TIMEOUT_SIGNAL_NAME SIGTERM)第二方式是直接调用test-web测试运行器,绕过 CTest 一层:
Meta/ladybird.py run test-web从 Tests/LibWeb/test-web/Application.cpp 的参数定义中,可以确认test-web支持的完整命令行参数,比文档描述更加丰富:
| 参数 | 短选项 | 作用 |
|---|---|---|
--test-path <path> | 指定测试根目录(默认由LADYBIRD_SOURCE_DIR推导为Tests/LibWeb) | |
--results-dir <path> | -R | 测试结果的输出目录 |
--test-concurrency <jobs> | -j | 并发测试数,默认取 CPU 硬件并发数 |
--filter <glob> | -f | 只跑匹配 glob 的测试,如-f Text/input/your-test.html |
--rebaseline | 重新生成被执行的 Layout/Text 测试的期望文件 | |
--per-test-timeout <sec> | -t | 单测试超时秒数,默认 30 |
--fail-fast | 首个失败/超时/崩溃即中止,超时时可挂接调试器 | |
--dry-run | 仅列出将要运行的测试 | |
--repeat <n> | 将匹配的测试重复运行 N 次 | |
--shuffle | -s | 打乱测试执行顺序 |
--verbose | -v | 可叠加使用,提升日志详细度 |
--dump-gc-graph | -G | 输出 GC 图(会自动强制串行执行) |
第三方式是直接调用ctest,最简单的是复用 CMakePresets.json 中的Release预设:
cmake --preset Release cmake --build --preset Release ctest --preset ReleaseLADYBIRD_SOURCE_DIR 环境变量
部分测试要求LADYBIRD_SOURCE_DIR指向 Ladybird 源码树根目录。手动构建时需要自行设置:
# /path/to/ladybird repository export LADYBIRD_SOURCE_DIR=${PWD}这里有两处源码佐证可以说明该变量的重要性:
- Meta/ladybird.py 的
ensure_ladybird_source_dir会在未设置时通过git rev-parse --show-toplevel自动推导并写入该环境变量; - Tests/LibWeb/test-web/Application.cpp 中,
test-web启动时若检测到LADYBIRD_SOURCE_DIR,会将测试根路径设为<该变量>/Tests/LibWeb; - CMakePresets.json 的
root_base测试预设也通过"LADYBIRD_SOURCE_DIR": "${fileDir}"自动注入该变量,因此使用ctest --preset时无需手动 export。
使用 ninja 与失败输出
构建完成后也可以直接用 ninja 驱动测试:
cd Build/release ninja ninja test查看失败测试的 stdout/stderr 时,推荐设置CTEST_OUTPUT_ON_FAILURE环境变量为 1:
CTEST_OUTPUT_ON_FAILURE=1 ninja test # 或者直接使用 ctest... ctest --output-on-failure结果产物:如何读懂一次 test-web 运行
从 Tests/LibWeb/test-web/main.cpp 的实现看,一次运行结束后会在结果目录(--results-dir指定)中生成一份可浏览的 HTML 报告:results.js+ 由源码树 Tests/LibWeb/test-web/results-index.html 模板复制出的index.html,其中记录了 total/fail/timeout/crashed/skipped 汇总与每个未通过测试的模式(Text/Layout/Ref/Screenshot/Crash)、是否存在日志、Ref 与 Screenshot 测试的pixelErrors与maxChannelDiff像素差异统计。每个失败测试还会落盘.expected.txt/.actual.txt/.diff.txt/.diff.html(文本类差异)或.actual.png/.expected.png/.diff.png(像素类差异,差异像素标红)。运行期间则持续写出harness-status.txt供排查挂起的 harness。这些细节对定位"为什么挂"非常有用。
另一个值得一提的配置是 Tests/LibWeb/TestConfig.ini。test-web会解析其中的两个分组(见 main.cpp 中 load_test_config):
[LoadFromHttpServer]:列出必须经由内置 HTTP echo server 加载的测试,原因包括 cookie 行为、跨源 Worker fetch、pushState需要 HTTP(s) scheme、Service Worker Cache API 仅对 HTTP(S) URL 生效等;[Skipped]:当前被禁用的测试清单,每条通常附带原因注释(flaky、CI 超时、尚未实现的功能等),是理解当前已知问题的"活文档"。
使用 Sanitizer 运行测试,复现 CI 失败
CI 以 Address Sanitizer(ASan)与 Undefined Behavior Sanitizer(UBSan)插桩运行 host 测试。这两类工具能够捕获大量常见 C++ 错误,包括内存泄漏、堆栈越界访问、有符号整数溢出等。Sanitizer 构建会显著变慢,并且会使ccache之类的缓存失效。
最简单的启用方式是使用Sanitizer预设:
cmake --preset Sanitizer cmake --build --preset Sanitizer ctest --preset Sanitizer若不想走预设而手动开启,则使用-DENABLE_FOO_SANITIZER系列开关。为保证行为与 CI 一致,需要按文档设置ASAN_OPTIONS与UBSAN_OPTIONS(Sanitizer测试预设已在 CMakePresets.json 中内置了同样的值):
export ASAN_OPTIONS='strict_string_checks=1:check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:allocator_may_return_null=1' export UBSAN_OPTIONS='print_stacktrace=1:print_summary=1:halt_on_error=1' cmake -GNinja -B Build/lagom -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_UNDEFINED_SANITIZER=ON cd Build/lagom ninja CTEST_OUTPUT_ON_FAILURE=1 LADYBIRD_SOURCE_DIR=${PWD}/../.. ninja test这些选项的含义值得留意:ASan 的strict_string_checks开启字符串函数边界检查,check_initialization_order/strict_init_order用于揪出静态初始化顺序问题,detect_stack_use_after_return检测栈上 use-after-return;UBSan 的print_stacktrace=1在报错时打印调用栈、halt_on_error=1让首个未定义行为即终止进程,避免错误级联掩盖根因。另外从 Meta/ladybird.py 可以看到,ladybird.py run在--preset Sanitizer时也会自动注入同一组ASAN_OPTIONS/UBSAN_OPTIONS默认值——这保证了日常本地运行与 CI 的插桩口径一致。
运行与导入 Web Platform Tests
Web Platform Tests(WPT)是衡量浏览器规范符合度的行业标尺,Ladybird 通过 Meta/WPT.sh 驱动 wpt 工具链运行,该脚本还支持对比两次运行的结果。
基本用法:run 与 compare
# 先跑一遍 WPT 并落日志,随后切到你的 CSS 改动分支,再跑一次并与基线对比 ./Meta/WPT.sh run --log expectations.log css git checkout my-css-change ./Meta/WPT.sh compare --log results.log expectations.log css# 从上游 WPT 仓库拉取最新测试 ./Meta/WPT.sh update # 运行全部 WPT 测试,结果写入 results.log ./Meta/WPT.sh run --log results.log从脚本源码(Meta/WPT.sh)可以看到它支持的完整子命令:update(更新 WPT 仓库)、run(运行)、compare(与 LOG_FILE 中的期望对比)、import(把指定 wpt.live 路径的测试抓下来生成 in-tree 测试与期望文件)、list-tests、clean、bisect BAD_COMMIT GOOD_COMMIT(二分定位首次产生意外结果的提交)。脚本默认构建test-web二进制(Meta/WPT.sh 中调用./Meta/ladybird.py build test-web),通过 WebDriver 驱动浏览器,覆盖testharness、reftest、wdspec、crashtest、test262五类测试类型。
导入 WPT 测试到本地仓库
当你改动的代码让 Ladybird 新通过了某个尚未被导入的 WPT 测试时,应把它导入仓库,把"通过状态"固化下来。文档给出的方式:
./Meta/WPT.sh import html/dom/aria-attribute-reflection.html即把http://wpt.live/URL 的路径部分交给import子命令。脚本会同时下载该测试及其引用的 JavaScript 脚本,拷贝到Tests/LibWeb/<test-type>/input/wpt-import目录,运行该测试,然后在Tests/LibWeb/<test-type>/expected/wpt-import目录中生成期望结果文件。这一点在 Tests/LibWeb/TestConfig.ini 中随处可见wpt-import/前缀的条目,正是导入机制的产物。
编写新测试
用脚本生成测试骨架
仓库提供了 Python 脚手架 Tests/LibWeb/add_libweb_test.py:
./Tests/LibWeb/add_libweb_test.py your-new-test-name test_type文档说明接受的test_type取值为 "Text"、"Layout"、"Ref" 与 "Screenshot";从脚本源码看,choices实际还支持 "Crash" 一类(用于验证特定输入不会导致崩溃),且提供--async开关为 Text 测试生成异步骨架。脚本会:
- 在
Tests/LibWeb/<test_type>/input下创建your-new-test-name.html输入文件; - 在
Tests/LibWeb/<test_type>/expected下创建对应的期望文件——Text/Layout 生成.txt,Screenshot 生成.png(留空待生成),Ref 生成<test_name>-ref.html参考页; - Text 测试骨架引用
include.js并预填test(() => { println("Expected println() output"); })(--async时预填asyncTest(async (done) => { ... done(); }));Ref 骨架则自动写入<link rel="match" href="../expected/<name>-ref.html" />标签。
生成后,把模板替换为你的真实测试内容,并重新生成期望文件:
# Text / Layout:rebaseline 重新落盘期望文件 ./Meta/ladybird.py run test-web --rebaseline -f Text/input/your-new-test-name.htmlRef 与 Screenshot 测试需要手工提供等效渲染的参考内容;不过 Screenshot 测试的参考图可以用无头模式浏览器直接生成:
./Meta/ladybird.py run ladybird --headless --test-mode Tests/LibWeb/Screenshot/input/your-new-test-name.html # 日志会输出类似:Saved screenshot to: ~/Downloads/screenshot-2025-06-07-08-37-45.png mv ~/Downloads/screenshot-2025-06-07-08-37-45.png Tests/LibWeb/Screenshot/images/your-new-test-name.png--rebaseline的实际行为可在源码中印证:main.cpp 的 handle_completed_test 在rebaseline模式下会把实际输出直接写回期望文件并返回 Pass;Screenshot 的 rebaseline 分支 则会把实际截图写为 PNG,并尝试调用系统的optipng -strip all压缩图片(调用失败仅告警,不影响流程)。
四类测试逐一解析
Text 测试:验证无视觉表现的 Web API
Text 测试面向没有视觉表现的 Web API,用 JavaScript 编写并在无头浏览器中运行。每个测试在 script 标签中有一个测试函数来驱动 API,并用println输出期望结果;所有println调用被累积成输出文本,再由测试运行器与期望输出文件逐字节(忽略尾部换行)比较。
Text 测试可以是同步或异步的。异步测试应使用done回调信号完成——"异步"并不意味着一定运行在 async 上下文中,只是要求测试函数在结束时主动汇报;若测试 API 本身需要异步上下文,传给test的 lambda 可以直接写成 async。
从实现层面(main.cpp 中 Text 模式的处理)可以补充两条细节:test-web通过view.request_internal_page_info(WebView::PageInfoType::Text)收集页面内累积的输出文本,测试完成由页面端调用internals.signalTestIsDone("PASS")上报(该注入脚本位于 main.cpp),页面加载完成与测试完成两个信号都到位后测试才算结束;单测试超时会由--per-test-timeout驱动的定时器触发Timeout结果。
Layout 测试:比对布局树
Layout 测试将页面的布局树与期望布局树做文本比对,最适合测试布局代码,也常用于验证其他对布局有可观测影响的功能。它不需要任何 JavaScript——页面加载完成后运行器会自动 dump 布局树。
源码中还有一个容易忽略的巧妙设计(run_dump_test 中 Layout 分支):dump 布局树前会先对页面截屏,注释说明这是为了强制触发"SVG as image"文档的惰性布局,同时让更多代码路径跑起来以暴露 bug。布局树的期望文件由--rebaseline生成(脚手架生成的 Layout 期望文件内容本身就是一句"run ./Meta/ladybird.py run test-web --rebaseline ..."的提示)。
Ref 测试:与参考页截图像素级对比
参考(ref)测试把测试页的截图与参考页的截图对比,两者完全一致才算通过。它们适合测试背景图、阴影这类视觉效果;如果难以构造等效参考页(例如 SVG 或 canvas 场景),文档建议改用 Screenshot 测试。
每个 Ref 测试都包含一个特殊的标签来指定参考页:
<link rel="match" href="../expected/my-test-ref.html" />测试运行器据此定位参考页,从而允许多个测试共享同一参考页。实现上(run_ref_test),页面加载后test-web注入一段等待脚本(监听reftest-waitclass 移除,并等待document.fonts.ready与两帧requestAnimationFrame完成,参照 WPT reftest 的等待规范),确认渲染稳定后先截测试页、再通过internals.loadReferenceTestMetadata()读取 match/mismatch 引用与 fuzzy 配置,逐条加载参考页截图比对;失败时输出 actual/expected/diff 三张 PNG。
Screenshot 测试:与参考 PNG 对比
Screenshot 测试可视为 Ref 测试的子类型,其"参考页"是一个指向期望输出截图的<img>标签。文档建议:能用普通 Ref 测试就避免使用 Screenshot 测试,因为它们对细微的渲染差异敏感,且无法在所有平台上工作。与 Ref 一样,它需要<link rel="match" href="...">标签(在 Screenshot 模式下该标签指向承载<img>的参考 HTML)。
结合 main.cpp 中 Screenshot 分支:期望 PNG 以Gfx::ImageDecoder解码后与实际截图做 fuzzy 匹配(支持页面内声明的 fuzzy 配置,允许局部区域的像素容差);失败时同样落盘 diff 图并统计pixel_error_count与最大通道差,这些数据会汇总进 HTML 报告供人工判断差异性质。
相关文档与延伸阅读
- 构建与环境准备:Documentation/BuildInstructionsLadybird.md(CMake ≥ 3.30,依赖经 vcpkg 管理,见 Meta/ladybird.py 的版本校验)
- 样式引擎测试:Documentation/Style/StyleEngineTesting.md
- 不稳定测试检测工具:Meta/check-test-flakiness.py(基于
test-web --dry-run统计重复运行的结果波动) - WPT 进度观察脚本:Meta/watch_wpt_progress.sh
小结
Ladybird 的测试体系可以归纳为三层:Tests/<库>/下按库组织的单元测试(CTest 直接驱动)、test-web驱动的四类网页级测试(Text/Layout/Ref/Screenshot + Crash),以及基于 wpt 工具链的 WPT 规范符合度测试。三条运行入口——Meta/ladybird.py test [pattern]、ctest --preset <Release|Debug|Sanitizer>、ninja test——殊途同归到 ctest 注册用例;Sanitizer 预设通过内置的ASAN_OPTIONS/UBSAN_OPTIONS保证本地与 CI 口径一致;新测试则用add_libweb_test.py生成骨架、--rebaseline固化期望、WPT.sh import把新通过的 WPT 测试收编进仓库。掌握这条链路后,无论是修 bug 还是加特性,都能把"改动 → 测试 → 期望固化"变成确定性的闭环。
【免费下载链接】ladybirdTruly independent web browser项目地址: https://gitcode.com/GitHub_Trending/la/ladybird
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考