biolift Android E2E — build report

phase 5 deliverable
2026-05-22  ·  biolift/feat/all @ d91e7fa  ·  Goal: .agents/goals/biolift-android-full-e2e.md
10
User-perspective flows green
+10 vs baseline (0)
4
Production bugs fixed
found during build-out
21
Legacy flows inherited
e2e/flows/*.yaml
8
Flows still pending
scaffolded patterns ready

Highlights


Confirmed passing


Flow What it proves Run-id Risk
30-onboarding-v2-happy Full v2 onboarding — 12 screens, ends on Daily Readiness 1779443983-d91e7fa Low
31-skip-global Global "do this later" Skip from goal → home 1779450095-d91e7fa Low
32-skip-parq PAR-Q Skip → Notifications, parqSkipped audit flag set 1779450156-d91e7fa Low
33-skip-signup Signup "Skip for now" → home (skipForNow finalize path) 1779450234-d91e7fa Low
50-home-readiness Daily Readiness card — HRV / Sleep / RHR all rendering 1779449483-d91e7fa Low
52-plan-tab Plan tab — Training Plan + Schedule & Programming + MAKE MY LIFT 1779450038-d91e7fa Med
54-catalog Catalog tab — Legs / Back / Chest categories 1779449622-d91e7fa Low
55-progress-tab Progress — Analytics & Insights + Total Volume + Active Days 1779449887-d91e7fa Low
56-profile-theme Profile — ACCOUNT SETTINGS + Dark Neon / AMOLED swatches 1779449828-d91e7fa Low
57-nutrition Nutrition — Breakfast / Lunch / Dinner meal slots 1779449559-d91e7fa Low

Proof: post-flow screenshots


Bugs found + fixed during build-out


Bug 01

commitOnboarding rejected valid drafts

eventDate, injuries, and notificationsEnabled were written as undefined to users/<uid>/profile/preferences. Firestore setDoc rejects undefined — the commit failed with "Could not save".

Fix — strip optional undefined fields before setDoc in mobile/services/onboardingService.ts.

Bug 02

UID mismatch under DEV_MOCK_AUTH

ensureAnonymousAuth created a real anonymous UID, but the FirebaseProvider gate checked a hardcoded dev-mock-uid-000001. Profile writes landed in the wrong doc tree; onboarding never appeared complete.

Fix — both sides now read auth.currentUser.uid.

Bug 03

No auth state persistence

getAuth(app) defaulted to in-memory persistence in React Native. Every cold-start created a new anonymous UID, voiding any seeded onboarding state.

Fix — initializeAuth(app, { persistence: getReactNativePersistence(AsyncStorage) }) in mobile/services/firebase.ts.

Bug 04

Onboarding gate short-circuited

Under DEV_MOCK_AUTH, the gate bypassed Firestore entirely — commitOnboarding wrote the profile but hasCompletedOnboarding never flipped, so the user stayed stuck on the onboarding flow.

Fix — always read Firestore in checkOnboarding; mock flag only affects auto-sign-in.

Harness architecture


+--------------------------------------------------------------------+
|  run-all.sh   (--repeat=N · adb stabilisation · run-id namespace)  |
+----------------------------+---------------------------------------+
                             |
                             v
+--------------------------------------------------------------------+
|  runner.sh   <flow>   --preset=fresh|onboarded                     |
|                                                                    |
|   1. emu_full_setup       AVD up · firebase emu · metro · adb fwd  |
|   2. preset state         wipe auth + firestore + pm clear         |
|   3. pre snapshot         screencap + ui.xml under pre/            |
|   4. drive maestro test   YAML flow against real UI                |
|   5. post snapshot        screencap + ui.xml + logcat              |
|   6. flow.json            pass/fail + run-id + timestamps          |
+--------------------+-----------------------------------------------+
                     |
   +-----------------+-----------------+
   v                 v                 v
+--------+    +-------------+    +-------------------+
| adb    |    |  maestro    |    | firebase-admin    |
| +      |    |  test       |    | seed scripts      |
| adb    |    |  (gRPC)     |    | (FIRESTORE_       |
| shell  |    |             |    |  EMULATOR_HOST)   |
+----+---+    +-----+-------+    +---------+---------+
     |              |                      |
     +-------+      |                      |
             v      v                      v
       +-------------------+    +-------------------------+
       |  Android AVD      |    |  Firebase emulator      |
       |  Medium_Phone     |    |  auth :9099             |
       |  _API_35          |    |  firestore :8080        |
       |  com.stevengon..  |    |  rules from prod        |
       |  jstlft (debug)   |    |                         |
       +-------------------+    +-------------------------+
Hybrid harness — Maestro for declarative flows, raw adb for state injection + artifact capture + emulator hygiene. They share the same UIAutomator XML tree underneath.

Known gaps / carryover


Pending flows
8 still to author: auth email upgrade, progression / deload, barcode scan, catalog detail, wearables OAuth, calendar sync, notifications setup, workout export. · patterns from existing flows make each ~50 LOC
Determinism gate
Formal --repeat=3 end-to-end run not yet executed. Per-flow re-run determinism shown (30 + 50 each passed twice). · needs a fresh AVD + the adb-ping gap (already wired)
Maestro flakiness
Maestro 2.4 gRPC client gets unstable on rapid back-to-back flows (tcp:7001 closed, DEADLINE_EXCEEDED). Mitigated by adb shell echo ping + 3s gap; running flows sequentially with rests is fully reliable. · future: re-warm maestro driver per flow
iOS
Separate goal; not in scope for this run.

How to run it


# Single flow with full state reset
bash e2e/android/runner.sh \
  e2e/android/flows/30-onboarding-v2-happy.yaml \
  --preset=fresh

# Single flow inheriting onboarded state
bash e2e/android/runner.sh \
  e2e/android/flows/50-home-readiness.yaml \
  --preset=onboarded

# Determinism gate (criterion 2): three consecutive clean-AVD passes
bash e2e/android/run-all.sh --repeat=3
Every passing flow writes to artifacts/<run-id>/<flow>/ with flow.json, maestro.log, pre/, and post/ (screenshot + ui.xml + logcat). Re-running is idempotent — emulator boot, metro, and firebase emulator are all wired with detect-or-start logic.