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
218 changes: 218 additions & 0 deletions .github/workflows/e2e-mobile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: E2E Mobile

on:
workflow_dispatch:
inputs:
test_target:
description: "Optional Patrol target (blank = full suite)"
required: false
default: ""
type: string

jobs:
e2e-android:
name: E2E Android (Emulator)
runs-on: ubuntu-latest
timeout-minutes: 90
permissions:
contents: read
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MOBILE_APP_ENV: ${{ secrets.MOBILE_APP_ENV }}
E2E_TEST_ENV: ${{ secrets.E2E_TEST_ENV }}
GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
quantus_sdk/rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('quantus_sdk/rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-

- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
mobile-app/android/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('mobile-app/android/**/*.gradle*', 'mobile-app/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Set up Java 17
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: "17"
cache: gradle

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
pub-cache-key: "pub-${{ runner.os }}-${{ hashFiles('**/pubspec.lock', 'melos.yaml') }}"
cache-key: "flutter-${{ runner.os }}-stable"

- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android,i686-linux-android

- name: Install Melos
run: dart pub global activate melos

- name: Install Patrol CLI
run: dart pub global activate patrol_cli 4.6.1

- name: Add pub-cache bin to PATH
run: echo "$HOME/.pub-cache/bin" >> "$GITHUB_PATH"

- name: Bootstrap Melos
run: melos bootstrap
env:
GIT_TERMINAL_PROMPT: 0

- name: Create .env file
run: echo "$MOBILE_APP_ENV" > mobile-app/.env

- name: Create .env.test file
run: echo "$E2E_TEST_ENV" > mobile-app/.env.test

- name: Create google-services.json
run: echo "$GOOGLE_SERVICES_JSON" > mobile-app/android/app/google-services.json

- name: Generate splash screen
working-directory: mobile-app
run: |
flutter pub get
dart run flutter_native_splash:create

- name: Run Patrol E2E on Android Emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
profile: pixel_6
disable-animations: true
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect
script: |
cd mobile-app
if [ -n "${{ github.event.inputs.test_target }}" ]; then
bash scripts/patrol_android_emulator.sh "${{ github.event.inputs.test_target }}"
else
bash scripts/patrol_android_emulator.sh
fi

- name: Capture adb logcat
if: failure()
run: adb logcat -d > mobile-app/adb-logcat.txt || true

- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-android-failure-${{ github.run_id }}
path: |
mobile-app/build/
mobile-app/adb-logcat.txt
if-no-files-found: ignore

e2e-ios:
name: E2E iOS (Simulator)
runs-on: macos-latest
timeout-minutes: 90
permissions:
contents: read
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MOBILE_APP_ENV: ${{ secrets.MOBILE_APP_ENV }}
E2E_TEST_ENV: ${{ secrets.E2E_TEST_ENV }}
GOOGLE_SERVICES_PLIST: ${{ secrets.GOOGLE_SERVICES_PLIST }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
quantus_sdk/rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('quantus_sdk/rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-

- name: Cache CocoaPods
uses: actions/cache@v4
with:
path: |
mobile-app/ios/Pods
~/Library/Caches/CocoaPods
key: ${{ runner.os }}-pods-${{ hashFiles('mobile-app/ios/Podfile', 'mobile-app/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-pods-

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
pub-cache-key: "pub-${{ runner.os }}-${{ hashFiles('**/pubspec.lock', 'melos.yaml') }}"
cache-key: "flutter-${{ runner.os }}-stable"

- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install Melos
run: dart pub global activate melos

- name: Install Patrol CLI
run: dart pub global activate patrol_cli 4.6.1

- name: Add pub-cache bin to PATH
run: echo "$HOME/.pub-cache/bin" >> "$GITHUB_PATH"

- name: Bootstrap Melos
run: melos bootstrap
env:
GIT_TERMINAL_PROMPT: 0

- name: Create .env file
run: echo "$MOBILE_APP_ENV" > mobile-app/.env

- name: Create .env.test file
run: echo "$E2E_TEST_ENV" > mobile-app/.env.test

- name: Create GoogleService-Info.plist
run: echo "$GOOGLE_SERVICES_PLIST" > mobile-app/ios/Runner/GoogleService-Info.plist

- name: Install CocoaPods dependencies
working-directory: mobile-app/ios
run: pod install

- name: Run Patrol E2E on iOS Simulator
working-directory: mobile-app
run: |
if [ -n "${{ github.event.inputs.test_target }}" ]; then
bash scripts/patrol_ios_simulator.sh -s "iPhone 16" "${{ github.event.inputs.test_target }}"
else
bash scripts/patrol_ios_simulator.sh -s "iPhone 16"
fi

- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-ios-failure-${{ github.run_id }}
path: mobile-app/build/
if-no-files-found: ignore
64 changes: 64 additions & 0 deletions mobile-app/patrol_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Patrol E2E tests

End-to-end tests for Quantus Wallet, driven by [Patrol](https://patrol.leancode.co/). Tests live under `patrol_test/`; runner scripts are in `scripts/`.

## Layout

| Path | Purpose |
|------|---------|
| `smoke/` | Quick sanity checks (minimal app setup) |
| `flows/` | Full user flows (create/import wallet, send, recovery phrase, …) |
| `support/` | Shared helpers (launchers, selectors, test env, timeouts) |

## Local runs

From `mobile-app/`:

```bash
# Android emulator
scripts/patrol_android_emulator.sh

# iOS Simulator
scripts/patrol_ios_simulator.sh
```

Run a single test:

```bash
scripts/patrol_android_emulator.sh patrol_test/smoke/hello_world_test.dart
scripts/patrol_ios_simulator.sh patrol_test/flows/create_wallet_test.dart
```

Pass zero targets to run the **whole suite** (every `*_test.dart` under `patrol_test/`).

### Test secrets (`.env.test`)

Flows that import a wallet or send funds need compile-time defines. Locally, create a gitignored `mobile-app/.env.test`:

```
TEST_IMPORT_MNEMONIC=word1 word2 ...
TEST_SEND_RECIPIENT_ADDRESS=ss58...
```

Scripts pass `--dart-define-from-file=.env.test` automatically when the file exists. See `support/test_env.dart`.

**Send flow:** the wallet for `TEST_IMPORT_MNEMONIC` must be funded on testnet; `send_flow_test.dart` checks balance before sending (`support/send_preflight.dart`).

## GitHub Actions (`E2E Mobile` workflow)

Phase 1 is **manual dispatch only** (Actions → E2E Mobile → Run workflow). Android and iOS jobs run in parallel.

Optional input `test_target` runs a single file (e.g. `patrol_test/smoke/hello_world_test.dart`); leave blank for the full suite.

### Required repository secrets

| Secret | Jobs | Purpose |
|--------|------|---------|
| `MOBILE_APP_ENV` | Both | Full `mobile-app/.env` contents (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `IOS_FIREBASE_API_KEY`, `ANDROID_FIREBASE_API_KEY`, …) |
| `GOOGLE_SERVICES_JSON` | Android | Full `google-services.json` (Gradle plugin) |
| `GOOGLE_SERVICES_PLIST` | iOS | Full `GoogleService-Info.plist` (Xcode bundle resource) |
| `E2E_TEST_ENV` | Both | Multiline `.env.test` body (`TEST_IMPORT_MNEMONIC`, `TEST_SEND_RECIPIENT_ADDRESS`, …) |

Generate Firebase config files locally with FlutterFire (`firebase.json`). Both native config files are gitignored.

After reliability is proven, the workflow can be extended to run on PRs touching `mobile-app/**` or `quantus_sdk/**`.
Loading