mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
* chore: streamline ios e2e workflow * fix version * fix * fixes * fix path * fix path issue * revert back to dev. test locally first * fix missing dep * update lock * fix version * fix ndk * fix scheme * fixes * fix e2e * fixes * fix pipeline * fix and unify xcode version * update workflow * fix pipeline * implement pipeline fix * fix ios version * remove || true
451 lines
19 KiB
YAML
451 lines
19 KiB
YAML
name: Mobile E2E
|
|
|
|
env:
|
|
# Build environment versions
|
|
JAVA_VERSION: 17
|
|
ANDROID_API_LEVEL: 33
|
|
ANDROID_NDK_VERSION: 27.0.11718014
|
|
XCODE_VERSION: 16.4
|
|
# Cache versions
|
|
GH_CACHE_VERSION: v1 # Global cache version
|
|
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
|
|
# 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
|
|
- name: Read and sanitize Node.js version
|
|
shell: bash
|
|
run: |
|
|
if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then
|
|
echo "❌ .nvmrc is missing or empty"; exit 1;
|
|
fi
|
|
VERSION="$(tr -d '\r\n' < .nvmrc)"
|
|
VERSION="${VERSION#v}"
|
|
if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
|
|
echo "Invalid .nvmrc content: '$VERSION'"; exit 1;
|
|
fi
|
|
echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV"
|
|
echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV"
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
- 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 }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-node-${{ env.NODE_VERSION_SANITIZED }}-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: ./.github/actions/cache-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
|
|
env:
|
|
# iOS project configuration - hardcoded for E2E testing stability
|
|
# Note: During migration, project name is "Self" but scheme is still "OpenPassport"
|
|
# mobile-deploy.yml uses secrets for production deployment
|
|
IOS_PROJECT_NAME: "Self"
|
|
IOS_PROJECT_SCHEME: "OpenPassport"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Read and sanitize Node.js version
|
|
shell: bash
|
|
run: |
|
|
if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then
|
|
echo "❌ .nvmrc is missing or empty"; exit 1;
|
|
fi
|
|
VERSION="$(tr -d '\r\n' < .nvmrc)"
|
|
VERSION="${VERSION#v}"
|
|
if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
|
|
echo "Invalid .nvmrc content: '$VERSION'"; exit 1;
|
|
fi
|
|
echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV"
|
|
echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV"
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
- 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 }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-node-${{ env.NODE_VERSION_SANITIZED }}-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: Set up Xcode
|
|
uses: maxim-lobanov/setup-xcode@v1
|
|
with:
|
|
xcode-version: ${{ env.XCODE_VERSION }}
|
|
- name: Configure Xcode path
|
|
run: |
|
|
echo "🔧 Configuring Xcode path to fix iOS SDK issues..."
|
|
# Fix for macOS 15 runner iOS SDK issues
|
|
# See: https://github.com/actions/runner-images/issues/12758
|
|
sudo xcode-select --switch /Applications/Xcode_${{ env.XCODE_VERSION }}.app
|
|
echo "✅ Xcode path configured"
|
|
|
|
# Verify Xcode setup
|
|
echo "Xcode version:"
|
|
xcodebuild -version
|
|
echo "Xcode path:"
|
|
xcode-select -p
|
|
- name: Cache Node modules
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: app/node_modules
|
|
key: ${{ runner.os }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('app/yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-node-${{ env.NODE_VERSION_SANITIZED }}-
|
|
- name: Cache Ruby gems
|
|
uses: ./.github/actions/cache-bundler
|
|
with:
|
|
path: app/vendor/bundle
|
|
lock-file: app/Gemfile.lock
|
|
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
|
|
- name: Cache Pods
|
|
uses: ./.github/actions/cache-pods
|
|
with:
|
|
path: |
|
|
app/ios/Pods
|
|
~/Library/Caches/CocoaPods
|
|
lock-file: app/ios/Podfile.lock
|
|
- 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/${{ env.IOS_PROJECT_NAME }}.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-v1
|
|
restore-keys: |
|
|
${{ runner.os }}-simulator-
|
|
- name: Verify iOS Runtime
|
|
run: |
|
|
echo "📱 Verifying iOS Runtime availability..."
|
|
echo "Available iOS runtimes:"
|
|
xcrun simctl list runtimes | grep iOS
|
|
- 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..."
|
|
|
|
# First, check what simulators are actually available
|
|
echo "Available simulators:"
|
|
xcrun simctl list devices available || {
|
|
echo "❌ Failed to list available devices"
|
|
echo "Trying to list all devices:"
|
|
xcrun simctl list devices || {
|
|
echo "❌ Failed to list any devices"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Find iPhone SE (3rd generation) simulator
|
|
echo "Finding iPhone SE (3rd generation) simulator..."
|
|
AVAILABLE_SIMULATOR=$(xcrun simctl list devices available | grep "iPhone SE (3rd generation)" | head -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
|
|
|
if [ -z "$AVAILABLE_SIMULATOR" ]; then
|
|
echo "iPhone SE (3rd generation) not found, trying any iPhone SE..."
|
|
AVAILABLE_SIMULATOR=$(xcrun simctl list devices available | grep "iPhone SE" | head -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
|
fi
|
|
|
|
if [ -z "$AVAILABLE_SIMULATOR" ]; then
|
|
echo "No iPhone SE found, trying any iPhone..."
|
|
AVAILABLE_SIMULATOR=$(xcrun simctl list devices available | grep "iPhone" | head -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
|
fi
|
|
|
|
if [ -z "$AVAILABLE_SIMULATOR" ]; then
|
|
echo "❌ No available iPhone simulator found"
|
|
echo "Creating a new iPhone SE (3rd generation) simulator..."
|
|
# Create a new iPhone SE (3rd generation) simulator
|
|
xcrun simctl create "iPhone SE (3rd generation)" "iPhone SE (3rd generation)" || {
|
|
echo "❌ Failed to create iPhone SE (3rd generation) simulator"
|
|
echo "Trying to create any iPhone SE simulator..."
|
|
xcrun simctl create "iPhone SE" "iPhone SE" || {
|
|
echo "❌ Failed to create simulator"
|
|
exit 1
|
|
}
|
|
}
|
|
AVAILABLE_SIMULATOR=$(xcrun simctl list devices | grep "iPhone SE" | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
|
fi
|
|
|
|
echo "Using simulator: $AVAILABLE_SIMULATOR"
|
|
|
|
# Get simulator name for display
|
|
SIMULATOR_NAME=$(xcrun simctl list devices | grep "$AVAILABLE_SIMULATOR" | sed -E 's/^[[:space:]]*([^(]+).*/\1/' | xargs)
|
|
echo "Simulator name: $SIMULATOR_NAME"
|
|
|
|
# Boot simulator and wait for it to be ready
|
|
echo "Booting simulator..."
|
|
xcrun simctl boot "$AVAILABLE_SIMULATOR" || {
|
|
echo "❌ Failed to boot simulator"
|
|
exit 1
|
|
}
|
|
|
|
echo "Waiting for simulator to be ready..."
|
|
xcrun simctl bootstatus "$AVAILABLE_SIMULATOR" -b
|
|
|
|
# Wait for simulator to be fully ready
|
|
echo "Waiting for simulator to be fully ready..."
|
|
sleep 15
|
|
|
|
echo "Simulator status:"
|
|
xcrun simctl list devices | grep "$AVAILABLE_SIMULATOR"
|
|
|
|
# Store simulator ID for later use
|
|
echo "IOS_SIMULATOR_ID=$AVAILABLE_SIMULATOR" >> $GITHUB_ENV
|
|
echo "IOS_SIMULATOR_NAME=$SIMULATOR_NAME" >> $GITHUB_ENV
|
|
- name: Build iOS App
|
|
run: |
|
|
echo "Building iOS app..."
|
|
echo "Project: ${{ env.IOS_PROJECT_NAME }}, Scheme: ${{ env.IOS_PROJECT_SCHEME }}"
|
|
|
|
# Verify workspace exists before building
|
|
WORKSPACE_PATH="app/ios/${{ env.IOS_PROJECT_NAME }}.xcworkspace"
|
|
if [ ! -d "$WORKSPACE_PATH" ]; then
|
|
echo "❌ Workspace not found at: $WORKSPACE_PATH"
|
|
echo "Available workspaces:"
|
|
find app/ios -name "*.xcworkspace" -type d
|
|
exit 1
|
|
fi
|
|
|
|
# Verify scheme exists by listing available schemes
|
|
echo "Verifying scheme availability..."
|
|
AVAILABLE_SCHEMES=$(xcodebuild -list -workspace "$WORKSPACE_PATH" 2>/dev/null | grep -A 200 "Schemes:" | grep -v "Schemes:" | xargs)
|
|
echo "Available schemes (first 20): $(echo $AVAILABLE_SCHEMES | cut -d' ' -f1-20)..."
|
|
|
|
if [[ ! "$AVAILABLE_SCHEMES" =~ ${{ env.IOS_PROJECT_SCHEME }} ]]; then
|
|
echo "❌ Scheme '${{ env.IOS_PROJECT_SCHEME }}' not found"
|
|
echo "Full scheme list:"
|
|
xcodebuild -list -workspace "$WORKSPACE_PATH" 2>/dev/null | grep -A 200 "Schemes:" | grep -v "Schemes:" | head -50
|
|
exit 1
|
|
fi
|
|
|
|
echo "✅ Using workspace: $WORKSPACE_PATH"
|
|
echo "✅ Using scheme: ${{ env.IOS_PROJECT_SCHEME }}"
|
|
|
|
# Use cached derived data and enable parallel builds for faster compilation
|
|
# Use the simulator that was set up earlier in the workflow
|
|
xcodebuild -workspace "$WORKSPACE_PATH" -scheme ${{ env.IOS_PROJECT_SCHEME }} -configuration Release -destination "id=${{ env.IOS_SIMULATOR_ID }}" -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"
|
|
|
|
# Use the dynamic simulator ID
|
|
SIMULATOR_ID="${IOS_SIMULATOR_ID:-iPhone SE (3rd generation)}"
|
|
echo "Installing on simulator: $SIMULATOR_ID"
|
|
|
|
echo "Removing any existing app installation..."
|
|
xcrun simctl uninstall "$SIMULATOR_ID" "$IOS_BUNDLE_ID" 2>/dev/null || true
|
|
|
|
echo "Installing app..."
|
|
xcrun simctl install "$SIMULATOR_ID" "$APP_PATH"
|
|
if [ $? -ne 0 ]; then
|
|
echo "❌ iOS app installation failed"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Verifying app installation..."
|
|
if xcrun simctl get_app_container "$SIMULATOR_ID" "$IOS_BUNDLE_ID" app >/dev/null 2>&1; then
|
|
echo "✅ App successfully installed"
|
|
else
|
|
echo "❌ App installation verification failed"
|
|
exit 1
|
|
fi
|
|
|
|
echo "🚀 Testing app launch capability..."
|
|
xcrun simctl launch "$SIMULATOR_ID" "$IOS_BUNDLE_ID" || {
|
|
echo "⚠️ Direct app launch test failed - this might be expected."
|
|
}
|
|
|
|
echo "⏰ Checking simulator readiness..."
|
|
sleep 10
|
|
# Probe container as readiness check instead of listapps
|
|
xcrun simctl get_app_container "$SIMULATOR_ID" "$IOS_BUNDLE_ID" app >/dev/null 2>&1 || 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
|