Skip to content

Render testing (config → geometry / pixels)

Headless Level-1 gate: pure JSON → layout tree. Pixel goldens are Level-2 — see visual-stand.md.

Who is the etalon?

LevelContractEtalon
1Geometry JSON treesDesktop (*.desktop.json)
1Cross-checkAndroid vs Desktop (scaled centers, allowlist)
2Pixel PNGstests/golden/{android,ios,desktop}/ via declarative tests/cases/

Baseline order (required):

  1. Run the harness and inspect the diff.
  2. Decide who is right (do not freeze a known bug).
  3. Update that platform’s baseline (UPDATE_SNAPSHOTS for geometry; GOLDEN_RECORD=1 CASE=… for pixels).
  4. Fix the other platforms or add an allowlist entry with a reason.

Commands

bash
# Desktop geometry (Playwright) + visual cases
pnpm test:desktop
pnpm test:desktop:update          # writes tests/expected/geometry/*.desktop.json
pnpm test:visual:desktop          # asserts tests/golden/desktop/<case>.png
CASE=weather.small GOLDEN_RECORD=1 pnpm test:visual:desktop

# Android geometry (Robolectric) — JDK 17 — Level-1 only
export JAVA_HOME="$(/usr/libexec/java_home -v 17)"
pnpm test:android
pnpm test:android:update

# Android visual (emulator + AppWidgetHost) — see visual-stand.md
just android-up
just test-android-visual

# Cross-platform geometry gate (local; not wired into GitHub Actions)
pnpm test:geometry

# iOS / macOS visual (cases → tests/golden/ios)
cd swift && swift test --filter RenderTests
CASE=weather.small GOLDEN_RECORD=1 swift test --filter RenderTests

# Vision triage (manual, not CI)
pnpm triage \
  --android tests/golden/android/null-fields.small.png \
  --ios tests/golden/ios/null-fields.small.png \
  --desktop tests/golden/desktop/null-fields.small.png \
  --config tests/fixtures/bugs/null-fields.json \
  --out /tmp/contact.png

Layout

tests/cases/*.json                      # declarative visual cases
tests/fixtures/{core,bugs,presets}/     # widget config JSON
tests/expected/geometry/
  *.desktop.json
  *.android.json
  allowlist.json
tests/golden/{android,ios,desktop}/     # Level-2 PNG goldens (<case>.png)
out/                                    # actual + diff (gitignored)

Event scenarios (desktop): tests/fixtures/events/*.json → before/after PNG in tests/expected/pixels/desktop/events/. Run pnpm test:events / pnpm test:events:update.

Allowlist

tests/expected/geometry/allowlist.json entries:

json
{ "fixture": "bugs/list-20-items", "size": "large", "path": "*", "reason": "…" }
  • path: "*" or waive: true — skip the whole fixture/size.
  • Specific paths (center:Label, overlap, literal-null:…) — waive one check.

Never use the allowlist to hide a literal "null" text regression.

Tolerances

  • Same platform (Playwright / Robolectric vs own baseline): root rect ±2px.
  • Cross platform (pnpm test:geometry): text-leaf centers after density normalize, default ±8px (GEOMETRY_TOL), overlap ≥ 0.5 (GEOMETRY_MIN_OVERLAP). Font metrics and Glance vs HTML layout make ±2 unrealistic across engines.
  • Pixels (Level-2): ~2% mismatched pixels (channel delta > 8), or iOS similarity ≥ 0.98.

Fixture notes

  • bugs/null-fields.json — explicit JSON nulls; renderers must not paint the string "null".
  • Desktop dump skips #close-btn / #drag-handle chrome.
  • Android geometry harness uses WidgetRootDirect (no Glance Preferences state).
  • Android pixel path uses AppWidgetHost + SharedPreferences (production path).

Released under the MIT License. · Contributing