[TEMP] Bitrise iOS E2E POC #11
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: | |
| workflow_dispatch: | |
| inputs: | |
| run_smoke_tests: | |
| description: 'Run iOS smoke tests after build' | |
| required: false | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: read | |
| id-token: write | |
| # SECURITY: Only workflow_dispatch is allowed. No pull_request trigger. | |
| # This ensures only collaborators with Write access can trigger this workflow, | |
| # preventing malicious PRs from executing code with secrets on self-hosted runners. | |
| # This is a public repo — any PR trigger on self-hosted runners is a secret exfiltration risk. | |
| jobs: | |
| build-ios-on-bitrise: | |
| name: Build iOS E2E App (Bitrise Runners) | |
| 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 | |
| # Diagnostic: print essential runner info for benchmarking (sanitized — no secrets, paths, or hostnames) | |
| - name: Print runner environment diagnostics | |
| run: | | |
| echo "=== Runner Diagnostics (sanitized) ===" | |
| 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 | |
| # Bitrise runners use Vagrant: $HOME=/Users/vagrant, no /Users/runner. | |
| # GitHub Actions tools (setup-node, setup-ruby) hardcode /Users/runner paths | |
| # via the runner context, ignoring RUNNER_TOOL_CACHE env overrides. | |
| # Fix: create /Users/runner symlink → /Users/vagrant so all paths resolve. | |
| - name: Fix Vagrant environment paths | |
| run: | | |
| echo "Fixing paths for Vagrant-based Bitrise runner..." | |
| echo "Current user: $(whoami)" | |
| echo "Current HOME: $HOME" | |
| # Create /Users/runner → /Users/vagrant symlink if it doesn't exist | |
| if [ ! -e /Users/runner ]; then | |
| sudo ln -s /Users/vagrant /Users/runner | |
| echo "Created symlink: /Users/runner → /Users/vagrant" | |
| else | |
| echo "/Users/runner already exists: $(ls -la /Users/runner)" | |
| 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: ${{ runner.name }}" | |
| 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 "This data should be recorded in the benchmarking spreadsheet:" | |
| echo "https://docs.google.com/spreadsheets/d/1qzDVKYxbSohlOwSrgmwk0bzxf_JzLbrGsDDmCerQvpU/edit?gid=0#gid=0" | |
| shell: bash | |
| # Run iOS E2E smoke tests on Bitrise runners (uses boolean runner toggle) | |
| e2e-smoke-tests-ios: | |
| name: 'iOS E2E Smoke Tests (Bitrise)' | |
| if: ${{ github.event.inputs.run_smoke_tests == 'true' }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [build-ios-on-bitrise] | |
| uses: ./.github/workflows/run-e2e-smoke-tests-ios.yml | |
| with: | |
| selected_tags: '["SmokeTrade"]' | |
| use_bitrise_runner: true | |
| secrets: inherit |