Files
self/.github/workflows/mobile-e2e.yml
Justin Hernandez 5305ef83fc Feat: Improved import export sorting for app and common (#833)
* save import sorting work

* remove dupe headers and fix type errors

* sort imports and exports

* fix errors from export sorting

* fix tests

* codex feedback

* fix exports

* fix exports and tweak test build

* fix export and format

* fix license headers

* fix app building and clean up test errors

* fix android local e2e test

* improve caching

* final fixes

* remove invalid option

* fix sorting and get random values loading

* fix import sorting
2025-08-06 15:18:42 -07:00

318 lines
12 KiB
YAML

name: Mobile E2E
env:
# Build environment versions
NODE_VERSION: 18
JAVA_VERSION: 17
ANDROID_API_LEVEL: 33
ANDROID_NDK_VERSION: 27.0.11718014
# Performance optimizations
GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true -Dorg.gradle.caching=true
CI: true
# Disable Maestro analytics in CI
MAESTRO_CLI_NO_ANALYTICS: true
MAESTRO_VERSION: 1.41.0
on:
push:
branches: [main, release/**]
paths:
- "app/**"
- ".github/workflows/mobile-e2e.yml"
pull_request:
paths:
- "app/**"
- ".github/workflows/mobile-e2e.yml"
jobs:
e2e-android:
# TODO: The Android E2E test job is temporarily disabled due to a recurring
# Maestro driver timeout issue in the CI environment. The emulator becomes
# unresponsive, preventing Maestro from connecting. This needs further
# investigation, but has been disabled to unblock the pipeline.
# To test locally, run `./scripts/test-e2e-local.sh android --workflow-match`
if: false
concurrency:
group: ${{ github.workflow }}-android-${{ github.ref }}
cancel-in-progress: true
timeout-minutes: 45
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: corepack enable
- run: corepack prepare yarn@4.6.0 --activate
- name: Cache Yarn dependencies
uses: actions/cache@v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable --silent
- name: Cache Maestro
id: cache-maestro
uses: actions/cache@v4
with:
path: ~/.maestro
key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }}
- name: Install Maestro
if: steps.cache-maestro.outputs.cache-hit != 'true'
run: curl -Ls "https://get.maestro.mobile.dev" | bash
- name: Add Maestro to path
run: echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name: Setup Java environment
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
accept-android-sdk-licenses: true
- name: Cache NDK
uses: actions/cache@v4
with:
path: ${{ env.ANDROID_HOME }}/ndk/${{ env.ANDROID_NDK_VERSION }}
key: ${{ runner.os }}-ndk-${{ env.ANDROID_NDK_VERSION }}
- name: Install NDK
run: sdkmanager "ndk;${{ env.ANDROID_NDK_VERSION }}"
- name: Build dependencies (outside emulator)
run: |
echo "Building dependencies..."
yarn workspace @selfxyz/mobile-app run build:deps --silent || { echo "❌ Dependency build failed"; exit 1; }
echo "✅ Dependencies built successfully"
- name: Cache Android build
uses: actions/cache@v4
with:
path: |
app/android/app/build
app/android/.gradle
key: ${{ runner.os }}-android-build-${{ hashFiles('app/android/**/*.gradle*', 'app/android/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-android-build-
- name: Build Android APK
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.ANDROID_API_LEVEL }}
arch: x86_64
target: google_apis
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -camera-front none -memory 8192 -cores 4 -accel on
disable-animations: true
script: |
echo "Building Android APK..."
chmod +x app/android/gradlew
(cd app/android && ./gradlew assembleRelease --quiet --parallel --build-cache --no-configuration-cache) || { echo "❌ Android build failed"; exit 1; }
echo "✅ Android build succeeded"
- name: Install and Test on Android
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.ANDROID_API_LEVEL }}
arch: x86_64
target: google_apis
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -camera-front none -memory 8192 -cores 4 -accel on
disable-animations: true
script: |
echo "Installing app on emulator..."
APK_PATH="app/android/app/build/outputs/apk/release/app-release.apk"
[ -f "$APK_PATH" ] || { echo "❌ APK not found at $APK_PATH"; exit 1; }
adb install -r "$APK_PATH" || { echo "❌ App installation failed"; exit 1; }
echo "✅ App installed successfully"
echo "⏰ Giving the emulator a moment to settle..."
sleep 5
echo "🎭 Running Maestro tests..."
export MAESTRO_DRIVER_STARTUP_TIMEOUT=180000
maestro test tests/e2e/launch.android.flow.yaml --format junit --output app/maestro-results.xml
env:
E2E_BUILD: "true"
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-results-android
path: app/maestro-results.xml
if-no-files-found: warn
e2e-ios:
timeout-minutes: 45
runs-on: macos-latest
concurrency:
group: ${{ github.workflow }}-ios-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: corepack enable
- run: corepack prepare yarn@4.6.0 --activate
- name: Cache Yarn dependencies
uses: actions/cache@v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable --silent
- name: Cache Maestro
id: cache-maestro
uses: actions/cache@v4
with:
path: ~/.maestro
key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }}
- name: Install Maestro
if: steps.cache-maestro.outputs.cache-hit != 'true'
run: curl -Ls "https://get.maestro.mobile.dev" | bash
- name: Add Maestro to path
run: echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name: Cache Node modules
uses: actions/cache@v4
with:
path: app/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('app/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-
- name: Cache Ruby gems
uses: actions/cache@v4
with:
path: app/vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('app/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Cache Pods
uses: actions/cache@v4
with:
path: |
app/ios/Pods
~/Library/Caches/CocoaPods
key: ${{ runner.os }}-pods-${{ hashFiles('app/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Cache Xcode build
uses: actions/cache@v4
with:
path: |
app/ios/build
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/com.apple.dt.Xcode
key: ${{ runner.os }}-xcode-${{ hashFiles('app/ios/Podfile.lock') }}-${{ hashFiles('app/ios/OpenPassport.xcworkspace/contents.xcworkspacedata') }}
restore-keys: |
${{ runner.os }}-xcode-${{ hashFiles('app/ios/Podfile.lock') }}-
${{ runner.os }}-xcode-
- name: Cache Xcode Index
uses: actions/cache@v4
with:
path: app/ios/build/Index.noindex
key: ${{ runner.os }}-xcode-index-${{ hashFiles('app/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-xcode-index-
- name: Cache iOS Simulator
uses: actions/cache@v4
with:
path: |
~/Library/Developer/CoreSimulator/Devices
~/Library/Developer/Xcode/iOS DeviceSupport
key: ${{ runner.os }}-simulator-${{ hashFiles('app/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-simulator-
- name: Build dependencies (outside main flow)
run: |
echo "Building dependencies..."
yarn workspace @selfxyz/mobile-app run build:deps --silent || { echo "❌ Dependency build failed"; exit 1; }
echo "✅ Dependencies built successfully"
- name: Install iOS dependencies
run: |
echo "Installing iOS dependencies..."
(cd app/ios && pod install --silent) || { echo "❌ Pod install failed"; exit 1; }
echo "✅ Pods installed successfully"
- name: Setup iOS Simulator
run: |
echo "Setting up iOS Simulator..."
echo "Available simulators:"
xcrun simctl list devices | grep "iPhone 15"
# Boot simulator and wait for it to be ready
xcrun simctl boot "iPhone 15" || true
xcrun simctl bootstatus "iPhone 15" -b
# Wait for simulator to be fully ready
echo "Waiting for simulator to be ready..."
sleep 10
echo "Simulator status:"
xcrun simctl list devices | grep "iPhone 15"
- name: Build iOS App
run: |
echo "Building iOS app..."
# Use cached derived data and enable parallel builds for faster compilation
xcodebuild -workspace app/ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration Release -sdk iphonesimulator -derivedDataPath app/ios/build -jobs "$(sysctl -n hw.ncpu)" -parallelizeTargets -quiet || { echo "❌ iOS build failed"; exit 1; }
echo "✅ iOS build succeeded"
- name: Install and Test on iOS
run: |
echo "Installing app on simulator..."
APP_PATH=$(find app/ios/build/Build/Products/Release-iphonesimulator -name "*.app" | head -1)
[ -z "$APP_PATH" ] && { echo "❌ Could not find built iOS app"; exit 1; }
echo "Found app at: $APP_PATH"
echo "🔍 Determining app bundle ID from built app..."
IOS_BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "$APP_PATH/Info.plist")
[ -z "$IOS_BUNDLE_ID" ] && { echo "❌ Could not determine bundle ID from $APP_PATH/Info.plist"; exit 1; }
echo "✅ App Bundle ID: $IOS_BUNDLE_ID"
echo "Removing any existing app installation..."
xcrun simctl uninstall "iPhone 15" "$IOS_BUNDLE_ID" 2>/dev/null || true
echo "Installing app..."
xcrun simctl install "iPhone 15" "$APP_PATH"
if [ $? -ne 0 ]; then
echo "❌ iOS app installation failed"
exit 1
fi
echo "Verifying app installation..."
if xcrun simctl listapps "iPhone 15" | grep -q "$IOS_BUNDLE_ID"; then
echo "✅ App successfully installed"
else
echo "❌ App installation verification failed"
exit 1
fi
echo "🚀 Testing app launch capability..."
xcrun simctl launch "iPhone 15" "$IOS_BUNDLE_ID" || {
echo "⚠️ Direct app launch test failed - this might be expected."
}
echo "⏰ Checking simulator readiness..."
sleep 10
xcrun simctl listapps "iPhone 15" > /dev/null || sleep 5
echo "Running Maestro tests..."
echo "Starting test execution..."
maestro test app/tests/e2e/launch.ios.flow.yaml --format junit --output app/maestro-results.xml || {
echo "Maestro test failed, but continuing to upload results..."
exit 1
}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-results-ios
path: app/maestro-results.xml
if-no-files-found: warn