Apply this workflow to new features and behavior fixes. Expand existing coverage through regression tests; report that work accurately rather than claiming retrospective TDD.
If an existing behavior passes its new test immediately, classify it as regression coverage. Do not temporarily damage production code or manufacture a failing assertion to claim TDD. For a reproduced defect, keep the failing regression test and fix the defect through the same boundary.
| Observable contract | Smallest sufficient test |
|---|---|
| Screen text, loading/content/empty/error states, conditional controls, callbacks | Plain state-driven Compose test with controlled state and callbacks |
| ViewModel intents, durable state, retries, request ownership and cancellation | Unit test through process(intent) and exposed state, with controlled external-boundary responses |
| HTTP decoding, request paths/language, response failures | Repository integration test with the real Retrofit/Gson/mapper stack and a local HTTP server |
| Movie/person destination identity, Back, restored scroll, lifecycle or DI | App journey/instrumentation test |
| Layout, image composition, typography, light/dark contrast | Deterministic Paparazzi screenshot and human baseline inspection |
Compose screenshots complement behavioral assertions; they do not prove clicks, retries or navigation. Previews are development aids, not executed tests.
Run ./gradlew verifyPaparazziDebug from the repository root to verify every Paparazzi module. CI runs this as a dedicated screenshot job, with separate failure artifacts from unit tests. test alone does not enable golden comparison.
Screen baselines use Pixel 3, English locale and both themes. Render each plain screen inside SantoroTheme and a full-size Material Surface so the background and inherited content colors match the app shell. Use fixed content, version values and null remote image paths. Restore locale after each test. Keep loading captures at the deterministic initial animation frame.
| Screen | Captured states in each theme |
|---|---|
| Login | Idle, loading, sign-in error |
| Search | Dashboard, results, loading, empty results, error, suggestions |
| See all movies | Content, loading, empty, error, stale saved content |
| Movie detail | Content, loading, error |
| Watchlist | Content, loading, empty collection, no search matches, error |
| Watched movies | Content, loading, empty collection, no search matches, error |
| Profile | Guest, signed-in account, link error |
| Settings | Guest and account actions |
| Person detail | Content, missing biography, credits error, director, large credits preview, profile loading/error, empty credits |
| Person filmography | Full credits, loading, error, empty credits |
The screen suite has 88 captures: 76 added in the screen-coverage change and 12 existing person captures. Component snapshots remain separate. This inventory covers initial screen viewports; it does not claim every dialog, scrolled region, device size or locale. Use Compose/journey tests for interactions and add focused visual variants when a feature changes those regions.
To record intentional baselines, target one module and test class at a time, for example ./gradlew :feature:login:impl:recordPaparazziDebug --tests '*LoginScreenScreenshotTest*'. Gradle task options attach to the preceding task: one trailing --tests filter does not filter every earlier task in a multi-task command. Review every changed image, then run independent root verification. Never commit unrelated golden changes, widen pixel tolerances to hide drift, or use recording as verification.
Before distributing a release candidate, install the signed, minified Release APK on a test emulator and run python3 tools/check-release-startup.py --serial <device-serial> from the repository root. Pass --adb <path> if ADB is not on the command path. This regression check requires a non-debuggable app, performs a cold launch, rejects fresh package-specific crashes, and verifies process survival and a resumed activity. It preserves app data. A passing Debug or journey-test build does not validate Release navigation after R8 shrinking; this startup check complements the Play upgrade and fresh-install checks.
For each new change, record the agreed boundaries in design.md and use vertical test-first tasks. Keep red and green commands/results in the change evidence. A checked task means both its behavior and its validation passed. Document fixture/compiler/environment failures separately from behavioral red.
For regression-only work, record the previously unprotected scenario, the test added and the passing result. Report every modified production/test class and supporting document. Keep manual, screenshot, process-restoration and external gates visibly pending until verified; a normal JVM test task does not substitute for explicit Paparazzi or instrumented execution.
Run Gradle through the managed gradle-run wrapper. Run affected unit/UI/journey checks first. The repository aggregate gate is test detekt ktlintCheck koverVerify assembleDebug assembleRelease; additionally invoke the applicable Paparazzi and connected tests explicitly. Inspect their reports and preserve failed gate evidence.