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>
144 lines
4.3 KiB
YAML
144 lines
4.3 KiB
YAML
# TS-05 — Payment expired → retry preserves targeting
|
|
# (requirement/phase4-customer-flow.md → Test Scenarios → TS-05).
|
|
#
|
|
# §4 branch covered: PickMethod → PickDuration → PayMethod → WaitPay →
|
|
# PayStat(timeout 20 min) → PayExpired → Pay(retry) → paid →
|
|
# PairRoute(lama) → Targeted → S10.
|
|
#
|
|
# What this proves: the `targetedMitraId` on the payment draft survives
|
|
# the expired-retry round trip (Stage 5.1 `resetExceptTarget` invariant).
|
|
# After the retry pays, the customer lands on /chat/waiting-targeted/<SAME
|
|
# mitraId> — NOT a fresh blast.
|
|
#
|
|
# Pre-reqs (HARD):
|
|
# - Backend reachable; NODE_ENV != 'production'.
|
|
# - >= 1 mitra online — they're the targeted mitra throughout. The flow
|
|
# does NOT drive accept (we stop at the targeted-waiting screen — the
|
|
# assertion that targeting survived the retry is the goal).
|
|
#
|
|
# Run:
|
|
# maestro test client_app/.maestro/flows/ts-05_payment_expired_retry_preserves_targeting.yaml
|
|
appId: com.halobestie.client.client_app
|
|
env:
|
|
TEST_PHONE: "+6281234567890"
|
|
BACKEND_INTERNAL_URL: http://localhost:3001
|
|
---
|
|
# --- Cold-start reset + onboarding prelude ---
|
|
- runScript:
|
|
file: ../scripts/reset_phone.js
|
|
env:
|
|
TEST_PHONE: ${TEST_PHONE}
|
|
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
|
- launchApp:
|
|
clearState: true
|
|
- runFlow: ../subflows/onboarding_returning_user.yaml
|
|
|
|
# --- Seed history with online M1 (targeted throughout). ---
|
|
- runScript:
|
|
file: ../scripts/seed_history_session.js
|
|
env:
|
|
TEST_PHONE: ${TEST_PHONE}
|
|
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
|
|
|
- swipe:
|
|
start: "50%, 30%"
|
|
end: "50%, 80%"
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*curhatan sebelumnya.*"
|
|
timeout: 10000
|
|
|
|
# --- Walk TS-01 steps 1-6 to reach /payment/waiting for the targeted
|
|
# attempt against M1. ---
|
|
- tapOn:
|
|
text: "(?s).*curhat sama bestie baru.*"
|
|
retryTapIfNoChange: true
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*mau curhat sama siapa.*"
|
|
timeout: 5000
|
|
- tapOn: "(?s).*bestie yang udah kenal.*"
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*bestie kamu sebelumnya.*"
|
|
timeout: 5000
|
|
- tapOn: "(?s).*bestie ${output.MITRA_NAME_RE}.*"
|
|
|
|
# /payment/entry → /payment/method-pick (returning, no discount).
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*pilih cara curhat.*"
|
|
timeout: 10000
|
|
- tapOn:
|
|
text: "(?s).*tulis dan baca dengan tenang.*"
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*pilih durasi.*"
|
|
timeout: 10000
|
|
- tapOn: "(?s).*5 menit.*"
|
|
- tapOn: "(?s).*bayar Rp.*"
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*cara bayar.*"
|
|
timeout: 10000
|
|
- tapOn:
|
|
text: "(?s).*bayar Rp.*"
|
|
retryTapIfNoChange: true
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "scan QRIS untuk bayar"
|
|
timeout: 10000
|
|
|
|
# --- Force-expire the latest pending payment ---
|
|
- runScript:
|
|
file: ../scripts/force_expire_latest_payment.js
|
|
env:
|
|
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
|
|
|
# --- Poller picks `expired` within ~3s → /payment/expired/:paymentId ---
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*pembayaran kedaluwarsa.*"
|
|
timeout: 10000
|
|
- assertVisible: "(?s).*coba lagi.*"
|
|
|
|
# --- Tap retry → /payment/method (NOT /payment/method-pick — the draft
|
|
# was preserved via resetExceptTarget, so we skip mode + duration). ---
|
|
- tapOn: "(?s).*coba lagi.*"
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*cara bayar.*"
|
|
timeout: 10000
|
|
# Sanity check: we did NOT bounce back to the mode picker.
|
|
- assertNotVisible: "(?s).*pilih cara curhat.*"
|
|
|
|
# --- Re-pay ---
|
|
- tapOn:
|
|
text: "(?s).*bayar Rp.*"
|
|
retryTapIfNoChange: true
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "scan QRIS untuk bayar"
|
|
timeout: 10000
|
|
- runScript:
|
|
file: ../scripts/mark_latest_payment_paid.js
|
|
env:
|
|
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
|
|
|
# --- Notif-gate → targeted-waiting screen for the SAME mitra (M1).
|
|
# This is the load-bearing assertion: if targetedMitraId had been wiped
|
|
# by the expired-retry round trip, the customer would land on
|
|
# /chat/searching (blast) and we'd see "lagi nyari bestie" instead. ---
|
|
- runFlow:
|
|
when:
|
|
visible:
|
|
text: "(?s).*biar nggak ketinggalan.*"
|
|
commands:
|
|
- tapOn:
|
|
text: "(?s).*nanti aja.*"
|
|
- extendedWaitUntil:
|
|
visible:
|
|
text: "(?s).*MENUNGGU JAWABAN.*"
|
|
timeout: 20000
|
|
- assertVisible: "(?s).*lagi nungguin ${output.MITRA_NAME_RE}.*"
|