# ts-customer-06-01 — End-of-session via the Time-up Sheet's "cukup, # akhiri sesi" ghost CTA → ConfirmEndStep1 → ConfirmEndStep2 → Closing # Message Sheet → /home. # # Spec ref: requirement/flow_customer.mermaid.md §6 (End-of-session # sequence) + flow_customer.md "5.8.2.1.x". # # Feature under test: # - PricingBottomSheet now exposes 2 CTAs: primary "perpanjang Rp..." # (or "pilih durasi dulu" with no tier picked) AND a ghost # "cukup, akhiri sesi" that opens the 2-step confirm chain. The # four widgets — PricingBottomSheet, ConfirmEndStep1, ConfirmEndStep2, # ClosingMessageSheet — already existed but weren't wired together # before today's change in chat_screen.dart::_runEndSessionFlow. # # Path traced here: Time-up sheet → "cukup, akhiri sesi" → ConfirmEndStep1 # "lanjut akhiri" → ConfirmEndStep2 "tulis pesan penutup" → ClosingMessageSheet # "kirim & akhiri sesi" → /home. # # Pre-reqs: # - Backend reachable at BACKEND_INTERNAL_URL with NODE_ENV != 'production' # (needs the /internal/_test/* harness endpoints). # - ≥1 mitra online to accept the blast. appId: com.mybestie env: TEST_PHONE: "+6281234567890" BACKEND_INTERNAL_URL: http://localhost:3001 --- # ── Setup: auth + seed history + pair into an active chat ─────────────── - 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 - 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 # Curhat sama bestie baru → choice sheet → bestie baru → method-pick - tapOn: text: "(?s).*curhat sama bestie baru.*" retryTapIfNoChange: true - extendedWaitUntil: visible: text: "(?s).*mau curhat sama siapa.*" timeout: 5000 - tapOn: "(?s).*cari bestie baru yang siap dengerin.*" # Payment chain → confirm → pairing accepted → on chat screen - 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 # Pick a payment method first — bayar CTA is disabled until one is # selected. QRIS is always in the "paling cepat" section. - tapOn: "QRIS" - tapOn: text: "(?s).*bayar Rp.*" retryTapIfNoChange: true # Brief settle so the POST /api/client/payment-requests/ completes # (creates the pending row before mark_latest_payment_paid runs). # Avoids asserting on QR/Xendit content since the visible screen # depends on XENDIT_ENABLED — we don't care which renders, just that # the payment row exists in the backend for the mark-paid script. - waitForAnimationToEnd: timeout: 5000 - runScript: file: ../scripts/mark_latest_payment_paid.js env: BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL} - runFlow: when: visible: text: "(?s).*biar nggak ketinggalan.*" commands: - tapOn: text: "(?s).*nanti aja.*" - extendedWaitUntil: visible: text: "(?s).*lagi nyari bestie.*" timeout: 20000 - runScript: file: ../scripts/mitra_accept_latest_internal.js env: MITRA_ID: ${output.MITRA_ID} BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL} - extendedWaitUntil: visible: text: "tulis sesuatu..." timeout: 20000 # ── Force expires_at → 0 so the floating expired banner appears ───────── - runScript: file: ../scripts/force_session_expires_at.js env: BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL} SECONDS_FROM_NOW: "0" # The expired banner copy is "habis nih... mau lanjutin curhat sama X?". - extendedWaitUntil: visible: text: "(?s).*habis nih.*" timeout: 10000 # ── Step 1: tap "perpanjang" on the banner → time-up sheet opens ─────── # Tap by Semantics identifier (id) — Maestro's tap-by-text and tap-by-point # both fail to trigger this ElevatedButton's onPressed (raw `adb input # tap` works, so it's a maestro/dadb gesture quirk specific to this # button). `id:` routes through the Android a11y framework's # performAction(CLICK), which fires onPressed cleanly. The matching # Semantics wrapper is in chat_expired_banner.dart. - tapOn: id: "chat_extend_button" - extendedWaitUntil: visible: text: "cukup, akhiri sesi" timeout: 20000 # ── Step 2: assert primary CTA also renders + sheet header ────────────── # Primary "perpanjang Rp..." renders as "pilih durasi dulu" before any # tier is selected; that's the relevant assertion for the 2-CTA layout. - assertVisible: "pilih durasi dulu" - assertVisible: "waktu curhat habis" # ── Step 3: tap "cukup, akhiri sesi" → ConfirmEndStep1 popup ──────────── - tapOn: "cukup, akhiri sesi" - extendedWaitUntil: visible: text: "yakin mau akhiri sesi?" timeout: 5000 - assertVisible: "lanjut akhiri" - assertVisible: "gak jadi, balik" # ── Step 4: tap "lanjut akhiri" → ConfirmEndStep2 popup ───────────────── - tapOn: "lanjut akhiri" - extendedWaitUntil: visible: text: "mau tinggalin pesan penutup?" timeout: 5000 - assertVisible: "tulis pesan penutup" - assertVisible: "lewati saja" # ── Step 5: tap "tulis pesan penutup" → ClosingMessageSheet ───────────── - tapOn: "tulis pesan penutup" - extendedWaitUntil: visible: text: "pesan penutup" timeout: 5000 - assertVisible: "kirim & akhiri sesi" - assertVisible: "lewat — langsung akhiri" # ── Step 6: type + send → closes session, navigates to /home ──────────── - tapOn: text: "makasih ya bestie..." - inputText: "makasih bestie, sesi ini ngebantu banget" - hideKeyboard - tapOn: "kirim & akhiri sesi" # Home is the landing zone after closure. Customer has seeded + just-ended # sessions in their history, so they're in the "returning" home variant — # CTA is "curhat sama bestie baru", not "aku mau curhat". - extendedWaitUntil: visible: text: "(?s).*curhat sama bestie baru.*" timeout: 15000