Phase 4 §1/§5: notif banner detection on API <33 + chat-delivery WS→FCM lifecycle
§1 notif banner: permission_handler v11 returns granted unconditionally
for Permission.notification on Android <13 because POST_NOTIFICATIONS
didn't exist as a runtime permission. Result: SHome1st amber "notifikasi
off" banner never showed on API 24-32 even when the user toggled
notifications off in Settings → Apps. Add a
NotificationManagerCompat.areNotificationsEnabled() pre-check via
flutter_local_notifications (works from API 19+) so the banner reflects
the real OS state on older Android.
§5 chat delivery: the contract is "WS when foreground, FCM when
background", but the previous build only honoured (1) — Android keeps
the TCP socket alive after the Dart isolate is paused, so backend's
`socket.readyState === 1` check returned true and FCM never fired.
Fix has five parts (all required together):
1. Customer-side lifecycle observer in client_app/main.dart closes
chatProvider's WS on paused/detached, reconnects on resumed.
2. `_appPaused` gate in main.dart suppresses the activeSessionProvider
listener's auto-reconnect (15s poll in active_session_notifier
would otherwise re-open the WS the next tick after the observer
closed it — defeating the fallback).
3. Mitra-side lifecycle observer in mitra_app/main.dart stashes
`_pausedChatSessionId`, calls mitraChatProvider.disconnect(), and
re-issues connect(saved) on resumed.
4. MitraChat gains a `_connectedSessionId` field + getter so the
observer in step 3 can read it back across disconnect (disconnect
clears it; the next connect overwrites it).
5. SearchingScreen resets pairingProvider when entering with a new
draft.paymentId — previously it retained PairingActiveData with
the *old* sessionId after a session ended, and the next pairing
flow navigated straight to that completed session showing
"Sesi sudah berakhir".
Backend additions under /internal/_test/* for assertion harness:
inspectSessionWsState + GET /ws-connection-state,
POST /send-chat-message-as-mitra (with delivered_via),
POST /send-chat-message-as-customer (with delivered_via),
POST /send-fcm-chat-message (raw FCM dispatch).
Maestro coverage:
- ts-customer-05-01: mitra → customer message when customer is
backgrounded → delivered_via=fcm.
- ts-customer-05-02: customer → mitra message when mitra is
backgrounded → delivered_via=fcm.
- ts-customer-01-01: §1 notif-denied banner on home. Documented
precondition: mitra must be force-stopped or backgrounded on the
chat screen before 05-02 runs (Maestro can only drive one --udid
per run; mitra-side lifecycle observer end-to-end is deferred).
Helper scripts under client_app/.maestro/scripts/:
inspect_ws_state.js, assert_ws_state.js,
send_chat_message_as_mitra.js, assert_delivered_via.js (takes
SENDER=mitra|customer to route to the matching backend endpoint).
README_section_05.md documents the test plan, helper scripts, and the
deferred mitra-side maestro driving. Both apps tested manually on
API 28 AVDs where FCM delivery is sub-second; API 24 has 5-30 min
heartbeats that make it impractical for FCM-related testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
93
client_app/.maestro/flows/README_section_05.md
Normal file
93
client_app/.maestro/flows/README_section_05.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# §5 — Chat Room test plan
|
||||
|
||||
Spec: [requirement/flow_customer.mermaid.md §5](../../../requirement/flow_customer.mermaid.md)
|
||||
plus the user-stated message-delivery contract:
|
||||
|
||||
> 1. WebSocket when app is foreground / WS connected.
|
||||
> 2. FCM when app is background / WS disconnected.
|
||||
|
||||
The lifecycle observers that enforce (2) live at:
|
||||
- Customer side: `client_app/lib/main.dart::_AppState.didChangeAppLifecycleState`
|
||||
- Mitra side: `mitra_app/lib/main.dart::_AppState.didChangeAppLifecycleState` +
|
||||
`mitra_app/lib/core/chat/mitra_chat_notifier.dart` (`_connectedSessionId`
|
||||
+ getter for the observer to read on `paused`).
|
||||
|
||||
A 15s `Timer.periodic` in `active_session_notifier.dart` would otherwise
|
||||
re-open the customer WS right after the observer closed it; the
|
||||
`_appPaused` gate in customer main.dart suppresses that race.
|
||||
|
||||
## Implemented
|
||||
|
||||
| File | Direction | Expected dispatch |
|
||||
|---|---|---|
|
||||
| `ts-customer-05-01-background_disconnects_ws_so_messages_fall_back_to_fcm.yaml` | mitra → customer, customer is backgrounded | `delivered_via=fcm` |
|
||||
| `ts-customer-05-02-customer_message_to_backgrounded_mitra_falls_back_to_fcm.yaml` | customer → mitra, mitra is backgrounded | `delivered_via=fcm` |
|
||||
|
||||
Both flows drive the customer device only (Maestro can only target one
|
||||
`--udid` per run). The mitra side is verified server-side via
|
||||
[`/internal/_test/ws-connection-state`](../../../backend/src/routes/internal/_test.routes.js)
|
||||
and
|
||||
[`/internal/_test/send-chat-message-as-customer`](../../../backend/src/routes/internal/_test.routes.js)
|
||||
(introduced 2026-05-18 with the lifecycle fix).
|
||||
|
||||
## Helper scripts (under `../scripts/`)
|
||||
|
||||
| Script | Purpose |
|
||||
|---|---|
|
||||
| `assert_ws_state.js` | Reads `/internal/_test/ws-connection-state?session_id=…`; throws on mismatch with `EXPECTED_CUSTOMER_CONNECTED` / `EXPECTED_MITRA_CONNECTED`. |
|
||||
| `assert_delivered_via.js` | Sends a chat message via the test endpoint matching `SENDER` (`mitra` default, or `customer`); asserts the response's `delivered_via` equals `EXPECTED_DELIVERED_VIA`. |
|
||||
| `inspect_ws_state.js` | Same data as `assert_ws_state.js` but exports the booleans into `output.CUSTOMER_CONNECTED` / `output.MITRA_CONNECTED` for downstream conditions without throwing. |
|
||||
| `send_chat_message_as_mitra.js` | Mitra→customer one-shot send. Captures `output.MESSAGE_ID` + `output.DELIVERED_VIA`. |
|
||||
|
||||
## Pre-reqs for both flows
|
||||
|
||||
- Backend reachable; `NODE_ENV != 'production'`.
|
||||
- ≥1 mitra online (the `seed_history_session.js` helper picks the
|
||||
most-recently-online mitra to act as the eventual acceptor).
|
||||
- For 05-01: customer device is the one Maestro drives; `pressKey: HOME`
|
||||
inside the flow backgrounds it. No mitra-app interaction required.
|
||||
- For 05-02: **the mitra app on `emulator-5556` must be backgrounded or
|
||||
force-stopped before the flow runs**. The `assert_ws_state.js` guard
|
||||
fails loudly if mitra is foreground (proving WS-over-FCM precedence —
|
||||
also a correct test outcome, just the wrong side of the matrix). To
|
||||
ensure FCM actually arrives on the device, the mitra account on
|
||||
`emulator-5556` must have a `fcm_token` in `mitras.fcm_token` (i.e.,
|
||||
the app has signed in and called `/api/shared/device-token` at some
|
||||
point) — verified by `seed_history_session.js`'s output mitra ID and
|
||||
the `MITRA_NAME_RE` it captures.
|
||||
|
||||
## Manual mitra-side verification
|
||||
|
||||
After 05-02 passes, optionally inspect the mitra device's notification
|
||||
shade to confirm the FCM tray notification rendered:
|
||||
|
||||
```bash
|
||||
export ADB_SERVER_SOCKET=tcp:172.22.240.1:5037
|
||||
adb -s emulator-5556 shell cmd statusbar expand-notifications
|
||||
adb -s emulator-5556 exec-out screencap -p > /tmp/mitra_shade.png
|
||||
adb -s emulator-5556 shell cmd statusbar collapse
|
||||
```
|
||||
|
||||
Expect to see a `Pesan baru dari Customer` notification within ~1s on an
|
||||
API 28+ AVD (older AVDs queue FCM for 5-30 min — see the per-AVD
|
||||
Play-Services notes in the project memory).
|
||||
|
||||
## Not yet automated
|
||||
|
||||
**Mitra-side lifecycle observer** end-to-end. To verify that the mitra
|
||||
app's `didChangeAppLifecycleState(paused)` actually closes its chat WS,
|
||||
the test would need to drive the mitra emulator (Maestro on
|
||||
`--udid emulator-5556`) through login → chat-screen → `pressKey: HOME` →
|
||||
assert backend WS state. This requires:
|
||||
|
||||
1. Pre-seeding an active chat session for the currently-signed-in mitra
|
||||
(no existing test endpoint for "seed customer + pair + active
|
||||
session"; the existing scripts assume the customer app drives it).
|
||||
2. Driving the mitra OTP login + chat-request acceptance UI from
|
||||
Maestro.
|
||||
|
||||
Both are feasible but out of scope for the current iteration. The
|
||||
mitra-side fix is covered indirectly: ts-customer-05-02 asserts the
|
||||
backend correctly dispatches via FCM whenever the mitra WS is closed,
|
||||
and the mitra app's lifecycle observer was verified manually
|
||||
(2026-05-18) to be the mechanism that closes that WS.
|
||||
@@ -0,0 +1,61 @@
|
||||
# ts-customer-01-01 — When OS-level notification permission is denied,
|
||||
# SHome1st renders the amber "notifikasi off" banner above the
|
||||
# 'aku mau curhat' CTA.
|
||||
# Spec ref: requirement/flow_customer.mermaid.md §1 — `NotifCheck → no →
|
||||
# HomeBanner` (line 24-26 of the mermaid).
|
||||
#
|
||||
# Banner widget: home_screen.dart::_NotifDeniedBanner. Gating provider:
|
||||
# notifPermissionStatusProvider (core/notifications/notif_permission.dart).
|
||||
# The provider reads `permission_handler::Permission.notification.status`
|
||||
# which, on Android 13+, reflects the runtime POST_NOTIFICATIONS state and
|
||||
# on older Android reflects the app's notification-enabled toggle.
|
||||
#
|
||||
# Pre-requisite (manual setup):
|
||||
# The test only exercises the "denied" branch. Disable notifications for
|
||||
# the customer app BEFORE invoking maestro:
|
||||
# - Android 13+: maestro's `permissions: { notifications: deny }` below
|
||||
# handles this (POST_NOTIFICATIONS is a runtime permission).
|
||||
# - Android 7-12 (incl. the API-24 dev AVD): the runtime permission
|
||||
# doesn't exist, so the maestro `permissions:` block is a no-op.
|
||||
# Instead, toggle Settings → Apps → Halo Bestie → Notifications →
|
||||
# Off manually. This sets the system `NotificationManagerCompat`
|
||||
# state which the app's `notif_permission.dart::readStatus`
|
||||
# pre-check picks up. `clearState: true` below is safe — `pm clear`
|
||||
# wipes app data but not the system-level notification toggle.
|
||||
# The backend FCM path still fires regardless of this branch (the OS
|
||||
# simply suppresses the notification); the banner is the only
|
||||
# user-visible signal that they're missing alerts, which is what we
|
||||
# assert below.
|
||||
appId: com.halobestie.client.client_app
|
||||
env:
|
||||
TEST_PHONE: "+6281234567890"
|
||||
BACKEND_INTERNAL_URL: http://localhost:3001
|
||||
---
|
||||
- runScript:
|
||||
file: ../scripts/reset_phone.js
|
||||
env:
|
||||
TEST_PHONE: ${TEST_PHONE}
|
||||
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
||||
- launchApp:
|
||||
clearState: true
|
||||
permissions:
|
||||
notifications: deny
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
text: "Mulai"
|
||||
timeout: 15000
|
||||
- tapOn:
|
||||
text: "Mulai"
|
||||
retryTapIfNoChange: true
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
text: "(?s).*aku mau curhat.*"
|
||||
timeout: 30000
|
||||
|
||||
# §1 NotifCheck=no → SHome1st banner. Text is the unique copy from
|
||||
# _NotifDeniedBanner; the icon is decorative-only and not part of the
|
||||
# a11y label.
|
||||
- assertVisible: "(?s).*notifikasi off.*"
|
||||
- assertVisible: "(?s).*kelewat chat dari bestie.*"
|
||||
- assertVisible: "(?s).*nyalain.*"
|
||||
- takeScreenshot: /tmp/notif_banner_design
|
||||
@@ -0,0 +1,151 @@
|
||||
# ts-customer-05-01 — When the customer app is backgrounded, the chat
|
||||
# WebSocket must close so backend `sendMessage` falls back to FCM.
|
||||
#
|
||||
# Spec ref: requirement/flow_customer.mermaid.md §5 (Chat Room) +
|
||||
# user-stated path:
|
||||
# 1. WebSocket when app is foreground / WS connected.
|
||||
# 2. FCM when app is background / WS disconnected.
|
||||
#
|
||||
# Fix under test: client_app/lib/main.dart `_AppState.didChangeAppLifecycleState`
|
||||
# closes the chat WS on AppLifecycleState.paused / detached. Before the
|
||||
# fix, Android kept the TCP socket alive after the Dart isolate paused, so
|
||||
# backend's `socket.readyState === 1` check returned true and FCM never
|
||||
# fired — the customer received no alert in background.
|
||||
#
|
||||
# Verification approach:
|
||||
# 1. Drive customer through pairing → active chat (same harness as
|
||||
# ts-customer-04-04).
|
||||
# 2. Assert customer-side WS is connected (backend state check).
|
||||
# 3. Press HOME to background → wait briefly for the lifecycle observer
|
||||
# to fire `disconnect()` and the socket-close to round-trip.
|
||||
# 4. Assert customer-side WS is now closed.
|
||||
# 5. Send a message AS mitra via the real `sendMessage` code path and
|
||||
# assert it was delivered via FCM (recipient.customer WS was down at
|
||||
# dispatch time).
|
||||
#
|
||||
# Pre-reqs:
|
||||
# - Backend reachable; NODE_ENV != 'production'.
|
||||
# - ≥1 mitra online (the seeded mitra acts as the blast acceptor and the
|
||||
# subsequent message sender).
|
||||
appId: com.halobestie.client.client_app
|
||||
env:
|
||||
TEST_PHONE: "+6281234567890"
|
||||
BACKEND_INTERNAL_URL: http://localhost:3001
|
||||
---
|
||||
# ── Setup: pair customer with a mitra, land in 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
|
||||
|
||||
# Seed history so the SHomeReturning view renders the choice sheet (the
|
||||
# seeded mitra also becomes the blast acceptor).
|
||||
- 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.
|
||||
- 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
|
||||
- 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: "(?s).*online.*"
|
||||
timeout: 20000
|
||||
|
||||
# ── Assertion 1: WS is connected when chat screen is in foreground ──────
|
||||
- runScript:
|
||||
file: ../scripts/assert_ws_state.js
|
||||
env:
|
||||
SESSION_ID: ${output.ACCEPTED_SESSION_ID}
|
||||
EXPECTED_CUSTOMER_CONNECTED: "true"
|
||||
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
||||
|
||||
# ── Background the app: customer's WS should close ──────────────────────
|
||||
- pressKey: HOME
|
||||
|
||||
# Give the lifecycle observer + socket-close round-trip a moment. The
|
||||
# disconnect() call posts a close frame; the backend reacts on
|
||||
# `socket.on('close')`. ~1s is usually sufficient on the dev backend.
|
||||
- waitForAnimationToEnd:
|
||||
timeout: 3000
|
||||
|
||||
# ── Assertion 2: backend now reports customer_connected=false ───────────
|
||||
- runScript:
|
||||
file: ../scripts/assert_ws_state.js
|
||||
env:
|
||||
SESSION_ID: ${output.ACCEPTED_SESSION_ID}
|
||||
EXPECTED_CUSTOMER_CONNECTED: "false"
|
||||
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
||||
|
||||
# ── Assertion 3: a message sent now is delivered via FCM, not WebSocket ─
|
||||
- runScript:
|
||||
file: ../scripts/assert_delivered_via.js
|
||||
env:
|
||||
SESSION_ID: ${output.ACCEPTED_SESSION_ID}
|
||||
CONTENT: "halo, balasan dari mitra sementara kamu di background"
|
||||
EXPECTED_DELIVERED_VIA: "fcm"
|
||||
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
||||
@@ -0,0 +1,144 @@
|
||||
# ts-customer-05-02 — Symmetric to 05-01: customer sends a message and the
|
||||
# mitra is in background → backend `sendMessage` falls back to FCM.
|
||||
#
|
||||
# Spec ref: requirement/flow_customer.mermaid.md §5 (Chat Room) +
|
||||
# the user-stated path:
|
||||
# 1. WebSocket when app is foreground / WS connected.
|
||||
# 2. FCM when app is background / WS disconnected.
|
||||
#
|
||||
# Fix under test: mitra_app/lib/main.dart `_AppState.didChangeAppLifecycleState`
|
||||
# closes the chat WS on paused/detached (and `mitra_chat_notifier.dart`
|
||||
# exposes `connectedSessionId` + clears it on disconnect). Without this,
|
||||
# Android keeps the TCP socket alive after the Dart isolate pauses, so
|
||||
# the backend believes the mitra is online and never fires FCM — the
|
||||
# mitra misses customer messages while the app is in background.
|
||||
#
|
||||
# Approach (Maestro can only drive one device per flow; here we drive the
|
||||
# customer, and the mitra side is verified server-side):
|
||||
# 1. Drive customer to active chat (same harness as ts-customer-04-04 /
|
||||
# ts-customer-05-01). The acceptor is whichever mitra the
|
||||
# seed_history_session.js helper picks (most-recently-online → on
|
||||
# our setup that is the signed-in mitra on emulator-5556).
|
||||
# 2. Assert backend says mitra_connected=false. This is true when:
|
||||
# (a) the mitra app is force-stopped / not running, OR
|
||||
# (b) the mitra app is on the chat screen AND backgrounded — the
|
||||
# lifecycle observer should have closed the WS.
|
||||
# Either condition fulfils the precondition for FCM fallback.
|
||||
# 3. Send a message AS customer via /internal/_test/send-chat-message-as-customer
|
||||
# and assert delivered_via="fcm".
|
||||
#
|
||||
# Pre-reqs (HARD):
|
||||
# - Backend reachable; NODE_ENV != 'production'.
|
||||
# - emulator-5556 mitra app: either force-stopped before the test, or
|
||||
# signed in with TestMitra-1501 (the seed_history_session pick) and
|
||||
# currently backgrounded. If the mitra is foreground in the chat
|
||||
# screen, mitra_connected=true and this test asserts will fail —
|
||||
# which is the correct failure mode (proves WS-over-FCM precedence).
|
||||
# - The currently-signed-in mitra must have a `fcm_token` row in
|
||||
# `mitras.fcm_token`; otherwise the FCM dispatch succeeds at the
|
||||
# backend code path but never reaches a device.
|
||||
appId: com.halobestie.client.client_app
|
||||
env:
|
||||
TEST_PHONE: "+6281234567890"
|
||||
BACKEND_INTERNAL_URL: http://localhost:3001
|
||||
---
|
||||
# ── Setup: drive customer through onboarding → pair via blast ───────────
|
||||
- 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
|
||||
|
||||
- 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.*"
|
||||
|
||||
- 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
|
||||
- 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: "(?s).*online.*"
|
||||
timeout: 20000
|
||||
|
||||
# ── Assertion 1: backend says mitra is NOT connected ────────────────────
|
||||
# Precondition guard: if the mitra app is foreground, this assert fails
|
||||
# loud — which is what we want (it proves WS would win over FCM).
|
||||
- runScript:
|
||||
file: ../scripts/assert_ws_state.js
|
||||
env:
|
||||
SESSION_ID: ${output.ACCEPTED_SESSION_ID}
|
||||
EXPECTED_CUSTOMER_CONNECTED: "true"
|
||||
EXPECTED_MITRA_CONNECTED: "false"
|
||||
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
||||
|
||||
# ── Assertion 2: a customer-sent message is delivered via FCM ───────────
|
||||
- runScript:
|
||||
file: ../scripts/assert_delivered_via.js
|
||||
env:
|
||||
SESSION_ID: ${output.ACCEPTED_SESSION_ID}
|
||||
CONTENT: "halo bestie, ini pesan saat kamu di background"
|
||||
EXPECTED_DELIVERED_VIA: "fcm"
|
||||
SENDER: "customer"
|
||||
BACKEND_INTERNAL_URL: ${BACKEND_INTERNAL_URL}
|
||||
Reference in New Issue
Block a user