Skip to content
Merged
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
150 changes: 150 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: E2E Tests

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:

jobs:
e2e-ios:
name: E2E Tests (iOS)
runs-on: macos-latest
timeout-minutes: 45

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Install Maestro
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> $GITHUB_PATH

- name: Setup Expo
uses: expo/expo-github-action@v8
with:
expo-version: latest
token: ${{ secrets.EXPO_TOKEN }}

- name: Build iOS app
run: |
npx expo prebuild --platform ios --clean
xcodebuild -workspace ios/guildpassmobile.xcworkspace \
-scheme guildpassmobile \
-configuration Debug \
-sdk iphonesimulator \
-derivedDataPath ios/build \
-destination 'platform=iOS Simulator,name=iPhone 15'

- name: Boot iOS Simulator
run: |
xcrun simctl boot "iPhone 15" || true
xcrun simctl list devices

- name: Install app on simulator
run: |
xcrun simctl install booted ios/build/Build/Products/Debug-iphonesimulator/guildpassmobile.app

- name: Run Maestro E2E tests
run: |
maestro test .maestro/ --format junit --output test-results

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-test-results-ios
path: test-results/

- name: Upload Maestro recordings
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-recordings-ios
path: ~/.maestro/tests/**/*.mp4

e2e-android:
name: E2E Tests (Android)
runs-on: ubuntu-latest
timeout-minutes: 45

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Install Maestro
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> $GITHUB_PATH

- name: Setup Expo
uses: expo/expo-github-action@v8
with:
expo-version: latest
token: ${{ secrets.EXPO_TOKEN }}

- name: Setup Android Emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
target: google_apis
arch: x86_64
profile: pixel_6
script: echo "Emulator started"

- name: Build Android app
run: |
npx expo prebuild --platform android --clean
cd android
./gradlew assembleDebug

- name: Run Maestro E2E tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
target: google_apis
arch: x86_64
profile: pixel_6
script: |
adb install android/app/build/outputs/apk/debug/app-debug.apk
maestro test .maestro/ --format junit --output test-results

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-test-results-android
path: test-results/

- name: Upload Maestro recordings
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-recordings-android
path: ~/.maestro/tests/**/*.mp4
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ yarn-error.log*
.env
.env.*
!.env.example

# Maestro E2E test results and recordings
test-results/
*.mp4
16 changes: 16 additions & 0 deletions .maestro/01-onboarding-to-profile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
appId: xyz.guildpass.mobile
---
# E2E Test: Onboarding to Profile Navigation
# Validates: User can complete onboarding and navigate to profile screen

- launchApp
- assertVisible:
id: "onboarding-screen"
- assertVisible:
text: "Welcome to GuildPass"
- tapOn:
id: "onboarding-get-started-button"
- assertVisible:
id: "profile-screen"
- assertVisible:
text: "Connect Wallet"
21 changes: 21 additions & 0 deletions .maestro/02-wallet-entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
appId: xyz.guildpass.mobile
---
# E2E Test: Manual Wallet Entry
# Validates: User can manually enter a wallet address and connect

- launchApp
- tapOn:
id: "onboarding-get-started-button"
- assertVisible:
id: "wallet-connect-form"
- tapOn:
id: "wallet-address-input"
- inputText: "0x1234567890123456789012345678901234567890"
- tapOn:
id: "wallet-connect-button"
- assertVisible:
id: "profile-connected"
- assertVisible:
id: "connected-wallet-address"
- assertVisible:
text: "0x1234567890123456789012345678901234567890"
35 changes: 35 additions & 0 deletions .maestro/03-guild-navigation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
appId: xyz.guildpass.mobile
---
# E2E Test: Guild List and Detail Navigation
# Validates: User can navigate to guilds list and view guild details

- launchApp
- tapOn:
id: "onboarding-get-started-button"
- tapOn:
id: "wallet-address-input"
- inputText: "0x1234567890123456789012345678901234567890"
- tapOn:
id: "wallet-connect-button"
- assertVisible:
id: "profile-connected"

# Navigate to guilds list
- tapOn:
id: "navigate-guilds-button"
- assertVisible:
id: "guilds-screen"
- assertVisible:
id: "guilds-list"

# Tap on first guild card
- tapOn:
text: "Alpha Guild"
- assertVisible:
id: "guild-detail-screen"
- assertVisible:
id: "guild-name"
- assertVisible:
id: "membership-status-card"
- assertVisible:
id: "guild-roles-list"
39 changes: 39 additions & 0 deletions .maestro/04-access-check-success.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
appId: xyz.guildpass.mobile
env:
MOCK_API_SUCCESS: true
---
# E2E Test: Access Check Success Path
# Validates: User can perform an access check with successful result
# Note: Requires mock API to return success response

- launchApp
- tapOn:
id: "onboarding-get-started-button"
- tapOn:
id: "wallet-address-input"
- inputText: "0x1234567890123456789012345678901234567890"
- tapOn:
id: "wallet-connect-button"

# Navigate to access check
- tapOn:
id: "navigate-access-check-button"
- assertVisible:
id: "access-check-screen"

# Fill in access check form
- tapOn:
id: "access-check-guild-id-input"
- inputText: "guild_abc"
- tapOn:
id: "access-check-resource-id-input"
- inputText: "vip-door"

# Submit access check
- tapOn:
id: "check-access-button"

# Wait for result
- assertVisible:
id: "access-check-result"
timeout: 10000
39 changes: 39 additions & 0 deletions .maestro/05-access-check-failure.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
appId: xyz.guildpass.mobile
env:
MOCK_API_FAILURE: true
---
# E2E Test: Access Check Failure Path
# Validates: User sees appropriate error when access check fails
# Note: Requires mock API to return error response

- launchApp
- tapOn:
id: "onboarding-get-started-button"
- tapOn:
id: "wallet-address-input"
- inputText: "0x0000000000000000000000000000000000000000"
- tapOn:
id: "wallet-connect-button"

# Navigate to access check
- tapOn:
id: "navigate-access-check-button"
- assertVisible:
id: "access-check-screen"

# Fill in access check form with invalid data
- tapOn:
id: "access-check-guild-id-input"
- inputText: "invalid_guild"
- tapOn:
id: "access-check-resource-id-input"
- inputText: "invalid_resource"

# Submit access check
- tapOn:
id: "check-access-button"

# Wait for error
- assertVisible:
id: "access-check-error"
timeout: 10000
33 changes: 33 additions & 0 deletions .maestro/06-reset-app-state.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
appId: xyz.guildpass.mobile
---
# E2E Test: Reset App State
# Validates: User can reset app state from settings screen

- launchApp
- tapOn:
id: "onboarding-get-started-button"
- tapOn:
id: "wallet-address-input"
- inputText: "0x1234567890123456789012345678901234567890"
- tapOn:
id: "wallet-connect-button"
- assertVisible:
id: "profile-connected"

# Navigate to settings
- tapOn:
id: "navigate-settings-button"
- assertVisible:
id: "settings-screen"
- assertVisible:
id: "settings-api-url"
- assertVisible:
id: "settings-chain-id"

# Reset app state
- tapOn:
id: "reset-app-state-button"

# Should return to disconnected state
- assertVisible:
id: "wallet-connect-form"
Loading