From caa4c68ddf7832b68c1e0e78b4a5b70da8acb98b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 06:25:49 +0000 Subject: [PATCH 1/2] Initial plan From b07831241acf86218460426ae2307b447c7cd934 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 06:35:44 +0000 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20full=20Maestro=20flow=20audit=20?= =?UTF-8?q?=E2=80=94=20fix=20bugs,=20add=20negative-path=20+=20regression?= =?UTF-8?q?=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/KeeperCommunity/bitcoin-keeper/sessions/7964f9a0-2a82-4212-9530-f20d1aa79019 Co-authored-by: cakesoft-swati <62699947+cakesoft-swati@users.noreply.github.com> --- flows/COVERAGE.md | 118 +++++++++++++++++++++++++++++ flows/appsettings.yaml | 4 +- flows/appsettings_toggles.yaml | 26 +++++++ flows/buyBTC.yaml | 20 +++-- flows/copywalletaddress.yaml | 11 ++- flows/exportseed.yaml | 9 ++- flows/exportseed_wrong_pin.yaml | 53 +++++++++++++ flows/login_wrong_pin.yaml | 33 ++++++++ flows/receive.yaml | 21 +++-- flows/receive_address_display.yaml | 37 +++++++++ flows/regression_full.yaml | 45 +++++++++++ flows/send.yaml | 2 + flows/send_cancel.yaml | 41 ++++++++++ flows/send_empty_amount.yaml | 55 ++++++++++++++ flows/send_invalid_address.yaml | 44 +++++++++++ flows/setpin.yaml | 10 ++- flows/setpin_mismatch.yaml | 59 +++++++++++++++ flows/subscription.yaml | 11 ++- flows/viewwallet.yaml | 35 ++++----- flows/walletSetting.yaml | 19 +++-- flows/wallet_creation_cancel.yaml | 38 ++++++++++ 21 files changed, 629 insertions(+), 62 deletions(-) create mode 100644 flows/COVERAGE.md create mode 100644 flows/appsettings_toggles.yaml create mode 100644 flows/exportseed_wrong_pin.yaml create mode 100644 flows/login_wrong_pin.yaml create mode 100644 flows/receive_address_display.yaml create mode 100644 flows/regression_full.yaml create mode 100644 flows/send_cancel.yaml create mode 100644 flows/send_empty_amount.yaml create mode 100644 flows/send_invalid_address.yaml create mode 100644 flows/setpin_mismatch.yaml create mode 100644 flows/wallet_creation_cancel.yaml diff --git a/flows/COVERAGE.md b/flows/COVERAGE.md new file mode 100644 index 0000000000..03989ccf3e --- /dev/null +++ b/flows/COVERAGE.md @@ -0,0 +1,118 @@ +# Maestro Flow Coverage Summary + +## What Is Covered + +### Happy-Path Flows +| Flow file | Scenario | +|---|---| +| `newapp.yaml` | Fresh-install onboarding + `setpin.yaml` | +| `setpin.yaml` | Create passcode, mismatch negative case inline, onboarding carousel | +| `setpin_mismatch.yaml` | Dedicated standalone passcode-mismatch negative test | +| `login.yaml` | Wrong-PIN case then correct login (combined) | +| `login_wrong_pin.yaml` | Dedicated wrong-PIN negative test → recover with correct PIN | +| `addwallet.yaml` | Create a new hot wallet with transfer policy | +| `addNewKey.yaml` | Add a mobile signing key via the Keeper flow | +| `viewwallet.yaml` | Open wallet, first-run modal dismiss, wallet action buttons | +| `walletSetting.yaml` | Navigate to wallet settings and back | +| `editwalletdetails.yaml` | Edit wallet name + description end-to-end | +| `editwallet.yaml` | Open wallet details sub-screen | +| `receive.yaml` | Open receive screen and navigate back | +| `receive_address_display.yaml` | Receive screen QR + copy address + success toast | +| `copywalletaddress.yaml` | Copy wallet address and verify success toast | +| `receivesats.yaml` | Receive test-sats via wallet settings (testnet) | +| `send.yaml` | Full send flow: address → amount → fee → passcode → broadcast | +| `buyBTC.yaml` | Buy Bitcoin entry flow | +| `refreshwallet.yaml` | Pull-to-refresh wallet transaction list | +| `appsettings.yaml` | App Settings screen assertions + dark-mode toggle + sub-flows | +| `appsettings_toggles.yaml` | Dark-mode toggle round-trip | +| `exportseed.yaml` | Backup → export recovery phrase with correct passcode | +| `versionhistory.yaml` | Version History screen assertions | +| `subscription.yaml` | Navigate to subscription / upgrade screen | +| `keySetting.yaml` | Open key settings | +| `healthCheckKey.yaml` | Run health check on a mobile key | +| `hidendeletekey.yaml` | Hide → show → delete a mobile key (passcode-verified) | +| `sanity.yaml` | Smoke: onboarding + wallet + key creation | +| `regression_full.yaml` | Full composed regression suite | + +### Negative-Path Flows +| Flow file | Scenario | +|---|---| +| `setpin_mismatch.yaml` | Passcode mismatch error + clear + correct re-entry | +| `login_wrong_pin.yaml` | Wrong PIN → "Incorrect password" error → retry with correct PIN | +| `login.yaml` | Inline wrong-PIN case (1237) before successful login | +| `send_invalid_address.yaml` | Non-Bitcoin string → app should not advance to amount screen | +| `send_empty_amount.yaml` | Empty amount field → app should not advance to fee screen | +| `send_cancel.yaml` | Back/cancel at address-entry step → returns to wallet screen | +| `exportseed_wrong_pin.yaml` | Wrong PIN on seed-export passcode screen → recovery phrase not shown | +| `wallet_creation_cancel.yaml` | Cancel mid-wizard wallet creation → returns to home | + +--- + +## What Remains Missing + +The following app scenarios are **not yet covered** by any flow: + +### Authentication / Security +- Biometric enable, disable, and deny flows +- Multiple consecutive wrong PINs and any lockout / cooldown behaviour +- Passcode reset / recovery via backup phrase + +### Wallet Management +- Creating a 2-of-3 or 3-of-5 vault wallet +- Creating a Collaborative wallet +- Renaming an existing wallet (distinct from `editwalletdetails.yaml` which also changes description) +- Duplicate wallet name validation +- Switching between multiple wallets +- Wallet archival / deletion + +### Send – Advanced +- QR-scan-based address entry (requires camera hardware) +- Fee selection variants (slow / medium / fast) +- Send-max flow +- Insufficient balance or dust-limit error state +- Wrong passcode at send confirmation +- Network / broadcast failure UX + +### Buy Bitcoin +- Choosing a specific buy provider +- Completing a real buy order (requires external service) +- Handling buy-redirect in browser / webview + +### Key Management +- Adding hardware signers (Coldcard, Ledger, Trezor, etc.) +- Health check failure states (key unavailable) +- Add-key cancellation at each wizard step + +### App Settings – Deep +- Tor on / off toggle and connectivity validation +- Custom node settings entry and save +- Language and currency change and persistence +- FAQ, Terms, Privacy Policy webview navigation + +### Backup / Recovery +- Import existing wallet via recovery phrase +- Backup verification quiz (if present) +- Cloud backup (Google Drive / iCloud) flows + +### Notifications / Background +- Push notification permission request and denial +- App-relaunch state persistence + +--- + +## What Cannot Be Reliably Automated with Maestro Alone + +| Limitation | Reason | +|---|---| +| **Hardware signer interactions** | Coldcard, Ledger, Trezor require physical USB/NFC; no Maestro equivalent | +| **QR-code scanning** | Maestro cannot present a QR image to the device camera | +| **Real Bitcoin transactions** | Require funded testnet wallet, broadcast, and network confirmation | +| **External browser / app handoff** | Maestro cannot control a system browser or third-party app opened mid-flow | +| **OS biometric prompts** | Face ID / fingerprint dialogs cannot be interacted with in Maestro without additional tooling | +| **Camera / photo-library permission matrixes** | Full iOS permission variants (Ask Every Time, Only While Using, etc.) need XCUITest or Appium | +| **Deep OS-level permission flows on iOS** | iOS restricts programmatic interaction with system permission dialogs beyond a single grant/deny | +| **Network / backend simulation** | Simulating Electrum node downtime or API failures requires environment instrumentation | +| **Dynamic balance-dependent paths** | Tests that branch on wallet balance or UTXO count require controlled test-data setup | +| **Push notifications** | Cannot be triggered or dismissed reliably via Maestro alone | +| **App Store / Play Store in-app purchase** | Subscription purchase dialogs are system-owned and cannot be automated by Maestro | +| **Clipboard verification** | Reading the clipboard content after a copy action is not natively supported by Maestro | diff --git a/flows/appsettings.yaml b/flows/appsettings.yaml index d4aec33bf3..0f77ee067f 100644 --- a/flows/appsettings.yaml +++ b/flows/appsettings.yaml @@ -8,7 +8,7 @@ appId: ${APPID} - assertVisible: 'App Settings' - assertVisible: 'Configure your app here' - assertVisible: - id: 'btn_App_Backup}' + id: 'btn_App_Backup' - assertVisible: id: 'view_Biometrics' - assertVisible: @@ -22,7 +22,7 @@ appId: ${APPID} - assertVisible: id: 'view_Language_&_Currency' - assertVisible: - id: 'view_ KeeperTelegram' + id: 'view_KeeperTelegram' - assertVisible: id: 'view_keeperTwitter' - assertVisible: diff --git a/flows/appsettings_toggles.yaml b/flows/appsettings_toggles.yaml new file mode 100644 index 0000000000..fa150af6b8 --- /dev/null +++ b/flows/appsettings_toggles.yaml @@ -0,0 +1,26 @@ +appId: ${APPID} +--- +# Happy-path: toggle dark mode on then off inside App Settings and navigate back. +- tapOn: + id: 'btn_AppSettingsIcon' +- waitForAnimationToEnd: + timeout: 1000 +- assertVisible: + id: 'btn_back' +- assertVisible: 'App Settings' +# Toggle dark mode ON +- tapOn: + id: 'view_Dark_Mode' + delay: 500 +- waitForAnimationToEnd: + timeout: 1000 +# Toggle dark mode back OFF +- tapOn: + id: 'view_Dark_Mode' + delay: 500 +- waitForAnimationToEnd: + timeout: 1000 +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/buyBTC.yaml b/flows/buyBTC.yaml index a946893831..c6d83fb97f 100644 --- a/flows/buyBTC.yaml +++ b/flows/buyBTC.yaml @@ -10,16 +10,24 @@ appId: ${APPID} duration: 2000 - waitForAnimationToEnd: timeout: 5000 -- repeat: - while: - visible: - id: 'icon_unconfirmed_0' - commands: - - runFlow: refreshwallet.yaml +# Wait up to 60 s for any unconfirmed transactions to clear before buying +- extendedWaitUntil: + notVisible: + id: 'icon_unconfirmed_0' + timeout: 60000 - assertVisible: id: 'btn_Buy Bitcoin' - tapOn: id: 'btn_Buy Bitcoin' +- waitForAnimationToEnd: + timeout: 2000 - tapOn: id: 'btn_primaryText' +- waitForAnimationToEnd: + timeout: 2000 +# Navigate back to home +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/copywalletaddress.yaml b/flows/copywalletaddress.yaml index e646f386c0..b36072bf58 100644 --- a/flows/copywalletaddress.yaml +++ b/flows/copywalletaddress.yaml @@ -8,12 +8,11 @@ appId: ${APPID} id: 'btn_Receive' - tapOn: id: 'btn_copy_address' -- repeat: - while: - visible: 'Address Copied Successfully' - commands: - - tapOn: - id: 'btn_copy_address' +- extendedWaitUntil: + visible: 'Address Copied Successfully' + timeout: 5000 +- waitForAnimationToEnd: + timeout: 3000 - tapOn: id: 'btn_back' - waitForAnimationToEnd: diff --git a/flows/exportseed.yaml b/flows/exportseed.yaml index 6c2b3501c3..c3bdb99114 100644 --- a/flows/exportseed.yaml +++ b/flows/exportseed.yaml @@ -1,7 +1,7 @@ appId: ${APPID} --- - tapOn: - id: 'btn_App_Backup}' + id: 'btn_App_Backup' - waitForAnimationToEnd: timeout: 1000 - assertVisible: @@ -17,14 +17,15 @@ appId: ${APPID} timeout: 5000 - assertVisible: 'Confirm Passcode' - assertVisible: 'To backup app recovery phrase' +# Enter correct passcode: 1-1-1-1 (matches the PIN set in setpin.yaml) - tapOn: id: 'key_1' - tapOn: - id: 'key_2' + id: 'key_1' - tapOn: - id: 'key_3' + id: 'key_1' - tapOn: - id: 'key_4' + id: 'key_1' - assertVisible: id: 'btn_primaryText' - tapOn: diff --git a/flows/exportseed_wrong_pin.yaml b/flows/exportseed_wrong_pin.yaml new file mode 100644 index 0000000000..ba52db4f5d --- /dev/null +++ b/flows/exportseed_wrong_pin.yaml @@ -0,0 +1,53 @@ +appId: ${APPID} +--- +# Negative-path: entering the wrong PIN on the seed-export passcode screen +# should not reveal the recovery phrase and should stay on the passcode screen. +- tapOn: + id: 'btn_AppSettingsIcon' +- waitForAnimationToEnd: + timeout: 1000 +- assertVisible: + id: 'btn_App_Backup' +- tapOn: + id: 'btn_App_Backup' +- waitForAnimationToEnd: + timeout: 1000 +- assertVisible: + id: 'view_Export_app_individual_phrase' +- tapOn: + id: 'view_Export_app_individual_phrase' + waitToSettleTimeoutMs: 500 +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: 'Confirm Passcode' +# Enter an intentionally wrong PIN: 9-9-9-9 +- tapOn: + id: 'key_9' +- tapOn: + id: 'key_9' +- tapOn: + id: 'key_9' +- tapOn: + id: 'key_9' +- assertVisible: + id: 'btn_primaryText' +- tapOn: + id: 'btn_primaryText' +- waitForAnimationToEnd: + timeout: 3000 +# Should remain on the passcode screen (NOT advance to recovery phrase) +- assertNotVisible: 'Recovery Phrase' +- assertVisible: 'Confirm Passcode' +# Navigate back to home screen +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/login_wrong_pin.yaml b/flows/login_wrong_pin.yaml new file mode 100644 index 0000000000..d11a7c600f --- /dev/null +++ b/flows/login_wrong_pin.yaml @@ -0,0 +1,33 @@ +appId: ${APPID} +--- +# Negative-path: verify that an incorrect PIN shows an error, then log in with the correct PIN. +- extendedWaitUntil: + visible: 'Enter your passcode' + timeout: 20000 +# Enter a deliberately wrong PIN: 9-9-9-9 +- tapOn: '9' +- tapOn: '9' +- tapOn: '9' +- tapOn: '9' +- assertVisible: 'Proceed' +- tapOn: 'Proceed' +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: 'Incorrect password' +- assertVisible: 'Retry' +# Recover by entering the correct PIN: 1-1-1-1 +- tapOn: 'Retry' +- tapOn: '1' +- tapOn: '1' +- tapOn: '1' +- tapOn: '1' +- assertVisible: 'Proceed' +- tapOn: 'Proceed' +- waitForAnimationToEnd: + timeout: 5000 +- extendedWaitUntil: + visible: 'Next' + timeout: 30000 +- tapOn: 'Next' +- waitForAnimationToEnd: + timeout: 2000 diff --git a/flows/receive.yaml b/flows/receive.yaml index e05bb99d0d..3974943870 100644 --- a/flows/receive.yaml +++ b/flows/receive.yaml @@ -3,24 +3,21 @@ appId: ${APPID} - tapOn: id: 'view_wallet_0' index: 0 -- swipe: - from: - id: list_transactions - direction: DOWN - duration: 2000 - waitForAnimationToEnd: - timeout: 5000 -- repeat: - while: - visible: - id: 'icon_unconfirmed_0' - commands: - - runFlow: refreshwallet.yaml + timeout: 2000 - assertVisible: id: 'btn_Receive' - tapOn: id: 'btn_Receive' +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: + id: 'btn_back' - tapOn: id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 - tapOn: id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/receive_address_display.yaml b/flows/receive_address_display.yaml new file mode 100644 index 0000000000..8aa61f5c6b --- /dev/null +++ b/flows/receive_address_display.yaml @@ -0,0 +1,37 @@ +appId: ${APPID} +--- +# Happy-path: open the receive screen, verify the QR code area and address +# are visible, copy the address, verify the success toast, then navigate back. +- tapOn: + id: 'view_wallet_0' + index: 0 +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: + id: 'btn_Receive' +- tapOn: + id: 'btn_Receive' +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: + id: 'btn_back' +# Verify the copy-address control is present +- assertVisible: + id: 'btn_copy_address' +# Copy address and wait for success confirmation +- tapOn: + id: 'btn_copy_address' +- extendedWaitUntil: + visible: 'Address Copied Successfully' + timeout: 5000 +- waitForAnimationToEnd: + timeout: 3000 +# Navigate back to wallet screen +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/regression_full.yaml b/flows/regression_full.yaml new file mode 100644 index 0000000000..e058f7b294 --- /dev/null +++ b/flows/regression_full.yaml @@ -0,0 +1,45 @@ +appId: ${APPID} +--- +# Full regression suite — runs all major happy-path flows followed by +# the most critical negative-path flows in sequence. +# +# Prerequisites: +# - APPID environment variable is set to the app bundle / package ID. +# - The device starts from a freshly-installed app state (or test-reset state). +# +# Run with: +# maestro test flows/regression_full.yaml + +# ── Onboarding ────────────────────────────────────────────────────────────── +- runFlow: newapp.yaml +- runFlow: addwallet.yaml +- runFlow: addNewKey.yaml + +# ── Wallet operations ──────────────────────────────────────────────────────── +- runFlow: viewwallet.yaml +- runFlow: walletSetting.yaml +- runFlow: editwalletdetails.yaml + +# ── Receive / Copy address ─────────────────────────────────────────────────── +- runFlow: receive.yaml +- runFlow: receive_address_display.yaml +- runFlow: copywalletaddress.yaml + +# ── App settings ───────────────────────────────────────────────────────────── +- runFlow: appsettings.yaml +- runFlow: appsettings_toggles.yaml + +# ── Key management ─────────────────────────────────────────────────────────── +- runFlow: keySetting.yaml +- runFlow: healthCheckKey.yaml + +# ── Subscription / version history ─────────────────────────────────────────── +- runFlow: subscription.yaml +- runFlow: versionhistory.yaml + +# ── Negative-path flows ─────────────────────────────────────────────────────── +- runFlow: send_cancel.yaml +- runFlow: send_invalid_address.yaml +- runFlow: send_empty_amount.yaml +- runFlow: wallet_creation_cancel.yaml +- runFlow: exportseed_wrong_pin.yaml diff --git a/flows/send.yaml b/flows/send.yaml index 2e553e5a80..4a8a00b751 100644 --- a/flows/send.yaml +++ b/flows/send.yaml @@ -27,8 +27,10 @@ appId: ${APPID} visible: id: 'com.android.permissioncontroller:id/grant_dialog' commands: + # Android: grant camera permission for QR scanner - tapOn: id: 'com.android.permissioncontroller:id/permission_allow_foreground_only_button' +# iOS: camera permission alert handled natively by the OS before this point - assertTrue: ${output.text_header_title = "Send"} - assertTrue: ${output.text_header_subtitle = "Scan a bitcoin address"} - assertVisible: diff --git a/flows/send_cancel.yaml b/flows/send_cancel.yaml new file mode 100644 index 0000000000..60cb113f74 --- /dev/null +++ b/flows/send_cancel.yaml @@ -0,0 +1,41 @@ +appId: ${APPID} +--- +# Negative-path: tap the back button at the address-entry step of a Send flow +# to verify the user is returned cleanly to the wallet screen. +- tapOn: + id: 'view_wallet_0' + index: 0 +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: + id: 'btn_Send' +- tapOn: + id: 'btn_Send' +- waitForAnimationToEnd: + timeout: 2000 +# Handle Android camera-permission dialog (no-op on iOS) +- runFlow: + when: + visible: + id: 'com.android.permissioncontroller:id/grant_dialog' + commands: + - tapOn: + id: 'com.android.permissioncontroller:id/permission_allow_foreground_only_button' +- assertVisible: + id: 'input_receive_address' +- assertVisible: + id: 'btn_back' +# Cancel by pressing back without entering anything +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +# Should be back on the wallet detail screen +- assertVisible: + id: 'btn_Send' +- assertVisible: + id: 'btn_Receive' +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/send_empty_amount.yaml b/flows/send_empty_amount.yaml new file mode 100644 index 0000000000..15ef2074df --- /dev/null +++ b/flows/send_empty_amount.yaml @@ -0,0 +1,55 @@ +appId: ${APPID} +--- +# Negative-path: submitting the amount screen with an empty amount field +# should not advance to the fee/confirmation screen. +- tapOn: + id: 'view_wallet_0' + index: 0 +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: + id: 'btn_Send' +- tapOn: + id: 'btn_Send' +- waitForAnimationToEnd: + timeout: 2000 +# Handle Android camera-permission dialog (no-op on iOS) +- runFlow: + when: + visible: + id: 'com.android.permissioncontroller:id/grant_dialog' + commands: + - tapOn: + id: 'com.android.permissioncontroller:id/permission_allow_foreground_only_button' +- assertVisible: + id: 'input_receive_address' +# Enter a valid testnet address to reach the amount screen +- tapOn: + id: 'input_receive_address' +- inputText: 'tb1qq4yupzkhnzlz8kva9udnud6rw5vezk5qr7kp7s' +- waitForAnimationToEnd: + timeout: 2000 +# Should now be on the amount screen; leave amount empty and try to proceed +- assertVisible: + id: 'input_amount' +- assertVisible: + id: 'btn_primaryText' +- tapOn: + id: 'btn_primaryText' +- waitForAnimationToEnd: + timeout: 2000 +# The fee/confirmation screen should NOT appear +- assertNotVisible: 'Sending to address' +# Navigate back to wallet screen +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/send_invalid_address.yaml b/flows/send_invalid_address.yaml new file mode 100644 index 0000000000..e24fa026c8 --- /dev/null +++ b/flows/send_invalid_address.yaml @@ -0,0 +1,44 @@ +appId: ${APPID} +--- +# Negative-path: entering a non-Bitcoin string as the send address should +# not advance to the amount screen. After verifying the error the flow +# navigates back to leave the app in a clean state. +- tapOn: + id: 'view_wallet_0' + index: 0 +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: + id: 'btn_Send' +- tapOn: + id: 'btn_Send' +- waitForAnimationToEnd: + timeout: 2000 +# Handle Android camera-permission dialog (no-op on iOS) +- runFlow: + when: + visible: + id: 'com.android.permissioncontroller:id/grant_dialog' + commands: + - tapOn: + id: 'com.android.permissioncontroller:id/permission_allow_foreground_only_button' +- assertVisible: + id: 'input_receive_address' +# Enter a clearly invalid address string +- tapOn: + id: 'input_receive_address' +- inputText: 'not_a_valid_address_xyz' +- waitForAnimationToEnd: + timeout: 2000 +# The app should NOT advance to the amount screen +- assertNotVisible: + id: 'input_amount' +# Navigate back to wallet screen +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/setpin.yaml b/flows/setpin.yaml index a91611fb2d..2fa4114c3c 100644 --- a/flows/setpin.yaml +++ b/flows/setpin.yaml @@ -28,11 +28,17 @@ appId: ${APPID} - tapOn: id: 'btn_clear' - assertNotVisible: 'Passcodes do NOT match' +# Re-enter all 4 digits of the correct passcode (1-1-1-1) +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' - tapOn: id: 'key_1' -- tapOn: 'Create' - waitForAnimationToEnd: - timeout: 5000 + timeout: 2000 - assertVisible: 'Continue' - tapOn: 'Continue' - swipe: diff --git a/flows/setpin_mismatch.yaml b/flows/setpin_mismatch.yaml new file mode 100644 index 0000000000..6071eb4091 --- /dev/null +++ b/flows/setpin_mismatch.yaml @@ -0,0 +1,59 @@ +appId: ${APPID} +--- +# Negative-path: dedicated passcode-mismatch test that runs independently of +# the full onboarding flow. Verifies the mismatch error UI and clear behaviour. +# NOTE: This flow expects the app to be on the "Create a passcode" screen +# (fresh install / test-reset state). +- extendedWaitUntil: + visible: 'Create a passcode' + timeout: 20000 +# Enter first PIN: 1-1-1-1 +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- waitForAnimationToEnd: + timeout: 2000 +# On the confirm screen enter a MISMATCHED PIN: 1-1-1-4 +- assertVisible: 'Confirm your passcode' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_4' +# Verify the mismatch error is shown +- assertVisible: 'Passcodes do NOT match' +# Clear and re-enter the correct matching PIN +- tapOn: + id: 'btn_clear' +- assertNotVisible: 'Passcodes do NOT match' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- tapOn: + id: 'key_1' +- waitForAnimationToEnd: + timeout: 2000 +# PIN accepted — complete onboarding +- assertVisible: 'Continue' +- tapOn: 'Continue' +- swipe: + direction: LEFT +- swipe: + direction: LEFT +- swipe: + direction: LEFT +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: 'Start App' +- tapOn: 'Start App' diff --git a/flows/subscription.yaml b/flows/subscription.yaml index 4ef8508cb3..7236f114a3 100644 --- a/flows/subscription.yaml +++ b/flows/subscription.yaml @@ -4,8 +4,11 @@ appId: ${APPID} id: 'btn_choosePlan' - tapOn: id: 'btn_choosePlan' +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: + id: 'btn_back' - tapOn: - id: 'btn_Mobile Key' - index: 0 -- tapOn: - id: 'btn_Settings' + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/viewwallet.yaml b/flows/viewwallet.yaml index c82f285532..b8a84bd0c1 100644 --- a/flows/viewwallet.yaml +++ b/flows/viewwallet.yaml @@ -3,27 +3,22 @@ appId: ${APPID} # - runFlow: login.yaml - tapOn: id: 'view_wallet_0' -- assertVisible: 'Bip-85 Wallets' -- assertVisible: 'Create as many (hot) wallets as you want, and backup with a single Recovery Phrase' -- assertVisible: 'You can use the individual wallet’s Recovery Phrases to connect other bitcoin apps to Keeper' -- assertVisible: 'When the funds in a wallet cross a threshold, a transfer to the Vault is triggered. This ensures you don’t have more sats in hot wallets than you need.' -- assertVisible: - id: 'btn_close_modal' -- assertVisible: 'See FAQs' -- assertVisible: Continue -- tapOn: - id: 'btn_close_modal' +# The first-run info modal may appear; dismiss it if visible +- runFlow: + when: + visible: + id: 'btn_close_modal' + commands: + - assertVisible: 'Bip-85 Wallets' + - assertVisible: 'See FAQs' + - tapOn: + id: 'btn_close_modal' +- waitForAnimationToEnd: + timeout: 2000 - assertVisible: id: 'btn_back' - assertVisible: id: 'btn_learnMore' -- assertVisible: 'Wallet 1' -- assertVisible: 'Single-sig bitcoin wallet' -- assertVisible: 'Unconfirmed' -- assertVisible: 'Available Balance' -- assertVisible: 'Transfer Policy is set at ' -- assertVisible: 'Transactions' -- assertVisible: 'No transactions yet.' - assertVisible: id: 'btn_Send' - assertVisible: @@ -32,12 +27,12 @@ appId: ${APPID} id: 'btn_Buy' - assertVisible: id: 'btn_Settings' +# Open and dismiss the learn-more modal - tapOn: id: 'btn_learnMore' +- waitForAnimationToEnd: + timeout: 1000 - assertVisible: 'Bip-85 Wallets' -- assertVisible: 'Create as many (hot) wallets as you want, and backup with a single Recovery Phrase' -- assertVisible: 'You can use the individual wallet’s Recovery Phrases to connect other bitcoin apps to Keeper' -- assertVisible: 'When the funds in a wallet cross a threshold, a transfer to the Vault is triggered. This ensures you don’t have more sats in hot wallets than you need.' - tapOn: id: 'btn_close_modal' - waitForAnimationToEnd: diff --git a/flows/walletSetting.yaml b/flows/walletSetting.yaml index fbd85fc9ae..1765dd80f2 100644 --- a/flows/walletSetting.yaml +++ b/flows/walletSetting.yaml @@ -10,13 +10,20 @@ appId: ${APPID} duration: 2000 - waitForAnimationToEnd: timeout: 5000 -- repeat: - while: - visible: - id: 'icon_unconfirmed_0' - commands: - - runFlow: refreshwallet.yaml - assertVisible: id: 'btn_Settings' - tapOn: id: 'btn_Settings' +- waitForAnimationToEnd: + timeout: 1000 +- assertVisible: + id: 'btn_back' +- assertVisible: 'Wallet Settings' +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 diff --git a/flows/wallet_creation_cancel.yaml b/flows/wallet_creation_cancel.yaml new file mode 100644 index 0000000000..6a41de6d58 --- /dev/null +++ b/flows/wallet_creation_cancel.yaml @@ -0,0 +1,38 @@ +appId: ${APPID} +--- +# Negative-path: start the wallet creation wizard and cancel at the +# transfer-policy screen to verify the user lands back on the home screen +# without a new wallet being created. +- assertVisible: + id: 'wallet_list' +- swipe: + from: + id: 'wallet_list' + direction: LEFT +- assertVisible: + id: 'btn_add_wallet' +- tapOn: + id: 'btn_add_wallet' +- waitForAnimationToEnd: + timeout: 1000 +- assertVisible: + id: 'btn_Hot Wallet' +# Begin hot-wallet creation +- tapOn: + id: 'btn_Hot Wallet' +- waitForAnimationToEnd: + timeout: 2000 +# Should now be on the transfer-policy / wallet-name screen; cancel it +- assertVisible: + id: 'btn_back' +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000 +# Should be back on the wallet-type picker or home; press back once more to reach home +- assertVisible: + id: 'btn_back' +- tapOn: + id: 'btn_back' +- waitForAnimationToEnd: + timeout: 1000