OTP test infrastructure for Maestro flows

Dev-only /internal/_test/peek-otp + /internal/_test/reset-phone endpoints
gated by NODE_ENV !== 'production'. peek-otp reads the latest stub OTP
out of an in-memory map populated by otp.service.js fazpassSendStub;
reset-phone wipes otp_requests rows (and optionally the customers row)
so flows can re-run without tripping cooldowns.

JS + shell helpers under .maestro/scripts/ wrap the endpoints for use
inside Maestro runScript steps. 01_smoke.yaml expanded from a launch-only
sanity check to a full cold-start onboarding -> force-register -> OTP ->
home walk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 16:19:22 +08:00
parent d33d4419ea
commit 4680c36e34
8 changed files with 212 additions and 8 deletions

View File

@@ -1,14 +1,99 @@
# Smoke test: launch the app and assert the home screen renders.
# Use this flow first to verify Maestro can talk to your device/emulator at all.
# Smoke test: cold-start onboarding, registers a new customer via the
# anonymity-disabled force-register path, lands on home screen.
#
# Exercises (in order): onboarding carousel -> welcome -> display name ->
# force-register (because anonymity_enabled=false in dev) -> OTP via peek
# endpoint -> home.
#
# Run:
# maestro test client_app/.maestro/flows/01_smoke.yaml
#
# Pre-req: client_app debug APK installed on the connected device, signed in as a customer.
appId: ${APP_ID_ANDROID} # Maestro uses APP_ID_IOS automatically when running against an iOS device
# Pre-req: client_app debug APK installed, backend reachable at
# BACKEND_URL/BACKEND_INTERNAL_URL, NODE_ENV != 'production' (so the
# /internal/_test/peek-otp + /internal/_test/reset-phone routes register).
appId: com.halobestie.client.client_app
env:
TEST_PHONE: "+628155556677"
BACKEND_INTERNAL_URL: http://localhost:3001
---
# Wipe any prior state for TEST_PHONE so repeated runs don't trip cooldowns
# or hit IDENTITY_CONFLICT on a previously-claimed customer row.
- runScript:
file: ../scripts/reset_phone.js
env:
TEST_PHONE: ${TEST_PHONE}
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
- launchApp:
clearState: false # keep existing auth — set to true to test cold-start onboarding
- assertVisible:
text: "Mulai Curhat"
timeout: 10000 # 10s — give Riverpod time to hydrate the home screen
clearState: true
- extendedWaitUntil:
visible:
text: "Mulai"
timeout: 15000 # onboarding carousel auto-advances; "Mulai" appears on slide 3
- tapOn:
text: "Mulai"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Lanjut sebagai Tamu"
timeout: 10000
- tapOn:
text: "Lanjut sebagai Tamu"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Nama panggilan"
timeout: 10000
- tapOn:
text: "Nama panggilan"
- inputText: "Maestro"
- hideKeyboard
- tapOn:
text: "Lanjut"
retryTapIfNoChange: true
# Force-register kicks in (anonymity_enabled=false in dev DB)
- extendedWaitUntil:
visible:
text: "Verifikasi Akun"
timeout: 15000
- tapOn:
text: "Nomor HP"
- inputText: ${TEST_PHONE}
- hideKeyboard
- tapOn:
text: "Kirim OTP"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Masukkan OTP"
timeout: 15000
# Pull the stub-generated OTP code from the in-memory map on the backend
- runScript:
file: ../scripts/peek_otp.js
env:
TEST_PHONE: ${TEST_PHONE}
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
# inputText fills the autofocused first box; Flutter's onChanged advances
# focus per char, so all 6 digits land in the right boxes and auto-submit.
- inputText: ${output.OTP}
# Post-OTP, force-register flow lands on /auth/set-name (anonymous display
# name doesn't carry to the upgraded row). Wait for OTP screen to fade,
# then re-fill display name and continue to home.
- extendedWaitUntil:
notVisible:
text: "Masukkan OTP"
timeout: 15000
- extendedWaitUntil:
visible:
text: "Nama panggilan"
timeout: 10000
- tapOn:
text: "Nama panggilan"
- inputText: "Maestro"
- hideKeyboard
- tapOn:
text: "Lanjut"
retryTapIfNoChange: true
- extendedWaitUntil:
visible:
text: "Mulai Curhat"
timeout: 20000