test(fcm): Maestro flow proving FCM cold-wake delivers a sync - #54
Merged
Conversation
End-to-end test for the headline contract of `dioxus_fcm_sync`:
when the Android app is killed (swiped from recents), a write made
by another peer reaches the killed phone via FCM wake-up. The
engine wakes briefly in the background, syncs, and shuts back down
— all without the user opening the app.
Three new files in the example directory:
- `test.maestro.phase-a.yaml` — launchApp clear, add a sentinel
("from-phone-A"), `killApp`. We use Maestro's `killApp` (process
kill, FCM-deliverable) rather than `am force-stop` (puts package
in stopped-state, FCM-blocked) — the former is the realistic
shape of "user swiped from recents", which is what the
push-sync feature is designed to handle.
- `test.maestro.phase-b.yaml` — launchApp without clearState,
assert the writer's row is visible within 5s of first paint.
A row appearing only later means FCM wake didn't happen and
foreground-resume sync caught up after the fact — that's a
separate failure mode the README documents.
- `test.sh` — orchestrator that boots a local relay (configured
with FCM creds), starts a writer-peer (the existing `test-peer`
binary from `tests-e2e/`) connected to the same relay+topic+
passphrase, builds & installs the APK with
`WAVESYNC_RELAY_OVERRIDE` pointing at the local relay, runs
phase A, drives the writer to add a uniquely-titled row while
the phone is killed, sleeps 90s for FCM delivery + background
sync, runs phase B.
Two small support changes to keep the example pristine for
non-test users:
- `src/main.rs` reads `WAVESYNC_RELAY_OVERRIDE` via `option_env!()`
with the production relay as fallback. Test infra sets it; users
building normally see no change.
- `build.rs` adds `cargo:rerun-if-env-changed=WAVESYNC_RELAY_OVERRIDE`
so flipping it triggers a rebuild.
Plus `TESTING.md` with: prerequisites (Play-Store AVD,
FCM_CREDENTIALS pointing at a service-account JSON from the same
Firebase project as the bundled google-services.json), running
instructions, pass/fail interpretation, known limitations, and a
note on why `killApp` is the right tool and `force-stop` isn't.
The project's .gitignore says 'Keep doc files tracked — only ignore generated markdown' but the actual rule is **/*.md which catches everything. Force-add this README per the comment's intent so users discovering this directory have prerequisites and pass/fail interpretation in front of them.
Verified end-to-end on a Google Play Services emulator
(system-images;android-36.1;google_apis_playstore;x86_64), with
WaveSyncDB's actual Firebase project. Concrete result:
sentinel reached writer after 1s
Writer adds task 'from-cli-1778192513-2978327' while phone is killed
Sleeping 90s for FCM to deliver and wake the engine in background...
Maestro phase B (relaunch + assert)
Assert that "from-cli-1778192513-2978327" is visible... COMPLETED
Assert that "from-phone-1778192513-2978327" is visible... COMPLETED
PASS — FCM cold-wake delivered 'from-cli-1778192513-2978327'
to the killed app while the UI was dead.
Five issues surfaced and fixed during the run:
1. Relay CLI uses --listen-addr (single, default TCP) +
--ws-listen-addr; multiple --listen-addr is rejected. Switched to
one TCP listen-addr + advertise additional QUIC + ws via
--external-address (mirrors what qr_pairing/test.sh does).
2. Relay's pinned PeerId can't be hardcoded — it's derived from the
identity keypair. Now read from the relay's startup log (`Relay
server PeerId: …`) and substituted into both the writer's
RELAY_ADDR and the APK's WAVESYNC_RELAY_OVERRIDE.
3. Writer-peer's `cargo run --release` cold-compile took 5+ minutes;
my 20-second health-check loop fired before it was up. Added an
up-front `cargo build --release --quiet` for both relay and writer
so the runtime startup timeouts are about binary boot, not compile.
4. Android emulators NAT to the host as 10.0.2.2; the host's LAN IP
isn't reachable from inside the emulator. APK build now uses
10.0.2.2 when ANDROID_SERIAL starts with `emulator-`, LAN-IP
otherwise (physical devices on the same WiFi).
5. Maestro selectors targeted "Tasks" — the actual h1 in
src/main.rs is "WaveSyncDB Mobile Demo". Fixed.
6. Per-run UUID-suffixed sentinels (`from-phone-$RUN_TAG`,
`from-cli-$RUN_TAG`) replace the previous hardcoded strings.
Wired through Maestro `--env` so phase A and phase B agree on
the same row to look for. Eliminates false-positives from
residual SQLite state surviving `clearState`.
7. APK output path under dx is at
`${CARGO_TARGET_DIR}/dx/example-dioxus-fcm-sync/release/android/app/`
not `$HERE/target/`. Find updated to look in the right place.
Plus an `.gitignore` rule for the harness's runtime log/pid dirs.
pvg13
force-pushed
the
feat/fcm-android-maestro-test
branch
from
May 12, 2026 19:46
ca0261f to
2563632
Compare
Three small scope-independent cleanups: - `.gitignore` line 31 comment claimed the `**/*.md` rule kept doc files tracked — it doesn't (the rule excludes everything including the project's working-tree instructions file, which is why we force-add TESTING.md). Replace with an accurate one-liner about force-adding tracked docs. - Two integration tests had module-level doc comments referencing an internal working-tree rule-numbering scheme. Inline the actual substance (mDNS is process-wide, parallel tests cross-discover) so the comment is self-contained for anyone reading the code.
pvg13
force-pushed
the
feat/fcm-android-maestro-test
branch
2 times, most recently
from
May 12, 2026 20:09
70e975d to
51af4ed
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
End-to-end test for the headline contract of `examples/dioxus_fcm_sync`: when the Android app is killed (swiped from recents), a write made by another peer reaches the killed phone via FCM wake-up. Verified empirically — see "Test result" below.
A pass means:
Test result (Google Play Services emulator, AMD x86_64, Linux 6.19)
```
==> Maestro phase A (launch + sentinel + killApp)
phone sentinel: from-phone-1778192513-2978327
==> Waiting for writer to see the sentinel 'from-phone-1778192513-2978327'...
sentinel reached writer after 1s
==> Writer adds task 'from-cli-1778192513-2978327' while phone is killed
==> Sleeping 90s for FCM to deliver and wake the engine in background...
==> Maestro phase B (relaunch + assert)
Assert that "from-cli-1778192513-2978327" is visible... COMPLETED
Assert that "from-phone-1778192513-2978327" is visible... COMPLETED
============================================================
PASS — FCM cold-wake delivered 'from-cli-1778192513-2978327'
to the killed app while the UI was dead.
```
The writer-peer's row landed in the app's local SQLite at first paint of phase B's relaunch — proving the engine was woken in background by FCM, sync ran with the UI dead, and the row was committed before the user re-opened the app.
Why `killApp` and not `am force-stop`
`killApp` (Maestro) is the realistic shape of "user swiped from recents": process exits, FCM still deliverable. `am force-stop` puts the package in stopped state, which Android uses as a security flag to disable FCM until the user manually relaunches — exactly the wrong shape for testing this feature. `TESTING.md` spells this out so future contributors don't 'fix' a flake by switching to `force-stop` and silently invalidating the test.
Files
Sequence
Two small support changes to keep the example pristine for non-test users
Issues fixed during validation
The first run failed and revealed a series of harness bugs. Each commit on the branch documents the fix; the second commit's message has the full list. Highlights:
Test plan