diff --git a/.github/workflows/mobile:validate-build-pre-eas.yml b/.github/workflows/mobile:validate-build-pre-eas.yml
index 7e8ae6de4f..a9ab7eaccd 100644
--- a/.github/workflows/mobile:validate-build-pre-eas.yml
+++ b/.github/workflows/mobile:validate-build-pre-eas.yml
@@ -2,46 +2,78 @@ name: mobile:validate-build-pre-eas
permissions:
contents: read
+ checks: read
on:
# Run on release-please dev releases
release:
types: [published]
-
- # Run on PRs that affect mobile
+
+ # Run on PRs that affect mobile or its runtime dependencies
pull_request:
paths:
- 'apps/mobile/**'
- - 'packages/**'
+ - 'packages/analytics/**'
+ - 'packages/bitcoin/**'
+ - 'packages/constants/**'
+ - 'packages/crypto/**'
+ - 'packages/features/**'
+ - 'packages/models/**'
+ - 'packages/provider/**'
+ - 'packages/queries/**'
+ - 'packages/query/**'
+ - 'packages/rpc/**'
+ - 'packages/services/**'
+ - 'packages/stacks/**'
+ - 'packages/state/**'
+ - 'packages/tokens/**'
+ - 'packages/ui/**'
+ - 'packages/utils/**'
- 'pnpm-lock.yaml'
- 'turbo.json'
-
+
# Allow manual trigger
workflow_dispatch:
jobs:
+ code-quality-gate:
+ name: code-quality-gate
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+
+ steps:
+ - name: await-repo-code-checks
+ uses: wechuli/allcheckspassed@v1
+ with:
+ checks_include: lint-eslint,typecheck,lint-prettier,test-unit
+ polling_interval: 0.5
+ retries: 50
+ delay: 0.5
+
mobile-validate-build:
name: validate-mobile-build
+ needs: code-quality-gate
+ if: ${{ !cancelled() && (needs.code-quality-gate.result == 'success' || needs.code-quality-gate.result == 'skipped') }}
runs-on: ubuntu-latest
-
+
steps:
- name: checkout-code
uses: actions/checkout@v4
-
+
- name: setup-node
uses: actions/setup-node@v4
with:
node-version: '22.15.0'
-
+
- name: setup-pnpm
uses: pnpm/action-setup@v4
-
+
- name: get-pnpm-store-directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> "$GITHUB_OUTPUT"
-
+
- name: setup-pnpm-cache
uses: actions/cache@v4
with:
@@ -49,10 +81,10 @@ jobs:
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
-
+
- name: run-mobile-build-validation
run: ./scripts/test-mobile-build.sh
-
+
- name: upload-build-logs
if: failure()
uses: actions/upload-artifact@v4
diff --git a/apps/mobile/.eas/workflows/android-maestro-test.yml b/apps/mobile/.eas/workflows/android-maestro-test.yml
index 497f8adb9d..e71c4d171c 100644
--- a/apps/mobile/.eas/workflows/android-maestro-test.yml
+++ b/apps/mobile/.eas/workflows/android-maestro-test.yml
@@ -26,4 +26,5 @@ jobs:
params:
build_id: ${{ needs.build_android.outputs.build_id }}
flow_path:
- - maestro/flows/smoke-tests.yaml
+ - maestro/flows/smoke/
+ - maestro/flows/android/
diff --git a/apps/mobile/.eas/workflows/ios-maestro-test.yml b/apps/mobile/.eas/workflows/ios-maestro-test.yml
index d76804d309..a1d4096135 100644
--- a/apps/mobile/.eas/workflows/ios-maestro-test.yml
+++ b/apps/mobile/.eas/workflows/ios-maestro-test.yml
@@ -26,4 +26,4 @@ jobs:
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
flow_path:
- - maestro/flows/smoke-tests.yaml
+ - maestro/flows/smoke/
diff --git a/apps/mobile/.eas/workflows/maestro-smoke-test.yml b/apps/mobile/.eas/workflows/maestro-smoke-test.yml
index d3d73cbe71..1401c74ba3 100644
--- a/apps/mobile/.eas/workflows/maestro-smoke-test.yml
+++ b/apps/mobile/.eas/workflows/maestro-smoke-test.yml
@@ -1,4 +1,4 @@
-name: Maestro Smoke Test
+name: Maestro E2E Tests
on:
pull_request:
@@ -21,6 +21,7 @@ jobs:
run_eas_update:
name: EAS Update
type: update
+ environment: development
params:
platform: all
channel: cicd
@@ -61,8 +62,12 @@ jobs:
platform: ios
profile: devClient
- maestro_android_cached:
- name: Android Maestro (cached)
+ # ============================================
+ # Smoke tests — fast fail gate
+ # ============================================
+
+ smoke_android_cached:
+ name: Android Smoke (cached)
needs: [get_android_build, run_eas_update]
if: ${{ needs.get_android_build.outputs.build_id }}
type: maestro
@@ -72,8 +77,8 @@ jobs:
build_id: ${{ needs.get_android_build.outputs.build_id }}
flow_path: maestro/flows/smoke-tests-ci.yaml
- maestro_ios_cached:
- name: iOS Maestro (cached)
+ smoke_ios_cached:
+ name: iOS Smoke (cached)
needs: [get_ios_build, run_eas_update]
if: ${{ needs.get_ios_build.outputs.build_id }}
type: maestro
@@ -83,8 +88,8 @@ jobs:
build_id: ${{ needs.get_ios_build.outputs.build_id }}
flow_path: maestro/flows/smoke-tests-ci.yaml
- maestro_android_fresh:
- name: Android Maestro (fresh)
+ smoke_android_fresh:
+ name: Android Smoke (fresh)
needs: [build_android, run_eas_update]
type: maestro
env:
@@ -93,8 +98,8 @@ jobs:
build_id: ${{ needs.build_android.outputs.build_id }}
flow_path: maestro/flows/smoke-tests-ci.yaml
- maestro_ios_fresh:
- name: iOS Maestro (fresh)
+ smoke_ios_fresh:
+ name: iOS Smoke (fresh)
needs: [build_ios, run_eas_update]
type: maestro
env:
@@ -102,3 +107,49 @@ jobs:
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
flow_path: maestro/flows/smoke-tests-ci.yaml
+
+ # ============================================
+ # Full suite — runs only after smoke passes
+ # ============================================
+
+ maestro_android_cached:
+ name: Android Full Suite (cached)
+ needs: [smoke_android_cached, get_android_build]
+ if: ${{ needs.get_android_build.outputs.build_id }}
+ type: maestro
+ env:
+ MAESTRO_DEEP_LINK_URL: 'exp+leather://expo-development-client/?url=https://u.expo.dev/c03c1f22-be7b-4b76-aa1b-3ebf716bd2cc?channel-name=cicd'
+ params:
+ build_id: ${{ needs.get_android_build.outputs.build_id }}
+ flow_path: maestro/flows/full-suite-ci.yaml
+
+ maestro_ios_cached:
+ name: iOS Full Suite (cached)
+ needs: [smoke_ios_cached, get_ios_build]
+ if: ${{ needs.get_ios_build.outputs.build_id }}
+ type: maestro
+ env:
+ MAESTRO_DEEP_LINK_URL: 'exp+leather://expo-development-client/?url=https://u.expo.dev/c03c1f22-be7b-4b76-aa1b-3ebf716bd2cc?channel-name=cicd'
+ params:
+ build_id: ${{ needs.get_ios_build.outputs.build_id }}
+ flow_path: maestro/flows/full-suite-ci.yaml
+
+ maestro_android_fresh:
+ name: Android Full Suite (fresh)
+ needs: [smoke_android_fresh, build_android]
+ type: maestro
+ env:
+ MAESTRO_DEEP_LINK_URL: 'exp+leather://expo-development-client/?url=https://u.expo.dev/c03c1f22-be7b-4b76-aa1b-3ebf716bd2cc?channel-name=cicd'
+ params:
+ build_id: ${{ needs.build_android.outputs.build_id }}
+ flow_path: maestro/flows/full-suite-ci.yaml
+
+ maestro_ios_fresh:
+ name: iOS Full Suite (fresh)
+ needs: [smoke_ios_fresh, build_ios]
+ type: maestro
+ env:
+ MAESTRO_DEEP_LINK_URL: 'exp+leather://expo-development-client/?url=https://u.expo.dev/c03c1f22-be7b-4b76-aa1b-3ebf716bd2cc?channel-name=cicd'
+ params:
+ build_id: ${{ needs.build_ios.outputs.build_id }}
+ flow_path: maestro/flows/full-suite-ci.yaml
diff --git a/apps/mobile/maestro/flows/android/010-create-wallet-sheet.yaml b/apps/mobile/maestro/flows/android/010-create-wallet-sheet.yaml
new file mode 100644
index 0000000000..7445ac21cf
--- /dev/null
+++ b/apps/mobile/maestro/flows/android/010-create-wallet-sheet.yaml
@@ -0,0 +1,16 @@
+appId: io.leather.mobilewallet
+name: "Android: Create Wallet via Sheet"
+---
+- launchApp
+- runFlow: ../../shared/clean-up.yaml
+- tapOn:
+ id: 'homeCreateWalletCard'
+- tapOn: 'Create new wallet'
+- tapOn:
+ id: 'walletCreationTapToReveal'
+- tapOn:
+ id: 'walletCreationBackedUpButton'
+- tapOn: 'Skip for now'
+- tapOn: 'Proceed'
+- assertVisible:
+ id: 'homeAccountCard-0'
diff --git a/apps/mobile/maestro/flows/android/020-restore-wallet-sheet.yaml b/apps/mobile/maestro/flows/android/020-restore-wallet-sheet.yaml
new file mode 100644
index 0000000000..27b0ed825f
--- /dev/null
+++ b/apps/mobile/maestro/flows/android/020-restore-wallet-sheet.yaml
@@ -0,0 +1,18 @@
+appId: io.leather.mobilewallet
+name: "Android: Restore Wallet via Sheet"
+---
+- launchApp
+- runFlow: ../../shared/clean-up.yaml
+- tapOn:
+ id: 'homeCreateWalletCard'
+- tapOn:
+ id: 'restoreWalletSheetButton'
+- tapOn:
+ id: 'restoreWalletTextInput'
+- inputText: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus'
+- tapOn:
+ id: 'restoreWalletContinue'
+- tapOn: 'Skip for now'
+- tapOn: 'Proceed'
+- assertVisible:
+ id: 'homeAccountCard-0'
diff --git a/apps/mobile/maestro/flows/full-suite-ci.yaml b/apps/mobile/maestro/flows/full-suite-ci.yaml
new file mode 100644
index 0000000000..10bed56100
--- /dev/null
+++ b/apps/mobile/maestro/flows/full-suite-ci.yaml
@@ -0,0 +1,201 @@
+# Full E2E Test Suite (CI with EAS Update deep link)
+# This version loads the JS bundle via EAS Update for faster CI testing
+appId: io.leather.mobilewallet
+name: Full Test Suite CI
+---
+# Launch the app first (required for deep links to work)
+- launchApp
+
+# Wait for app to fully initialize and register URL schemes (iOS needs this)
+- waitForAnimationToEnd
+
+# Then open the deep link to load EAS Update
+- openLink: ${MAESTRO_DEEP_LINK_URL}
+
+# Handle iOS deep link confirmation prompt if it appears
+- runFlow:
+ when:
+ visible: 'Open'
+ commands:
+ - tapOn: 'Open'
+
+# Wait for EAS Update to load - shows empty home screen
+- extendedWaitUntil:
+ visible: 'Add account'
+ timeout: 30000
+
+# ============================================
+# SECTION 0: Capture Environment Info
+# ============================================
+
+# Navigate to dev console and screenshot env vars for debugging
+- tapOn:
+ id: 'homeDeveloperToolsButton'
+- extendedWaitUntil:
+ visible: 'Environment'
+ timeout: 5000
+- takeScreenshot: env-debug-info
+- tapOn:
+ id: 'backButton'
+
+# ============================================
+# SECTION 1: Wallet Creation
+# ============================================
+
+# Verify empty home screen
+- assertVisible:
+ id: 'homeCreateWalletCard'
+
+# Create a wallet via developer console (sheets don't present reliably in EAS Update CI)
+- runFlow: ../shared/create-wallet-dev-console.yaml
+
+# Verify wallet created
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# ============================================
+# SECTION 2: Settings Navigation
+# ============================================
+
+# Navigate to Settings
+- tapOn:
+ id: 'homeSettingsButton'
+- assertVisible: 'SETTINGS'
+
+# Test Wallets and Accounts
+- tapOn:
+ id: 'settingsWalletAndAccountsButton'
+- assertVisible: 'Wallet 1'
+- tapOn:
+ id: 'backButton'
+
+# Test Display settings
+- tapOn:
+ id: 'settingsDisplayButton'
+- assertVisible: 'DISPLAY'
+- tapOn:
+ id: 'backButton'
+
+# Test Security settings
+- tapOn:
+ id: 'settingsSecurityButton'
+- assertVisible: 'SECURITY'
+- tapOn:
+ id: 'backButton'
+
+# Test Networks settings
+- tapOn:
+ id: 'settingsNetworkButton'
+- assertVisible: 'NETWORKS'
+- tapOn:
+ id: 'backButton'
+
+# Test Help settings
+- tapOn:
+ id: 'settingsHelpButton'
+- assertVisible: 'HELP'
+- tapOn:
+ id: 'backButton'
+
+# Back to home
+- tapOn:
+ id: 'backButton'
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# ============================================
+# SECTION 3: Send Flow
+# ============================================
+
+# Open send flow
+- tapOn:
+ text: 'Send'
+- assertVisible: 'Select asset'
+- assertVisible: 'Bitcoin'
+- assertVisible: 'Stacks'
+
+# Close send sheet (no back button on sheets, swipe to dismiss)
+- swipe:
+ start: 50%, 15%
+ end: 50%, 85%
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# ============================================
+# SECTION 4: Receive Flow
+# ============================================
+
+# Open receive flow
+- tapOn:
+ text: 'Receive'
+- assertVisible: 'Select asset'
+
+# Test Bitcoin address
+- tapOn:
+ id: 'receiveAssetItem'
+ index: 0
+- assertVisible: 'Native Segwit'
+- tapOn:
+ id: 'backButton'
+
+# Close receive sheet (no back button on first sheet screen, swipe to dismiss)
+- swipe:
+ start: 50%, 15%
+ end: 50%, 85%
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# ============================================
+# SECTION 5: Network Switching
+# ============================================
+
+# Switch to Testnet
+- tapOn:
+ id: 'homeSettingsButton'
+- tapOn:
+ id: 'settingsNetworkButton'
+- tapOn: 'Testnet4.*Disabled'
+- tapOn:
+ id: 'backButton'
+- tapOn:
+ id: 'backButton'
+- assertVisible: 'Testnet4'
+
+# Switch back to Mainnet
+- tapOn:
+ id: 'homeSettingsButton'
+- tapOn:
+ id: 'settingsNetworkButton'
+- tapOn: 'Mainnet.*Disabled'
+- tapOn:
+ id: 'backButton'
+- tapOn:
+ id: 'backButton'
+- assertNotVisible: 'Testnet4'
+
+# ============================================
+# SECTION 6: Cleanup - Remove Wallet
+# ============================================
+
+# Remove the wallet
+- runFlow: ../shared/remove-wallet.yaml
+
+# Verify back to empty state
+- assertVisible:
+ id: 'homeCreateWalletCard'
+
+# ============================================
+# SECTION 7: Wallet Restore
+# ============================================
+
+# Restore a wallet from mnemonic
+- runFlow: ../shared/restore-wallet.yaml
+
+# Verify wallet restored
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# Final verification - settings accessible
+- tapOn:
+ id: 'homeSettingsButton'
+- assertVisible: 'SETTINGS'
diff --git a/apps/mobile/maestro/flows/full-suite.yaml b/apps/mobile/maestro/flows/full-suite.yaml
new file mode 100644
index 0000000000..857f63639d
--- /dev/null
+++ b/apps/mobile/maestro/flows/full-suite.yaml
@@ -0,0 +1,169 @@
+# Full E2E Test Suite
+# Runs all tests in sequence, optimized for speed by reusing wallet state
+# This is the recommended flow for comprehensive testing
+appId: io.leather.mobilewallet
+name: Full Test Suite
+---
+# Start fresh
+- launchApp:
+ clearState: true
+
+# ============================================
+# SECTION 1: Wallet Creation
+# ============================================
+
+# Verify empty home screen
+- assertVisible:
+ id: 'homeCreateWalletCard'
+
+# Create a wallet
+- runFlow: ../shared/create-wallet.yaml
+
+# Verify wallet created
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# ============================================
+# SECTION 2: Settings Navigation
+# ============================================
+
+# Navigate to Settings
+- tapOn:
+ id: 'homeSettingsButton'
+- assertVisible: 'SETTINGS'
+
+# Test Wallets and Accounts
+- tapOn:
+ id: 'settingsWalletAndAccountsButton'
+- assertVisible: 'Wallet 1'
+- tapOn:
+ id: 'backButton'
+
+# Test Display settings
+- tapOn:
+ id: 'settingsDisplayButton'
+- assertVisible: 'DISPLAY'
+- tapOn:
+ id: 'backButton'
+
+# Test Security settings
+- tapOn:
+ id: 'settingsSecurityButton'
+- assertVisible: 'SECURITY'
+- tapOn:
+ id: 'backButton'
+
+# Test Networks settings
+- tapOn:
+ id: 'settingsNetworkButton'
+- assertVisible: 'NETWORKS'
+- tapOn:
+ id: 'backButton'
+
+# Test Help settings
+- tapOn:
+ id: 'settingsHelpButton'
+- assertVisible: 'HELP'
+- tapOn:
+ id: 'backButton'
+
+# Back to home
+- tapOn:
+ id: 'backButton'
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# ============================================
+# SECTION 3: Send Flow
+# ============================================
+
+# Open send flow
+- tapOn:
+ text: 'Send'
+- assertVisible: 'Select asset'
+- assertVisible: 'Bitcoin'
+- assertVisible: 'Stacks'
+
+# Close send sheet
+- tapOn:
+ id: 'backButton'
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# ============================================
+# SECTION 4: Receive Flow
+# ============================================
+
+# Open receive flow
+- tapOn:
+ text: 'Receive'
+- assertVisible: 'Select asset'
+
+# Test Bitcoin address
+- tapOn:
+ id: 'receiveAssetItem'
+ index: 0
+- assertVisible: 'Native Segwit'
+- tapOn:
+ id: 'backButton'
+
+# Close receive sheet
+- tapOn:
+ id: 'backButton'
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# ============================================
+# SECTION 5: Network Switching
+# ============================================
+
+# Switch to Testnet
+- tapOn:
+ id: 'homeSettingsButton'
+- tapOn:
+ id: 'settingsNetworkButton'
+- tapOn: 'Testnet4.*Disabled'
+- tapOn:
+ id: 'backButton'
+- tapOn:
+ id: 'backButton'
+- assertVisible: 'Testnet4'
+
+# Switch back to Mainnet
+- tapOn:
+ id: 'homeSettingsButton'
+- tapOn:
+ id: 'settingsNetworkButton'
+- tapOn: 'Mainnet.*Disabled'
+- tapOn:
+ id: 'backButton'
+- tapOn:
+ id: 'backButton'
+- assertNotVisible: 'Testnet4'
+
+# ============================================
+# SECTION 6: Cleanup - Remove Wallet
+# ============================================
+
+# Remove the wallet
+- runFlow: ../shared/remove-wallet.yaml
+
+# Verify back to empty state
+- assertVisible:
+ id: 'homeCreateWalletCard'
+
+# ============================================
+# SECTION 7: Wallet Restore
+# ============================================
+
+# Restore a wallet from mnemonic
+- runFlow: ../shared/restore-wallet.yaml
+
+# Verify wallet restored
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# Final verification - settings accessible
+- tapOn:
+ id: 'homeSettingsButton'
+- assertVisible: 'SETTINGS'
diff --git a/apps/mobile/maestro/flows/receive-flow.yaml b/apps/mobile/maestro/flows/receive-flow.yaml
new file mode 100644
index 0000000000..6d87599fb0
--- /dev/null
+++ b/apps/mobile/maestro/flows/receive-flow.yaml
@@ -0,0 +1,42 @@
+# Test: Receive flow UI
+# Verifies the receive flow opens and shows address options
+appId: io.leather.mobilewallet
+name: Receive Flow
+---
+- launchApp:
+ clearState: true
+
+# Create a wallet first
+- runFlow: ../shared/create-wallet.yaml
+
+# Open receive flow by tapping Receive button
+- tapOn:
+ text: 'Receive'
+
+# Verify receive sheet opens with asset selection
+- assertVisible: 'Select asset'
+
+# Verify Bitcoin address types are available
+- assertVisible:
+ id: 'receiveAssetItem'
+ index: 0
+
+# Tap on first receive asset (Bitcoin Native Segwit)
+- tapOn:
+ id: 'receiveAssetItem'
+ index: 0
+
+# Verify QR code / address screen appears
+- assertVisible: 'Native Segwit'
+
+# Go back to asset selection
+- tapOn:
+ id: 'backButton'
+
+# Close the sheet
+- tapOn:
+ id: 'backButton'
+
+# Verify we're back on home screen
+- assertVisible:
+ id: 'homePrivacyButton'
diff --git a/apps/mobile/maestro/flows/send-flow.yaml b/apps/mobile/maestro/flows/send-flow.yaml
new file mode 100644
index 0000000000..9efcf3e779
--- /dev/null
+++ b/apps/mobile/maestro/flows/send-flow.yaml
@@ -0,0 +1,31 @@
+# Test: Send flow UI
+# Verifies the send flow opens and shows asset selection
+appId: io.leather.mobilewallet
+name: Send Flow
+---
+- launchApp:
+ clearState: true
+
+# Create a wallet first
+- runFlow: ../shared/create-wallet.yaml
+
+# Open send flow by tapping Send button
+- tapOn:
+ text: 'Send'
+
+# Verify send sheet opens with asset selection
+- assertVisible: 'Select asset'
+
+# Verify Bitcoin is available as an option
+- assertVisible: 'Bitcoin'
+
+# Verify Stacks is available as an option
+- assertVisible: 'Stacks'
+
+# Close the sheet by going back
+- tapOn:
+ id: 'backButton'
+
+# Verify we're back on home screen
+- assertVisible:
+ id: 'homePrivacyButton'
diff --git a/apps/mobile/maestro/flows/settings-navigation.yaml b/apps/mobile/maestro/flows/settings-navigation.yaml
new file mode 100644
index 0000000000..002e737342
--- /dev/null
+++ b/apps/mobile/maestro/flows/settings-navigation.yaml
@@ -0,0 +1,53 @@
+# Test: Settings navigation
+# Verifies all settings screens are accessible
+appId: io.leather.mobilewallet
+name: Settings Navigation
+---
+- launchApp:
+ clearState: true
+
+# Create a wallet first (settings content varies based on wallet state)
+- runFlow: ../shared/create-wallet.yaml
+
+# Navigate to Settings
+- tapOn:
+ id: 'homeSettingsButton'
+- assertVisible: 'SETTINGS'
+
+# Test Wallets and Accounts navigation
+- tapOn:
+ id: 'settingsWalletAndAccountsButton'
+- assertVisible: 'Wallet 1'
+- tapOn:
+ id: 'backButton'
+
+# Test Display settings navigation
+- tapOn:
+ id: 'settingsDisplayButton'
+- assertVisible: 'DISPLAY'
+- tapOn:
+ id: 'backButton'
+
+# Test Security settings navigation
+- tapOn:
+ id: 'settingsSecurityButton'
+- assertVisible: 'SECURITY'
+- tapOn:
+ id: 'backButton'
+
+# Test Networks settings navigation
+- tapOn:
+ id: 'settingsNetworkButton'
+- assertVisible: 'NETWORKS'
+- tapOn:
+ id: 'backButton'
+
+# Test Help settings navigation
+- tapOn:
+ id: 'settingsHelpButton'
+- assertVisible: 'HELP'
+- tapOn:
+ id: 'backButton'
+
+# Verify we're back at settings
+- assertVisible: 'SETTINGS'
diff --git a/apps/mobile/maestro/flows/settings-network.yaml b/apps/mobile/maestro/flows/settings-network.yaml
new file mode 100644
index 0000000000..16046595d2
--- /dev/null
+++ b/apps/mobile/maestro/flows/settings-network.yaml
@@ -0,0 +1,45 @@
+# Test: Network switching
+# Verifies network can be changed and persists
+appId: io.leather.mobilewallet
+name: Settings Network
+---
+- launchApp:
+ clearState: true
+
+# Create a wallet first
+- runFlow: ../shared/create-wallet.yaml
+
+# Verify wallet exists on home screen
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# Navigate to Settings > Networks
+- tapOn:
+ id: 'homeSettingsButton'
+- tapOn:
+ id: 'settingsNetworkButton'
+- assertVisible: 'NETWORKS'
+
+# Switch to Testnet
+- tapOn: 'Testnet4.*Disabled'
+- tapOn:
+ id: 'backButton'
+
+# Verify network badge shows Testnet
+- tapOn:
+ id: 'backButton'
+- assertVisible: 'Testnet4'
+
+# Switch back to Mainnet
+- tapOn:
+ id: 'homeSettingsButton'
+- tapOn:
+ id: 'settingsNetworkButton'
+- tapOn: 'Mainnet.*Disabled'
+- tapOn:
+ id: 'backButton'
+- tapOn:
+ id: 'backButton'
+
+# Verify network badge no longer shows Testnet
+- assertNotVisible: 'Testnet4'
diff --git a/apps/mobile/maestro/flows/smoke-tests-ci.yaml b/apps/mobile/maestro/flows/smoke-tests-ci.yaml
index ffe6f3dd84..4791ca50eb 100644
--- a/apps/mobile/maestro/flows/smoke-tests-ci.yaml
+++ b/apps/mobile/maestro/flows/smoke-tests-ci.yaml
@@ -23,7 +23,11 @@ name: Smoke Tests CI
visible: 'Add account'
timeout: 30000
-# When the user toggles the settings button it should work
+# Run smoke tests: create wallet via dev console (sheets unreliable in EAS Update CI)
+- runFlow: ../shared/clean-up.yaml
+- runFlow: ../shared/create-wallet-dev-console.yaml
+
+# Verify settings navigation
- tapOn:
id: 'homeSettingsButton'
- assertVisible: 'SETTINGS'
diff --git a/apps/mobile/maestro/flows/smoke-tests.yaml b/apps/mobile/maestro/flows/smoke-tests.yaml
index e8e628b90c..67535da18a 100644
--- a/apps/mobile/maestro/flows/smoke-tests.yaml
+++ b/apps/mobile/maestro/flows/smoke-tests.yaml
@@ -2,13 +2,13 @@
appId: io.leather.mobilewallet
name: Smoke Tests
---
-# Scenario: App launches and user can create a new wallet
-- launchApp
+- launchApp:
+ clearState: true
-# Given the user is on the home screen
-- assertVisible: 'All accounts'
+# Create wallet via standard flow
+- runFlow: ../shared/create-wallet.yaml
-# When the user toggles the settings button it should work
+# Verify settings navigation
- tapOn:
id: 'homeSettingsButton'
- assertVisible: 'SETTINGS'
diff --git a/apps/mobile/maestro/flows/smoke/010-create-wallet.yaml b/apps/mobile/maestro/flows/smoke/010-create-wallet.yaml
new file mode 100644
index 0000000000..7d5c366eee
--- /dev/null
+++ b/apps/mobile/maestro/flows/smoke/010-create-wallet.yaml
@@ -0,0 +1,6 @@
+appId: io.leather.mobilewallet
+name: 'Smoke: Create Wallet'
+---
+- launchApp:
+ clearState: true
+- runFlow: ../../shared/create-wallet.yaml
diff --git a/apps/mobile/maestro/flows/smoke/020-settings-navigation.yaml b/apps/mobile/maestro/flows/smoke/020-settings-navigation.yaml
new file mode 100644
index 0000000000..8edb5353cc
--- /dev/null
+++ b/apps/mobile/maestro/flows/smoke/020-settings-navigation.yaml
@@ -0,0 +1,7 @@
+appId: io.leather.mobilewallet
+name: 'Smoke: Settings Navigation'
+---
+- launchApp
+- tapOn:
+ id: 'homeSettingsButton'
+- assertVisible: 'SETTINGS'
diff --git a/apps/mobile/maestro/flows/wallet-create.yaml b/apps/mobile/maestro/flows/wallet-create.yaml
new file mode 100644
index 0000000000..dbdca7d690
--- /dev/null
+++ b/apps/mobile/maestro/flows/wallet-create.yaml
@@ -0,0 +1,28 @@
+# Test: Create a new wallet
+# Verifies the complete wallet creation flow without biometric security
+appId: io.leather.mobilewallet
+name: Create Wallet
+---
+- launchApp:
+ clearState: true
+
+# Verify we start on the empty home screen
+- assertVisible:
+ id: 'homeCreateWalletCard'
+
+# Create a new wallet using the shared flow
+- runFlow: ../shared/create-wallet.yaml
+
+# Verify wallet was created successfully
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# Verify settings is accessible
+- tapOn:
+ id: 'homeSettingsButton'
+- assertVisible: 'SETTINGS'
+
+# Navigate to wallets and accounts to verify wallet exists
+- tapOn:
+ id: 'settingsWalletAndAccountsButton'
+- assertVisible: 'Wallet 1'
diff --git a/apps/mobile/maestro/flows/wallet-restore.yaml b/apps/mobile/maestro/flows/wallet-restore.yaml
new file mode 100644
index 0000000000..6ba0fea77b
--- /dev/null
+++ b/apps/mobile/maestro/flows/wallet-restore.yaml
@@ -0,0 +1,28 @@
+# Test: Restore a wallet from mnemonic
+# Verifies the wallet restoration flow using the test mnemonic
+appId: io.leather.mobilewallet
+name: Restore Wallet
+---
+- launchApp:
+ clearState: true
+
+# Verify we start on the empty home screen
+- assertVisible:
+ id: 'homeCreateWalletCard'
+
+# Restore a wallet using the shared flow
+- runFlow: ../shared/restore-wallet.yaml
+
+# Verify wallet was restored successfully
+- assertVisible:
+ id: 'homePrivacyButton'
+
+# Verify settings is accessible
+- tapOn:
+ id: 'homeSettingsButton'
+- assertVisible: 'SETTINGS'
+
+# Navigate to wallets and accounts to verify wallet exists
+- tapOn:
+ id: 'settingsWalletAndAccountsButton'
+- assertVisible: 'Wallet 1'
diff --git a/apps/mobile/maestro/legacy-flows/README.md b/apps/mobile/maestro/legacy-flows/README.md
deleted file mode 100644
index 3a2d928d6f..0000000000
--- a/apps/mobile/maestro/legacy-flows/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Legacy maestro tests
-
-The tests in this folder are legacy tests that are outdated and failing.
-
-They need to be grouped together better and re-written but are left temporarily as a reference of the coverage we had before and of some useful patterns in maestro.
-
-A lot of these tests used text assertion which makes them easy to read however soon became outdated with our CrowdIn setup.
-
-They also relied heavily on `Developer tools` which was deprecated
diff --git a/apps/mobile/maestro/legacy-flows/accounts-drawer.yaml b/apps/mobile/maestro/legacy-flows/accounts-drawer.yaml
deleted file mode 100644
index beaa0568bb..0000000000
--- a/apps/mobile/maestro/legacy-flows/accounts-drawer.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-# Accounts drawer doesn't open if no accounts are present
-- tapOn: 'My accounts'
-- assertNotVisible:
- id: 'settingsWalletAndAccountsButton'
-- runFlow:
- file: ../shared/add-wallet.yaml
-# Accounts drawer should open now
-- tapOn: 'My accounts'
-- assertVisible: 'Accounts'
-- assertVisible:
- id: 'settingsWalletAndAccountsButton'
-- tapOn:
- id: 'settingsWalletAndAccountsButton'
-- assertVisible: 'WALLETS'
diff --git a/apps/mobile/maestro/legacy-flows/accounts-page.yaml b/apps/mobile/maestro/legacy-flows/accounts-page.yaml
deleted file mode 100644
index 4d3e699784..0000000000
--- a/apps/mobile/maestro/legacy-flows/accounts-page.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- tapOn:
- id: 'homeAccountCard-0'
-- assertVisible:
- text: 'Account 1'
-- assertVisible:
- id: 'tokenBalanceItem-BTC'
-- assertVisible:
- id: 'tokenBalanceItem-STX'
-- assertVisible: 'Send'
-- assertVisible: 'Receive'
-- assertVisible: 'Swap'
-- tapOn:
- id: 'backButton'
-- assertNotVisible:
- id: 'backButton'
diff --git a/apps/mobile/maestro/legacy-flows/change-account-icon.yaml b/apps/mobile/maestro/legacy-flows/change-account-icon.yaml
deleted file mode 100644
index c1e6e0b573..0000000000
--- a/apps/mobile/maestro/legacy-flows/change-account-icon.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- assertNotVisible:
- id: 'toastContainer'
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn:
- id: 'settingsWalletAndAccountsButton'
-- tapOn:
- id: 'walletListAccountCard'
-- tapOn:
- text: 'Avatar'
-- tapOn:
- id: 'defaultAccountIcon_alien'
-- tapOn:
- id: 'backButton'
-- assertVisible:
- id: 'defaultAccountIcon_sparkles'
-- tapOn:
- text: 'Avatar'
-- tapOn:
- id: 'defaultAccountIcon_alien'
-- tapOn:
- text: 'Confirm'
-- assertVisible:
- id: 'defaultAccountIcon_alien'
-- tapOn:
- id: 'backButton'
-- assertVisible:
- id: 'defaultAccountIcon_alien'
diff --git a/apps/mobile/maestro/legacy-flows/change-network.yaml b/apps/mobile/maestro/legacy-flows/change-network.yaml
deleted file mode 100644
index 0ce1bd25b1..0000000000
--- a/apps/mobile/maestro/legacy-flows/change-network.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn:
- id: 'settingsNetworkButton'
-- tapOn: Testnet4 Disabled
-- tapOn:
- id: 'backButton'
-- assertVisible: Testnet4
-- tapOn:
- id: 'backButton'
-- assertVisible: Testnet4
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn: Networks Mainnet, testnet or signet
-- tapOn: Mainnet Disabled
-- tapOn:
- id: 'backButton'
-- assertNotVisible: Testnet4
-- tapOn:
- id: 'backButton'
-- assertNotVisible: Testnet4
diff --git a/apps/mobile/maestro/legacy-flows/change-theme.yaml b/apps/mobile/maestro/legacy-flows/change-theme.yaml
deleted file mode 100644
index 00883e40e0..0000000000
--- a/apps/mobile/maestro/legacy-flows/change-theme.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn: Display Theme and account identifier
-- tapOn: Theme System
-- tapOn: Light
-- tapOn: DISPLAY
-- assertVisible: Theme Light
-- assertNotVisible: Theme System
-- assertNotVisible: Theme Dark
-- tapOn: Theme Light
-- tapOn: DISPLAY
-- tapOn: Theme Light
-- tapOn: Dark
-- tapOn: DISPLAY
-- assertVisible: Theme Dark
-- assertNotVisible: Theme System
-- assertNotVisible: Theme Light
diff --git a/apps/mobile/maestro/legacy-flows/create-wallet-without-security.yaml b/apps/mobile/maestro/legacy-flows/create-wallet-without-security.yaml
deleted file mode 100644
index 3f6a8fbedc..0000000000
--- a/apps/mobile/maestro/legacy-flows/create-wallet-without-security.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
diff --git a/apps/mobile/maestro/legacy-flows/hide-account.yaml b/apps/mobile/maestro/legacy-flows/hide-account.yaml
deleted file mode 100644
index 8faad76e43..0000000000
--- a/apps/mobile/maestro/legacy-flows/hide-account.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- assertNotVisible:
- id: 'toastContainer'
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn:
- text: 'Wallets and accounts Add, configure and remove'
-- tapOn:
- id: 'walletListAccountCard'
-- tapOn:
- text: 'Hide account'
-- assertVisible: 'Un-hide account'
-- tapOn:
- id: 'backButton'
-- assertNotVisible:
- id: 'walletListAccountCard'
-- tapOn:
- text: 'Hidden accounts 1 hidden accounts'
-- tapOn:
- id: 'walletListAccountCard'
-- tapOn: Un-hide account
-- tapOn:
- id: 'backButton'
-- tapOn:
- id: 'backButton'
-- assertNotVisible: Hidden accounts 0 hidden accounts
diff --git a/apps/mobile/maestro/legacy-flows/receive.yaml b/apps/mobile/maestro/legacy-flows/receive.yaml
deleted file mode 100644
index c18f3fbfa8..0000000000
--- a/apps/mobile/maestro/legacy-flows/receive.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- tapOn: 'Receive'
-- assertVisible: 'Select account'
-- tapOn:
- id: 'walletListAccountCard'
- index: 0
-- tapOn:
- id: 'receiveAssetItem'
- index: 0
-- assertVisible: 'This is your Native Segwit address.'
-- tapOn:
- id: 'backButton'
-- tapOn:
- id: 'receiveAssetItem'
- index: 1
-- assertVisible: "This is your Taproot address. Use it to receive tokens and collectibles\
- \ on the bitcoin network."
-- tapOn:
- id: 'backButton'
-- tapOn:
- id: 'receiveAssetItem'
- index: 2
-- assertVisible: 'This is your Stacks address.'
-- tapOn:
- id: 'backButton'
-- tapOn:
- id: 'backButton'
-- assertVisible: 'Select account'
diff --git a/apps/mobile/maestro/legacy-flows/rename-account.yaml b/apps/mobile/maestro/legacy-flows/rename-account.yaml
deleted file mode 100644
index 55cadbce2d..0000000000
--- a/apps/mobile/maestro/legacy-flows/rename-account.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- assertNotVisible:
- id: 'toastContainer'
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn:
- text: 'Wallets and accounts Add, configure and remove'
-- tapOn:
- id: 'walletListAccountCard'
-- tapOn:
- id: 'walletSettingsAccountNameCell'
-- tapOn:
- id: 'accountChangeNameSheetInput'
-- eraseText
-- inputText: 'testAccount'
-- tapOn:
- text: 'Save'
-- assertVisible:
- text: 'testAccount'
diff --git a/apps/mobile/maestro/legacy-flows/rename-wallet.yaml b/apps/mobile/maestro/legacy-flows/rename-wallet.yaml
deleted file mode 100644
index d06b5e7822..0000000000
--- a/apps/mobile/maestro/legacy-flows/rename-wallet.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- assertNotVisible:
- id: 'toastContainer'
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn:
- text: 'Wallets and accounts Add, configure and remove'
-- tapOn:
- id: 'walletListSettingsButton'
- index: 0
-- tapOn:
- text: 'Rename wallet'
-# # Doing this because iOS input is a bit flaky on maestro,
-# # It's known issue and they are on it.
-# # https://maestro.mobile.dev/api-reference/commands/erasetext
-# - longPressOn:
-# id: 'walletChangeNameSheetInput'
-# - tapOn: 'Select All'
-- tapOn:
- id: 'walletChangeNameSheetInput'
-- eraseText
-
-- inputText: 'testWallet'
-- tapOn:
- text: 'Save'
-- assertVisible:
- text: 'testWallet'
diff --git a/apps/mobile/maestro/legacy-flows/restore-wallet.yaml b/apps/mobile/maestro/legacy-flows/restore-wallet.yaml
deleted file mode 100644
index 57d00c0b71..0000000000
--- a/apps/mobile/maestro/legacy-flows/restore-wallet.yaml
+++ /dev/null
@@ -1,54 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- tapOn:
- text: 'Add Wallet'
-- tapOn:
- id: 'restoreWalletSheetButton'
-# verify advanced options appear
-- tapOn: 'Advanced options'
-- tapOn: 'BIP39 passphrase Disabled'
-- assertVisible: 'Passphrase'
-- assertVisible: 'Confirm'
-- tapOn: 'Confirm'
-- assertVisible: 'BIP39 passphrase Disabled'
-- tapOn: 'Advanced options'
-- assertNotVisible: 'BIP39 passphrase Disabled'
-# check validation of invalid mnemonic
-- tapOn:
- id: 'restoreWalletTextInput'
-- inputText: 'definitely not a mnemonic'
-- pressKey: Enter
-- assertVisible: 'Invalid words: definitely, not, a, mnemonic'
-- tapOn:
- id: 'restoreWalletTextInput'
-- eraseText: 26
-- tapOn:
- id: 'backButton'
-- tapOn:
- text: 'Add Wallet'
-# verify validation of valid mnemonic
-- tapOn:
- id: 'createNewWalletSheetButton'
-- tapOn:
- id: 'walletCreationTapToReveal'
-- tapOn:
- text: 'Copy'
-- tapOn:
- id: 'backButton'
-- tapOn:
- text: 'Add Wallet'
-- tapOn:
- id: 'restoreWalletSheetButton'
-- assertNotVisible: 'Invalid words: definitely, not, a, mnemonic'
-- tapOn:
- text: 'Paste'
-- tapOn:
- text: 'Continue'
-- tapOn:
- text: 'Skip for now'
-- tapOn:
- text: 'Proceed'
-- assertVisible:
- id: 'homeAccountCard-0'
diff --git a/apps/mobile/maestro/legacy-flows/secure/create-wallet.yaml b/apps/mobile/maestro/legacy-flows/secure/create-wallet.yaml
deleted file mode 100644
index f00c4ef885..0000000000
--- a/apps/mobile/maestro/legacy-flows/secure/create-wallet.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../../shared/clean-up.yaml
-- runFlow:
- when:
- platform: iOS
- file: ../../shared/add-wallet.yaml
- env:
- SECURE: yes
diff --git a/apps/mobile/maestro/legacy-flows/secure/delete-wallet.yaml b/apps/mobile/maestro/legacy-flows/secure/delete-wallet.yaml
deleted file mode 100644
index 30836ea51f..0000000000
--- a/apps/mobile/maestro/legacy-flows/secure/delete-wallet.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../../shared/clean-up.yaml
-- runFlow:
- file: ../../shared/add-wallet.yaml
- env:
- SECURE: yes
-- assertNotVisible:
- id: 'toastContainer'
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn:
- text: 'Wallets and accounts Add, configure and remove'
-- tapOn:
- id: 'walletListSettingsButton'
- index: 0
-- tapOn:
- text: 'Remove wallet'
-- tapOn:
- text: 'Proceed'
-- tapOn:
- id: 'backButton'
-- tapOn:
- id: 'backButton'
-- assertVisible:
- text: 'Add Wallet'
-- assertVisible:
- id: 'homeCreateWalletCard'
diff --git a/apps/mobile/maestro/legacy-flows/send.yaml b/apps/mobile/maestro/legacy-flows/send.yaml
deleted file mode 100644
index 761494163b..0000000000
--- a/apps/mobile/maestro/legacy-flows/send.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- tapOn: 'Send'
-- assertVisible: 'Select account'
-- tapOn: '$0.00'
-- assertVisible: 'Select asset'
-- tapOn: '$0.00'
-- assertVisible: 'Send'
-- assertVisible: 'Account 1'
diff --git a/apps/mobile/maestro/legacy-flows/settings-guides.yaml b/apps/mobile/maestro/legacy-flows/settings-guides.yaml
deleted file mode 100644
index 03308e2373..0000000000
--- a/apps/mobile/maestro/legacy-flows/settings-guides.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn: 'Help Support, guides and articles'
-- tapOn: 'Guides Dive into feature details'
-- assertVisible: 'USER GUIDES'
diff --git a/apps/mobile/maestro/legacy-flows/settings-learn.yaml b/apps/mobile/maestro/legacy-flows/settings-learn.yaml
deleted file mode 100644
index 944bf15139..0000000000
--- a/apps/mobile/maestro/legacy-flows/settings-learn.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- tapOn:
- id: 'homeSettingsButton'
-- tapOn:
- id: 'settingsHelpButton'
-- tapOn: 'Learn Expand your knowledge'
-- assertVisible: 'LEARN'
-- assertVisible: 'Dive Deeper into Bitcoin & Leather: Essential Knowledge & Beyond'
-- launchApp:
- appId: io.leather.mobilewallet
- clearState: false
diff --git a/apps/mobile/maestro/legacy-flows/settings-support.yaml b/apps/mobile/maestro/legacy-flows/settings-support.yaml
deleted file mode 100644
index 8e79738d4d..0000000000
--- a/apps/mobile/maestro/legacy-flows/settings-support.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- tapOn:
- id: 'homeSettingsButton'
-- assertVisible: 'Help Support, guides and articles'
-- tapOn:
- id: 'settingsHelpButton'
-- tapOn: 'Support and feedback Contact our support team'
-# view the support page in the browser
-- assertVisible: 'GET SUPPORT'
diff --git a/apps/mobile/maestro/legacy-flows/wallet-add-account.yaml b/apps/mobile/maestro/legacy-flows/wallet-add-account.yaml
deleted file mode 100644
index 1538f5a90c..0000000000
--- a/apps/mobile/maestro/legacy-flows/wallet-add-account.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- tapOn: 'Add account All accounts in one'
-- tapOn: 'Add to existing wallet Choose existing leather wallet'
-- assertVisible: 'WALLETS'
-- assertVisible:
- id: 'walletListAccountCard'
- index: 0
-- tapOn: 'Add account'
-- assertVisible:
- id: 'walletListAccountCard'
- index: 1
diff --git a/apps/mobile/maestro/legacy-flows/wallet-management.yaml b/apps/mobile/maestro/legacy-flows/wallet-management.yaml
deleted file mode 100644
index be5e8ec58d..0000000000
--- a/apps/mobile/maestro/legacy-flows/wallet-management.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-- tapOn: My accounts
-- tapOn:
- id: 'settingsWalletAndAccountsButton'
-- tapOn: Wallet 1
-- assertNotVisible: Add account
-- assertNotVisible:
- id: 'homeAccountCard-0'
-- tapOn: Wallet 1
-- tapOn:
- id: 'walletListSettingsButton'
-# Test advanced options menu
-- tapOn: Advanced options
-- assertVisible: Address reuse
-- assertVisible: Address scan range
-- assertVisible: Export xPub
-- tapOn: Advanced options
-- assertNotVisible: Address reuse
-# Test removing wallet flows
-- tapOn: Remove wallet
-- tapOn: Cancel
-- assertVisible: CONFIGURE WALLET
-- tapOn: Remove wallet
-- assertVisible: >-
- The wallet will be removed from this device. You will lose access
- to all tokens and collectibles associated with this wallet. Before proceeding, make sure you have securely saved your secret key. Without it, you won't be able to access your tokens or collectibles from another device.
-- tapOn: Proceed
-- assertNotVisible: CONFIGURE WALLET
-- tapOn:
- id: 'backButton'
-# Ensure wallet is removed and not other wallets are present
-- assertNotVisible: Wallet 1
-- assertVisible: Create or restore wallet Create, Import or connect instantly
-- assertVisible: Add Wallet
diff --git a/apps/mobile/maestro/legacy-flows/wallet-multi-wallet.yaml b/apps/mobile/maestro/legacy-flows/wallet-multi-wallet.yaml
deleted file mode 100644
index 516e404b3e..0000000000
--- a/apps/mobile/maestro/legacy-flows/wallet-multi-wallet.yaml
+++ /dev/null
@@ -1,65 +0,0 @@
-appId: io.leather.mobilewallet
----
-- launchApp
-- runFlow: ../shared/clean-up.yaml
-- runFlow:
- file: ../shared/add-wallet.yaml
-# add a second wallet - TODO move into flow
-- tapOn: 'Add account All accounts in one'
-- tapOn: 'Add to new wallet Create new wallet'
-- tapOn: 'Create new wallet Create a new Bitcoin and Stacks wallet'
-- assertVisible: 'BACK UP YOUR SECRET KEY'
-- tapOn: "I've backed it up"
-- assertVisible: 'Wallet added successfully'
-- assertVisible:
- id: 'homeAccountCard-0'
-- assertVisible:
- id: 'homeAccountCard-1'
-- tapOn: 'My accounts'
-- assertVisible: 'Wallet 1'
-- assertVisible: 'Wallet 2'
-# test multi wallethidden accounts
-- tapOn:
- id: 'settingsWalletAndAccountsButton'
-- tapOn: 'Hidden accounts 0 hidden accounts'
-# ensure empty hidden accounts list shows fallback
-- assertVisible: 'View and manage your hidden accounts'
-- tapOn:
- id: 'backButton'
-- tapOn:
- id: 'walletListAccountCard'
- index: 0
-- tapOn: 'Hide account'
-- tapOn:
- id: 'backButton'
-- tapOn: 'Hidden accounts 1 hidden accounts'
-- assertVisible:
- id: 'walletListAccountCard'
-- tapOn:
- id: 'backButton'
-- tapOn: 'Hidden accounts 1 hidden accounts'
-- assertVisible: 'Wallet 1'
-# ensure wallet 2 is not visible as no hidden accounts
-- assertNotVisible: 'Wallet 2'
-- tapOn:
- id: 'backButton'
-- tapOn:
- id: 'walletListSettingsButton'
- index: 0
-- tapOn:
- id: 'walletSettingsRemoveWalletButton'
-- tapOn: 'Proceed'
-- tapOn:
- id: 'walletListSettingsButton'
-- tapOn: 'Remove wallet'
-- tapOn: 'Proceed'
-- assertVisible: 'View and manage your wallets all in one place'
-- tapOn: 'Add or create wallet'
-- assertVisible: 'ADD WALLET'
-- assertVisible:
- id: 'createNewWalletSheetButton'
-- tapOn: 'WALLETS'
-- tapOn:
- id: 'backButton'
-- assertVisible: 'Create or restore wallet Create, Import or connect instantly'
-- assertVisible: 'Add Wallet'
diff --git a/apps/mobile/maestro/shared/clean-up.yaml b/apps/mobile/maestro/shared/clean-up.yaml
index 314a518cca..60e0ddc3ac 100644
--- a/apps/mobile/maestro/shared/clean-up.yaml
+++ b/apps/mobile/maestro/shared/clean-up.yaml
@@ -7,14 +7,8 @@ appId: io.leather.mobilewallet
- tapOn:
id: 'homeDeveloperToolsButton'
- tapOn:
- text: 'Wallet management'
-- tapOn:
- text: 'Clear'
-- tapOn:
- id: 'backButton'
+ id: 'devConsoleClearWalletsButton'
- tapOn:
id: 'backButton'
- assertVisible:
- id: 'homeAddWalletButton'
-- assertNotVisible:
- id: 'homeAccountCard-0'
+ id: 'homeCreateWalletCard'
diff --git a/apps/mobile/maestro/shared/create-wallet-dev-console.yaml b/apps/mobile/maestro/shared/create-wallet-dev-console.yaml
new file mode 100644
index 0000000000..85f3fe6217
--- /dev/null
+++ b/apps/mobile/maestro/shared/create-wallet-dev-console.yaml
@@ -0,0 +1,10 @@
+appId: io.leather.mobilewallet
+---
+- tapOn:
+ id: 'homeDeveloperToolsButton'
+- tapOn:
+ id: 'devConsoleCreateWalletButton'
+- tapOn:
+ id: 'backButton'
+- assertVisible:
+ id: 'homeAccountCard-0'
diff --git a/apps/mobile/maestro/shared/create-wallet.yaml b/apps/mobile/maestro/shared/create-wallet.yaml
new file mode 100644
index 0000000000..86a53e2a0e
--- /dev/null
+++ b/apps/mobile/maestro/shared/create-wallet.yaml
@@ -0,0 +1,39 @@
+# Creates a new wallet without biometric security
+# Assumes: App is on home screen without any existing wallets
+# Result: Wallet created and user is on home screen with account visible
+appId: io.leather.mobilewallet
+---
+# Tap the create wallet card on empty home screen
+- tapOn:
+ id: 'homeCreateWalletCard'
+
+# Wait for add wallet sheet to fully present
+- extendedWaitUntil:
+ visible: 'Create new wallet'
+ timeout: 10000
+
+# Select "Create new wallet" from the sheet
+- tapOn: 'Create new wallet'
+
+# Reveal the secret key (required before proceeding)
+- tapOn:
+ id: 'walletCreationTapToReveal'
+
+# Confirm backup
+- tapOn:
+ id: 'walletCreationBackedUpButton'
+
+# Skip biometric security
+- tapOn:
+ text: 'Skip for now'
+
+# Confirm skipping security in the warning sheet
+- tapOn:
+ text: 'Continue'
+
+# Wait for wallet generation and verify we're back on home with account
+# homePrivacyButton is only visible on home screen when a wallet exists
+- extendedWaitUntil:
+ visible:
+ id: 'homePrivacyButton'
+ timeout: 10000
diff --git a/apps/mobile/maestro/shared/remove-wallet.yaml b/apps/mobile/maestro/shared/remove-wallet.yaml
new file mode 100644
index 0000000000..e5cb22eaea
--- /dev/null
+++ b/apps/mobile/maestro/shared/remove-wallet.yaml
@@ -0,0 +1,31 @@
+# Removes the first wallet via Settings
+# Assumes: At least one wallet exists and app is on home screen
+# Result: First wallet removed, returns to home screen
+appId: io.leather.mobilewallet
+---
+# Navigate to Settings
+- tapOn:
+ id: 'homeSettingsButton'
+
+# Navigate to Wallets and Accounts
+- tapOn:
+ id: 'settingsWalletAndAccountsButton'
+
+# Tap the settings gear on the first wallet
+- tapOn:
+ id: 'walletListSettingsButton'
+
+# Tap Remove wallet
+- tapOn:
+ id: 'walletSettingsRemoveWalletButton'
+- waitForAnimationToEnd
+
+# Confirm removal in the warning sheet
+- tapOn:
+ text: 'Continue'
+
+# Navigate back to home
+- tapOn:
+ id: 'backButton'
+- tapOn:
+ id: 'backButton'
diff --git a/apps/mobile/maestro/shared/restore-wallet.yaml b/apps/mobile/maestro/shared/restore-wallet.yaml
new file mode 100644
index 0000000000..2ba052d632
--- /dev/null
+++ b/apps/mobile/maestro/shared/restore-wallet.yaml
@@ -0,0 +1,46 @@
+# Restores a wallet from a test mnemonic without biometric security
+# Assumes: App is on home screen without any existing wallets
+# Result: Wallet restored and user is on home screen with account visible
+# Uses the standard test mnemonic from @leather.io/test-config
+appId: io.leather.mobilewallet
+---
+# Tap the create wallet card on empty home screen
+- tapOn:
+ id: 'homeCreateWalletCard'
+
+# Wait for add wallet sheet to fully present
+- extendedWaitUntil:
+ visible:
+ id: 'restoreWalletSheetButton'
+ timeout: 10000
+
+# Select "Restore wallet" from the sheet
+- tapOn:
+ id: 'restoreWalletSheetButton'
+
+# Enter the test mnemonic (from @leather.io/test-config)
+- tapOn:
+ id: 'restoreWalletTextInput'
+- inputText: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus'
+- pressKey: Enter
+
+# Continue to security screen
+- tapOn:
+ id: 'restoreWalletContinue'
+
+# Skip biometric security (only shown if securityLevelPreference is 'not-selected')
+- runFlow:
+ when:
+ visible: 'Skip for now'
+ commands:
+ - tapOn:
+ text: 'Skip for now'
+ - tapOn:
+ text: 'Continue'
+
+# Wait for wallet generation and verify we're back on home with account
+# homePrivacyButton is only visible on home screen when a wallet exists
+- extendedWaitUntil:
+ visible:
+ id: 'homePrivacyButton'
+ timeout: 10000
diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx
index 16c68b56e2..3e555f9e27 100644
--- a/apps/mobile/src/app/_layout.tsx
+++ b/apps/mobile/src/app/_layout.tsx
@@ -81,6 +81,7 @@ function App() {
+
{currentAccount && (
<>
diff --git a/apps/mobile/src/app/developer-console.tsx b/apps/mobile/src/app/developer-console.tsx
new file mode 100644
index 0000000000..f02484ea8a
--- /dev/null
+++ b/apps/mobile/src/app/developer-console.tsx
@@ -0,0 +1,55 @@
+import { ScrollView } from 'react-native';
+
+import { Screen } from '@/components/screen/screen';
+import { TestId } from '@/shared/test-id';
+import { useKeyStore } from '@/store/key-store';
+import { useSettings } from '@/store/settings/settings';
+import { useWallets } from '@/store/wallets/wallets.read';
+import { t } from '@lingui/core/macro';
+
+import { Box, Button, Text } from '@leather.io/ui/native';
+
+export default function DeveloperConsole() {
+ const keyStore = useKeyStore();
+ const wallets = useWallets();
+ const { toggleNetwork } = useSettings();
+
+ function onCreateWallet() {
+ void keyStore.createNewSoftwareWallet();
+ }
+
+ function onClearWallets() {
+ for (const wallet of wallets.list) {
+ wallets.remove(wallet.fingerprint);
+ }
+ }
+
+ return (
+
+ {t`Developer Console`}} />
+
+
+ {t`Wallet Manager`}
+
+
+
+ {/* eslint-disable lingui/no-unlocalized-strings */}
+ {t`Environment`}
+ {`__DEV__: ${String(__DEV__)}`}
+ {`NODE_ENV: ${process.env.EXPO_PUBLIC_NODE_ENV ?? 'undefined'}`}
+ {`LAUNCH_DARKLY: ${process.env.EXPO_PUBLIC_LAUNCH_DARKLY ? 'set' : 'unset'}`}
+ {`SENTRY_DSN: ${process.env.EXPO_PUBLIC_SENTRY_DSN ? 'set' : 'unset'}`}
+ {`MIXPANEL: ${process.env.EXPO_PUBLIC_MIXPANEL_TOKEN ? 'set' : 'unset'}`}
+ {`MAESTRO_CI: ${process.env.EXPO_PUBLIC_MAESTRO_CI ?? 'undefined'}`}
+ {/* eslint-enable lingui/no-unlocalized-strings */}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/action-buttons.tsx b/apps/mobile/src/components/action-buttons.tsx
index 7a2d7ee2f4..ad31e30784 100644
--- a/apps/mobile/src/components/action-buttons.tsx
+++ b/apps/mobile/src/components/action-buttons.tsx
@@ -1,4 +1,5 @@
import { useOnramperBuyFlag, useOnramperSellFlag, useSwapFlag } from '@/features/feature-flags';
+import { TestId } from '@/shared/test-id';
import { t } from '@lingui/core/macro';
import { Button, ButtonProps } from '@leather.io/ui/native';
@@ -29,7 +30,13 @@ export function ActionButtons({
return (
<>
{onSend && (
-
@@ -51,6 +59,7 @@ export function ActionButtons({
size={size}
variant="outline"
flex={fullWidth ? 1 : 0}
+ testID={TestId.actionButtonBuy}
>
{t`Buy`}
@@ -62,6 +71,7 @@ export function ActionButtons({
size={size}
variant="outline"
flex={fullWidth ? 1 : 0}
+ testID={TestId.actionButtonSell}
>
{t`Sell`}
@@ -73,6 +83,7 @@ export function ActionButtons({
size={size}
variant="outline"
flex={fullWidth ? 1 : 0}
+ testID={TestId.actionButtonSwap}
>
{t`Swap`}
diff --git a/apps/mobile/src/components/home/home-without-account-screen.tsx b/apps/mobile/src/components/home/home-without-account-screen.tsx
index dc6d56544e..c89b3a7e88 100644
--- a/apps/mobile/src/components/home/home-without-account-screen.tsx
+++ b/apps/mobile/src/components/home/home-without-account-screen.tsx
@@ -3,18 +3,26 @@ import { HeaderActions } from '@/components/screen/screen-header/components/head
import { useGlobalSheets } from '@/core/global-sheet-provider';
import { CreateWalletCard } from '@/features/account/components/create-wallet-card';
import { NetworkBadge } from '@/features/settings/network-badge';
+import { TestId } from '@/shared/test-id';
+import { useRouter } from 'expo-router';
-import { Box, LeatherLogomarkIcon } from '@leather.io/ui/native';
+import { Box, LeatherLogomarkIcon, Pressable } from '@leather.io/ui/native';
export function HomeScreenWithoutAccount() {
const { addWalletSheetRef } = useGlobalSheets();
+ const router = useRouter();
return (
-
+ router.navigate('/developer-console')}
+ testID={TestId.homeDeveloperToolsButton}
+ >
+
+
}
diff --git a/apps/mobile/src/components/screen/screen-header/components/header-actions.tsx b/apps/mobile/src/components/screen/screen-header/components/header-actions.tsx
index 363cf34d75..4e7030e81f 100644
--- a/apps/mobile/src/components/screen/screen-header/components/header-actions.tsx
+++ b/apps/mobile/src/components/screen/screen-header/components/header-actions.tsx
@@ -4,7 +4,14 @@ import { useWallets } from '@/store/wallets/wallets.read';
import { t } from '@lingui/core/macro';
import { useRouter } from 'expo-router';
-import { Box, Eye1ClosedIcon, Eye1Icon, IconButton, SettingsGearIcon } from '@leather.io/ui/native';
+import {
+ Box,
+ CodeIcon,
+ Eye1ClosedIcon,
+ Eye1Icon,
+ IconButton,
+ SettingsGearIcon,
+} from '@leather.io/ui/native';
export function HeaderActions() {
const router = useRouter();
@@ -17,6 +24,12 @@ export function HeaderActions() {
return (
+ }
+ onPress={() => router.navigate('/developer-console')}
+ testID={TestId.homeDeveloperToolsButton}
+ />
{hasWallets && (