Files
houseplan-card/docs/testing-notes/infrastructure.md
T
Claude 43459bcbe0 ci(mutants): an unmatched --test-name-pattern is setup-failure, not survived (#650)
A name-filtered `node --test` that matches no test exits 0 and TAP reports
only the file itself. The clean run took that for a healthy witness and the
mutant run for `survived`, although no assertion ran either time — as in
Validate 36151140394 on #649, where a renamed test left a guard pattern empty.

- mutation-guard-outcome: nodeTestSelection/executedTestNames/
  emptyTestSelection; a green oracle with a name filter and zero executed
  named tests is `setup-failure` (clean run: «FAIL чистая подготовка»,
  diff mode: attributed like any setup failure, #568).
- mutation-gate --check: staticTestSelectionProblems — every pattern must
  match a literal test(/it(/describe( name in the guard's files; files with
  ${…} names only warn. POSIX quoting keeps \( inside "…".
- Registry today: 349 unique name-filtered commands run on dev, all execute
  ≥ 1 test; --check: 0 errors, 3 warnings (dynamic names).
- Tests: 6 new in test/mutation-guard-outcome.test.mjs (incl. the installed
  node's real TAP and a registry-wide static check); 3 mutants.
- Docs: TESTING.md pointer, testing-notes/infrastructure.md section.

Issue: #650
User-Visible: no
2026-09-25 18:31:17 +03:00

9.1 KiB
Raw Blame History

Инфраструктура тестов и съёмки

Приложение к docs/TESTING.md: перенесено оттуда дословно (#634). Индекс всех приложений — README.md.

Issue #73 baseline and implementation (2026-08-11)

The published v1.61.0-beta.6 exact SHA is the renderer baseline for #73: it contains the accepted visibility-based light model and the matrix-v7 published baseline; local review fixes prepare matrix-v8 with a semantic warm-pixel gate, including lighting-opaque-glow-two-doorways-dark; canonical light behaviour is docs/LIGHT.md. The owner explicitly started #73 on 2026-08-11.

test/visual-continuity.test.mjs covers tokens, quick/long return, delayed overlay timing, paint barriers and fingerprints. demo/smoke_visual_continuity.mjs samples presented frames and rejects hidden/empty plans, viewport rollback, overlay over a stale frame, missing production attributes and unbounded or sensitive trace data. It supplements rather than replaces warm-remount, websocket-resilience and golden verification.

npm run continuity:screencast is the separate compositor-level gate required before a stable release. It captures acknowledged PNG frames through CDP Page.startScreencast, drives an oscillating multi-frame touch pinch, crops the real plan stage, requires multiple compositor-presented pinch frames and rejects uniform, black, white or transparent-looking frame regressions. It writes the exact frames plus metrics to artifacts/continuity-screencast. Prereleases keep the faster mandatory rAF smoke; the stable release workflow installs Chromium and runs the screencast before attaching the public card asset.

The #579 live-viewport witness is split deliberately. test/live-viewport.test.mjs proves that a budget refresh keeps the identity transform, transform origin, will-change and overflow without a demotion; demo/smoke_live_pan_coverage.mjs also holds the pointer through a full Lit commit in flat, isometric and kiosk modes, then proves terminal cleanup. Before a beta, repeat a physical pinch in HA Companion on the reported Android WebView: Chromium CDP proves the contract and records presented frames, but cannot claim that a vendor WebView compositor has no separate defect.

Lazy editor runtime and frontend asset tree (#337)

  • A cold configured View reaches a complete interactive frame without any request for houseplan-editor-runtime-*.js. The first Plan/Devices/ Background intent requests it once; later editor switches do not repeat the request [auto: smoke_lazy_editor_chunk].
  • Two failed network requests leave mode, camera and plan in View, show the localized retry advice, and the next explicit press starts a fresh load cycle that opens the editor once the network is back. A runtime with a different build fingerprint is terminal and shows the refresh advice (#353) [auto: editor-runtime-loader.test, smoke_lazy_editor_chunk].
  • A cached stale entry whose main chunk the server no longer lists shows a localized "reload the page" panel instead of a silently dead card; hashed chunks are served immutable and an orphan chunk on disk fails the bundle tree check (#353) [auto: smoke_entry_stale, bundle-assets.test, test_frontend_assets].
  • An empty installation requests the dedicated onboarding chunk, displays the first-space dialog and still has no editor request. Saving a drawn first space requests the editor once and continues into Plan; async getConfigElement() still returns houseplan-card-editor [auto: smoke_lazy_editor_chunk].
  • bundle:budget follows transitive static imports and keeps initial View at or below 300000 B gzip (#352: ~10% headroom over the calibrated fact, printed with the trend on every run; recalibrated in #367 from 282000 after the fact moved 255 993 → 273 697 B, of which +14 KB came in a single step — furniture plan-art in the eager graph. Recalibration records the growth, it does not fix it: the lever is a lazy graph. A warning fires while headroom is still 15000 B, two average features before the wall). Bundle sync, demo freshness, CI artifacts and release zip validation fail when any manifest-listed asset is missing or its SHA-256 differs [auto: bundle-assets.test, bundle-freshness.test, release-contract tests].

Съёмка документации запускается с флагами детерминизма (#424)

  • demo/docs/browser-args.mjs содержит --disable-partial-raster и --run-all-compositor-stages-before-draw — оба, а не один. По отдельности ни один дрейф не убирает: проверено перебором, восемь прогонов на конфигурацию.

Первый запрещает переиспользовать ранее нарисованные куски тайла (иначе кадр зависит от того, что композитор рисовал до него), второй заставляет пройти все стадии композитора до отрисовки (иначе снимок берётся на полпути). Подпись дефекта, если он вернётся: один-два случайных кадра из десяти расходятся между прогонами на единицы пикселей и два уровня, а --stability=3 при этом зелёный — внутри одного процесса всё стабильно.

Воспроизводимость съёмки документации (#410, #422)

Кадр в docs/images/ обязан зависеть только от коммита. Если он зависит ещё и от прогона, приёмка скриншотов теряет смысл: «изменилось десять кадров» перестаёт что-либо означать, и разобрать, продукт это или среда, нечем.

Проверяется по двум осям, и одна не заменяет другую:

  • node scripts/capture-determinism.mjs — снимает набор дважды в разных процессах и сравнивает хеши. Ловит дрейф между прогонами — тот самый, что был в #410 (дробная обрезка пересчитывалась от раскладки).
  • node demo/docs/capture.mjs --stability=3 — три снимка подряд в одном процессе и одном состоянии страницы. Ловит дрейф внутри состояния — анимацию, не успевшую замереть, или зависимость от времени.

Обе команды ничего не принимают и не трогают манифест: это измерение, а не съёмка. Первая перезаписывает docs/images (дважды), поэтому запускать её удобнее до приёмки, а не после.

Целочисленность обрезки — единственная часть съёмки, которую можно проверить без браузера: node --test test/capture-clip.test.mjs.

Пустое совпадение --test-name-pattern (#650)

Зелёный node --test с --test-name-pattern, который не исполнил ни одного теста: TAP называет только сам файл (ok 1 - test/x.test.mjs), ассертов не было ни в чистом прогоне, ни на мутанте. Так бывает, когда задача переименовала тест, а гвард реестра остался со старым именем; раньше чистый прогон считал такого свидетеля здоровым, а мутант — выжившим. Шаблон — это RegExp: (, +, ? в имени экранируются (в команде гварда \(отбор\), в JS-строке реестра \\(отбор\\)). mutation-gate --check проверяет то же статически: шаблон обязан совпасть хотя бы с одним литеральным именем test(/it(/describe( в файлах гварда; если в файле есть имена ${…}, несовпадение — предупреждение WARN, иначе FAIL.

Это setup-failure (#550): в дифф-режиме он атрибутируется как любой другой (#568) — переименование теста в задаче даёт introduced, старый пустой шаблон — pre-existing. Свидетель — test/mutation-guard-outcome.test.mjs (тесты #650 …); проверка установленного node на настоящем выводе TAP — там же.