This article is a complete tutorial for the matery theme’s automated testing system, paired with the other 4 feature test articles (Content Tags / Interaction & Visual / Layout & Pages / Markdown Syntax) — those 4 are the “test arena”, this one is the “test engine”.
Authoritative sources:tools/tests/release-test.sh(L1-L14),docs/superpowers/specs/2026-08-06-layout-test-cases.md.
When to Read This Article?
| You are… | Read this section | What you get |
|---|---|---|
| User (wondering if a feature works) | Appendix “Test ↔ Feature Map” | Find the test file by feature name, confirm “this feature has automated verification” |
| Contributor (adding/modifying a test) | §12 “How to Add a Test Case” | Complete zero-to-gate steps with code templates |
| Maintainer (running the gate before release) | §2 “release-test.sh Usage” | Commands for local iteration / release gate / visual assertions |
| Debugging (gate shows red) | §5 “External Dependency Retry” + §6 “Third-Party Interception” + Known Pitfalls | Distinguish “environment noise” from “real bug” |
1. Testing Pyramid Overview
┌──────────────┐
│ L2 Prod URL │ After release: 20 pages HTTP 200 + feature markers (curl)
│ L9/L10 SW │ Before release: Vercel test domain SW smoke (playwright)
├──────────────┤
│ L11 Playwright Integration │ Real browser: effects/wordcloud/lazyload/read mode, 18 items
├──────────────┤
│ L14 Visual Assertions │ visual-assert precise values: 7 suites (production-blocking, skipped locally by default)
├──────────────┤
│ L13 Unit Gate │ node direct: 16 .test.js + 4 shell unit tests + 1 tutorial-structure gate
├──────────────┤
│ L1 Build Verify │ hexo g Success + artifact complete (index.html > 1000B)
└──────────────┘| Layer | Name | How It Runs | Dependencies |
|---|---|---|---|
| L1 | Build verification | hexo generate + artifact check | none |
| L13 | Full unit test gate | node tools/tests/*.test.js + shell unit tests | no chromium |
| L11 | Playwright integration | real browser + local server | chromium + server |
| L2/L2b/L2x/L2y/L2i | Production URL assertions | curl + grep | production/local server |
| L3-L8 | Feature marker checks | curl + static assertions | production/local server |
| L9 | Static assets + SW smoke | curl + playwright | production domain |
| L10 | SW smoke (test domain) | playwright | Vercel test domain blog2.17lai.site |
| L13a | Coverage statistics | node --experimental-test-coverage scans unit/** | none |
| L14 | Visual assertions | node tools/visual-assert.js run (7 suites) | chromium + server |
2. release-test.sh Usage
# Full run (build + production checks + SW smoke + playwright integration) — release gate
tools/tests/release-test.sh
# Skip build (public/ already ready)
tools/tests/release-test.sh --skip-build
# Local iteration (tests localhost:4000, skips remote SW smoke L10 + playwright integration L11)
tools/tests/release-test.sh --local
# Skip only remote SW smoke (keep playwright integration)
tools/tests/release-test.sh --skip-l10| Flag | Effect |
|---|---|
--skip-build | Skips L1 build + artifact verification (use when already built) |
--local | Target switches to adaptive local base (port detection, see base address section below), skips L10 (remote SW smoke 5min+) and L11 (playwright serial >800s) |
--skip-l10 | Skips only L10, keeps L11 playwright integration |
--prepare | Auto-prepares environment: starts container if not running, rebuilds if artifacts invalid, rechecks at the end |
RUN_L14=1 | Enables L14 visual assertions in local mode (skipped locally by default) |
SKIP_L14=1 | Forces L14 skip (even if RUN_L14=1 is set) |
Base address: production is fixed at https://blog.17lai.site (port 443, never a port number); local uses LOCAL_BASE with adaptive port detection — probe order: BLOG_PORT env var → TEST_BASE env var → root _config.yml server.port (docker 4000) → 4100, take the first HTTP 200; on success exports TEST_BASE/TEST_URL, and all 36 test files follow automatically.
Execution order (156 lines of pure orchestration, not documentation order):
L0 preflight → L10 → L01 → L02 → L03_05 → L07 → L08 → L09 → L11 → L12 → L2i → L13 → L13a → L14 → TC index check → summary. Note L10 runs first (before L01) — old documentation often lists it incorrectly.
Release rule: after
tools/cicd.sh -rpublishes, you MUST runtools/tests/release-test.shto verify the production environment.
3. L1-L14 Layer Coverage
| Layer | Coverage | Assertion Example |
|---|---|---|
| L1 | Build verification | hexo g Success + public/index.html > 1000B |
| L2 | 20 layout page URLs reachable | /posts/fc764afd/, /archives/, /categories/, /tags/, /about/, /friends/, /galleries/, /bb/, /musics/, /movies/, /msg/ + 4 test articles → HTTP 200 |
| L2b | Multilingual verification | en article real translation / fallback notice banner / comment path normalization / default language no prefix |
| L2x | Unified language fallback | MATERY_LANG injection / fallback pages / standalone page multilingual (incl. gallery sub-pages) / aggregation page multilingual |
| L2y | Encrypt/decrypt refresh | wiki sidebar no encrypt_ junk titles / encrypted container complete (hbeForm + storage-key) |
| L2i | SEO structured data | og:image / JSON-LD / fetchpriority |
| L3 | Comment section check | msg/friends/bb pages contain #comments |
| L4 | href=/ style errors | page href=/ references should be 0 |
| L5 | Version consistency | config vs sw.tmp.js consistent in 3 places |
| L6 | Key resource check | 404 page / key JS versions |
| L7 | Core feature coverage | gallery lightbox / encrypted gallery / search / JS module merge / TOC collapse / dark mode / comments / infinite-scroll / wiki code blocks / wiki component styles (main has post-container / .wiki-content word-wrap / admonition .note rule precisely scoped) |
| L8 | Enhanced feature coverage | tag plugin rendering (note/timeline/tabs/label/githubCard/mermaid) / encrypted posts / Feed / static assets / analytics |
| L9 | Static assets + SW smoke | main.css > 50KB / utils.js > 5KB / production domain 7 pages no SW errors |
| L10 | SW smoke (test domain) | blog2.17lai.site 7 pages no SW errors + interaction verification (search/reward/lightbox/TOC/dark mode) |
| L11 | Playwright integration (18 items) | see table below |
| L12 | Localized asset check | mermaid / aplayer assets exist |
| L13 | Full unit test gate | 16 *.test.js + 4 *.test.sh = 20 files (plus 1 tutorial-structure gate) |
| L13a | Coverage statistics | unit/** coverage scan (incidentally runs all unit/** unit tests) |
| L14 | Visual assertions (7 suites) | see §9 |
L11 Playwright Integration List (real browser)
| Test File | Coverage |
|---|---|
effects-integration.test.js | Page effects (sakura/snowflake/fireworks loading) |
videobg-integration.test.js | Video background (threshold/light-dark pool/webm contract/disabled state) |
wordcloud.test.js | Word cloud / no jQuery / TOC anti-flicker / dark-light switching |
i18n.test.js | Multilingual completion (MATERY_I18N injection + button copy + en translations) |
musics-integration.test.js | Online music APlayer (multi-domain failover) |
lazyload-integration.test.js | Lazyload handleCard three-way dispatch (51 assertions) |
floating-panel-integration.test.js | Floating panel (toggle/font scaling/persistence) |
content-width.test.js | Content width unification (4K/FHD/2K/single-column cap) |
readmode-integration.test.js | Read mode / code fullscreen / scroll-down-bar |
wiki-styles.test.js | Wiki tag component styles (note / ::: container colors + centering + width consistency) |
code-widget.test.js | codeWidget delegation idempotency (second invocation still works with single click) |
core-events-integration.test.js | Event bus / progress bar linkage |
wiki-prism-integration.test.js | Wiki code blocks prism + line-numbers |
misc-interactions.test.js | Reward popup / trad-simp conversion (G14/G15) |
lang-client-strategy.test.js | Client language strategy (site-wide follow/crawler skip/panel echo, 7 assertions) |
rich-content-integration.test.js | Rich content rendering (math MathJax+KaTeX/mermaid/markmap/typewriter/share/Live2D, 11 assertions) |
wiki-integration.test.js | Multilingual wiki reachability / sidebar sections / language switcher (18 assertions) |
lightbox.test.js | Lightbox (gallery/image zoom interaction) |
4. Unit Test List (tools/tests/)
L13 Gate Unit Tests (run directly by release-test.sh)
# node unit tests (pure unit/static, no browser dependency)
unit-toc.test.js # TOC collapse config (collapseDepth 0||6 falsy trap)
unit-search.test.js # search three engines
unit-multilang.test.js # multilingual config
unit-encrypt2.test.js # encryption extensions (fragment + wiki)
unit-wiki-helper.test.js # wiki helper
helpers.test.js # word-count helpers (wordcount/min2read/totalcount; encrypted origin / wiki de-dup / fallback / cache reset, 52 assertions)
lang-fallback.test.js # fallback chain 4 levels (13 assertions)
unit-i18n-core.test.js # i18n core fallback semantics (resolveI18n 4-level chain + cfg defense, 10 cases)
unit-i18n-helper.test.js # t() helper (fallback_lang + case tolerance, 5 cases)
echarts-charts.test.js # ECharts charts
banner-bg.test.js # banner & background preference linkage
homepage.test.js # homepage structure
imgsize-unit.test.js # image size
search-integration.test.js
infinite-scroll-aos.test.js
unit-standalone.test.js # standalone page exclusion logic + language prefix exemption (22 assertions)
unit-markmap-prune.test.js # markmap resource pruning on pages without mindmaps (6 cases; covered by L13a_cov via unit/** glob)
unit-updated.test.js # "Updated on" takes the git last-commit time (4 cases: CJK path / missing / overwrite as moment / author value preserved)
# shell unit tests
unit-css-architecture.test.sh # CSS architecture (key selectors in compiled output)
unit-version.test.sh # version increment carry (prevents 12.99→12.100)
unit-retry.test.sh # backoff retry library
unit-deploy-target.test.sh # deploy target switching (hexo/hexoback dual-activation verification)Other Unit Tests (former run-tests.sh L1 / L8 sections, now merged into release-test.sh)
unit-encrypt.test.js # self-built encryption system (AES-GCM + PBKDF2 closed loop, 9 cases)
unit-fontawesome.test.py # FontAwesome minify script5. External Dependency Retry & Degradation (lib/retry.sh)
Third-party network jitter is unavoidable (Algolia API / CDN videos / music APIs), and an external service being unavailable is not a product defect and should not block a release. tools/tests/lib/retry.sh handles this uniformly:
source tools/tests/lib/retry.sh
# exit-code based: backoff retry 3 times (1s/4s/9s + jitter)
retry_run "L2 search" "node tools/tests/search-integration.test.js"
# output-pattern based (used by release-test L11)
retry_match "L2d video" "node .../videobg-integration.test.js" "PASS=.*FAIL=0"Failure classification (when all retries fail, distinguish by log signature):
| Signature | Verdict | Result |
|---|---|---|
ERR_NAME_NOT_RESOLVED / ECONNREFUSED / ETIMEDOUT / 429 / 50x etc. | external unavailable | ⚠️ degraded warn (returns 0, does not block release) |
| no external signature | business assertion failure | ❌ fail (returns 1, real bug) |
6. Third-Party Interception + ERR_ABORTED Exclusion (lib/browser-env.js)
Shared environment helper for playwright tests, unifying “page’s own errors” attribution:
const { interceptThirdParty, collectPageErrors } = require('./lib/browser-env');
const env = collectPageErrors(page, BASE); // continuously accumulates JS exceptions + same-origin resource failures
await interceptThirdParty(page); // intercepts known pollution sources
// assert: env.jsErrors.length === 0 && env.sameOriginFailures.length === 0| Mechanism | Description |
|---|---|
interceptThirdParty | Intercepts known pollution sources (e.g. webpushr returning non-JSON → page unhandled rejection), returns valid JSON instead of aborting (abort produces net::ERR_FAILED, becoming new noise) |
collectPageErrors | Collects only JS exceptions + same-origin (localhost/production domain) resource failures; third-party CDN/image-host failures are environment noise and not counted |
ERR_ABORTED exclusion | Navigation interruption (multiple goto/leaving the page cancels in-flight requests) is not a resource error, skipped directly |
7. Minify Switch Verification (cicd.sh)
tools/cicd.sh injects the minify switch per mode (sed replaces the # minify-switch marker lines in the root _config.yml):
| Mode | minify value | Description |
|---|---|---|
-d / --debug | false | no minification (local debugging, readable output) |
-r / -c / -t | true | minified (hexo-minify always minifies at generate time, so its enable must be toggled) |
Verification method:
# 1. debug build (unminified)
tools/cicd.sh -d
head -c 300 public/css/main.css # multi-line, indented, with comments
# 2. minified build (-t local compile test, minified without CDN)
tools/cicd.sh -t
head -c 300 public/css/main.css # single line, no comments, significantly smallerAfter building, cicd.sh restores
_config.yml(only when the changes involve just the minify-switch marker lines), keeping the working tree clean.
8. Environment Preflight, Single-Instance Lock & Base Address Adaptation
Before the gate runs, preflight (step 0) checks environment readiness; if not ready it fails immediately and prints fix commands, preventing “running halfway才发现 server is down”:
| Check | Failure Behavior |
|---|---|
| Local server reachable (HTTP 200) | Prints fix commands then exits |
Build artifacts valid (public/index.html > 1000B) | Prints rebuild commands |
| playwright available | Skips browser-dependent layers |
themes/matery/_config.yml not polluted by build | Auto git checkout to restore exactly |
| Network base address reachable (release mode only) | Reports base address unreachable |
# Three fix commands printed when not ready (pick any one)
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 auto-prepares: starts container if not running, rebuilds if artifacts invalid, rechecks at the end.
Single-instance lock: the gate acquires a lock at ${TMPDIR:-/tmp}/opencode/gate.lock on startup; concurrent runs are rejected outright (prints the lock-holding PID); stale locks are automatically taken over, trap EXIT releases. Concurrent gates are forbidden — they kill each other’s local server.
Base address & port adaptation: see §2’s base address section (production fixed at 443 / local port detection) — 36 test files follow automatically via exported TEST_BASE/TEST_URL.
9. L14 Visual Assertion Layer
tools/visual-assert/*.json has 16 suites total; the gate L14 only runs 7 of them: layout-checks, dark-mode, search-modal, reward-modal, homepage, about, footer.
| Property | Value |
|---|---|
| Semantics | Production-blocking (failure = FAIL); skipped locally by default |
| Enable locally | RUN_L14=1 tools/tests/release-test.sh |
| Force skip | SKIP_L14=1 |
| Failure handling | A suite that fails is automatically retried once (load jitter); only fails if still failing after retry, with details printed |
| Duration | ~299s (heavy) |
⚠️
make visual-asserttrap: that make target depends onbuild, which overwritesthemes/matery/_config.ymlplaceholders in-place → triggers L5 version consistency 4-item failures. Correct usage:node tools/visual-assert.js run --local [tools/visual-assert/<suite>.json], or let the L14 layer run it.
10. TC Index & Git Hooks
The authoritative source for tests is tools/tests/**; the generated TC table lives between <!-- GENERATED:START --> and <!-- GENERATED:END --> in docs/superpowers/specs/2026-08-06-layout-test-cases.md.
| Mechanism | Description |
|---|---|
node tools/tests/gen-tc-index.js / make tc-index | Regenerate the TC index |
make tc-index-check | Check if the index is stale |
.githooks/pre-commit | When a commit touches tools/tests/, auto-regenerates and stages the document → submitting a stale index is structurally impossible |
Gate --check | Runs gen-tc-index.js --check before summary (covers --no-verify and other bypasses) |
First clone requires one-time setup: git config core.hooksPath .githooks. When the gate reports “TC index inconsistent”, run make tc-index then commit (bypass: git commit --no-verify).
Current index: 13 layers / 42 test files / 3 lib files / 284 assertions / 406 test names.
11. Flake Visibility & Baseline Reference Values
Flake visibility: items that pass after retry are recorded in FLAKY_LIST; the gate prints ⚠️ This gate had N flaky items (…) at the end; it does not change PASS/FAIL counts, it only makes jitter visible.
| Baseline | Expected | Command |
|---|---|---|
| Local | All pass (0 failures) | tools/tests/release-test.sh --local |
| Local + L14 | All pass (0 failures, including visual assertions) | RUN_L14=1 tools/tests/release-test.sh |
| Production | All pass (0 failures) | tools/tests/release-test.sh (after release) |
The exact PASS count grows with each version (every added assertion changes it); rely on the actual output instead of a hard-coded number.
3 libs:
assert.sh(82 lines),retry.sh(114 lines, responsible for flake tracking),preflight.sh(230 lines).browser-env.jsis a playwright shared helper (see §6).
12. How to Add a Test Case
Steps
- Determine the test type:
- Pure unit / static assertions (no browser) → L13 (node direct)
- playwright integration (needs chromium + server) → L11
- Create the test file
tools/tests/xxx.test.js(node:test or playwright) - External dependency tests: wrap with
retry_run/retry_match(lib/retry.sh), auto-classify and degrade on total failure - playwright tests: use
lib/browser-env.js’sinterceptThirdParty+collectPageErrorsfor unified error attribution - Register in release-test.sh at the corresponding layer (L11 or L13 section), align the output format (
PASS=N FAIL=0orN passed / 0 failed) - Local verification:
tools/tests/release-test.sh --local(runs L13 + curl assertions); the release gate runs the full suite - Sync TC index: after adding/modifying, the TC index must stay in sync (
.githooks/pre-commitdoes this automatically; or manually runmake tc-index) — otherwise the gate--checkreports “TC index inconsistent”
Known coverage gaps:
wiki-integration.test.jsandinfinite-scroll-integration.test.jsexist but are not hooked into any layer (pure playwright, always fails without a browser; covered by L11 or a manual pre-release playwright run). Be careful not to repeat this pattern when adding new tests.
Mocks Must Model Real Hexo Objects (Query, Not Plain Arrays)
When unit-testing with a mocked site / locals, model Hexo’s real objects — otherwise a fully green suite still lets a build-breaking bug through:
site.posts/site.pagesare Hexo Query objects (array-like):.filter()returns another Query, not an array, and they cannot befor...of-iterated — call.toArray()first.helpers.test.js(word count) first mocked them as plain arrays: unit tests passed, but the real build threwwikiPages is not iterable→ every HTML output was 0 bytes. The mock only exercised the real path aftertoArray()was added.
Lesson: the closer a mock is to the real object, the better it catches build-level failures; substituting a plain array for a Query hides a missing
toArray().
Code Templates
L13 unit test template (node:test, no browser):
// 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');
});
});
// Output at end (release-test.sh uses this line to judge pass/fail)
const pass = 1, fail = 0;
console.log(`\nResult: PASS=${pass} FAIL=${fail}`);
process.exit(fail > 0 ? 1 : 0);L11 playwright template (needs 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'); }
// Check no JS errors
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(`\nResult: PASS=${pass} FAIL=${fail}`);
process.exit(fail > 0 ? 1 : 0);
})();Hooking Into the Gate: Where to Add a Line
L13 (node unit test): Append one line in the ## L13 section of release-test.sh:
run_test "my-feature" "node tools/tests/my-feature.test.js"L11 (playwright integration): Append in the ## L11 section:
retry_match "my-interaction" "node tools/tests/my-interaction.test.js" "PASS=.*FAIL=0"
retry_matchwraps external-dependency tests — auto-retries 3 times on network jitter; total failure degrades to warn instead of fail (see §5).
Worked Example: Adding a “Sidebar Collapse” Test
- Requirement: Verify the post sidebar auto-collapses on narrow screens
- Type: Needs browser → L11 playwright
- Create
tools/tests/sidebar-collapse.test.js(using the L11 template above) - Hook in: Add a
retry_matchline in the L11 section ofrelease-test.sh - Verify:
tools/tests/release-test.sh --localconfirms PASS - TC sync:
make tc-index(or let the pre-commit hook do it automatically)
Assertion Output Convention
release-test.sh judges pass/fail by output pattern, so the test file must print a matchable summary line at the end:
console.log(`\nResult: PASS=${pass} FAIL=${fail}`);
process.exit(fail > 0 ? 1 : 0);Authoritative Test Case List
docs/superpowers/specs/2026-08-06-layout-test-cases.md (full layout test cases) + docs/testing/TESTING-PYRAMID-GUIDE.md (testing methodology) — after adding a feature, update the case list and release-test.sh coverage in sync.
13. Performance Verification (LCP Baseline, Manual)
Performance metrics (LCP) are not part of the automated gate — they require Lighthouse sampling on real devices with controlled network conditions, which assertion-style tests cannot express stably. Verification is by baseline document + reproduction commands:
- Baseline:
docs/analysis/LCP-V2-BASELINE-2026-09-18.md(resource counts / Lighthouse desktop+mobile / LCP element identity / §5 reproduction commands / §7 optimization work) - Four optimizations (2026-09-18~20): critical CSS preload · highlight conditional loading by layout · markmap localized + async · post hero-image preload
- Threshold: mobile LCP ≤ 2.5s (Search Console “40 URLs over threshold” is the observation basis)
- Pitfall: not stripping
noscriptinflates the resource count by 4 per page (baseline §1.3) — always strip before re-measuring - Trigger: re-measure whenever head-level resource loading order/conditions change (CSS/JS/images)
⚠️ Known Pitfalls
Pitfall 5: Config Changes Require Clean Build
Symptom: L1 build verification passes but feature assertions fail when running the gate.
Cause: Local preview uses stale cached artifacts; test assertions are checking old behavior.
Fix: Before running the gate, you must npm run clean && npm run build, or use tools/tests/release-test.sh --prepare for auto-preparation.
Pitfall 6: source/_data/* Incremental Cache Misjudgment
Symptom: After modifying friends/gallery data, L2 URL assertions show blank pages or data doesn’t update.
Cause: Hexo’s incremental cache incorrectly judges “cached, unchanged”.
Fix: After modifying data files, run rm -f db.json && npm run build before the gate. In container environments, use tools/cicd.sh -d (includes clean).
Appendix: Test ↔ Feature Map
Purpose: Want to know “is feature X covered by automated tests?” — look up by feature name below.
| Feature | Test File | Layer | What It Verifies |
|---|---|---|---|
| Page effects (sakura/snowflakes/fireworks) | effects-integration.test.js | L11 | Effect scripts load per gate, no errors |
| Video background | videobg-integration.test.js | L11 | Threshold/light-dark pool/webm contract/disabled state |
| Word cloud | wordcloud.test.js | L11 | Rendering / no jQuery / TOC anti-flicker / dark-light switch |
| Multilingual | i18n.test.js | L11 | MATERY_I18N injection + button copy + en translations |
| Music APlayer | musics-integration.test.js | L11 | Multi-domain failover |
| Lazyload | lazyload-integration.test.js | L11 | handleCard three-way dispatch (51 assertions) |
| Floating panel | floating-panel-integration.test.js | L11 | Toggle / font scaling / persistence |
| Content width | content-width.test.js | L11 | 4K/FHD/2K/single-column cap |
| Read mode | readmode-integration.test.js | L11 | Fullscreen enter/exit / floating ball / code fullscreen / expand |
| Wiki styles | wiki-styles.test.js | L11 | Note / ::: container colors + centering + width consistency |
| codeWidget idempotency | code-widget.test.js | L11 | Second invocation still works with single click |
| Event bus | core-events-integration.test.js | L11 | Event bus / progress bar linkage |
| Wiki prism | wiki-prism-integration.test.js | L11 | Code blocks prism + line-numbers |
| Reward / trad-simp | misc-interactions.test.js | L11 | Reward popup / simplified-traditional conversion |
| Client language strategy | lang-client-strategy.test.js | L11 | Site-wide follow / crawler skip / panel echo |
| Rich content | rich-content-integration.test.js | L11 | Math / mermaid / markmap / typewriter / share / Live2D |
| Wiki multilingual | wiki-integration.test.js | L11 | Multilingual wiki reachability / sidebar / language switcher |
| Lightbox | lightbox.test.js | L11 | Gallery/image zoom interaction |
| Search | unit-search.test.js | L13 | Three-engine config |
| Multilingual config | unit-multilang.test.js | L13 | Language config parsing |
| Encryption extensions | unit-encrypt2.test.js | L13 | Fragment + wiki encryption |
| Wiki helper | unit-wiki-helper.test.js | L13 | Language parsing / sidebar rendering / canonical fallback |
| Sidebar i18n | unit-sidebar-i18n.test.js | L13 | Lookup + fallback chain |
| Sidebar keys | unit-sidebar-keys.test.js | L13 | Gate validator 4 wikis × 9 languages |
| Fallback chain | lang-fallback.test.js | L13 | 4-level fallback (13 assertions) |
| i18n core | unit-i18n-core.test.js | L13 | resolveI18n 4-level chain + cfg defense |
| i18n helper | unit-i18n-helper.test.js | L13 | t() helper fallback |
| ECharts | echarts-charts.test.js | L13 | Chart rendering |
| Banner background | banner-bg.test.js | L13 | Banner & background preference linkage |
| Homepage structure | homepage.test.js | L13 | Homepage DOM structure |
| Image size | imgsize-unit.test.js | L13 | normalizeImgUrl + dimension reading |
| Standalone pages | unit-standalone.test.js | L13 | Exclusion logic + language prefix exemption |
| CSS architecture | unit-css-architecture.test.sh | L13 | Key selectors in compiled output |
| Version numbers | unit-version.test.sh | L13 | Increment carry |
| Retry library | unit-retry.test.sh | L13 | Backoff retry |
| Deploy targets | unit-deploy-target.test.sh | L13 | hexo/hexoback dual-activation |
Appendix: Triple-Role Self-Check List
When adding or modifying an article in the “hexo theme feature test” series, check these three items:
| # | Check | Pass Criteria |
|---|---|---|
| ① | Verifiable | Every feature has a clear input → expected output (automated assertion marker or manual verification standard) |
| ② | Demonstrated | Every feature describes its rendered effect (text description, rendered example, or screenshot) |
| ③ | Actionable | Every feature has syntax / config steps + caveats (parameter order, prerequisites, common pitfalls) |
Quick check: If a feature “only lists its name without saying how to verify” → missing ①; “only lists syntax without showing effect” → missing ②; “doesn’t explain how to use it or is ambiguous” → missing ③.
Appendix: Testing System Quick Reference
| Command | Purpose |
|---|---|
tools/tests/release-test.sh | Full release gate (L1-L14) |
tools/tests/release-test.sh --local | Local iteration (adaptive port, skips L10/L11/L14) |
tools/tests/release-test.sh --prepare | Auto-prepare environment (container + build + recheck) |
RUN_L14=1 tools/tests/release-test.sh --local | Enable L14 visual assertions locally |
make tc-index / make tc-index-check | Generate / verify TC index (.githooks/pre-commit auto-syncs) |
tools/tests/run-tests.sh | Removed 2026-09-13 (overlapped with L11); see tools/tests/release-test.sh |
node tools/tests/gen-tc-index.js | Generate/verify TC index (make tc-index) |
make test-pwa | Vercel test domain SW smoke (7 pages) + interaction verification |
make visual-compare / visual-assert | Visual regression (playwright numeric assertions); ⚠️ make visual-assert includes a build dependency that pollutes _config.yml, prefer node tools/visual-assert.js run --local |
tools/visual-regression.sh report | Visual comparison report (20 pages × 4 breakpoints) |
References
系列教程
Hexo 主题功能测试系列
- Hexo主题功能测试-布局与页面篇 —— 页面布局、导航、侧边栏、卡片等结构功能
- Hexo主题功能测试-交互视觉篇 —— 代码高亮、图片灯箱、动画、明暗切换等交互
- Hexo主题功能测试-内容tag篇 —— note / tabs / timeline / label / button 等 tag 插件
- Hexo主题功能测试-Markdown语法篇 —— Markdown 渲染与扩展语法
- Hexo主题功能测试-自动化测试篇 —— 主题功能的自动化测试方案

