--- name: emulator-check description: Verify an S2 change on the WSL desktop emulator in minutes — build once, lease a lane, install, run the scripted playback checks (debug receivers) and Maestro UI flows, add a new check, stop the lane. Use before landing any user-visible change, when briefing an emulator-verification worker, or when writing a new Maestro flow or check script. --- # Emulator check Scripted, repeatable verification on a `remote-emu.sh` lane. Drive state with adb broadcasts; for anything the UI must show or be tapped, write or reuse a Maestro flow — not ad-hoc `tap-text`, uiautomator dumps or screenshot-and-look loops, which cost many turns and don't survive for the next change. A full run of the existing checks is minutes, not the 40+ a UI-driven worker used to take for the same ground. ## Run it One call, in the foreground, with a generous timeout: ```bash support/scripts/emu-verify.sh # builds/reuses the APK, runs the full check suite support/scripts/emu-verify.sh --check restore-queue --flow support/maestro/nav/open-settings.yaml support/scripts/emu-verify.sh --apk /tmp/s2-apk/.apk --no-seed --check rapid-skip ``` It starts a lane, installs, seeds the `playback` fixture, runs the named `--check`/`--flow` args (repeatable; `checks/run-all.sh` if neither is given), and always stops the lane on exit (trap), even on failure or Ctrl-C, unless `--keep`. Full command output goes to the log file it prints, not stdout; stdout stays to one line per setup step plus one PASS/FAIL line per check/flow. `--help` for the rest of the flags (`--apk`, `--no-seed`, `--keep`). `--remote ` signs in and imports from a seeded server (`seed-remote-provider.sh`) instead of seeding local media, and exports `S2_REMOTE=` so `remote-reporting` and `remote-playback` run instead of SKIPping. With `--remote` and no `--check`, only those two remote checks run, not the full local suite: ```bash support/scripts/emu-verify.sh --remote jellyfin support/scripts/emu-verify.sh --remote plex --check remote-playback ``` - Re-running the same flow/check after a small code change, on the lane you already leased and seeded this session? Add `--no-reset`: skips the ~2-3 min reset+reseed, since `seed-test-media.sh --if-needed` (always on) is a no-op when the fixture's already there (#412). Never use it for the landing verification -- only the default (reset every time) catches state a prior run left behind. - Run the whole script in the foreground. A headless worker that backgrounds it ends its run (#303). - Never start a local `emulator` or hand-roll `sleep`/`getprop sys.boot_completed` loops: `start` (which `emu-verify.sh` calls) already waits for boot (up to 300 s) and fails loudly. Those loops were the top source of 10-minute Bash timeouts in the Sep 2026 token sweep. - Another job building in the same worktree? Pass `--apk ` to skip Gradle there. - Lane 1 often belongs to another project; `emu-verify.sh` (via `remote-emu.sh start`) picks a free one. ### Manual fallback For a one-off command outside `emu-verify.sh` (a raw `adb` call, a different fixture, leaving the lane up to poke at by hand): ```bash support/scripts/remote-emu.sh start && eval "$(support/scripts/remote-emu.sh env)" support/scripts/remote-emu.sh reset && support/scripts/remote-emu.sh install # [N] for a prebuilt one support/scripts/seed-test-media.sh playback --skip-onboarding support/scripts/checks/run-all.sh # PASS/FAIL per check, non-zero on failure support/scripts/remote-emu.sh stop # ALWAYS, even on failure ``` If a command fails with "device offline"/"device not found" mid-run, the tunnel dropped: `s2-debug.sh` and `checks/*.sh` already reconnect and retry once on their own; for a raw `adb` call run `support/scripts/remote-emu.sh reconnect` yourself first (no reboot, lease kept). ## Pieces | Need | Use | |---|---| | Play, pause, skip, seek, remove from queue, shuffle/repeat, reimport, read state as JSON | `support/scripts/s2-debug.sh ` (the `debug-receivers` skill) | | Ready-made checks | `support/scripts/checks/*.sh` (queue-remove-current, rapid-skip, restore-position, restore-queue, folder-art) | | Taps where the UI is the subject | a Maestro flow in `support/maestro/`, run by a `checks/` wrapper that sets up state first | | One-off taps, dumps, screenshots | `remote-emu.sh tap-text` / `dump-texts`, or the `android-device` skill with the `env` exports | | Notification / lock screen | `adb shell cmd statusbar expand-notifications`, `remote-emu.sh lockscreen on` | | Emulator console (incoming call, ...) | `remote-emu.sh emu gsm call 5551234` / `emu gsm cancel 5551234` (runs `adb emu` on the box; the console port isn't tunnelled) | Maestro: `brew install mobile-dev-inc/tap/maestro` (plain `brew install maestro` is an unrelated app). It only talks to the Mac's adb server on 5037, so pass `--device "$(support/scripts/remote-emu.sh serial)"`. ## Adding a check 1. A new `support/scripts/checks/.sh`, sourcing `_lib.sh`: set up state with `s2`, wait with `wait_for ""`, then assert with `state `, and finish with `pass` or `fail ""`. `run-all.sh` picks it up automatically. 2. Only if taps are the subject: add `support/maestro/.yaml`, reusing `support/maestro/nav/` subflows via `runFlow` for common navigation (see `support/maestro/README.md`) and selecting by visible text otherwise. Call it from the wrapper (see `restore-queue.sh`), or run it directly with `emu-verify.sh --flow support/maestro/.yaml`. Each `maestro test` costs 10–20 s to start, so use one flow per check. 3. Pause playback (`s2-debug.sh PAUSE`) before any uiautomator or Maestro step. While music plays the UI never goes idle and dumps fail. 4. Prefer adding a check that proves the fix over one-off manual steps, so the next change is covered too. 5. A check whose subject is Compose navigation or state, not the device, belongs in a Robolectric test in `:android:app` instead (`support/maestro/CLASSIFICATION.md`). ## Screenshot tour For design audits, `support/scripts/design-shots.sh` (via `longjob.sh start design-shots -- ...`) drives a fixed Maestro flow per screen (`support/maestro/design/`) across themes, text sizes and form factors and writes `shots//` with a `manifest.md`; `--help` has the flags. ## Reporting Report PASS/FAIL per check with its key `DUMP_STATE` values, and screenshots only where the UI is the evidence: Read the PNG so it renders inline. Also confirm that the lane was stopped.