UI testing workflow
Drift-prevention: This Starlight page is the canonical docs-site version. The legacy
docs/*.mdpath now points here instead of carrying a second copy.
Ferrex Player has deterministic app-shell UI flows recorded as .ice scripts
under crates/ferrex-player-app/tests/ui/ and a headless screenshot CLI built
into the ferrex-player binary.
Generated screenshots are developer artifacts. Write them to target/ (the
just shortcuts default to target/ui-screenshots/) or to an explicit caller
path, and do not commit them as visual baselines unless a future issue adds a
reviewed baseline policy.
Discover scenarios
Section titled “Discover scenarios”List the deterministic presets that the screenshot harness can boot:
cargo run -p ferrex-player --profile priority -- screenshot list# orjust screenshot-listThe list is produced from the same preset registry used by the app and includes
names such as FirstRunAuth, DesktopLibraryHome, DesktopMovieDetail,
DesktopSeriesDetail, DesktopSeasonDetail, DesktopEpisodeDetail,
SettingsDevices, TenFootHome, TenFootDetail, and
PlayerLoadingOverlay.
Capture a screenshot
Section titled “Capture a screenshot”A direct capture chooses a preset, logical viewport, scale factor, emulator mode, settle time, and output path:
cargo run -p ferrex-player --profile priority -- screenshot \ --preset DesktopLibraryHome \ --viewport 1280x720 \ --scale-factor 1 \ --mode Immediate \ --settle-ms 200 \ --output target/ui-screenshots/desktop-library-1280x720.pngEach capture writes the PNG plus a JSON sidecar next to it, for example
desktop-library-1280x720.metadata.json. The sidecar records the preset,
logical viewport, physical PNG size, scale factor, mode, settle time, and any
.ice metadata used for replay.
Options:
--preset <NAME>boots a deterministic app state. Usescreenshot listfor names and descriptions.--viewport <WIDTHxHEIGHT>sets the logical app/emulator viewport.--scale-factor <N>controls the physical PNG size. For example,1280x720at--scale-factor 2writes a2560x1440PNG while layout still uses a1280x720viewport.--mode <Zen|Patient|Immediate>selects the iced-test runtime strategy.Immediateis fastest for static preset captures; usePatientor metadata from an.icefile when replaying recorded flows that wait on UI events.--settle-ms <MS>drains runtime actions for a short period before capture.--output <PATH>is required for captures. Parent directories are created.
Replay a .ice script before capture
Section titled “Replay a .ice script before capture”Use --ice to run a recorded UI script before taking the screenshot:
cargo run -p ferrex-player --profile priority -- screenshot \ --ice crates/ferrex-player-app/tests/ui/register_admin.ice \ --output target/ui-screenshots/register-admin-after-flow.pngThe harness reads .ice metadata (viewport, mode, and optional preset) and
uses it as defaults. If you pass explicit --preset, --viewport, or --mode,
they must match the script metadata; mismatches fail early so agents do not
capture the wrong layout.
Short just wrappers
Section titled “Short just wrappers”Common capture commands are available from any subdirectory:
just screenshot-desktop-720just screenshot-first-run-900just screenshot-tv-1080just screenshot-tv-800pThey cover the review viewports currently expected by Ferrex UI work:
| Shortcut | Preset | Viewport | Default artifact |
|---|---|---|---|
just screenshot-desktop-720 | DesktopLibraryHome | 1280x720 | target/ui-screenshots/desktop-library-1280x720.png |
just screenshot-first-run-900 | FirstRunAuth | 1280x900 | target/ui-screenshots/first-run-1280x900.png |
just screenshot-tv-1080 | TenFootHome | 1920x1080 | target/ui-screenshots/tenfoot-home-1920x1080.png |
just screenshot-tv-800p | TenFootHome | 1280x800 | target/ui-screenshots/tenfoot-home-1280x800.png |
Override the artifact path by passing the recipe argument:
just screenshot-tv-1080 /tmp/ferrex-tv-home.pngFor one-off captures, the generic wrapper forwards arguments to the CLI:
just screenshot --preset TenFootDetail --viewport 1920x1080 \ --scale-factor 1 --mode Immediate --output target/ui-screenshots/detail.pngAdaptive detail QA matrix
Section titled “Adaptive detail QA matrix”The adaptive detail routes have deterministic screenshot presets for the final QA pass:
| Route | Preset | Primary viewport coverage |
|---|---|---|
| Movie detail | DesktopMovieDetail | 480x900, 640x480, 800x600, 1024x768, 1280x720, 1366x768, 1920x1080, 2560x1440, 3440x1440, 900x1600 |
| Series detail | DesktopSeriesDetail | Same desktop matrix; verifies season rail, disabled/recovery states when rows are missing, and next-episode actions when rows exist. |
| Season detail | DesktopSeasonDetail | Same desktop matrix; verifies episode rail sizing, primary play action, and Back/Home recovery actions. |
| Episode detail | DesktopEpisodeDetail | Same desktop matrix; verifies responsive still artwork and playback/MPV actions. |
| 10-foot detail | TenFootDetail | 1280x720, 1280x800, 1366x768, 1920x1080, 2560x1440, 3440x1440 |
Expected invariants for every detail capture:
- Hero art stays inside the content stage; poster routes keep a poster aspect and episode routes keep a 16:9 still aspect.
- Title, metadata pills, and primary actions remain visible without horizontal clipping; compact/tall portrait windows stack hero content vertically.
- Backdrop/aspect controls align to the foreground detail plan and do not cover primary actions.
- Relationship rails keep bounded horizontal scrolling/virtualization; ultrawide captures center the readable stage instead of stretching cards edge to edge.
- Missing repository rows or missing artwork render designed notices or neutral artwork panels with Back/Home recovery where applicable.
- 10-foot detail preserves visible focus rings, D-pad/keyboard focus movement, scroll-follow margins, and one- vs two-row panel windows according to the solved viewport plan.
Example matrix capture command:
for preset in DesktopMovieDetail DesktopSeriesDetail DesktopSeasonDetail DesktopEpisodeDetail; do for viewport in 480x900 640x480 800x600 1024x768 1280x720 1366x768 1920x1080 2560x1440 3440x1440 900x1600; do just screenshot --preset "$preset" --viewport "$viewport" \ --scale-factor 1 --mode Immediate --settle-ms 200 \ --output "target/ui-screenshots/${preset}-${viewport}.png" donedoneRun UI tests and smoke tests
Section titled “Run UI tests and smoke tests”Replay every committed .ice script:
cargo test -p ferrex-player-app --test ui_end_to_endRun the renderer-dependent screenshot smoke test:
cargo test -p ferrex-player-app --test screenshot_smoke -- --nocaptureWhen a headless renderer is available, the smoke test captures one small
FirstRunAuth PNG in a temporary directory and verifies the PNG plus metadata
sidecar were written. When iced/WGPU cannot initialize a headless adapter, it
skips with the same actionable renderer-unavailable reason as the .ice replay
test.
Renderer troubleshooting
Section titled “Renderer troubleshooting”The tests and CLI both use iced_test::Emulator, so they require a usable WGPU
adapter even though no display server is required.
Existing skip behavior:
ui_end_to_endcatches the known iced-test renderer initialization panic and prints an actionable skip reason instead of failing unrelated validation.screenshot_smokecaptures when possible and prints the same skip reason when the renderer cannot initialize.- A skipped renderer-dependent test means the current machine could not provide the emulator renderer; it does not prove the UI flow or screenshot succeeded.
The screenshot CLI differs from tests: ferrex-player screenshot ... is an
artifact-producing command, so renderer initialization failure exits non-zero and
prints the WGPU/headless troubleshooting hint rather than silently skipping.
Useful diagnostics and workarounds:
FERREX_SCREENSHOT_TRACE=1 cargo run -p ferrex-player --profile priority -- screenshot ...WGPU_BACKEND=vulkan cargo test -p ferrex-player-app --test screenshot_smoke -- --nocaptureWGPU_BACKEND=gl LIBGL_ALWAYS_SOFTWARE=1 WGPU_ADAPTER_NAME=llvmpipe \ cargo test -p ferrex-player-app --test screenshot_smoke -- --nocaptureIf those still fail, confirm Mesa/Vulkan software rendering libraries are
available in the shell. Native platforms can also try WGPU_BACKEND=metal or
WGPU_BACKEND=dx12 as appropriate.
Player crate graph checks
Section titled “Player crate graph checks”When UI extraction work changes player crate dependencies or domain/update boundaries, run the dependency guard before broader validation:
./scripts/check-player-crate-boundaries.shPair it with focused player checks as needed:
cargo check -p ferrex-player-app --all-targetscargo test -p ferrex-player-app --test ui_end_to_endcargo test -p ferrex-player-app --test screenshot_smoke -- --nocapturecargo test -p ferrex-player-library -p ferrex-player-media -p ferrex-player-searchFocused visual QA checklist
Section titled “Focused visual QA checklist”Use this checklist when validating UI extraction work or follow-up visual polish. Record the app mode, viewport, server/demo data source, and any screenshots or regressions in the PR/release note.
Desktop surfaces
Section titled “Desktop surfaces”- Library/home: poster grids and virtual carousels render images, text, hover or keyboard focus, sort/filter controls, and loading/error states without clipped content.
- Detail: movie and TV detail routes show poster/backdrop art, cast cards, technical details, play/resume actions, and watch-state badges.
- Auth: first-run setup, user selection, credential entry, PIN setup/login, loading, and retry/error flows preserve focus and do not require clearing app data to recover.
- Settings: sidebar navigation plus profile, security, devices, libraries, display, playback, performance, server, theme, and users sections render and retain input/focus state while switching sections.
10-foot surfaces
Section titled “10-foot surfaces”- Home: TV rails, poster focus rings, context menu close/restore behavior, and vertical navigation across empty/non-empty rails work at 1920x1080 and a small 800p-style viewport.
- Detail: hero content, two-row focus window, resume/start-over labels, related media columns, and D-pad movement stay visible in TV mode.
- Player overlay: transparent video container, command focusables, progress bar, time labels, hidden-control filtering, and spatial navigation remain inside the viewport.
- If you add/change
.icescripts, keep them small and stable. - Prefer screenshots under
target/ui-screenshots/for PR evidence so cleanup is automatic with other build artifacts. - Interactive recording remains experimental; the default
ferrex-playerbinary is daemon-based and does not currently expose a dedicated record mode.