diff --git a/.detoxrc.js b/.detoxrc.js index d95a9e0f1f7f..0cdf6bf15f38 100644 --- a/.detoxrc.js +++ b/.detoxrc.js @@ -58,6 +58,10 @@ module.exports = { device: 'android.bitrise.emulator', app: 'android.release', }, + 'android.github_ci.release': { + device: 'android.github_ci.emulator', + app: 'android.release', + }, 'android.emu.flask.release': { device: 'android.bitrise.emulator', app: 'android.flask.release', @@ -75,10 +79,19 @@ module.exports = { device: { avdName: 'emulator', }, - // to be used by github action runners later on - // bootArgs: '-skin 1080x2340 -memory 4096 -cores 4 -gpu swiftshader_indirect -no-audio -no-boot-anim -partition-size 4096', - // forceAdbInstall: true, - // gpuMode: 'swiftshader_indirect', + // optimized for Bitrise CI runners + bootArgs: '-verbose -show-kernel -no-audio -netdelay none -no-snapshot -wipe-data -gpu auto -no-window -no-boot-anim -read-only', + forceAdbInstall: true, + }, + 'android.github_ci.emulator': { + type: 'android.emulator', + device: { + avdName: 'emulator', + }, + // optimized for GitHub Actions CI runners + bootArgs: '-skin 1080x2340 -memory 6144 -cores 4 -gpu swiftshader_indirect -no-audio -no-boot-anim -partition-size 4096 -no-snapshot-save -no-snapshot-load -cache-size 1024 -accel on -wipe-data -read-only', + forceAdbInstall: true, + gpuMode: 'swiftshader_indirect', }, 'android.emulator': { type: 'android.emulator', diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 000000000000..c851a1c3fff6 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,21 @@ +self-hosted-runner: + # Labels of self-hosted runner in array of strings. + labels: + - "gha-mmsdk-scale-set-ubuntu-22.04-amd64-xl" + - "gha-mmsdk-scale-set-ubuntu-22.04-amd64-large" + - "gha-mm-scale-set-ubuntu-22.04-amd64-large" + - "macos-15" + +# Configuration variables in array of strings defined in your repository or +# organization. `null` means disabling configuration variables check. +# Empty array means no configuration variable is allowed. +config-variables: null + +# Configuration for file paths. The keys are glob patterns to match to file +# paths relative to the repository root. The values are the configurations for +# the file paths. Note that the path separator is always '/'. +# The following configurations are available. +# +# "ignore" is an array of regular expression patterns. Matched error messages +# are ignored. This is similar to the "-ignore" command line option. +paths: diff --git a/.github/workflows/build-android-e2e.yml b/.github/workflows/build-android-e2e.yml new file mode 100644 index 000000000000..690234a2d02a --- /dev/null +++ b/.github/workflows/build-android-e2e.yml @@ -0,0 +1,126 @@ +# Pure Android E2E build workflow - just builds APKs and uploads artifacts +# No testing, no unnecessary setup - optimized for speed and efficiency + +name: Build Android E2E APKs + +on: + workflow_call: + outputs: + apk-uploaded: + description: 'Whether the APK was successfully uploaded' + value: ${{ jobs.build-android-apks.outputs.apk-uploaded }} + aab-uploaded: + description: 'Whether the AAB was successfully uploaded' + value: ${{ jobs.build-android-apks.outputs.aab-uploaded }} + sourcemap-uploaded: + description: 'Whether the sourcemap was successfully uploaded' + value: ${{ jobs.build-android-apks.outputs.sourcemap-uploaded }} + +jobs: + build-android-apks: + name: Build Android E2E APKs + runs-on: gha-mmsdk-scale-set-ubuntu-22.04-amd64-xl + outputs: + apk-uploaded: ${{ steps.upload-apk.outcome == 'success' }} + aab-uploaded: ${{ steps.upload-aab.outcome == 'success' }} + sourcemap-uploaded: ${{ steps.upload-sourcemap.outcome == 'success' }} + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup Android Build Environment + uses: MetaMask/github-tools/.github/actions/setup-e2e-env@self-hosted-runners-config + with: + platform: android + setup-simulator: false + configure-keystores: true + target: qa + + - name: Cache Gradle dependencies + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + android/.gradle + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + + - name: Build Android E2E APKs + run: | + echo "๐Ÿš€ Setting up project..." + yarn setup:github-ci --no-build-ios + + echo "๐Ÿ— Building Android E2E APKs..." + export NODE_OPTIONS="--max-old-space-size=8192" + cp android/gradle.properties.github android/gradle.properties + yarn build:android:main:e2e + shell: bash + env: + PLATFORM: android + 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" + MM_UNIFIED_SWAPS_ENABLED: "true" + MM_BRIDGE_ENABLED: "true" + BRIDGE_USE_DEV_APIS: "true" + RAMP_INTERNAL_BUILD: "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 }} + MM_INFURA_PROJECT_ID: ${{ secrets.MM_INFURA_PROJECT_ID }} + + - name: Upload Android APK + id: upload-apk + uses: actions/upload-artifact@v4 + with: + name: app-prod-release.apk + path: android/app/build/outputs/apk/prod/release/app-prod-release.apk + retention-days: 7 + if-no-files-found: error + + - name: Upload Android Test APK + id: upload-test-apk + uses: actions/upload-artifact@v4 + with: + name: app-prod-release-androidTest.apk + path: android/app/build/outputs/apk/androidTest/prod/release/app-prod-release-androidTest.apk + retention-days: 7 + if-no-files-found: error + + - name: Upload Android AAB + id: upload-aab + uses: actions/upload-artifact@v4 + with: + name: app-prod-release.aab + path: android/app/build/outputs/bundle/prodRelease/app-prod-release.aab + retention-days: 7 + if-no-files-found: warn + continue-on-error: true + + - name: Upload Android Source Map + id: upload-sourcemap + uses: actions/upload-artifact@v4 + with: + name: index.android.bundle.map + path: sourcemaps/android/index.android.bundle.map + retention-days: 7 + if-no-files-found: warn + continue-on-error: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29d5dbbf8cfb..cd714dc822b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -306,7 +306,7 @@ jobs: run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/62dc61a45fc95efe8c800af7a557ab0b9165d63b/scripts/download-actionlint.bash) 1.7.1 shell: bash - name: Check workflow files - run: ${{ steps.download-actionlint.outputs.executable }} -color + run: ${{ steps.download-actionlint.outputs.executable }} -color -config-file .github/actionlint.yaml shell: bash all-jobs-pass: name: All jobs pass diff --git a/.github/workflows/run-e2e-api-specs.yml b/.github/workflows/run-e2e-api-specs.yml index 7a60ca8b1d5c..53d0d3923899 100644 --- a/.github/workflows/run-e2e-api-specs.yml +++ b/.github/workflows/run-e2e-api-specs.yml @@ -7,13 +7,14 @@ on: workflow_call: workflow_dispatch: pull_request: - types: [labeled] + types: [opened, synchronize] jobs: api-specs-ios: name: "api-specs-ios" if: false runs-on: macos-latest-xlarge + continue-on-error: true env: METAMASK_ENVIRONMENT: 'local' diff --git a/.github/workflows/run-e2e-mobile.yml b/.github/workflows/run-e2e-mobile.yml deleted file mode 100644 index 1f4e14ef252e..000000000000 --- a/.github/workflows/run-e2e-mobile.yml +++ /dev/null @@ -1,238 +0,0 @@ -# This workflow runs mobile E2E tests for a specific test category. -# It passes matrix sharding info to the test framework via environment variables. - -name: Test Mobile E2E Category - -on: - workflow_call: - inputs: - test-suite-name: - description: 'Name of the test suite' - required: true - type: string - platform: - description: 'Platform to test (ios or android)' - required: true - type: string - test_suite_tag: - description: 'The Cucumber tag expression to use for filtering tests' - required: true - type: string - use_prebuilt_apps: - description: 'Use pre-built apps from GitHub release instead of building' - required: false - type: boolean - default: true - -jobs: - test-e2e-mobile: - name: ${{ inputs.test-suite-name }} - runs-on: ${{ inputs.platform == 'ios' && 'macos-latest-xlarge' || 'large-mm-test' }} - #runs-on: ${{ inputs.platform == 'ios' && 'macos-latest' || 'ubuntu-latest' }} - - env: - METAMASK_ENVIRONMENT: 'local' - METAMASK_BUILD_TYPE: 'main' - TEST_SUITE_TAG: ${{ inputs.test_suite_tag }} - MM_TEST_WALLET_SRP: ${{ secrets.MM_TEST_WALLET_SRP }} - 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 }} - 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 }} - SEGMENT_REGULATIONS_ENDPOINT_QA: ${{ secrets.SEGMENT_REGULATIONS_ENDPOINT_QA }} - MM_SENTRY_DSN_TEST: ${{ secrets.MM_SENTRY_DSN_TEST }} - MM_SOLANA_E2E_TEST_SRP: ${{ secrets.MM_SOLANA_E2E_TEST_SRP }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref || github.ref }} - clean: true - fetch-depth: 0 - - - name: Set up E2E environment - uses: MetaMask/github-tools/.github/actions/setup-e2e-env@e2e-env-actions - with: - platform: ${{ inputs.platform }} - setup-simulator: ${{ inputs.platform == 'ios' }} - android-avd-name: emulator - android-abi: x86_64 - configure-keystores: false - - - name: Build Detox framework cache (iOS) - if: ${{ inputs.platform == 'ios' }} - run: | - echo "Building Detox framework cache for iOS..." - yarn detox clean-framework-cache - yarn detox build-framework-cache - - #- name: Build E2E app - # if: ${{ !inputs.use_prebuilt_apps }} - # run: | - # platform="${{ inputs.platform }}" - # - # # Set environment variables for build - # export METAMASK_ENVIRONMENT='local' - # export METAMASK_BUILD_TYPE='main' - # export IS_TEST='true' - # export IGNORE_BOXLOGS_DEVELOPMENT="true" - # - # echo "Building E2E app for $platform..." - # - # if [[ "$platform" == "ios" ]]; then - # yarn test:e2e:ios:build:qa-release - # else - # yarn test:e2e:android:build:qa-release - # fi - - - name: Setup pre-built apps from GitHub release - if: ${{ inputs.use_prebuilt_apps }} - run: | - platform="${{ inputs.platform }}" - - echo "๐Ÿš€ Setting up pre-built apps for $platform..." - - # Base URL for artifacts - base_url="https://github.com/MetaMask/tmp-bitrise-migration-artifacts/releases/download/test6" - - # Create required directories - mkdir -p android/app/build/outputs/apk/qa/release/ - mkdir -p ios/build/Build/Products/Release-iphonesimulator/ - - if [[ "$platform" == "ios" ]]; then - echo "๐Ÿ“ฅ Downloading iOS artifacts..." - - # Clean up any existing lock files - find . -name "*.lock" -type f -delete 2>/dev/null || true - - # Download Release-iphonesimulator.zip - if curl -L --fail -o /tmp/Release-iphonesimulator.zip "${base_url}/Release-iphonesimulator.zip"; then - echo "โœ… Downloaded Release-iphonesimulator.zip" - echo "๐Ÿ“ฆ Extracting iOS app..." - - # Extract preserving directory structure (remove -j flag) - cd ios/build/Build/Products/Release-iphonesimulator/ - unzip -o /tmp/Release-iphonesimulator.zip "MetaMask-QA.app/*" - cd - > /dev/null - - # Set proper permissions for the app bundle - chmod -R 755 "ios/build/Build/Products/Release-iphonesimulator/MetaMask-QA.app/" 2>/dev/null || true - - else - echo "โŒ Failed to download iOS artifacts (Release-iphonesimulator.zip)" - exit 1 - fi - - # Verify iOS setup - if [[ -f "ios/build/Build/Products/Release-iphonesimulator/MetaMask-QA.app/Info.plist" ]]; then - echo "โœ… iOS app ready for E2E tests" - echo "๐Ÿ“‹ App bundle contents:" - find "ios/build/Build/Products/Release-iphonesimulator/MetaMask-QA.app/" -maxdepth 1 -exec ls -la {} \; | head -10 - else - echo "โŒ iOS app setup failed - Info.plist not found" - echo "๐Ÿ“‹ Directory contents:" - find "ios/build/Build/Products/Release-iphonesimulator/" -maxdepth 1 -exec ls -la {} \; 2>/dev/null || true - exit 1 - fi - - else - echo "๐Ÿ“ฅ Downloading Android artifacts..." - - if curl -L --fail -o /tmp/outputs.zip "${base_url}/outputs.zip"; then - echo "โœ… Downloaded outputs.zip" - echo "๐Ÿ“ฆ Extracting Android APKs..." - - # Create required directories for both main and test APKs - mkdir -p android/app/build/outputs/apk/androidTest/qa/release/ - - # Extract main APK - unzip -o -j /tmp/outputs.zip "apk/qa/release/app-qa-release.apk" -d "android/app/build/outputs/apk/qa/release/" - - # Extract test APK (androidTest) - unzip -o -j /tmp/outputs.zip "apk/androidTest/qa/release/app-qa-release-androidTest.apk" -d "android/app/build/outputs/apk/androidTest/qa/release/" - - # Verify Android setup - if [[ -f "android/app/build/outputs/apk/qa/release/app-qa-release.apk" ]]; then - echo "โœ… Android main APK ready for E2E tests" - else - echo "โŒ Android main APK setup failed" - exit 1 - fi - - if [[ -f "android/app/build/outputs/apk/androidTest/qa/release/app-qa-release-androidTest.apk" ]]; then - echo "โœ… Android test APK ready for E2E tests" - else - echo "โŒ Android test APK setup failed" - exit 1 - fi - else - echo "โŒ Failed to download Android artifacts (outputs.zip not available)" - exit 1 - fi - fi - - - name: Clean environment before tests (iOS only) - if: ${{ inputs.platform == 'ios' }} - run: | - echo "๐Ÿงน Cleaning iOS environment before E2E tests..." - - # Clean up lock files (iOS-specific issue) - find . -name "*.lock" -type f -delete 2>/dev/null || true - - # Reset iOS simulator - xcrun simctl shutdown all 2>/dev/null || true - xcrun simctl erase all 2>/dev/null || true - - # Clean any hanging processes - pkill -f "Metro\|node\|npm" 2>/dev/null || true - - echo "โœ… iOS environment cleaned" - - - name: Run E2E tests - run: | - platform="${{ inputs.platform }}" - test_suite_tag="${{ inputs.test_suite_tag }}" - - echo "๐Ÿš€ Running ${{ inputs.test-suite-name }} tests on $platform" - - # Validate required test suite tag - if [[ -z "$test_suite_tag" ]]; then - echo "โŒ Error: test_suite_tag is required for non-api-specs tests" - exit 1 - fi - - export TEST_SUITE_TAG="$test_suite_tag" - echo "Using TEST_SUITE_TAG: $TEST_SUITE_TAG" - - # Run tests (Detox/Jest handle retries internally) - echo "๐Ÿš€ Starting E2E tests..." - if [[ "$platform" == "ios" ]]; then - export BITRISE_TRIGGERED_WORKFLOW_ID="ios_workflow" - else - export BITRISE_TRIGGERED_WORKFLOW_ID="android_workflow" - fi - - ./scripts/run-e2e-tags.sh - - echo "โœ… Test execution completed" - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: ${{ inputs.test-suite-name }}-test-results - path: e2e/reports/ - retention-days: 7 - - - name: Upload screenshots - if: failure() || cancelled() - uses: actions/upload-artifact@v4 - with: - name: ${{ inputs.test-suite-name }}-screenshots - path: artifacts/ - retention-days: 7 diff --git a/.github/workflows/run-e2e-workflow.yml b/.github/workflows/run-e2e-workflow.yml new file mode 100644 index 000000000000..d964ac1dd504 --- /dev/null +++ b/.github/workflows/run-e2e-workflow.yml @@ -0,0 +1,184 @@ +# This workflow runs mobile E2E tests for a specific test category. +# It passes matrix sharding info to the test framework via environment variables. + +name: Test Mobile E2E Category + +on: + workflow_call: + inputs: + test-suite-name: + description: 'Name of the test suite' + required: true + type: string + platform: + description: 'Platform to test (ios or android)' + required: true + type: string + test_suite_tag: + description: 'The Cucumber tag expression to use for filtering tests' + required: true + type: string + + +jobs: + + test-e2e-mobile: + name: ${{ inputs.test-suite-name }} + runs-on: ${{ inputs.platform == 'ios' && 'macos-latest-xlarge' || 'gha-mmsdk-scale-set-ubuntu-22.04-amd64-large' }} + continue-on-error: true + + env: + METAMASK_ENVIRONMENT: 'qa' + METAMASK_BUILD_TYPE: 'main' + TEST_SUITE_TAG: ${{ inputs.test_suite_tag }} + GITHUB_CI: 'true' + MM_UNIFIED_SWAPS_ENABLED: 'true' + RAMP_INTERNAL_BUILD: 'true' + MM_BRIDGE_ENABLED: 'true' + BRIDGE_USE_DEV_APIS: 'true' + MM_TEST_WALLET_SRP: ${{ secrets.MM_TEST_WALLET_SRP }} + 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 }} + 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 }} + 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 }} + GOOGLE_SERVICES_B64_IOS: ${{ secrets.GOOGLE_SERVICES_B64_IOS }} + GOOGLE_SERVICES_B64_ANDROID: ${{ secrets.GOOGLE_SERVICES_B64_ANDROID }} + MM_SOLANA_E2E_TEST_SRP: ${{ secrets.MM_SOLANA_E2E_TEST_SRP }} + MM_INFURA_PROJECT_ID: ${{ secrets.MM_INFURA_PROJECT_ID }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref }} + clean: true + fetch-depth: 0 + + - name: Set up E2E environment + uses: MetaMask/github-tools/.github/actions/setup-e2e-env@self-hosted-runners-config + with: + platform: ${{ inputs.platform }} + setup-simulator: ${{ inputs.platform == 'ios' }} + android-avd-name: emulator + configure-keystores: false + + - name: Build Detox framework cache (iOS) + if: ${{ inputs.platform == 'ios' }} + run: | + echo "Building Detox framework cache for iOS..." + yarn detox clean-framework-cache + yarn detox build-framework-cache + + - name: Setup Android artifacts from build job + if: ${{ inputs.platform == 'android' }} + run: | + echo "๐Ÿ— Setting up Android artifacts from build job..." + + # Create required directories + mkdir -p android/app/build/outputs/apk/prod/release/ + + - name: Download Android build artifacts + if: ${{ inputs.platform == 'android' }} + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: Move Android artifacts to expected locations + if: ${{ inputs.platform == 'android' }} + run: | + # Move main APK + if [[ -f "artifacts/app-prod-release.apk/app-prod-release.apk" ]]; then + mkdir -p "android/app/build/outputs/apk/prod/release/" + cp "artifacts/app-prod-release.apk/app-prod-release.apk" "android/app/build/outputs/apk/prod/release/" + echo "โœ… Android main APK ready for E2E tests" + else + echo "โŒ Android main APK not found" + ls -la artifacts/ + exit 1 + fi + + # Move test APK + if [[ -f "artifacts/app-prod-release-androidTest.apk/app-prod-release-androidTest.apk" ]]; then + mkdir -p "android/app/build/outputs/apk/androidTest/prod/release/" + cp "artifacts/app-prod-release-androidTest.apk/app-prod-release-androidTest.apk" "android/app/build/outputs/apk/androidTest/prod/release/" + echo "โœ… Android test APK ready for E2E tests" + else + echo "โŒ Android test APK not found" + ls -la artifacts/ + exit 1 + fi + + - name: Clean environment before tests (iOS only) + if: ${{ inputs.platform == 'ios' }} + run: | + echo "๐Ÿงน Cleaning iOS environment before E2E tests..." + + # Clean up lock files (iOS-specific issue) + find . -name "*.lock" -type f -delete 2>/dev/null || true + + # Reset iOS simulator + xcrun simctl shutdown all 2>/dev/null || true + xcrun simctl erase all 2>/dev/null || true + + # Clean any hanging processes + pkill -f "Metro\|node\|npm" 2>/dev/null || true + + echo "โœ… iOS environment cleaned" + + - name: Run E2E tests + run: | + platform="${{ inputs.platform }}" + test_suite_tag="${{ inputs.test_suite_tag }}" + + echo "๐Ÿš€ Running ${{ inputs.test-suite-name }} tests on $platform" + + # Validate required test suite tag + if [[ -z "$test_suite_tag" ]]; then + echo "โŒ Error: test_suite_tag is required for non-api-specs tests" + exit 1 + fi + + export TEST_SUITE_TAG="$test_suite_tag" + echo "Using TEST_SUITE_TAG: $TEST_SUITE_TAG" + + # Run tests (Detox/Jest handle retries internally) + echo "๐Ÿš€ Starting E2E tests..." + if [[ "$platform" == "ios" ]]; then + export BITRISE_TRIGGERED_WORKFLOW_ID="ios_workflow" + else + export BITRISE_TRIGGERED_WORKFLOW_ID="android_workflow" + fi + + ./scripts/run-e2e-tags.sh + + echo "โœ… Test execution completed" + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.test-suite-name }}-test-results + path: e2e/reports/ + retention-days: 7 + + - name: Prepare screenshots + if: failure() + run: | + echo "Removing APKs from artifacts..." + find artifacts/ -name "*app-*" -delete 2>/dev/null || true + continue-on-error: true + + - name: Upload screenshots + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.test-suite-name }}-screenshots + path: artifacts/ + retention-days: 7 diff --git a/.github/workflows/run-e2e-smoke.yml b/.github/workflows/run-e2e.yml similarity index 74% rename from .github/workflows/run-e2e-smoke.yml rename to .github/workflows/run-e2e.yml index 487076035467..b30a3840fa10 100644 --- a/.github/workflows/run-e2e-smoke.yml +++ b/.github/workflows/run-e2e.yml @@ -7,13 +7,23 @@ on: workflow_call: workflow_dispatch: pull_request: - types: [labeled] + types: [opened, synchronize] + +permissions: + contents: write + id-token: write jobs: + # Build Android APKs once for all Android tests + build-android-apks: + name: "Build Android APKs (Shared)" + uses: ./.github/workflows/build-android-e2e.yml + secrets: inherit + smoke-confirmations-ios: name: "Confirmations Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-confirmations-ios platform: ios @@ -22,8 +32,9 @@ jobs: smoke-confirmations-android: name: "Confirmations Smoke (Android)" - if: false - uses: ./.github/workflows/run-e2e-mobile.yml + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-confirmations-android platform: android @@ -33,7 +44,7 @@ jobs: smoke-confirmations-redesigned-ios: name: "Confirmations Redesigned Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-confirmations-redesigned-ios platform: ios @@ -43,7 +54,7 @@ jobs: smoke-trade-ios: name: "Trade Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-trade-ios platform: ios @@ -52,8 +63,9 @@ jobs: smoke-trade-android: name: "Trade Smoke (Android)" - if: false - uses: ./.github/workflows/run-e2e-mobile.yml + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-trade-android platform: android @@ -63,7 +75,7 @@ jobs: smoke-wallet-platform-ios: name: "Wallet Platform Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-wallet-platform-ios platform: ios @@ -72,8 +84,9 @@ jobs: smoke-wallet-platform-android: name: "Wallet Platform Smoke (Android)" - if: false - uses: ./.github/workflows/run-e2e-mobile.yml + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-wallet-platform-android platform: android @@ -83,7 +96,7 @@ jobs: smoke-identity-ios: name: "Identity Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-identity-ios platform: ios @@ -92,8 +105,9 @@ jobs: smoke-identity-android: name: "Identity Smoke (Android)" - if: false - uses: ./.github/workflows/run-e2e-mobile.yml + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-identity-android platform: android @@ -103,7 +117,7 @@ jobs: smoke-accounts-ios: name: "Accounts Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-accounts-ios platform: ios @@ -112,8 +126,9 @@ jobs: smoke-accounts-android: name: "Accounts Smoke (Android)" - if: false - uses: ./.github/workflows/run-e2e-mobile.yml + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-accounts-android platform: android @@ -123,7 +138,7 @@ jobs: smoke-network-abstraction-ios: name: "Network Abstraction Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-network-abstraction-ios platform: ios @@ -132,8 +147,9 @@ jobs: smoke-network-abstraction-android: name: "Network Abstraction Smoke (Android)" - if: false - uses: ./.github/workflows/run-e2e-mobile.yml + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-network-abstraction-android platform: android @@ -143,7 +159,7 @@ jobs: smoke-network-expansion-ios: name: "Network Expansion Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-network-expansion-ios platform: ios @@ -152,8 +168,9 @@ jobs: smoke-network-expansion-android: name: "Network Expansion Smoke (Android)" - if: false - uses: ./.github/workflows/run-e2e-mobile.yml + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-network-expansion-android platform: android @@ -163,7 +180,7 @@ jobs: smoke-performance-ios: name: "Performance Smoke (iOS)" if: false - uses: ./.github/workflows/run-e2e-mobile.yml + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-performance-ios platform: ios @@ -172,8 +189,9 @@ jobs: smoke-performance-android: name: "Performance Smoke (Android)" - if: false - uses: ./.github/workflows/run-e2e-mobile.yml + needs: build-android-apks + if: always() && needs.build-android-apks.result != 'failure' + uses: ./.github/workflows/run-e2e-workflow.yml with: test-suite-name: smoke-performance-android platform: android diff --git a/.github/workflows/test-android-build-app.yml b/.github/workflows/test-android-build-app.yml new file mode 100644 index 000000000000..dd7749d1ddf6 --- /dev/null +++ b/.github/workflows/test-android-build-app.yml @@ -0,0 +1,178 @@ +name: Test Android Build QA App + +on: + workflow_dispatch: + inputs: + retention_days: + description: 'Number of days to retain artifacts' + required: false + default: '7' + type: string + +permissions: + contents: write + id-token: write + +jobs: + android-build: + name: Test Android Build QA App + #runs-on: gha-mm-scale-set-ubuntu-22.04-amd64-large + runs-on: gha-mmsdk-scale-set-ubuntu-22.04-amd64-xl + outputs: + artifacts-url: ${{ steps.set-artifacts-url.outputs.artifacts-url }} + apk-uploaded: ${{ steps.upload-apk.outcome == 'success' }} + aab-uploaded: ${{ steps.upload-aab.outcome == 'success' }} + sourcemap-uploaded: ${{ steps.upload-sourcemap.outcome == 'success' }} + steps: + # Get the source code from the repository + - name: Checkout repo + uses: actions/checkout@v4 + + # Install Android SDK, Node.js, and other Android development dependencies + - name: Installing Android Environment Setup + # uses: MetaMask/github-tools/.github/actions/setup-e2e-env@e2e-env-actions + uses: MetaMask/github-tools/.github/actions/setup-e2e-env@self-hosted-runners-config + with: + platform: android + setup-simulator: false + + # Cache Gradle dependencies (most important for build speed) + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + android/.gradle + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + + + + # Display Android development tools information for debugging + - name: Print Android Environment Info + run: | + echo "๐Ÿ”ง Node.js Version:" + node -v || echo "Node not found" + echo "Android Studio Version:" + /Applications/Android\ Studio.app/Contents/MacOS/studio --version || echo "Check manually via finder" + echo "๐Ÿงถ Yarn Version:" + yarn -v || echo "Yarn not found" + echo "๐Ÿ“ฆ SDK Manager Version:" + sdkmanager --version || echo "sdkmanager not found" + echo "๐Ÿ“ฑ ADB Version:" + adb version || echo "adb not found" + echo "๐Ÿ–ฅ๏ธ Emulator Version:" + emulator -version || echo "emulator not found" + echo "๐Ÿงฑ NDK Info:" + echo "NDK Dir: $ANDROID_SDK_ROOT/ndk/${NDK_VERSION:-unknown}" + if [ -n "${NDK_VERSION}" ] && [ -f "$ANDROID_SDK_ROOT/ndk/${NDK_VERSION}/source.properties" ]; then + grep "Pkg.Revision" "$ANDROID_SDK_ROOT/ndk/${NDK_VERSION}/source.properties" || echo "NDK version info not found" + else + echo "NDK not found or NDK_VERSION not set" + fi + echo "๐Ÿ”ง Checking for ndk-build:" + command -v ndk-build || echo "ndk-build not found in PATH" + echo "๐Ÿ”ง Checking for clang:" + command -v clang || echo "clang not found in PATH" + echo "๐Ÿ”ง Checking for llvm-ar:" + command -v llvm-ar || echo "llvm-ar not found in PATH" + echo "๐Ÿ“ฑ Available AVD Devices:" + avdmanager list device || echo "avdmanager not found" + echo "๐Ÿ“ฑ Available System Images:" + sdkmanager --list | grep "system-images;" || echo "No system images listed" + echo "๐ŸŸข Emulator Processes:" + pgrep -fl emulator || echo "No running emulator processes" + echo "๐Ÿ“ฑ Connected Android Devices:" + adb devices || echo "adb devices failed" + shell: bash + + + + # Run project setup and build the Android QA app (APK and AAB) + - name: Setup and Build Android App + run: | + echo "๐Ÿš€ Finishing Android Setup..." + yarn setup:github-ci --no-build-ios + echo "๐Ÿ— Building Android APP..." + export NODE_OPTIONS="--max-old-space-size=8192" + cp android/gradle.properties.github android/gradle.properties + yarn build:android:qa + shell: bash + env: + PLATFORM: android + METAMASK_ENVIRONMENT: qa + METAMASK_BUILD_TYPE: main + IS_TEST: true + IGNORE_BOXLOGS_DEVELOPMENT: true + GITHUB_CI: "true" + CI: "true" + + NODE_OPTIONS: "--max-old-space-size=8192" + + 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 }} + + # Upload the Android APK file for device installation and testing + - name: Upload Android APK Artifact + id: upload-apk + uses: actions/upload-artifact@v4 + with: + name: app-qa-release.apk + path: android/app/build/outputs/apk/qa/release/app-qa-release.apk + retention-days: ${{ inputs.retention_days }} + if-no-files-found: error + continue-on-error: true + + # Upload the Android App Bundle (AAB) for Play Store distribution + - name: Upload Android AAB Artifact + id: upload-aab + uses: actions/upload-artifact@v4 + with: + name: app-qa-release.aab + path: android/app/build/outputs/bundle/qaRelease/app-qa-release.aab + retention-days: ${{ inputs.retention_days }} + if-no-files-found: warn + continue-on-error: true + + # Upload source map file for crash debugging and error tracking + - name: Upload Android Source Map + id: upload-sourcemap + uses: actions/upload-artifact@v4 + with: + name: index.android.bundle.map + path: sourcemaps/android/index.android.bundle.map + retention-days: ${{ inputs.retention_days }} + if-no-files-found: warn + continue-on-error: true + + # Generate artifact download URL and display upload status summary + - 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 "- ๐Ÿค– APK: ${{ steps.upload-apk.outcome }}" + echo "- ๐Ÿ“ฆ AAB Bundle: ${{ steps.upload-aab.outcome }}" + echo "- ๐Ÿ“ฆ Source Map: ${{ steps.upload-sourcemap.outcome }}" + + env: + GITHUB_REPOSITORY: "${{ github.repository }}" + GITHUB_RUN_ID: "${{ github.run_id }}" diff --git a/.github/workflows/test-ios-build-app.yml b/.github/workflows/test-ios-build-app.yml new file mode 100644 index 000000000000..e770691f5944 --- /dev/null +++ b/.github/workflows/test-ios-build-app.yml @@ -0,0 +1,169 @@ +name: Test iOS Build QA App + +on: + workflow_dispatch: + inputs: + retention_days: + description: 'Number of days to retain the uploaded artifacts' + required: false + default: '7' + type: string + +permissions: + contents: write + id-token: write + +jobs: + ios-build: + name: Test iOS QA Build App + runs-on: macos-15 + outputs: + artifacts-url: ${{ steps.set-artifacts-url.outputs.artifacts-url }} + app-uploaded: ${{ steps.upload-app.outcome == 'success' }} + ipa-uploaded: ${{ steps.upload-ipa.outcome == 'success' }} + archive-uploaded: ${{ steps.upload-archive.outcome == 'success' }} + sourcemap-uploaded: ${{ steps.upload-sourcemap.outcome == 'success' }} + env: + GITHUB_CI: "true" # โœ… This ensures it's available during pod install + steps: + # Get the source code from the repository + - name: Checkout repo + uses: actions/checkout@v4 + + # Display system information for debugging purposes + - name: Detect CPU architecture + run: | + echo "Arch: $(uname -m)" + if [[ "$(uname -m)" == "x86_64" ]]; then + echo "Detected Intel runner" + else + echo "Detected Apple Silicon runner" + fi + + - name: Print system resources + run: | + echo "๐Ÿง  Memory info:" + vm_stat + echo "" + echo "๐Ÿ’ป CPU info:" + sysctl -n hw.ncpu + sysctl -n hw.memsize | awk '{ byte =$1 /1024/1024/1024; print byte " GB" }' + shell: bash + + # Install Node.js, Xcode tools, and other iOS development dependencies + - name: Installing iOS Environment Setup + uses: MetaMask/github-tools/.github/actions/setup-e2e-env@e2e-env-actions + with: + platform: ios + setup-simulator: false + + - name: Print iOS tool versions + run: | + echo "๐Ÿ”ง Node.js Version:" + node -v || echo "Node not found" + echo "๐Ÿงถ Yarn Version:" + yarn -v || echo "Yarn not found" + echo "๐Ÿ“ฆ CocoaPods Version:" + pod --version || echo "CocoaPods not found" + echo "๐Ÿ› ๏ธ Xcode Path:" + xcode-select -p || echo "Xcode not found" + echo "๐Ÿ“ฑ Booted iOS Simulators:" + xcrun simctl list | grep Booted || echo "No booted simulators found" + shell: bash + + # Run project setup and build the iOS QA app for simulator + - name: Setup iOS Environment + run: | + echo "๐Ÿš€ Finishing iOS Setup..." + yarn setup:github-ci --build-ios --no-build-android + echo "๐Ÿ— Building iOS APP..." + yarn build:ios:qa + shell: bash + env: + PLATFORM: ios + METAMASK_ENVIRONMENT: qa + METAMASK_BUILD_TYPE: main + IS_TEST: true + IGNORE_BOXLOGS_DEVELOPMENT: true + GITHUB_CI: "true" + CI: "true" + + NODE_OPTIONS: "--max_old_space_size=4096" # Increase memory limit for build, specially on GH Runners + + 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 }} + + # Upload the iOS .app file that works in simulators (no certificates needed) + - name: Upload iOS APP Artifact (Simulator) + id: upload-app + uses: actions/upload-artifact@v4 + with: + name: MetaMask-QA.app + path: ios/build/Build/Products/Release-iphonesimulator/MetaMask-QA.app + retention-days: ${{ inputs.retention_days }} + if-no-files-found: error + continue-on-error: true + + # Upload iOS .ipa file for device installation (requires certificates - may not exist) + - name: Upload iOS IPA Artifact (Device) + id: upload-ipa + uses: actions/upload-artifact@v4 + with: + name: MetaMask-QA.ipa + path: ios/build/output/MetaMask-QA.ipa + retention-days: ${{ inputs.retention_days }} + if-no-files-found: error + continue-on-error: true + + # Upload iOS .xcarchive file for debugging and re-signing (requires certificates - may not exist) + - name: Upload iOS Archive Artifact + id: upload-archive + uses: actions/upload-artifact@v4 + with: + name: MetaMask-QA.xcarchive + path: ios/build/MetaMask-QA.xcarchive + retention-days: ${{ inputs.retention_days }} + if-no-files-found: error + continue-on-error: true + + # Upload source map file for crash debugging and error tracking if exists + - name: Upload iOS Source Map + id: upload-sourcemap + uses: actions/upload-artifact@v4 + with: + name: index.js.map + path: sourcemaps/ios/index.js.map + retention-days: ${{ inputs.retention_days }} + if-no-files-found: error + continue-on-error: true + + # Generate artifact download URL and display upload status summary + - 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 "- IPA (Device): ${{ steps.upload-ipa.outcome }}" + echo "- Archive: ${{ steps.upload-archive.outcome }}" + echo "- Source Map: ${{ steps.upload-sourcemap.outcome }}" + + env: + GITHUB_REPOSITORY: "${{ github.repository }}" + GITHUB_RUN_ID: "${{ github.run_id }}" diff --git a/android/app/build.gradle b/android/app/build.gradle index 652453deafcc..2ea8aeec71bb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -304,8 +304,8 @@ android { } } - flavorDimensions "version" - productFlavors { + flavorDimensions "version" + productFlavors { qa { dimension "version" applicationIdSuffix ".qa" @@ -315,22 +315,26 @@ android { prod { dimension "version" applicationId "io.metamask" - signingConfig signingConfigs.mainProd - // Use the appropriate signing config based on environment - if (System.getenv("METAMASK_ENVIRONMENT") == 'exp') { - signingConfig signingConfigs.mainExp - } else if (System.getenv("METAMASK_ENVIRONMENT") == 'test') { - signingConfig signingConfigs.mainTest - } else if (System.getenv("METAMASK_ENVIRONMENT") == 'e2e') { - signingConfig signingConfigs.mainE2e - } else if (System.getenv("METAMASK_ENVIRONMENT") == 'prod') { - signingConfig signingConfigs.mainProd - } else if (System.getenv("METAMASK_ENVIRONMENT") == 'rc') { - signingConfig signingConfigs.mainRc - } else if (System.getenv("METAMASK_ENVIRONMENT") == 'beta') { - signingConfig signingConfigs.mainBeta - } else if (System.getenv("METAMASK_ENVIRONMENT") == 'local') { - signingConfig signingConfigs.mainDev + // Use appropriate signing config based on environment + def env = System.getenv("METAMASK_ENVIRONMENT") + def isE2E = System.getenv("E2E") + + if (env == 'exp') { + signingConfig signingConfigs.mainExp + } else if (env == 'test') { + signingConfig signingConfigs.mainTest + } else if (env == 'e2e') { + signingConfig signingConfigs.mainE2e + } else if (env == 'prod') { + signingConfig signingConfigs.mainProd + } else if (env == 'rc') { + signingConfig signingConfigs.mainRc + } else if (env == 'beta') { + signingConfig signingConfigs.mainBeta + } else if (env == 'local') { + signingConfig signingConfigs.mainDev + } else { + signingConfig signingConfigs.mainProd } } flask { @@ -348,7 +352,7 @@ android { signingConfig signingConfigs.flaskDev } } - } + } buildTypes.each { it.buildConfigField 'String', 'foxCode', "\"$System.env.MM_FOX_CODE\"" diff --git a/android/gradle.properties.github b/android/gradle.properties.github new file mode 100644 index 000000000000..4ab044e5f468 --- /dev/null +++ b/android/gradle.properties.github @@ -0,0 +1,56 @@ +# GitHub Actions-specific Gradle settings +# High-performance settings for 64GB/16CPU runners + +# JVM configuration - high-performance for GitHub Actions +org.gradle.jvmargs=-Xmx32g -XX:MaxMetaspaceSize=2g -XX:+UseG1GC -XX:G1HeapRegionSize=32m -XX:+UseStringDeduplication -XX:+OptimizeStringConcat + +# Enable all performance optimizations for GitHub Actions +org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.workers.max=12 +org.gradle.vfs.watch=true + +# CI-specific optimizations - enabled for GitHub Actions +kotlin.incremental=true +kotlin.incremental.android=true +kotlin.caching.enabled=true + +# File system optimizations +org.gradle.vfs.verbose=false +org.gradle.welcome=NEVER + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +android.enableJetifier=true + +# Enable AAPT2 PNG crunching +android.enablePngCrunchInReleaseBuilds=true + +# TODO: favour arch options here over cli options +# replace them +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=true + +# Use this property to enable or disable the Hermes JS engine. +# If set to false, you will be using JSC instead. +hermesEnabled=true + +# TODO: explain following config options +# Some of these are depreceated in RN 0.72.15 but when removed the app won't build +android.disableResourceValidation=true + +# Use legacy packaging to compress native libraries in the resulting APK. +expo.useLegacyPackaging=false diff --git a/e2e/jest.e2e.config.js b/e2e/jest.e2e.config.js index b3331991f554..ad314171de14 100644 --- a/e2e/jest.e2e.config.js +++ b/e2e/jest.e2e.config.js @@ -2,7 +2,7 @@ require('dotenv').config({ path: '.e2e.env' }); // Determine maxWorkers based on environment -let workers = process.env.CI ? 3 : 1; +let workers = process.env.GITHUB_CI ? 2 : process.env.CI ? 3 : 1; // Set maxWorkers to 1 for performance workflows if (process.env.BITRISE_TRIGGERED_WORKFLOW_ID) { diff --git a/package.json b/package.json index b25d4a9398bc..0c22f0735168 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "format": "prettier '**/*.{js,ts,tsx,json,feature}' --write", "format:check:changed": "git diff --name-only --diff-filter=ACM HEAD~1 | { grep -E '\\.(js|ts|tsx|json|feature)$' || true; } | xargs --no-run-if-empty prettier --check", "setup": "yarn clean && node scripts/setup.mjs", + "setup:github-ci": "node scripts/setup.mjs --build-on-github-ci", "setup:flask": "export METAMASK_BUILD_TYPE='flask' && yarn setup", "setup:expo": "yarn clean && node scripts/setup.mjs --no-build-ios --no-build-android", "setup:e2e": "cd wdio && yarn install", @@ -91,6 +92,7 @@ "test:api-specs": "detox reset-lock-file && detox test -c ios.sim.apiSpecs", "test:e2e:ios:main:prod": "IS_TEST='true' NODE_OPTIONS='--experimental-vm-modules' detox test -c ios.sim.main.release", "test:e2e:android:main:prod": "IS_TEST='true' NODE_OPTIONS='--experimental-vm-modules' detox test -c android.emu.release --headless --record-logs all", + "test:e2e:android:run:github:qa-release": "IS_TEST='true' NODE_OPTIONS='--experimental-vm-modules' detox test -c android.github_ci.release --headless --record-logs all", "test:e2e:ios:flask:prod": "IS_TEST='true' NODE_OPTIONS='--experimental-vm-modules' detox test -c ios.sim.flask.release", "test:e2e:android:flask:prod": "IS_TEST='true' NODE_OPTIONS='--experimental-vm-modules' detox test -c android.emu.flask.release --headless --record-logs all", "test:e2e:ios:build:main-release": "IS_TEST='true' detox build -c ios.sim.main.release", diff --git a/scripts/run-e2e-tags.sh b/scripts/run-e2e-tags.sh index a95b29eb67c9..a1459ac1ee63 100755 --- a/scripts/run-e2e-tags.sh +++ b/scripts/run-e2e-tags.sh @@ -41,8 +41,12 @@ if [[ "$BITRISE_TRIGGERED_WORKFLOW_ID" == *"ios"* ]]; then echo "Detected iOS workflow" IGNORE_BOXLOGS_DEVELOPMENT="true" \ yarn test:e2e:ios:$METAMASK_BUILD_TYPE:prod $TEST_FILES +elif [[ -n "${GITHUB_CI:-}" ]]; then + echo "Detected GitHub Actions workflow - using GitHub CI configuration" + IGNORE_BOXLOGS_DEVELOPMENT="true" \ + yarn test:e2e:android:run:github:qa-release $TEST_FILES else echo "Detected Android workflow" IGNORE_BOXLOGS_DEVELOPMENT="true" \ yarn test:e2e:android:$METAMASK_BUILD_TYPE:prod $TEST_FILES -fi \ No newline at end of file +fi diff --git a/scripts/setup.mjs b/scripts/setup.mjs index 26f02c41c8e3..5fe3a0ef9ab3 100644 --- a/scripts/setup.mjs +++ b/scripts/setup.mjs @@ -11,6 +11,8 @@ let BUILD_IOS = IS_OSX; let IS_NODE = false; let BUILD_ANDROID = true let INSTALL_PODS; +// GitHub CI pipeline flag - defaults to false +let GITHUB_CI = false; const args = process.argv.slice(2) || []; for (const arg of args) { switch (arg) { @@ -32,6 +34,9 @@ for (const arg of args) { case '--no-build-android': BUILD_ANDROID = false continue; + case '--build-on-github-ci': + GITHUB_CI = true; + continue; default: throw new Error(`Unrecognized CLI arg ${arg}`); } @@ -118,7 +123,10 @@ const buildPpomTask = { [ { title: 'Clean', - task: async () => { + task: async (_, task) => { + if (GITHUB_CI) { + return task.skip('Skipping clean in GitHub CI.'); + } await $ppom`yarn clean`; }, }, @@ -160,13 +168,19 @@ const setupIosTask = { const tasks = [ { title: 'Install bundler gem', - task: async () => { + task: async (_, task) => { + if (GITHUB_CI) { + return task.skip('Skipping bundler gem installation in GitHub CI.'); + } await $`gem install bundler -v 2.5.8`; }, }, { title: 'Install gems', - task: async () => { + task: async (_, task) => { + if (GITHUB_CI) { + return task.skip('Skipping gems installation in GitHub CI.'); + } await $`yarn gem:bundle:install`; }, },