本文是 matery 主题自动化测试体系的完整教程,与其余 4 篇功能测试文章(内容 tag / 交互视觉 / 布局页面 / Markdown 语法)配套——那 4 篇是"测试靶场",本文是"测试引擎"。权威依据:
tools/tests/release-test.sh(L1-L14,13 层 156 行纯编排)、docs/superpowers/specs/2026-08-06-layout-test-cases.md。
什么时候看这篇?
| 你是谁 | 你该看什么 | 你能得到什么 |
|---|---|---|
| 普通用户(想知道某个功能是否正常) | 附录「测试 ↔ 功能对照表」 | 按功能名找到对应测试文件,确认"这个功能有自动验证" |
| 贡献者(想新增/修改测试用例) | §12「如何新增测试用例」 | 从零到挂入门禁的完整步骤(含代码模板) |
| 维护者(发版前跑门禁) | §2「release-test.sh 用法」 | 本地迭代 / 发版门禁 / 视觉断言的命令 |
| 排查问题(门禁报红) | §5「外部依赖重试」+ §6「第三方拦截」+ ⚠️ 已知坑 | 区分"环境噪声"和"真 bug" |
1. 测试金字塔总览
┌──────────────┐
│ L2 生产 URL │ 发版后:20 页面 HTTP 200 + 功能标记(curl)
│ L9/L10 SW │ 发版前:Vercel 测试域 SW 冒烟(playwright)
├──────────────┤
│ L11 Playwright 集成 │ 真实浏览器:特效/词云/懒加载/阅读模式等 18 项
├──────────────┤
│ L14 视觉断言 │ visual-assert 精确数值:7 套件(生产阻塞,本地默认跳过)
├──────────────┤
│ L13 单测门禁 │ node 直跑:13 个 .test.js + 4 个 shell 单测
├──────────────┤
│ L1 构建验证 │ hexo g Success + 产物完整(index.html > 1000B)
└──────────────┘| 层 | 名称 | 运行方式 | 依赖 |
|---|---|---|---|
| L1 | 构建验证 | hexo generate + 产物检查 | 无 |
| L13 | 全量单测门禁 | node tools/tests/*.test.js + shell 单测 | 无 chromium |
| L11 | Playwright 集成 | 真实浏览器 + 本地 server | chromium + server |
| L2/L2b/L2x/L2y/L2i | 生产 URL 断言 | curl + grep | 生产/本地 server |
| L3-L8 | 功能标记检查 | curl + 静态断言 | 生产/本地 server |
| L9 | 静态资源 + SW 冒烟 | curl + playwright | 生产域名 |
| L13a | 覆盖率统计 | node --experimental-test-coverage 扫 unit/** | 无 |
| L14 | 视觉断言 | node tools/visual-assert.js run(7 套件) | chromium + server |
| L10 | SW 冒烟(测试域) | playwright | Vercel 测试域 blog2.17lai.site |
2. release-test.sh 用法
# 完整跑(构建 + 生产检查 + SW 冒烟 + playwright 集成)——发版门禁
tools/tests/release-test.sh
# 跳过构建(public/ 已就绪)
tools/tests/release-test.sh --skip-build
# 本地迭代(端口自适应探测,跳过远程 SW 冒烟 L10 + playwright 集成 L11)
tools/tests/release-test.sh --local
# 仅跳过远程 SW 冒烟(保留 playwright 集成)
tools/tests/release-test.sh --skip-l10| 参数 | 作用 |
|---|---|
--skip-build | 跳过 L1 构建 + 产物验证(已构建时用) |
--local | 目标改本地基址(端口自适应探测,见下方基址段),跳过 L10(远程 SW 冒烟 5min+)与 L11(playwright 串行 >800s) |
--skip-l10 | 仅跳过 L10,保留 L11 playwright 集成 |
--prepare | 自动就绪环境:容器未跑则启动、产物无效则重建、最后复检 |
RUN_L14=1 | 本地模式下启用 L14 视觉断言(默认本地跳过) |
SKIP_L14=1 | 强制跳过 L14(即使 RUN_L14=1) |
基址:生产固定 https://blog.17lai.site(443,绝无端口);本地 LOCAL_BASE 走端口自适应——探测顺序 BLOG_PORT 环境变量 → TEST_BASE 环境变量 → 根 _config.yml 的 server.port(docker 4000)→ 4100,取第一个 HTTP 200;成功后导出 TEST_BASE/TEST_URL,36 个测试文件自动跟随。
执行顺序(156 行纯编排,非文档顺序):
L0 preflight → L10 → L01 → L02 → L03_05 → L07 → L08 → L09 → L11 → L12 → L2i → L13 → L13a → L14 → TC 索引校验 → 汇总。注意 L10 最先跑(在 L01 之前)——旧文档常写反。
发版铁律:
tools/cicd.sh -r发布后必须跑tools/tests/release-test.sh验证生产环境。
3. L1-L14 各层覆盖内容
| 层 | 覆盖内容 | 断言示例 |
|---|---|---|
| L1 | 构建验证 | hexo g Success + public/index.html > 1000B |
| L2 | 20 个 layout 页面 URL 可达 | /posts/fc764afd/、/archives/、/categories/、/tags/、/about/、/friends/、/galleries/、/bb/、/musics/、/movies/、/msg/ + 4 篇测试文章 → HTTP 200 |
| L2b | 多语言验证 | en 文章真实翻译 / 回落提示条 / 评论 path 归一化 / 默认语言无前缀 |
| L2x | 统一语言回落 | MATERY_LANG 注入 / 回落页 / 独立页多语言(含 gallery 子页)/ 聚合页多语言 |
| L2y | 加密解密刷新 | wiki 侧栏无元数据/分区键泄漏(structure/i18n/encrypt_)/ 加密容器完整(hbeForm + storage-key) |
| L2i | SEO 结构化数据 | og:image / JSON-LD / fetchpriority |
| L3 | 评论区检查 | msg/friends/bb 页含 #comments |
| L4 | href=/ 样式错误 | 页面 href=/ 引用数应为 0 |
| L5 | 版本号一致性 | config vs sw.tmp.js 3 处一致 |
| L6 | 关键资源检查 | 404 页面 / 关键 JS 版本 |
| L7 | 核心功能覆盖 | 相册灯箱 / 加密相册 / 搜索 / JS 模块合并 / TOC 折叠 / 暗色 / 评论 / infinite-scroll / wiki 代码块 / wiki 组件样式(main 含 post-container / .wiki-content 折行 / admonition .note 规则精确限定) |
| L8 | 增强功能覆盖 | tag 插件渲染(note/timeline/tabs/label/githubCard/mermaid)/ 加密文章 / Feed / 静态资源 / 分析 |
| L9 | 静态资源 + SW 冒烟 | main.css > 50KB / utils.js > 5KB / 生产域名 7 页无 SW 错误 |
| L10 | SW 冒烟(测试域) | blog2.17lai.site 7 页无 SW 错误 + 交互验证(搜索/打赏/灯箱/TOC/暗色) |
| L11 | Playwright 集成(18 项) | 见下表 |
| L12 | 本地化资源检查 | mermaid / aplayer 资源存在 |
| L13 | 全量单测门禁 | 13 个 *.test.js + 4 个 *.test.sh = 17 文件 |
| L13a | 覆盖率统计 | unit/** 覆盖率扫描(附带执行 unit/** 全部单测) |
| L14 | 视觉断言(7 套件) | 见 §9 |
L11 Playwright 集成清单(真实浏览器)
| 测试文件 | 覆盖 |
|---|---|
effects-integration.test.js | 页面特效(sakura/snowflake/fireworks 加载) |
videobg-integration.test.js | 视频背景(门槛/亮暗池/webm 契约/禁用态) |
wordcloud.test.js | 词云 / 去 jQuery / TOC 防闪烁 / 明暗切换 |
i18n.test.js | 多语言补全(MATERY_I18N 注入 + 按钮文案 + en 翻译) |
musics-integration.test.js | 在线音乐 APlayer(多域名 failover) |
lazyload-integration.test.js | 懒加载 handleCard 三分发(51 断言) |
floating-panel-integration.test.js | 悬浮面板(开关/字体缩放/持久化) |
content-width.test.js | 内容宽度统一(4K/FHD/2K/单栏封顶) |
readmode-integration.test.js | 阅读模式 / 代码全屏 / scroll-down-bar |
wiki-styles.test.js | Wiki 页 tag 组件样式(note / ::: 容器色 + 居中 + 宽度一致性) |
code-widget.test.js | codeWidget 委托幂等(二次调用后单次点击仍生效) |
core-events-integration.test.js | 事件总线 / 进度条联动 |
wiki-prism-integration.test.js | Wiki 代码块 prism + line-numbers |
misc-interactions.test.js | 打赏弹窗 / 繁简转换(G14/G15) |
lang-client-strategy.test.js | 客户端语言策略(全站跟随/爬虫跳过/面板回显,7 断言) |
rich-content-integration.test.js | 富内容渲染(公式 MathJax+KaTeX/mermaid/markmap/打字机/分享/Live2D,11 断言) |
wiki-integration.test.js | 多语言 wiki 可达 / 侧栏章节 / 语言切换器(18 断言) |
lightbox.test.js | 灯箱(相册/画廊图片放大交互) |
4. 单测清单(tools/tests/)
L13 门禁单测(release-test.sh 直跑)
# node 单测(纯单测/静态,无浏览器依赖)
unit-toc.test.js # TOC 折叠配置(collapseDepth 0||6 falsy 陷阱)
unit-search.test.js # 搜索三引擎
unit-multilang.test.js # 多语言配置
unit-encrypt2.test.js # 加密扩展(片段 + wiki)
unit-wiki-helper.test.js # wiki helper(语言解析/侧栏渲染/canonical 回落,10 用例)
helpers.test.js # 字数统计三 helper(wordcount/min2read/totalcount;加密 origin/wiki 去重/回落/缓存重置,52 断言)
unit-sidebar-i18n.test.js # 侧栏 i18n 查表 + 回退链(当前→fallback→default→key,5 用例)
unit-sidebar-keys.test.js # 门禁校验器 sidebar-keys.js 行为(4 wiki × 9 语言覆盖,4 用例)
lang-fallback.test.js # 回落链 4 级(13 断言)
unit-i18n-core.test.js # i18n 核心回落语义(resolveI18n 四级链 + cfg 防御,10 用例)
unit-i18n-helper.test.js # t() helper(fallback_lang + 大小写容错,5 用例)
echarts-charts.test.js # ECharts 图表
banner-bg.test.js # banner 与背景偏好联动
homepage.test.js # 首页结构
imgsize-unit.test.js # 图片尺寸注入脚本的单元测试(normalizeImgUrl 协议相对 URL 补全 + 本地/网络图片尺寸读取逻辑)
search-integration.test.js
infinite-scroll-aos.test.js
unit-standalone.test.js # 独立页排除法判定 + 语言前缀豁免(22 断言)
# shell 单测
unit-css-architecture.test.sh # CSS 架构(编译产物关键选择器)
unit-version.test.sh # 版本递增进位(防 12.99→12.100)
unit-retry.test.sh # 退避重试库
unit-deploy-target.test.sh # 部署目标切换(hexo/hexoback 双激活校验)其他单测(原 run-tests.sh L1 / L8 段,已并入 release-test.sh)
unit-encrypt.test.js # 自研加密体系(AES-GCM + PBKDF2 闭环,9 用例)
unit-fontawesome.test.py # FontAwesome 精简脚本5. 外部依赖重试与降级(lib/retry.sh)
第三方网络抖动不可避免(Algolia API / CDN 视频 / 音乐 API),且外部服务不可用不是产品缺陷,不应拦发版。tools/tests/lib/retry.sh 统一处理:
source tools/tests/lib/retry.sh
# exit-code 判定:退避重试 3 次(1s/4s/9s + 抖动)
retry_run "L2 搜索" "node tools/tests/search-integration.test.js"
# 输出模式判定(release-test L11 用)
retry_match "L2d 视频" "node .../videobg-integration.test.js" "PASS=.*FAIL=0"失败分类(全失败时按日志签名区分):
| 签名 | 判定 | 结果 |
|---|---|---|
ERR_NAME_NOT_RESOLVED / ECONNREFUSED / ETIMEDOUT / 429 / 50x 等 | 外部不可用 | ⚠️ 降级 warn(返回 0,不拦发版) |
| 无外部签名 | 业务断言失败 | ❌ fail(返回 1,真 bug) |
6. 第三方拦截 + ERR_ABORTED 排除(lib/browser-env.js)
playwright 测试的共享环境辅助,统一"页面自身错误"归属:
const { interceptThirdParty, collectPageErrors } = require('./lib/browser-env');
const env = collectPageErrors(page, BASE); // 持续累积 JS 异常 + 同源资源失败
await interceptThirdParty(page); // 拦截已知污染源
// 断言:env.jsErrors.length === 0 && env.sameOriginFailures.length === 0| 机制 | 说明 |
|---|---|
interceptThirdParty | 拦截已知污染源(如 webpushr 返回非 JSON → 页面 unhandled rejection),返回合法 JSON 而非 abort(abort 会产生 net::ERR_FAILED 反成新噪声) |
collectPageErrors | 仅收集 JS 异常 + 同源(localhost/生产域)资源失败;第三方 CDN/图床失败为环境噪声不计入 |
ERR_ABORTED 排除 | 导航中断(多次 goto/离开页面取消在途请求)非资源错误,直接跳过 |
7. 压缩开关验证(cicd.sh)
tools/cicd.sh 按模式注入压缩开关(sed 替换根 _config.yml 的 # minify-switch 标记行):
| 模式 | minify 值 | 说明 |
|---|---|---|
-d / --debug | false | 不压缩(本地调试,产物可读) |
-r / -c / -t | true | 压缩(hexo-minify 在 generate 时恒压缩,故需改其 enable) |
验证方法:
# 1. debug 构建(未压缩)
tools/cicd.sh -d
head -c 300 public/css/main.css # 多行、带缩进、有注释
# 2. 压缩构建(-t 本地编译测试,压缩无 CDN)
tools/cicd.sh -t
head -c 300 public/css/main.css # 单行、无注释、体积显著变小构建后 cicd.sh 会还原
_config.yml(仅当改动只涉及 minify-switch 标记行时),避免工作区脏。
8. 环境就绪、单实例锁与基址自适应
门禁前先过 preflight(环境预检,step 0),未就绪则秒级失败并打印修复命令,避免"跑一半才发现 server 挂了":
| 检查项 | 不通过表现 |
|---|---|
| 本地 server 可达(HTTP 200) | 打印修复命令后退出 |
构建产物有效(public/index.html > 1000B) | 打印重建命令 |
| playwright 可用 | 跳过依赖浏览器的层 |
themes/matery/_config.yml 未被构建污染 | 自动 git checkout 精确恢复 |
| 网络基址可达(仅发版模式) | 提示基址不可达 |
# 未就绪时打印的 3 条修复命令(任选其一)
docker compose -f docker-compose.test.yml exec -T hexo bash -c 'tools/cicd.sh -d'
tools/test.sh server
docker compose -f docker-compose.test.yml exec -T hexo bash -c 'pm2 restart all'--prepare 自动就绪:容器未跑则启动、产物无效则重建、最后复检。
单实例锁:门禁启动即在 ${TMPDIR:-/tmp}/opencode/gate.lock 取锁,并发运行被直接拒绝(打印持锁 PID);陈旧锁自动接管,trap EXIT 释放。禁止并发门禁——并发会互相 kill 本地 server。
基址与端口自适应:见 §2 的基址段(生产 443 固定 / 本地端口探测)——36 个测试文件靠导出的 TEST_BASE/TEST_URL 自动跟随。
9. L14 视觉断言层
tools/visual-assert/*.json 共 16 套件,门禁 L14 只跑其中 7 套:layout-checks、dark-mode、search-modal、reward-modal、homepage、about、footer。
| 属性 | 值 |
|---|---|
| 语义 | 生产阻塞(失败即 FAIL);本地默认跳过 |
| 本地启用 | RUN_L14=1 tools/tests/release-test.sh |
| 强制跳过 | SKIP_L14=1 |
| 失败处理 | 某套件失败自动重试一次(负载抖动),仍失败才 FAIL,并打印细节 |
| 耗时 | 约 299s(重) |
⚠️
make visual-assert陷阱:该 make 目标依赖build,build 会原地改写themes/matery/_config.yml占位符 → 触发 L5 版本一致性 4 项失败。正确用法:node tools/visual-assert.js run --local [tools/visual-assert/<suite>.json],或交给 L14 层跑。
10. TC 索引与 git hooks
测试的权威来源是 tools/tests/**;生成的 TC 表位于 docs/superpowers/specs/2026-08-06-layout-test-cases.md 的 <!-- GENERATED:START --> 与 <!-- GENERATED:END --> 之间。
| 机制 | 说明 |
|---|---|
node tools/tests/gen-tc-index.js / make tc-index | 重新生成 TC 清单 |
make tc-index-check | 校验清单是否落后 |
.githooks/pre-commit | 提交涉及 tools/tests/ 时自动重生成并 stage 该文档 → 提交陈旧索引在结构上不可能 |
门禁 --check | 汇总前跑 gen-tc-index.js --check(覆盖 --no-verify 等旁路) |
首次克隆需启用一次:git config core.hooksPath .githooks。门禁报"TC 清单不一致"时,跑 make tc-index 再提交(旁路 git commit --no-verify)。
当前索引:13 层 / 33 测试文件 / 3 lib 文件 / 251 断言 / 84 用例名。
11. flake 可见化与基线参考值
flake 可见化:被重试后通过的项目记入 FLAKY_LIST,门禁末尾打印 ⚠️ 本次门禁 flaky: N 项(…);它不改变 PASS/FAIL 计数,只让抖动可见。
| 基线 | 预期 | 命令 |
|---|---|---|
| 本地 | 全部通过(0 失败) | tools/tests/release-test.sh --local |
| 本地 + L14 | 全部通过(0 失败,含视觉断言) | RUN_L14=1 tools/tests/release-test.sh |
| 生产 | 全部通过(0 失败) | tools/tests/release-test.sh(发版后) |
具体 PASS 条数随版本增长(每新增一条断言即变化),以实际输出为准,不写死数字。
3 个 lib:
assert.sh(82 行)、retry.sh(114 行,负责 flake 追踪)、preflight.sh(230 行)。browser-env.js为 playwright 共享辅助(见 §6)。
12. 如何新增一个测试用例
步骤
- 确定测试类型:
- 纯单测 / 静态断言(无浏览器)→ 归 L13(node 直跑)
- playwright 集成(需 chromium + server)→ 归 L11
- 创建测试文件
tools/tests/xxx.test.js(node:test 或 playwright) - 外部依赖测试:用
retry_run/retry_match包装(lib/retry.sh),全失败自动分类降级 - playwright 测试:用
lib/browser-env.js的interceptThirdParty+collectPageErrors统一错误归属 - 注册到 release-test.sh 对应层(L11 或 L13 段),输出格式对齐(
PASS=N FAIL=0或N 通过 / 0 失败) - 本地验证:
tools/tests/release-test.sh --local(跑 L13 + curl 断言);发版门禁跑全量 - 同步 TC 索引:新增/改动后必须让 TC 索引同步(
.githooks/pre-commit自动完成;或手动make tc-index)——否则门禁--check报"TC 清单不一致"
已知覆盖缺口:
wiki-integration.test.js、infinite-scroll-integration.test.js两个文件存在但未挂入任何层(纯 playwright,无浏览器时必失败;由 L11 或发版前手动 playwright 验证覆盖)。新增用例时注意别重蹈覆辙。
mock 必须模拟真实 Hexo 对象(Query 而非普通数组)
写单测 mock site / locals 时,必须贴合 Hexo 的真实对象,否则单测全绿也拦不住构建级故障:
site.posts/site.pages是 Hexo 的 Query(类数组对象),.filter()返回的仍是 Query 而非数组,且不能for...of——必须先.toArray()取真数组。helpers.test.js(字数统计)最初用普通数组 mock,单测全绿;真实构建却wikiPages is not iterable→ 全部 HTML 输出 0 字节。补上toArray()后才覆盖到真实路径。
教训:mock 越接近真实对象,单测才越能拦住构建级故障;用普通数组替代 Query 会测不出
toArray()缺失。
代码模板
L13 单测模板(node:test,无浏览器依赖):
// tools/tests/my-feature.test.js
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const PUBLIC = path.resolve(__dirname, '../../public');
describe('my-feature', () => {
it('should render expected element', () => {
const html = fs.readFileSync(path.join(PUBLIC, 'index.html'), 'utf8');
assert.ok(html.includes('expected-marker'), 'missing expected marker');
});
});
// 末尾输出(release-test.sh 通过此行判定)
const pass = 1, fail = 0;
console.log(`\n结果: PASS=${pass} FAIL=${fail}`);
process.exit(fail > 0 ? 1 : 0);L11 playwright 模板(需 chromium + server):
// tools/tests/my-interaction.test.js
const { chromium } = require('playwright');
const { interceptThirdParty, collectPageErrors } = require('./lib/browser-env');
const BASE = process.env.TEST_BASE || 'http://localhost:4000';
let browser, page;
let pass = 0, fail = 0;
async function setup() {
browser = await chromium.launch();
page = await browser.newPage();
await interceptThirdParty(page);
}
async function testFeature() {
const env = collectPageErrors(page, BASE);
await page.goto(`${BASE}/posts/xxxxx/`, { waitUntil: 'networkidle' });
const el = await page.$('.my-feature');
if (el) { pass++; } else { fail++; console.error('FAIL: .my-feature not found'); }
// 检查无 JS 异常
if (env.jsErrors.length > 0) { fail++; console.error('FAIL: JS errors', env.jsErrors); }
else { pass++; }
}
(async () => {
await setup();
await testFeature();
await browser.close();
console.log(`\n结果: PASS=${pass} FAIL=${fail}`);
process.exit(fail > 0 ? 1 : 0);
})();挂入门禁:在哪里加一行
L13(node 单测):在 release-test.sh 的 ## L13 段追加一行:
run_test "my-feature" "node tools/tests/my-feature.test.js"L11(playwright 集成):在 release-test.sh 的 ## L11 段追加:
retry_match "my-interaction" "node tools/tests/my-interaction.test.js" "PASS=.*FAIL=0"
retry_match包装外部依赖测试——网络抖动时自动重试 3 次,全失败降级为 warn 而非 fail(见 §5)。
完整示例:新增一个「侧边栏折叠」测试
- 需求:验证文章页侧边栏在窄屏下自动折叠
- 类型:需浏览器 → L11 playwright
- 创建
tools/tests/sidebar-collapse.test.js(用上面 L11 模板) - 挂入:在
release-test.shL11 段加retry_match行 - 验证:
tools/tests/release-test.sh --local确认 PASS - TC 同步:
make tc-index(或等 pre-commit hook 自动完成)
断言输出约定
release-test.sh 按输出模式判定通过/失败,测试文件末尾必须输出可匹配的汇总行:
console.log(`\n结果: PASS=${pass} FAIL=${fail}`);
process.exit(fail > 0 ? 1 : 0);测试用例权威清单
docs/superpowers/specs/2026-08-06-layout-test-cases.md(全量 layout 测试用例)+ docs/testing/TESTING-PYRAMID-GUIDE.md(测试方法论)——新增功能后同步更新用例清单与 release-test.sh 覆盖。
13. 性能验证(LCP 基线,手工)
性能指标(LCP)不在自动化门禁内——它需要 Lighthouse 真机采样 + 网络条件控制,断言型测试无法稳定表达。验证方式是基线文档 + 复现命令:
- 基线文档:
docs/analysis/LCP-V2-BASELINE-2026-09-18.md(资源计数 / Lighthouse desktop+mobile / LCP 元素身份 / §5 复现命令 / §7 优化实施) - 优化四手段(2026-09-18~20):关键 CSS preload · highlight 按 layout 条件加载 · markmap 本地化+异步 · 文章页首屏图 preload
- 门槛:mobile LCP ≤ 2.5s(Search Console「40 个 URL 超标」为观测口径)
- 陷阱:noscript 不剥离会让资源计数虚高 4 条/页(基线文档 §1.3),任何复测前必须先剥离
- 触发条件:改动 head 内资源加载顺序/条件(CSS/JS/图片)后必须复测
⚠️ 已知坑
坑 5:改配置后必须 clean 构建
现象:跑门禁时 L1 构建验证通过但功能断言失败。
原因:本地预览使用旧缓存产物,测试断言的是旧版行为。
正确做法:跑门禁前必须 npm run clean && npm run build,或使用 tools/tests/release-test.sh --prepare 自动就绪。
坑 6:source/_data/* 增量缓存误判
现象:修改友链/相册数据后,L2 URL 断言页面空白或数据不更新。
原因:Hexo 增量缓存误判「已缓存未变化」。
正确做法:修改数据文件后先 rm -f db.json && npm run build 再跑门禁。容器环境用 tools/cicd.sh -d(内置 clean)。
附:测试 ↔ 功能对照表
用途:想知道"某个功能有没有被自动测试覆盖"?在这里按功能名查找。
| 功能 | 测试文件 | 层 | 验证什么 |
|---|---|---|---|
| 页面特效(樱花/雪花/烟花) | effects-integration.test.js | L11 | 特效脚本按门控加载,不报错 |
| 视频背景 | videobg-integration.test.js | L11 | 门槛/明暗池/webm 契约/禁用态 |
| 词云 | wordcloud.test.js | L11 | 词云渲染 / 去 jQuery / TOC 防闪烁 / 明暗切换 |
| 多语言 | i18n.test.js | L11 | MATERY_I18N 注入 + 按钮文案 + en 翻译 |
| 音乐 APlayer | musics-integration.test.js | L11 | 多域名 failover |
| 懒加载 | lazyload-integration.test.js | L11 | handleCard 三分发(51 断言) |
| 悬浮面板 | floating-panel-integration.test.js | L11 | 开关 / 字体缩放 / 持久化 |
| 内容宽度 | content-width.test.js | L11 | 4K/FHD/2K/单栏封顶 |
| 阅读模式 | readmode-integration.test.js | L11 | 全屏进入/退出 / 悬浮球 / 代码全屏 / 展开 |
| Wiki 样式 | wiki-styles.test.js | L11 | note / ::: 容器色 + 居中 + 宽度一致 |
| codeWidget 幂等 | code-widget.test.js | L11 | 二次调用后单次点击仍生效 |
| 事件总线 | core-events-integration.test.js | L11 | 事件总线 / 进度条联动 |
| Wiki prism | wiki-prism-integration.test.js | L11 | 代码块 prism + line-numbers |
| 打赏 / 繁简 | misc-interactions.test.js | L11 | 打赏弹窗 / 繁简转换 |
| 客户端语言策略 | lang-client-strategy.test.js | L11 | 全站跟随 / 爬虫跳过 / 面板回显 |
| 富内容渲染 | rich-content-integration.test.js | L11 | 公式 / mermaid / markmap / 打字机 / 分享 / Live2D |
| Wiki 多语言 | wiki-integration.test.js | L11 | 多语言 wiki 可达 / 侧栏 / 语言切换 |
| 灯箱 | lightbox.test.js | L11 | 相册/画廊图片放大交互 |
| 搜索 | unit-search.test.js | L13 | 三引擎配置 |
| 多语言配置 | unit-multilang.test.js | L13 | 语言配置解析 |
| 加密扩展 | unit-encrypt2.test.js | L13 | 片段 + wiki 加密 |
| wiki helper | unit-wiki-helper.test.js | L13 | 语言解析 / 侧栏渲染 / canonical 回落 |
| 侧栏 i18n | unit-sidebar-i18n.test.js | L13 | 查表 + 回退链 |
| 侧栏 key | unit-sidebar-keys.test.js | L13 | 门禁校验器 4 wiki × 9 语言 |
| 回落链 | lang-fallback.test.js | L13 | 4 级回落(13 断言) |
| i18n 核心 | unit-i18n-core.test.js | L13 | resolveI18n 四级链 + cfg 防御 |
| i18n helper | unit-i18n-helper.test.js | L13 | t() helper fallback |
| ECharts | echarts-charts.test.js | L13 | 图表渲染 |
| Banner 背景 | banner-bg.test.js | L13 | banner 与背景偏好联动 |
| 首页结构 | homepage.test.js | L13 | 首页 DOM 结构 |
| 图片尺寸 | imgsize-unit.test.js | L13 | normalizeImgUrl + 尺寸读取逻辑 |
| 独立页 | unit-standalone.test.js | L13 | 排除法判定 + 语言前缀豁免 |
| CSS 架构 | unit-css-architecture.test.sh | L13 | 编译产物关键选择器 |
| 版本号 | unit-version.test.sh | L13 | 递增进位 |
| 重试库 | unit-retry.test.sh | L13 | 退避重试 |
| 部署目标 | unit-deploy-target.test.sh | L13 | hexo/hoback 双激活 |
附:三重定位自查清单
新增/修改一篇「hexo主题功能测试」系列文章时,检查以下三项:
| # | 检查项 | 通过标准 |
|---|---|---|
| ① | 可验证 | 每个功能项有明确的输入 → 期望输出(自动化断言标记或人工核对标准) |
| ② | 有效果 | 每个功能项描述了渲染后的实际效果(文字描述、示例渲染、或截图) |
| ③ | 会使用 | 每个功能项有语法/配置步骤 + 注意事项(参数顺序、前置条件、常见坑) |
快速判断:如果一个功能"只列了名字没说怎么验证"→ 缺 ①;"只列语法不给效果"→ 缺 ②;"没说怎么用或有歧义"→ 缺 ③。
附:测试体系速查表
| 命令 | 用途 |
|---|---|
tools/tests/release-test.sh | 发版全量门禁(L1-L14) |
tools/tests/release-test.sh --local | 本地迭代(端口自适应,跳 L10/L11/L14) |
tools/tests/release-test.sh --prepare | 自动就绪环境(容器 + 构建 + 复检) |
RUN_L14=1 tools/tests/release-test.sh --local | 本地启用 L14 视觉断言 |
make tc-index / make tc-index-check | 生成 / 校验 TC 清单(.githooks/pre-commit 自动同步) |
tools/tests/run-tests.sh | 已于 2026-09-13 删除(与 L11 功能重叠);等价能力见 tools/tests/release-test.sh |
node tools/tests/gen-tc-index.js | 生成/校验 TC 清单(make tc-index) |
make test-pwa | Vercel 测试域 SW 冒烟(7 页)+ 交互验证 |
make visual-compare / visual-assert | 视觉回归(playwright 数值断言);⚠️ make visual-assert 含 build 依赖会污染 _config.yml,优先 node tools/visual-assert.js run --local |
tools/visual-regression.sh report | 视觉对比报告(20 页面 × 4 断点) |
参考&致谢
系列教程
Hexo 主题功能测试系列
- Hexo主题功能测试-布局与页面篇 —— 页面布局、导航、侧边栏、卡片等结构功能
- Hexo主题功能测试-交互视觉篇 —— 代码高亮、图片灯箱、动画、明暗切换等交互
- Hexo主题功能测试-内容tag篇 —— note / tabs / timeline / label / button 等 tag 插件
- Hexo主题功能测试-Markdown语法篇 —— Markdown 渲染与扩展语法
- Hexo主题功能测试-自动化测试篇 —— 主题功能的自动化测试方案

