Files
halobestie-clone/client_app/.maestro
ramadhan sjamsani 14b5cc966b Phase 4 Stage 6: chat-room countdown UX + voice-call mode pill
Customer chat screen:
- Voice-call header pill (mode == 'call' renders accent-colored pill;
  chat mode renders no pill).
- HaloSnackbar fires once per session at 180s remaining ('sisa 3 menit
  lagi ya 🤍'), driven by the backend session_warning WS event.
- Last-2-min danger styling: timer pill flips to HaloTokens.danger +
  bold JetBrainsMono when remaining <= 120s.
- Floating ChatExpiredBanner widget injected above the input bar when
  remaining hits 0 in closing-grace state. perpanjang -> existing
  pricing bottom sheet.
- pricing_bottom_sheet.dart rewritten to the 5-option layout with
  chat|call mode toggle (mirrors duration-pick from Stage 3).

Mitra chat screen: voice-call header pill only (no countdown UX per PRD).

Backend:
- session.service.js getSessionById JOINs payment_sessions so mode +
  expires_at ship in /api/shared/chat/:id/info.
- session-timer.service.js onThreeMinuteWarning now emits expires_at +
  remaining_seconds for client resync.
- Dev-only POST /internal/_test/force-session-expires-at clears the
  3-min flag, reschedules the timer, and broadcasts WS resync. Lets
  the Maestro flow drive 175s -> 90s -> 0s without waiting live.

New chatRemainingSeconds StreamProvider derived from expiresAt, fed by
session_warning / session_timer / session_expired resync messages
(plan referenced a secondsLeftProvider that didn't actually exist).

Maestro 06_chat_countdown.yaml + force_session_expires_at.js helper.

Out of scope: meet.google.com URL launching - url_launcher isn't a
client_app dependency and message bubbles render plain Text. Defer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:25:11 +08:00
..

client_app Maestro flows

End-to-end UI automation for the customer Flutter app using Maestro. Single-emulator + curl-as-mitra pattern — the customer app is driven by real Maestro touches; the mitra side is simulated via backend API calls fired from runScript steps.

One-time install

Maestro is a global CLI (not a project dependency). Install on your dev machine once:

curl -Ls "https://get.maestro.mobile.dev" | bash

Verify with maestro --version. See the Maestro install docs for Homebrew / chocolatey / Docker alternatives.

You also need:

  • adb on your PATH (comes with Android Studio's platform-tools)
  • jq for the helper scripts (apt install jq / brew install jq)
  • One Android emulator OR one connected device — only one at a time (per project decision)

Folder layout

.maestro/
├── README.md                 # this file
├── config.yaml               # shared env: app IDs, backend URL, test credentials
├── flows/                    # the YAML test scripts
│   ├── 01_smoke.yaml
│   ├── 02_cta_disabled_when_no_mitra.yaml
│   └── 03_payment_to_chat_happy.yaml
└── scripts/                  # bash helpers invoked by `runScript` steps
    ├── mitra_accept_latest.sh
    └── force_all_mitras_offline.sh

Configure for your environment

Edit .maestro/config.yaml and fill in:

  • BACKEND_URL — must match the --dart-define=API_BASE_URL=... value the installed APK was built with
  • TEST_MITRA_ID and TEST_MITRA_JWT — used by the curl harness to "accept" requests from the customer's blast

The config file is committed because the values are dev-environment defaults. Sensitive credentials (real JWTs, CC operator tokens) should be passed at runtime instead — see "Per-machine overrides" below.

Run a flow

Single emulator (typical case — Maestro auto-picks the only attached device):

# from repo root or anywhere
maestro test client_app/.maestro/flows/01_smoke.yaml

# or run all flows in the directory
maestro test client_app/.maestro/flows/

If both an emulator and a real device happen to be connected, list them and pick one explicitly:

adb devices                                       # list attached devices
maestro --device emulator-5554 test client_app/.maestro/flows/01_smoke.yaml

Per-machine overrides

Override any config.yaml value at runtime with --env:

maestro test \
  --env BACKEND_URL=http://192.168.99.10:3000 \
  --env TEST_MITRA_JWT=eyJhbGc... \
  client_app/.maestro/flows/03_payment_to_chat_happy.yaml

Or export shell variables — runScript steps inherit them:

export CC_JWT=eyJhbGc...
maestro test client_app/.maestro/flows/02_cta_disabled_when_no_mitra.yaml

Single-emulator + curl pattern

Phase 3.7 flows often need a customer + a mitra acting in concert. Instead of running two emulators (RAM-heavy, flaky), the flows drive the customer side with Maestro and simulate the mitra via backend curl calls:

  1. Maestro flow drives customer up to the "Mencari Bestie..." state
  2. runScript: ../scripts/mitra_accept_latest.sh fires POST /api/mitra/chat-requests/:id/accept against the backend, using a pre-minted mitra JWT
  3. Maestro flow asserts the customer screen transitions to "Bestie Ditemukan" via the WS round-trip

This works for ~90% of multi-actor scenarios — including all the Section D ("Curhat lagi") and Section J ("Mitra goes offline mid-session") tests in phase3.7-testing.md. The 10% that needs both UIs running (e.g., asserting the mitra-side overlay countdown displays correctly) is in mitra_app/.maestro/ and runs separately.

Adding a new flow

Pick a Phase 3.7 testing checklist scenario (see phase3.7-testing.md), then:

  1. Copy an existing flow as a template (e.g., 03_payment_to_chat_happy.yaml)
  2. Update the pre-req comment, the steps, and the assertions
  3. If you need a "second actor" action, add a bash helper under scripts/ and call it via runScript:
  4. If you need new env vars, add them to config.yaml with sensible defaults

Tips

  • Find the right text to tap onmaestro studio opens a live UI inspector showing every visible label/widget. Run it while the app is on the screen you care about.
  • Slow it down for debuggingmaestro test --debug-output ./debug flows/foo.yaml saves screenshots + logs per step.
  • Add flows incrementally — Maestro's reload-on-save in maestro studio makes iteration fast.
  • Don't commit screenshots / debug output — add .maestro/output/ and .maestro/screenshots/ to .gitignore if you generate them locally.

Troubleshooting

  • maestro: device not found → run adb devices; if empty, start an emulator (emulator -avd <name>) or plug in a USB device with debugging enabled.
  • Element not visible errors → use maestro studio to inspect actual labels — they may have changed since the flow was written.
  • Flow hangs at assertVisible waiting for backend → check BACKEND_URL matches the APK's build-time value (grep API_BASE_URL build.gradle).
  • runScript exits non-zero → run the script directly to see its error: bash client_app/.maestro/scripts/mitra_accept_latest.sh. Most often a missing env var or stale JWT.