Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions flows/COVERAGE.md
Original file line number Diff line number Diff line change
@@ -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 |
4 changes: 2 additions & 2 deletions flows/appsettings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -22,7 +22,7 @@ appId: ${APPID}
- assertVisible:
id: 'view_Language_&_Currency'
- assertVisible:
id: 'view_ KeeperTelegram'
id: 'view_KeeperTelegram'
- assertVisible:
id: 'view_keeperTwitter'
- assertVisible:
Expand Down
26 changes: 26 additions & 0 deletions flows/appsettings_toggles.yaml
Original file line number Diff line number Diff line change
@@ -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
20 changes: 14 additions & 6 deletions flows/buyBTC.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

11 changes: 5 additions & 6 deletions flows/copywalletaddress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions flows/exportseed.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
appId: ${APPID}
---
- tapOn:
id: 'btn_App_Backup}'
id: 'btn_App_Backup'
- waitForAnimationToEnd:
timeout: 1000
- assertVisible:
Expand All @@ -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:
Expand Down
53 changes: 53 additions & 0 deletions flows/exportseed_wrong_pin.yaml
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions flows/login_wrong_pin.yaml
Original file line number Diff line number Diff line change
@@ -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
21 changes: 9 additions & 12 deletions flows/receive.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading