Skip to content

chore(INFRA-3591): add bitrise runners with resource monitoring #1331

chore(INFRA-3591): add bitrise runners with resource monitoring

chore(INFRA-3591): add bitrise runners with resource monitoring #1331

name: "[TEMP] Bitrise iOS E2E POC"
# TEMPORARY WORKFLOW for Bitrise managed runner benchmarking.
# Purpose: benchmark Bitrise GitHub Actions runners for iOS E2E.
# This workflow is not part of the CI gate and should be removed after the POC.
#
# Triggers: workflow_dispatch (Actions tab — pick branch under "Use workflow from"),
# pull_request (label bitrise-poc), and hourly schedule.
on:
workflow_dispatch:
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 KV Cache)
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:DemoFAXL" ]
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
mkdir -p "$HOME/hostedtoolcache" "$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: actions/cache@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: actions/cache/restore@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: actions/cache@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: actions/cache/restore@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 with KV cache only..."
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 }}
- 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 "Xcode cache hit (branch): ${{ steps.xcode-restore-cache.outputs.cache-hit }}"
echo "Xcode cache hit (main): ${{ steps.xcode-restore-cache-main.outputs.cache-hit || 'N/A' }}"
echo "App cache hit (branch): ${{ steps.cache-restore.outputs.cache-hit }}"
echo "App cache hit (main): ${{ steps.cache-restore-main.outputs.cache-hit || 'N/A' }}"
echo "Cache mode: Bitrise KV cache via actions/cache only"
echo "Record CPU and memory utilization from the VM monitoring CSV for this job."
shell: bash
# 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