Skip to content

chore(INFRA-3591): temp Bitrise RN cache + iOS E2E smoke POC #1

chore(INFRA-3591): temp Bitrise RN cache + iOS E2E smoke POC

chore(INFRA-3591): temp Bitrise RN cache + iOS E2E smoke POC #1

name: "[TEMP] Bitrise iOS RN cache + E2E POC"
# TEMPORARY WORKFLOW for INFRA-3591
# Purpose: Same as temp-bitrise-ios-rn.yml (Bitrise RN build cache + iOS E2E app build), then runs
# iOS E2E smoke tests on Bitrise. Not part of the CI gate; remove when benchmarking is complete.
on:
pull_request:
types: [ labeled, synchronize ]
# Hourly at :30 UTC — staggered from temp-bitrise-ios-rn.yml (:00). Note: temp-bitrise-ios-kv.yml
# also schedules at :30; expect concurrent Bitrise load unless offsets change.
schedule:
- cron: "30 * * * *"
concurrency:
group: bitrise-ios-rn-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 (RN 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:
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"
BITRISE_BUILD_CACHE_WORKSPACE_ID: ${{ vars.BITRISE_BUILD_CACHE_WORKSPACE_ID }}
BITRISE_BUILD_CACHE_AUTH_TOKEN: ${{ secrets.BITRISE_BUILD_CACHE_AUTH_TOKEN }}
steps:
- name: Checkout repo
uses: actions/checkout@v6
- 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
# ──────────────────────────────────────────────────────────────────────
# REMOVED: "Restore Xcode derived data from branch cache" step
# Was: cirruslabs/cache for ~/Library/Developer/Xcode/DerivedData + ios/build
# Reason: Replaced by Bitrise React Native Build Cache which provides
# granular, build-system-level caching (Xcode via LLVM CAS,
# C++ via ccache) instead of coarse tar-and-restore of DerivedData.
#
# REMOVED: "Restore Xcode derived data from main cache" step
# Was: cirruslabs/cache/restore (restore-only fallback to main branch)
# Reason: Same as above.
# ──────────────────────────────────────────────────────────────────────
- 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@v5
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
# ──────────────────────────────────────────────────────────────────────
# NEW: Activate Bitrise Build Cache for React Native
# Configures build-system-level remote caching for:
# - Xcode compilation outputs (LLVM CAS via Xcelerate)
# - C++ native modules (ccache backed by Bitrise ABCS)
# Must run BEFORE any step that triggers a native build (xcodebuild).
# Does NOT cache Metro JS bundling or node_modules.
# ──────────────────────────────────────────────────────────────────────
- name: Activate Bitrise Build Cache for React Native
env:
BITRISE_BUILD_CACHE_AUTH_TOKEN: ${{ secrets.BITRISE_BUILD_CACHE_AUTH_TOKEN }}
BITRISE_BUILD_CACHE_WORKSPACE_ID: ${{ vars.BITRISE_BUILD_CACHE_WORKSPACE_ID }}
run: |
#!/usr/bin/env bash
set -euxo pipefail
# Download Bitrise Build Cache CLI
curl --retry 5 -sSfL 'https://raw.githubusercontent.com/bitrise-io/bitrise-build-cache-cli/main/install/installer.sh' | sh -s -- -b /tmp/bin -d
# Activate React Native build cache (configures Xcode + ccache)
/tmp/bin/bitrise-build-cache activate react-native
echo "/tmp/bin" >> "$GITHUB_PATH"
shell: bash
- name: Generate current fingerprint
id: generate-fingerprint
run: |
FINGERPRINT=$(yarn fingerprint:generate)
echo "fingerprint=$FINGERPRINT" >> "$GITHUB_OUTPUT"
echo "Current fingerprint: ${FINGERPRINT}"
# ── Changed: cirruslabs/cache → actions/cache@v5 ──
- name: Restore iOS app matching fingerprint from branch cache
id: cache-restore
uses: actions/cache@v5
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 }}
# ── Changed: cirruslabs/cache/restore → actions/cache/restore@v5 ──
- 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@v5
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 }}
# ── Changed: Wrapped with bitrise-build-cache react-native run ──
- 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..."
bitrise-build-cache react-native run 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 }}
# Repack is JS-only bundling (no native build) — no wrapping needed
- 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@v6
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@v6
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 iOS RN cache — 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 "Build cache: Bitrise React Native Build Cache (Xcode LLVM CAS + ccache)"
echo "Record this data in the benchmark tracker referenced by the related ticket or PR description."
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