Skip to content

[TEMP] Bitrise iOS E2E POC #1226

[TEMP] Bitrise iOS E2E POC

[TEMP] Bitrise iOS E2E POC #1226

# .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
labels: ["bitrise_pool_name:DemoFA"]
timeout-minutes: 60
outputs:
artifacts-url: ${{ steps.set-artifacts-url.outputs.artifacts-url }}
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