feat(ci): POC Bitrise GH Action runners for iOS E2E #15
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # .github/workflows/temp-bitrise-ios-e2e.yml | |
| name: "[TEMP] Bitrise iOS E2E POC" | |
| # TEMPORARY WORKFLOW for INFRA-3527 | |
| # Purpose: Benchmark Bitrise GH Action runners vs Cirrus macOS runners for iOS E2E | |
| # This workflow is NOT part of the CI gate. It runs optionally for benchmarking. | |
| # Remove this workflow once benchmarking is complete. | |
| on: | |
| pull_request: | |
| types: [labeled, synchronize] | |
| schedule: | |
| - cron: '0 * * * *' | |
| concurrency: | |
| group: bitrise-ios-e2e-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| id-token: write | |
| jobs: | |
| build-ios-on-bitrise: | |
| name: Build iOS E2E App (Bitrise Runners) | |
| if: >- | |
| (github.event_name != 'pull_request' || | |
| contains(github.event.pull_request.labels.*.name, 'bitrise-poc')) && | |
| !github.event.pull_request.head.repo.fork | |
| runs-on: | |
| group: temp-bitrise-runners | |
| timeout-minutes: 60 | |
| outputs: | |
| artifacts-url: ${{ steps.set-artifacts-url.outputs.artifacts-url }} | |
| app-uploaded: ${{ steps.upload-app.outcome == 'success' }} | |
| env: | |
| XCODE_CACHE_VERSION: 1 | |
| IOS_APP_CACHE_VERSION: 2 | |
| RCT_NO_LAUNCH_PACKAGER: 1 | |
| XCODE_BUILD_SETTINGS: 'COMPILER_INDEX_STORE_ENABLE=NO' | |
| GITHUB_CI: 'true' | |
| PLATFORM: ios | |
| METAMASK_ENVIRONMENT: qa | |
| METAMASK_BUILD_TYPE: main | |
| IS_TEST: true | |
| E2E: 'true' | |
| IGNORE_BOXLOGS_DEVELOPMENT: true | |
| CI: 'true' | |
| NODE_OPTIONS: '--max-old-space-size=8192' | |
| BRIDGE_USE_DEV_APIS: 'true' | |
| RAMP_INTERNAL_BUILD: 'true' | |
| SEEDLESS_ONBOARDING_ENABLED: 'true' | |
| MM_NOTIFICATIONS_UI_ENABLED: 'true' | |
| MM_SECURITY_ALERTS_API_ENABLED: 'true' | |
| YARN_ENABLE_GLOBAL_CACHE: 'true' | |
| FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN: ${{ secrets.FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN }} | |
| FEATURES_ANNOUNCEMENTS_SPACE_ID: ${{ secrets.FEATURES_ANNOUNCEMENTS_SPACE_ID }} | |
| SEGMENT_WRITE_KEY_QA: ${{ secrets.SEGMENT_WRITE_KEY_QA }} | |
| SEGMENT_PROXY_URL_QA: ${{ secrets.SEGMENT_PROXY_URL_QA }} | |
| SEGMENT_DELETE_API_SOURCE_ID_QA: ${{ secrets.SEGMENT_DELETE_API_SOURCE_ID_QA }} | |
| SEGMENT_REGULATIONS_ENDPOINT_QA: ${{ secrets.SEGMENT_REGULATIONS_ENDPOINT_QA }} | |
| MM_SENTRY_DSN_TEST: ${{ secrets.MM_SENTRY_DSN_TEST }} | |
| MM_SENTRY_AUTH_TOKEN: ${{ secrets.MM_SENTRY_AUTH_TOKEN }} | |
| MAIN_IOS_GOOGLE_CLIENT_ID_UAT: ${{ secrets.MAIN_IOS_GOOGLE_CLIENT_ID_UAT }} | |
| MAIN_IOS_GOOGLE_REDIRECT_URI_UAT: ${{ secrets.MAIN_IOS_GOOGLE_REDIRECT_URI_UAT }} | |
| MAIN_ANDROID_APPLE_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_APPLE_CLIENT_ID_UAT }} | |
| MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT }} | |
| MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT }} | |
| GOOGLE_SERVICES_B64_IOS: ${{ secrets.GOOGLE_SERVICES_B64_IOS }} | |
| GOOGLE_SERVICES_B64_ANDROID: ${{ secrets.GOOGLE_SERVICES_B64_ANDROID }} | |
| MM_INFURA_PROJECT_ID: ${{ secrets.MM_INFURA_PROJECT_ID }} | |
| MM_PREDICT_GTM_MODAL_ENABLED: 'false' | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@v4 | |
| - name: Print runner environment diagnostics | |
| run: | | |
| echo "=== Runner Diagnostics ===" | |
| echo "Runner OS: ${{ runner.os }}" | |
| echo "Runner Arch: ${{ runner.arch }}" | |
| echo "macOS version: $(sw_vers -productVersion)" | |
| echo "Memory: $(sysctl -n hw.memsize | awk '{print $1/1024/1024/1024 " GB"}')" | |
| echo "Disk free: $(df -h / | tail -1 | awk '{print $4}')" | |
| echo "CPU cores: $(sysctl -n hw.ncpu)" | |
| echo "=== Xcode ===" | |
| xcode-select -p 2>/dev/null | sed "s|$HOME|~|g" || echo "No Xcode selected" | |
| xcodebuild -version 2>/dev/null | head -2 || echo "xcodebuild not available" | |
| echo "=== Ruby ===" | |
| ruby --version 2>/dev/null || echo "No ruby" | |
| echo "=== Node ===" | |
| node --version 2>/dev/null || echo "No node" | |
| shell: bash | |
| - name: Fix Vagrant environment paths | |
| run: | | |
| if [ -L /Users/runner ]; then | |
| current_target="$(readlink /Users/runner)" | |
| if [ "$current_target" = "/Users/vagrant" ]; then | |
| echo "Symlink already correct: /Users/runner → /Users/vagrant" | |
| else | |
| echo "Replacing incorrect symlink /Users/runner → $current_target" | |
| sudo rm /Users/runner | |
| sudo ln -s /Users/vagrant /Users/runner | |
| echo "Recreated symlink: /Users/runner → /Users/vagrant" | |
| fi | |
| elif [ -e /Users/runner ]; then | |
| echo "Error: /Users/runner exists but is not a symlink" | |
| ls -ld /Users/runner | |
| exit 1 | |
| else | |
| sudo ln -s /Users/vagrant /Users/runner | |
| echo "Created symlink: /Users/runner → /Users/vagrant" | |
| fi | |
| # Also set env vars as belt-and-suspenders | |
| mkdir -p "$HOME/hostedtoolcache" | |
| mkdir -p "$HOME/tmp" | |
| echo "RUNNER_TOOL_CACHE=$HOME/hostedtoolcache" >> "$GITHUB_ENV" | |
| echo "RUNNER_TEMP=$HOME/tmp" >> "$GITHUB_ENV" | |
| shell: bash | |
| - name: Restore Xcode derived data from branch cache | |
| id: xcode-restore-cache | |
| uses: cirruslabs/cache@bba69c6578b863ad0398ad40567bd2ef70290fe0 # v4 | |
| with: | |
| path: | | |
| ~/Library/Developer/Xcode/DerivedData | |
| ios/build | |
| key: bitrise-${{ runner.os }}-xcode-${{ github.ref_name }}-${{ env.XCODE_CACHE_VERSION }}-${{ hashFiles('ios/**/*.{h,m,mm,swift}', 'ios/**/Podfile.lock', 'yarn.lock') }} | |
| - name: Restore Xcode derived data from main cache | |
| if: ${{ steps.xcode-restore-cache.outputs.cache-hit != 'true' && github.ref_name != 'main' }} | |
| id: xcode-restore-cache-main | |
| uses: cirruslabs/cache/restore@bba69c6578b863ad0398ad40567bd2ef70290fe0 # v4 | |
| with: | |
| path: | | |
| ~/Library/Developer/Xcode/DerivedData | |
| ios/build | |
| key: bitrise-${{ runner.os }}-xcode-main-${{ env.XCODE_CACHE_VERSION }}-${{ hashFiles('ios/**/*.{h,m,mm,swift}', 'ios/**/Podfile.lock', 'yarn.lock') }} | |
| - name: Installing iOS Environment Setup | |
| timeout-minutes: 15 | |
| uses: ./.github/actions/setup-e2e-env | |
| with: | |
| platform: ios | |
| setup-simulator: false | |
| configure-keystores: false | |
| - name: Print iOS tool versions | |
| run: | | |
| echo "Node.js Version: $(node -v || echo 'not found')" | |
| echo "Yarn Version: $(yarn -v || echo 'not found')" | |
| echo "CocoaPods Version: $(pod --version || echo 'not found')" | |
| echo "Xcode Path: $(xcode-select -p || echo 'not found')" | |
| echo "Ruby Version: $(ruby --version || echo 'not found')" | |
| echo "Booted iOS Simulators:" | |
| xcrun simctl list | grep Booted || echo "No booted simulators found" | |
| shell: bash | |
| - name: Clean iOS plist files | |
| run: find ios -name "*.plist" -exec xattr -c {} \; | |
| - name: Restore .metamask folder | |
| id: restore-metamask | |
| uses: actions/cache@v4 | |
| with: | |
| path: .metamask | |
| key: .metamask-${{ hashFiles('package.json', 'yarn.lock') }} | |
| - name: Install Foundry if cache missed | |
| if: steps.restore-metamask.outputs.cache-hit != 'true' | |
| run: yarn install:foundryup | |
| - name: Setup project dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: | | |
| echo "Setting up project..." | |
| yarn setup:github-ci --build-ios --no-build-android | |
| - name: Generate current fingerprint | |
| id: generate-fingerprint | |
| run: | | |
| FINGERPRINT=$(yarn fingerprint:generate) | |
| echo "fingerprint=$FINGERPRINT" >> "$GITHUB_OUTPUT" | |
| echo "Current fingerprint: ${FINGERPRINT}" | |
| - name: Restore iOS app matching fingerprint from branch cache | |
| id: cache-restore | |
| uses: cirruslabs/cache@bba69c6578b863ad0398ad40567bd2ef70290fe0 # v4 | |
| with: | |
| path: | | |
| ios/build/Build/Products/Release-iphonesimulator/MetaMask.app | |
| key: bitrise-ios-app-${{ github.ref_name }}-v${{ env.IOS_APP_CACHE_VERSION }}-${{ steps.generate-fingerprint.outputs.fingerprint }} | |
| - name: Restore iOS app matching fingerprint from main cache | |
| if: ${{ steps.cache-restore.outputs.cache-hit != 'true' && github.ref_name != 'main' }} | |
| id: cache-restore-main | |
| uses: cirruslabs/cache/restore@bba69c6578b863ad0398ad40567bd2ef70290fe0 # v4 | |
| with: | |
| path: | | |
| ios/build/Build/Products/Release-iphonesimulator/MetaMask.app | |
| key: bitrise-ios-app-main-v${{ env.IOS_APP_CACHE_VERSION }}-${{ steps.generate-fingerprint.outputs.fingerprint }} | |
| - name: Build iOS E2E App | |
| if: ${{ steps.cache-restore.outputs.cache-hit != 'true' && steps.cache-restore-main.outputs.cache-hit != 'true' }} | |
| run: | | |
| echo "Building iOS E2E App on Bitrise runner..." | |
| yarn build:ios:main:e2e | |
| shell: bash | |
| env: | |
| PLATFORM: ios | |
| METAMASK_ENVIRONMENT: qa | |
| METAMASK_BUILD_TYPE: main | |
| IS_TEST: true | |
| IS_SIM_BUILD: 'true' | |
| IGNORE_BOXLOGS_DEVELOPMENT: true | |
| GITHUB_CI: 'true' | |
| CI: 'true' | |
| SEGMENT_WRITE_KEY_QA: ${{ secrets.SEGMENT_WRITE_KEY_QA }} | |
| SEGMENT_PROXY_URL_QA: ${{ secrets.SEGMENT_PROXY_URL_QA }} | |
| SEGMENT_DELETE_API_SOURCE_ID_QA: ${{ secrets.SEGMENT_DELETE_API_SOURCE_ID_QA }} | |
| SEGMENT_REGULATIONS_ENDPOINT_QA: ${{ secrets.SEGMENT_REGULATIONS_ENDPOINT_QA }} | |
| MM_SENTRY_DSN_TEST: ${{ secrets.MM_SENTRY_DSN_TEST }} | |
| MM_SENTRY_AUTH_TOKEN: ${{ secrets.MM_SENTRY_AUTH_TOKEN }} | |
| MAIN_IOS_GOOGLE_CLIENT_ID_UAT: ${{ secrets.MAIN_IOS_GOOGLE_CLIENT_ID_UAT }} | |
| MAIN_IOS_GOOGLE_REDIRECT_URI_UAT: ${{ secrets.MAIN_IOS_GOOGLE_REDIRECT_URI_UAT }} | |
| MAIN_ANDROID_APPLE_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_APPLE_CLIENT_ID_UAT }} | |
| MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT }} | |
| MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT }} | |
| GOOGLE_SERVICES_B64_IOS: ${{ secrets.GOOGLE_SERVICES_B64_IOS }} | |
| GOOGLE_SERVICES_B64_ANDROID: ${{ secrets.GOOGLE_SERVICES_B64_ANDROID }} | |
| - name: Repack iOS app with JS updates | |
| if: ${{ steps.cache-restore.outputs.cache-hit == 'true' || steps.cache-restore-main.outputs.cache-hit == 'true' }} | |
| run: | | |
| echo "Repacking iOS app with updated JavaScript bundle..." | |
| yarn build:repack:ios | |
| echo "Final app size: $(du -sh "ios/build/Build/Products/Release-iphonesimulator/MetaMask.app" | cut -f1)" | |
| env: | |
| PLATFORM: ios | |
| METAMASK_ENVIRONMENT: qa | |
| METAMASK_BUILD_TYPE: main | |
| IS_TEST: true | |
| E2E: 'true' | |
| IGNORE_BOXLOGS_DEVELOPMENT: true | |
| GITHUB_CI: 'true' | |
| CI: 'true' | |
| NODE_OPTIONS: '--max-old-space-size=8192' | |
| BRIDGE_USE_DEV_APIS: 'true' | |
| RAMP_INTERNAL_BUILD: 'true' | |
| SEEDLESS_ONBOARDING_ENABLED: 'true' | |
| MM_NOTIFICATIONS_UI_ENABLED: 'true' | |
| MM_SECURITY_ALERTS_API_ENABLED: 'true' | |
| FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN: ${{ secrets.FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN }} | |
| FEATURES_ANNOUNCEMENTS_SPACE_ID: ${{ secrets.FEATURES_ANNOUNCEMENTS_SPACE_ID }} | |
| SEGMENT_WRITE_KEY_QA: ${{ secrets.SEGMENT_WRITE_KEY_QA }} | |
| SEGMENT_PROXY_URL_QA: ${{ secrets.SEGMENT_PROXY_URL_QA }} | |
| SEGMENT_DELETE_API_SOURCE_ID_QA: ${{ secrets.SEGMENT_DELETE_API_SOURCE_ID_QA }} | |
| SEGMENT_REGULATIONS_ENDPOINT_QA: ${{ secrets.SEGMENT_REGULATIONS_ENDPOINT_QA }} | |
| MM_SENTRY_DSN_TEST: ${{ secrets.MM_SENTRY_DSN_TEST }} | |
| MM_SENTRY_AUTH_TOKEN: ${{ secrets.MM_SENTRY_AUTH_TOKEN }} | |
| MAIN_IOS_GOOGLE_CLIENT_ID_UAT: ${{ secrets.MAIN_IOS_GOOGLE_CLIENT_ID_UAT }} | |
| MAIN_IOS_GOOGLE_REDIRECT_URI_UAT: ${{ secrets.MAIN_IOS_GOOGLE_REDIRECT_URI_UAT }} | |
| MAIN_ANDROID_APPLE_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_APPLE_CLIENT_ID_UAT }} | |
| MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT }} | |
| MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT }} | |
| GOOGLE_SERVICES_B64_IOS: ${{ secrets.GOOGLE_SERVICES_B64_IOS }} | |
| GOOGLE_SERVICES_B64_ANDROID: ${{ secrets.GOOGLE_SERVICES_B64_ANDROID }} | |
| MM_INFURA_PROJECT_ID: ${{ secrets.MM_INFURA_PROJECT_ID }} | |
| - name: Fix iOS bundle executable case and permissions before upload | |
| run: | | |
| APP_PATH="ios/build/Build/Products/Release-iphonesimulator/MetaMask.app" | |
| BUNDLE_EXEC=$(/usr/libexec/PlistBuddy -c "Print CFBundleExecutable" "$APP_PATH/Info.plist" 2>/dev/null) | |
| if [ -z "$BUNDLE_EXEC" ]; then | |
| echo "Could not read CFBundleExecutable from Info.plist" | |
| exit 1 | |
| fi | |
| ACTUAL_PATH=$(find "$APP_PATH" -maxdepth 1 -iname "$BUNDLE_EXEC" -type f | head -1) | |
| if [ -z "$ACTUAL_PATH" ]; then | |
| echo "Bundle executable not found: $BUNDLE_EXEC" | |
| exit 1 | |
| fi | |
| if [ "$(basename "$ACTUAL_PATH")" != "$BUNDLE_EXEC" ]; then | |
| mv "$ACTUAL_PATH" "$APP_PATH/${BUNDLE_EXEC}_fix" | |
| mv "$APP_PATH/${BUNDLE_EXEC}_fix" "$APP_PATH/$BUNDLE_EXEC" | |
| fi | |
| chmod +x "$APP_PATH/$BUNDLE_EXEC" | |
| shell: bash | |
| - name: Upload iOS APP Artifact (Simulator) | |
| id: upload-app | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: main-qa-MetaMask.app | |
| path: ios/build/Build/Products/Release-iphonesimulator/MetaMask.app | |
| retention-days: 7 | |
| if-no-files-found: error | |
| - name: Upload iOS Source Map | |
| id: upload-sourcemap | |
| if: ${{ steps.cache-restore.outputs.cache-hit == 'true' || steps.cache-restore-main.outputs.cache-hit == 'true' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: main-qa-index.js.map | |
| path: sourcemaps/ios/index.js.map | |
| retention-days: 7 | |
| if-no-files-found: error | |
| continue-on-error: true | |
| - name: Set Artifacts URL and Status | |
| id: set-artifacts-url | |
| run: | | |
| ARTIFACTS_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" | |
| echo "artifacts-url=${ARTIFACTS_URL}" >> "$GITHUB_OUTPUT" | |
| echo "Artifacts available at: ${ARTIFACTS_URL}" | |
| echo "" | |
| echo "Upload Status Summary:" | |
| echo "- APP (Simulator): ${{ steps.upload-app.outcome }}" | |
| echo "- Source Map: ${{ steps.upload-sourcemap.outcome }}" | |
| env: | |
| GITHUB_REPOSITORY: '${{ github.repository }}' | |
| GITHUB_RUN_ID: '${{ github.run_id }}' | |
| # Record timing data for benchmarking | |
| - name: Record build timing summary | |
| if: always() | |
| run: | | |
| echo "=== Bitrise Runner Build Timing Summary ===" | |
| echo "Runner platform: ${{ runner.os }}/${{ runner.arch }}" | |
| echo "Build outcome: ${{ steps.upload-app.outcome }}" | |
| echo "Cache hit (branch): ${{ steps.cache-restore.outputs.cache-hit }}" | |
| echo "Cache hit (main): ${{ steps.cache-restore-main.outputs.cache-hit || 'N/A' }}" | |
| echo "Record this data in the benchmark tracker referenced by the related ticket or PR description." | |
| shell: bash | |
| # Run iOS E2E smoke tests on Bitrise runners (uses boolean runner toggle) | |
| e2e-smoke-tests-ios: | |
| name: 'iOS E2E Smoke Tests (Bitrise)' | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [build-ios-on-bitrise] | |
| uses: ./.github/workflows/run-e2e-smoke-tests-ios.yml | |
| with: | |
| use_bitrise_runner: true | |
| secrets: inherit |