Files
halobestie-clone/client_app/.maestro/flows/README_section_02.md
Ramadhan Sjamsani 093256ff7d Phase 4 §2 + §1/§4: OnboardingIntent post-OTP routing + test naming + register-screen overflow
Spec §2 (flow_customer.mermaid) routes post-OTP based on user-lookup +
has_transacted, but the implementation previously dumped every OTP
success on /home. Introduce `OnboardingIntent` provider: set to
`onboarding` by routeForVerifChoice's verified branch (the "aku mau
curhat" transaction journey), set to `recover` by SHome1st's masuk →
banner. Router redirect on AuthAuthenticatedData+isAuthRoute consumes it:
`onboarding` → /payment/entry (dispatches S6 paywall vs PickMethod via
first_session_discount.eligible); `recover` → /home. Intent is reset in
/payment/entry's initState so subsequent masuk → flows don't inherit it.

auth_notifier.verifyOtp uses .copyWithPrevious on AsyncError so
valueOrNull retains AuthOtpSentData/AuthAnonymousData through OTP
failures — required for the OTP-blocked recovery path
(/onboarding/anon/method → /payment/method-pick) to clear the global
redirect without bouncing to /home. Router also extends the
isAuthRoute/isOnboardingFlow carve-out to AuthOtpSentData.

Maestro tests adopt `ts-<app>-<NN>-<MM>-<descriptor>.yaml` convention:
NN = mermaid section, MM = sub-flow index. New ts-customer-02-01..05
cover the §2 branches (verified brand-new → S6, existing-no-tx → S6,
existing-tx → method-pick, OTP-blocked → method-pick, anonymous first-
timer → method-pick); deferred 02-06/07/08/09 documented in
README_section_02.md. TS-07 → ts-customer-02-10 (masuk → recovery);
TS-01..06 → ts-customer-04-01..06 (§4 returning-user). Shared
onboarding_new_user_verified.yaml subflow extracted.

Register screen's body Column now uses LayoutBuilder + SingleChildScrollView
+ ConstrainedBox + IntrinsicHeight so the keyboard-open layout no
longer overflows by 1.3 px (verified visually).

Spec prose updated at flow_customer.mermaid §2 to describe the
intent-driven routing + login-vs-transaction divergence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:50:04 +08:00

5.2 KiB

§2 — New-User Onboarding test plan

Spec: requirement/flow_customer.mermaid.md §2 + §2.1.

Tests use the naming convention ts-customer-<section>-<sub>-<description>.yaml:

  • <section> — flow_customer.mermaid section number (02 for §2 + §2.1).
  • <sub> — sub-flow index within the section.
  • <description> — snake_case summary of the branch under test.

Implemented

File Branch (spec ref) Expected destination
ts-customer-02-01-verified_brand_new_to_s6_paywall.yaml §2 verified · UserLookup=no (brand-new) /payment/discount-paywall (S6)
ts-customer-02-02-verified_existing_no_tx_to_s6_paywall.yaml §2 verified · existing customer · has_transacted=false /payment/discount-paywall (S6)
ts-customer-02-03-verified_existing_transacted_to_method_pick.yaml §2 verified · existing customer · has_transacted=true /payment/method-pick (PickMethod)
ts-customer-02-04-otp_blocked_fallback_anonymous_to_method_pick.yaml §2 verified · OTPok="too many retries" → OTPBlock → fallback /payment/method-pick (PickMethod)
ts-customer-02-05-anonymous_first_timer_to_method_pick.yaml §2 anonymous · USPGateB=first-timer → USPb /payment/method-pick (PickMethod)
ts-customer-02-10-recover_via_masuk_existing_user_to_home.yaml SHome1st "masuk →" recover · existing identified user /home (returning view)

The shared verified-onboarding prelude lives at ../subflows/onboarding_new_user_verified.yaml.

Deferred (not yet implemented — see reasons)

ts-customer-02-06 — anonymous · USP already seen → PickMethod directly

Branch: §2 anonymous · USPGateB=seen → skip USP → PickMethod.

Why deferred: verifying the seen=true skip path literally requires the user to reach VerifChoiceSheet while local flutter.usp_seen=true AND auth state is fresh (AuthInitialData, no anon customer). Maestro's launchApp clearState:true wipes both SharedPreferences and FlutterSecureStorage together — there's no built-in way to clear ONLY secure storage between passes. A two-pass test that runs USP once then relaunches preserves both prefs and the anonymous session, so Pass 2 lands on SHomeReturning instead of SHome1st, bypassing VerifChoiceSheet.

Possible future approach: add a shell-level pre-test step that runs adb shell run-as <pkg> rm shared_prefs/FlutterSecureStorage*.xml and preserves FlutterSharedPreferences.xml, then invoke maestro. Requires a wrapper script or a maestro extension that can call adb.

ts-customer-02-07 — §2.1 5.1 anon transact → OTP new phone → upgrade in place

ts-customer-02-08 — §2.1 5.2 anon transact → OTP existing phone → merge (existing has_transacted=true)

ts-customer-02-09 — §2.1 5.2 merge · existing has_transacted=false

Branch: §2.1 anonymous → existing-user merge sub-flow.

Why deferred: §2.1's prereq is "anonymous customer has completed at least one transaction". The app derives has_transacted from chat_sessions rows, and the auth flow ties customer identity to the device's FlutterSecureStorage refresh token. Reproducing the prereq in Maestro requires either:

  1. Driving the app UI all the way through anonymous transaction → payment confirm → pairing → chat → end-session (a full §3 + §5 + §6 traversal), then triggering OTP from the SHomeReturning state. The SHomeReturning "curhat sama bestie baru" CTA bypasses VerifChoiceSheet entirely, so the only OTP entry point from this state is the SHome1st "masuk →" banner — which is only shown on SHome1st (no identity), not on SHomeReturning.

  2. Pre-seeding a customer + chat_session in the DB and injecting the corresponding refresh token into the device's FlutterSecureStorage so the app boots as that anonymous-with-transactions customer. Maestro doesn't expose adb shell exec, so this requires a wrapper script.

In practice, the §2.1 merge logic is more naturally covered by backend integration tests against resolveCustomerForIdentity in backend/src/services/auth.service.js — the app-side behavior after re-login is the same /home landing (login intent) tested by 02-10. The merge state is a backend invariant, not a UI invariant.

Behavior notes

  • §2 verified branches route post-OTP via /payment/entry, which then dispatches based on the backend's first_session_discount.eligible (see backend/src/services/pricing.service.js::isCustomerEligibleForFirstSessionDiscount). This handles both "brand-new" and "existing-but-never-paid" with a single check.
  • The OnboardingIntent provider (added 2026-05-18 with the §2 fix) distinguishes transaction-CTA entries (onboarding) from login-recover entries (recover). Set at verif_choice_sheet.dart::routeForVerifChoice (verified branch) and at home_screen.dart::_LoginRecoverBanner (masuk →). Consumed by router.dart's post-OTP redirect.
  • auth_notifier.dart::verifyOtp uses .copyWithPrevious(previous) when setting AsyncError, so valueOrNull retains the prior AuthOtpSentData / AuthAnonymousData through the OTP-blocked popup → fallback path.