chore: Stable sync release 7.70.0 #120311
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
| name: ci | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| merge_group: | |
| types: [checks_requested] | |
| schedule: | |
| # Run the full suite "overnight," once every hour from 2:00am UTC until 6:00am UTC. | |
| # This helps to identy the flaky and failed tests on main branch | |
| - cron: '0 2-6 * * *' | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.sha || github.ref }} | |
| cancel-in-progress: ${{ !(contains(github.ref, 'refs/heads/main') || contains(github.ref, 'refs/heads/stable')) }} | |
| jobs: | |
| check-diff: | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-node@v3 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb #v1 | |
| with: | |
| ruby-version: '3.2.9' | |
| env: | |
| BUNDLE_GEMFILE: ios/Gemfile | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - 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: Clean state and following up dependencies installation with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn setup:github-ci | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| dedupe: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-node@v3 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - name: Deduplicate dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn deduplicate | |
| - name: Print error if duplicates found | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Duplicate dependencies detected; run 'yarn deduplicate' to remove them" | |
| exit 1 | |
| fi | |
| git-safe-dependencies: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-node@v3 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - name: Run @lavamoat/git-safe-dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn git-safe-dependencies | |
| scripts: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| scripts: | |
| - lint | |
| - lint:tsc | |
| - format:check | |
| - audit:ci | |
| - test:depcheck | |
| - test:tgz-check | |
| steps: | |
| - uses: actions/checkout@v3 | |
| with: | |
| fetch-depth: 2 | |
| - uses: actions/setup-node@v3 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - run: yarn ${{ matrix['scripts'] }} | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| js-bundle-size-check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --no-build-android | |
| - name: Generate iOS bundle | |
| run: yarn gen-bundle:ios | |
| env: | |
| NODE_OPTIONS: --max_old_space_size=12288 | |
| - name: Check bundle size | |
| run: ./scripts/js-bundle-stats.sh ios/main.jsbundle 53 | |
| - name: Upload iOS bundle | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ios-bundle | |
| path: ios/main.jsbundle | |
| ship-js-bundle-size-check: | |
| runs-on: ubuntu-latest | |
| needs: [js-bundle-size-check] | |
| if: ${{ github.ref == 'refs/heads/main' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download iOS bundle | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ios-bundle | |
| path: ios/main.jsbundle | |
| - name: Push bundle size to mobile_bundlesize_stats repo | |
| run: ./scripts/push-bundle-size.sh | |
| env: | |
| GITHUB_ACTOR: metamaskbot | |
| GITHUB_TOKEN: ${{ secrets.MOBILE_BUNDLESIZE_TOKEN }} | |
| check-workflows: | |
| name: Check workflows | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download actionlint | |
| id: download-actionlint | |
| 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 -config-file .github/actionlint.yaml | |
| shell: bash | |
| unit-tests: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-node@v3 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - name: Prepare results directory | |
| run: mkdir -p tests/results | |
| # The "10" in this command is the total number of shards. It must be kept | |
| # in sync with the length of matrix.shard | |
| - run: yarn test:unit --shard=${{ matrix.shard }}/10 --forceExit --silent --coverageReporters=json --json --outputFile=tests/results/unit-test-results-${{ matrix.shard }}.json | |
| env: | |
| NODE_OPTIONS: --max_old_space_size=20480 | |
| - name: Rename coverage report and extract test count for this shard | |
| shell: bash | |
| run: | | |
| mv ./tests/coverage/coverage-final.json ./tests/coverage/coverage-unit-${{ matrix.shard }}.json | |
| cp tests/results/unit-test-results-${{ matrix.shard }}.json ./tests/coverage/jest-results.json | |
| count=$(jq '(.numPassedTests // 0) + (.numFailedTests // 0)' tests/results/unit-test-results-${{ matrix.shard }}.json) | |
| echo "{\"count\": $count}" > ./tests/coverage/count.json | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-unit-${{ matrix.shard }} | |
| path: ./tests/coverage/ | |
| if-no-files-found: error | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| merge-unit-and-component-view-tests: | |
| runs-on: ubuntu-latest | |
| needs: [unit-tests, component-view-tests] | |
| if: ${{ !cancelled() && github.event_name != 'merge_group' }} | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-node@v3 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: coverage-* | |
| path: tests/coverage/ | |
| - name: Aggregate test counts and gather coverage reports | |
| shell: bash | |
| run: | | |
| unit_total=0 | |
| for file in ./tests/coverage/coverage-unit-*/count.json; do | |
| [ -f "$file" ] || continue | |
| count=$(jq '.count // 0' "$file") | |
| unit_total=$((unit_total + count)) | |
| done | |
| echo "{\"unit_test_number\": $unit_total}" > unit-test-stats.json | |
| echo "Unit test count: $unit_total" | |
| cv_total=0 | |
| for file in ./tests/coverage/coverage-cv-*/count.json; do | |
| [ -f "$file" ] || continue | |
| count=$(jq '.count // 0' "$file") | |
| cv_total=$((cv_total + count)) | |
| done | |
| echo "{\"component_view_test_number\": $cv_total}" > cv-test-stats.json | |
| echo "CV test count: $cv_total" | |
| mkdir -p tests/coverage-cv-merged | |
| for file in ./tests/coverage/coverage-cv-*/coverage-cv-*.json; do | |
| [ -f "$file" ] && cp "$file" ./tests/coverage-cv-merged/ | |
| done | |
| find ./tests/coverage/coverage-* -name 'coverage-*.json' -exec mv {} ./tests/coverage/ \; | |
| - run: yarn test:merge-coverage | |
| - run: yarn test:validate-coverage | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: lcov.info | |
| path: ./tests/merged-coverage/lcov.info | |
| if-no-files-found: error | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: cv-test-stats | |
| path: ./cv-test-stats.json | |
| if-no-files-found: error | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: unit-test-stats | |
| path: ./unit-test-stats.json | |
| if-no-files-found: error | |
| - name: Generate CV test HTML coverage report | |
| run: yarn nyc report --temp-dir ./tests/coverage-cv-merged --report-dir ./tests/coverage-cv-lcov --reporter html | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: cv-test-coverage-html | |
| path: ./tests/coverage-cv-lcov/ | |
| if-no-files-found: error | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| component-view-tests: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| shard: [1, 2] | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: actions/setup-node@v3 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: yarn | |
| - name: Install Yarn dependencies with retry | |
| uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 | |
| with: | |
| timeout_minutes: 10 | |
| max_attempts: 3 | |
| retry_wait_seconds: 30 | |
| command: yarn install --immutable | |
| - name: Clean state and following up dependencies installation | |
| run: yarn setup:github-ci --node | |
| - name: Prepare results directory | |
| run: mkdir -p tests/results | |
| - run: | | |
| yarn test:view:ci \ | |
| --shard=${{ matrix.shard }}/2 \ | |
| --json \ | |
| --outputFile=tests/results/cv-test-results-${{ matrix.shard }}.json | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=20480 | |
| - name: Rename coverage report and extract test count for this shard | |
| shell: bash | |
| run: | | |
| mv ./tests/coverage/coverage-final.json ./tests/coverage/coverage-cv-${{ matrix.shard }}.json | |
| cp tests/results/cv-test-results-${{ matrix.shard }}.json ./tests/coverage/jest-results.json | |
| count=$(jq '(.numPassedTests // 0) + (.numFailedTests // 0)' tests/results/cv-test-results-${{ matrix.shard }}.json) | |
| echo "{\"count\": $count}" > ./tests/coverage/count.json | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-cv-${{ matrix.shard }} | |
| path: ./tests/coverage/ | |
| if-no-files-found: error | |
| needs_e2e_build: | |
| uses: ./.github/workflows/needs-e2e-build.yml | |
| smart-e2e-selection: | |
| name: 'Smart E2E Selection' | |
| runs-on: ubuntu-latest | |
| if: ${{ !github.event.pull_request.head.repo.fork }} | |
| continue-on-error: true | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| outputs: | |
| ai_e2e_test_tags: ${{ steps.e2e-selection.outputs.ai_e2e_test_tags }} | |
| ai_confidence: ${{ steps.e2e-selection.outputs.ai_confidence }} | |
| force_run: ${{ steps.e2e-selection.outputs.force_run }} | |
| steps: | |
| - name: Checkout for action definition | |
| uses: actions/checkout@v4 | |
| with: | |
| sparse-checkout: | | |
| .github/actions/smart-e2e-selection | |
| sparse-checkout-cone-mode: false | |
| fetch-depth: 1 | |
| - name: Run Smart E2E Selection | |
| id: e2e-selection | |
| uses: ./.github/actions/smart-e2e-selection | |
| with: | |
| event-name: ${{ github.event_name }} | |
| claude-api-key: ${{ secrets.E2E_CLAUDE_API_KEY }} | |
| openai-api-key: ${{ secrets.E2E_OPENAI_API_KEY }} | |
| google-api-key: ${{ secrets.E2E_GEMINI_API_KEY }} | |
| github-token: ${{ github.token }} | |
| pr-number: ${{ github.event.pull_request.number }} | |
| repository: ${{ github.repository }} | |
| post-comment: 'true' | |
| base-ref: ${{ github.event.pull_request.base.ref }} | |
| # Main E2E tests | |
| build-android-apks: | |
| name: 'Build Android APKs' | |
| if: >- | |
| ${{ | |
| github.event_name != 'merge_group' && | |
| !github.event.pull_request.head.repo.fork && | |
| (needs.needs_e2e_build.outputs.android_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') && | |
| !(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]') | |
| }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [needs_e2e_build, smart-e2e-selection] | |
| uses: ./.github/workflows/build-android-e2e.yml | |
| with: | |
| build_type: 'main' | |
| metamask_environment: 'e2e' | |
| keystore_target: 'qa' | |
| secrets: inherit | |
| e2e-smoke-tests-android: | |
| name: 'Android E2E Smoke Tests' | |
| if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.android_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [needs_e2e_build, build-android-apks, smart-e2e-selection] | |
| uses: ./.github/workflows/run-e2e-smoke-tests-android.yml | |
| with: | |
| changed_files: ${{ needs.needs_e2e_build.outputs.changed_files }} | |
| selected_tags: >- | |
| ${{ | |
| (fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) || | |
| '["ALL"]' | |
| }} | |
| secrets: inherit | |
| build-ios-apps: | |
| name: 'Build iOS Apps' | |
| if: >- | |
| ${{ | |
| github.event_name != 'merge_group' && | |
| !github.event.pull_request.head.repo.fork && | |
| (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') && | |
| !(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]') | |
| }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [needs_e2e_build, smart-e2e-selection] | |
| uses: ./.github/workflows/build-ios-e2e.yml | |
| secrets: inherit | |
| ios-tests-ready: | |
| name: 'iOS Tests Ready' | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event_name != 'merge_group' && (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }} | |
| needs: [needs_e2e_build, build-ios-apps, smart-e2e-selection] | |
| steps: | |
| - name: iOS build complete | |
| run: echo "Dummy step to better visualize the Android and iOS E2E tests in Github workflow graph" | |
| e2e-smoke-tests-ios: | |
| name: 'iOS E2E Smoke Tests' | |
| if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [needs_e2e_build, ios-tests-ready, smart-e2e-selection] | |
| uses: ./.github/workflows/run-e2e-smoke-tests-ios.yml | |
| with: | |
| changed_files: ${{ needs.needs_e2e_build.outputs.changed_files }} | |
| selected_tags: >- | |
| ${{ | |
| (fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) || | |
| '["ALL"]' | |
| }} | |
| secrets: inherit | |
| # Flask E2E tests | |
| e2e-smoke-tests-android-flask: | |
| name: 'Android Flask E2E Smoke Tests' | |
| if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.android_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [needs_e2e_build, build-android-apks, smart-e2e-selection] | |
| uses: ./.github/workflows/run-e2e-smoke-tests-android-flask.yml | |
| with: | |
| changed_files: ${{ needs.needs_e2e_build.outputs.changed_files }} | |
| selected_tags: >- | |
| ${{ | |
| (fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) || | |
| '["ALL"]' | |
| }} | |
| secrets: inherit | |
| e2e-smoke-tests-ios-flask: | |
| name: 'iOS Flask E2E Smoke Tests' | |
| if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [needs_e2e_build, ios-tests-ready, smart-e2e-selection] | |
| uses: ./.github/workflows/run-e2e-smoke-tests-ios-flask.yml | |
| with: | |
| changed_files: ${{ needs.needs_e2e_build.outputs.changed_files }} | |
| selected_tags: >- | |
| ${{ | |
| (fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) || | |
| '["ALL"]' | |
| }} | |
| secrets: inherit | |
| # Fixture validation — ensures committed E2E fixtures match the live app state schema | |
| validate-e2e-fixtures: | |
| name: 'Validate E2E Fixtures' | |
| if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| needs: [needs_e2e_build, ios-tests-ready, smart-e2e-selection] | |
| uses: ./.github/workflows/run-e2e-workflow.yml | |
| with: | |
| test-suite-name: validate-e2e-fixtures | |
| platform: ios | |
| test_suite_tag: 'FixtureValidation' | |
| split_number: 1 | |
| total_splits: 1 | |
| build_type: 'main' | |
| metamask_environment: 'qa' | |
| secrets: inherit | |
| report-fixture-validation: | |
| name: 'Report Fixture Validation' | |
| runs-on: ubuntu-latest | |
| if: ${{ !cancelled() && needs.validate-e2e-fixtures.result != 'skipped' }} | |
| needs: [validate-e2e-fixtures] | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download fixture validation results | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: test-e2e-main-validate-e2e-fixtures-junit-results | |
| path: fixture-results/ | |
| - name: Report results | |
| env: | |
| RESULTS_PATH: fixture-results | |
| VALIDATION_RESULT: ${{ needs.validate-e2e-fixtures.result }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: node .github/scripts/e2e-report-fixture-validation.mjs | |
| sonar-cloud: | |
| runs-on: ubuntu-latest | |
| needs: merge-unit-and-component-view-tests | |
| if: ${{ !cancelled() && github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork }} | |
| steps: | |
| - uses: actions/checkout@v3 | |
| with: | |
| fetch-depth: 0 # SonarCloud needs a full checkout to perform necessary analysis | |
| - uses: actions/setup-node@v3 | |
| with: | |
| node-version-file: '.nvmrc' | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: lcov.info | |
| path: coverage/ | |
| - name: Upload coverage reports to Codecov | |
| if: ${{ always() }} | |
| continue-on-error: true | |
| uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 | |
| - name: SonarCloud Scan | |
| if: ${{ env.HAVE_SONAR_TOKEN == 'true' }} | |
| continue-on-error: true | |
| # This is SonarSource/sonarqube-scan-action@v7.0.0 | |
| uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 | |
| env: | |
| HAVE_SONAR_TOKEN: ${{ secrets.SONAR_TOKEN != '' }} | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| - name: Require clean working directory | |
| shell: bash | |
| run: | | |
| if ! git diff --exit-code; then | |
| echo "Working tree dirty at end of job" | |
| exit 1 | |
| else | |
| echo "No changes detected" | |
| fi | |
| # Revert git update-index --no-assume-unchanged for each entry | |
| echo "Reverting assume unchanged for the following paths:" | |
| for path in "${EXCLUDES[@]}"; do | |
| echo "$path" | |
| git update-index --no-assume-unchanged "$path" | |
| done | |
| sonar-cloud-quality-gate-status: | |
| runs-on: ubuntu-latest | |
| needs: sonar-cloud | |
| if: ${{ !cancelled() && github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v3 | |
| - name: SonarCloud Quality Gate Status | |
| id: sonar-status | |
| env: | |
| REPO: ${{ github.repository }} | |
| ISSUE_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Skip step if event is not a PR | |
| if [[ "${{ github.event_name }}" != "pull_request" ]]; then | |
| echo "This job only runs for pull requests." | |
| exit 0 | |
| fi | |
| # Bypass step if skip-sonar-cloud label is found | |
| LABEL=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/$REPO/issues/$ISSUE_NUMBER/labels" | \ | |
| jq -r '.[] | select(.name=="skip-sonar-cloud") | .name') | |
| if [[ "$LABEL" == "skip-sonar-cloud" ]]; then | |
| echo "skip-sonar-cloud label found. Skipping SonarCloud Quality Gate check." | |
| else | |
| sleep 30 | |
| PROJECT_KEY="metamask-mobile" | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| SONAR_TOKEN="${{ secrets.SONAR_TOKEN }}" | |
| if [ -z "$PR_NUMBER" ]; then | |
| echo "No pull request number found. Failing the check." | |
| exit 1 | |
| fi | |
| RESPONSE=$(curl -s -u "$SONAR_TOKEN:" \ | |
| "https://sonarcloud.io/api/qualitygates/project_status?projectKey=$PROJECT_KEY&pullRequest=$PR_NUMBER") | |
| echo "SonarCloud API Response: $RESPONSE" | |
| STATUS=$(echo "$RESPONSE" | jq -r '.projectStatus.status') | |
| if [[ "$STATUS" == "ERROR" ]]; then | |
| echo "Quality Gate failed." | |
| exit 1 | |
| elif [[ "$STATUS" == "OK" ]]; then | |
| echo "Quality Gate passed." | |
| else | |
| echo "Could not determine Quality Gate status." | |
| exit 1 | |
| fi | |
| fi | |
| all-jobs-pass: | |
| name: All jobs pass | |
| runs-on: ubuntu-latest | |
| if: ${{ !cancelled() }} | |
| needs: | |
| [ | |
| check-diff, | |
| dedupe, | |
| scripts, | |
| unit-tests, | |
| component-view-tests, | |
| check-workflows, | |
| js-bundle-size-check, | |
| sonar-cloud-quality-gate-status, | |
| ] | |
| outputs: | |
| ALL_JOBS_PASSED: ${{ steps.jobs-passed-status.outputs.ALL_JOBS_PASSED }} | |
| steps: | |
| - name: Set jobs passed status | |
| id: jobs-passed-status | |
| env: | |
| NEEDS_CONTEXT: ${{ toJSON(needs) }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| IS_FORK: ${{ github.event.pull_request.head.repo.fork }} | |
| run: | | |
| # Check results of all required jobs dynamically | |
| # On merge_group events, "skipped" is acceptable (some jobs intentionally skip) | |
| # On fork PRs, "skipped" is acceptable (secret-dependent jobs are intentionally skipped) | |
| # On other events (push to main), all jobs must succeed | |
| FAILED="false" | |
| while read -r job_name result; do | |
| if [[ "$result" == "failure" ]] || [[ "$result" == "cancelled" ]]; then | |
| echo "::error::Job '$job_name' failed with result: $result" | |
| FAILED="true" | |
| elif [[ "$result" == "skipped" ]]; then | |
| if [[ "$EVENT_NAME" == "merge_group" ]] || [[ "$IS_FORK" == "true" ]]; then | |
| echo "Job '$job_name' was skipped (OK for merge_group events and fork PRs)" | |
| else | |
| echo "::error::Job '$job_name' was unexpectedly skipped on $EVENT_NAME event" | |
| FAILED="true" | |
| fi | |
| else | |
| echo "Job '$job_name' passed" | |
| fi | |
| done < <(echo "$NEEDS_CONTEXT" | jq -r 'to_entries[] | "\(.key) \(.value.result)"') | |
| if [[ "$FAILED" == "true" ]]; then | |
| echo "Some required jobs failed" | |
| exit 1 | |
| fi | |
| echo "ALL_JOBS_PASSED=true" >> "$GITHUB_OUTPUT" | |
| check-all-jobs-pass: | |
| name: Check all jobs pass | |
| if: ${{ !cancelled() }} | |
| runs-on: ubuntu-latest | |
| needs: | |
| - all-jobs-pass | |
| - needs_e2e_build | |
| - e2e-smoke-tests-android | |
| - e2e-smoke-tests-ios | |
| - e2e-smoke-tests-android-flask | |
| - e2e-smoke-tests-ios-flask | |
| steps: | |
| - run: | | |
| # Check if all non-E2E jobs passed | |
| if [[ "${{ needs.all-jobs-pass.outputs.ALL_JOBS_PASSED }}" != "true" ]]; then | |
| echo "Non-E2E jobs failed" | |
| exit 1 | |
| fi | |
| # Check E2E jobs only if they should have run | |
| if [[ "${{ needs.needs_e2e_build.outputs.builds }}" == "true" ]]; then | |
| # Accept both 'success' and 'skipped' as valid results | |
| # 'skipped' occurs during merge_group events or when jobs are intentionally skipped | |
| # Only fail on 'failure' or 'cancelled' | |
| ANDROID_RESULT="${{ needs.e2e-smoke-tests-android.result }}" | |
| if [[ "$ANDROID_RESULT" == "failure" ]] || [[ "$ANDROID_RESULT" == "cancelled" ]]; then | |
| echo "Android E2E tests failed (result: $ANDROID_RESULT)" | |
| exit 1 | |
| fi | |
| IOS_RESULT="${{ needs.e2e-smoke-tests-ios.result }}" | |
| if [[ "$IOS_RESULT" == "failure" ]] || [[ "$IOS_RESULT" == "cancelled" ]]; then | |
| echo "iOS E2E tests failed (result: $IOS_RESULT)" | |
| exit 1 | |
| fi | |
| ANDROID_FLASK_RESULT="${{ needs.e2e-smoke-tests-android-flask.result }}" | |
| if [[ "$ANDROID_FLASK_RESULT" == "failure" ]] || [[ "$ANDROID_FLASK_RESULT" == "cancelled" ]]; then | |
| echo "Android Flask E2E tests failed (result: $ANDROID_FLASK_RESULT)" | |
| exit 1 | |
| fi | |
| IOS_FLASK_RESULT="${{ needs.e2e-smoke-tests-ios-flask.result }}" | |
| if [[ "$IOS_FLASK_RESULT" == "failure" ]] || [[ "$IOS_FLASK_RESULT" == "cancelled" ]]; then | |
| echo "iOS Flask E2E tests failed (result: $IOS_FLASK_RESULT)" | |
| exit 1 | |
| fi | |
| fi | |
| echo "All required jobs passed" | |
| log-merge-group-failure: | |
| name: Log merge group failure | |
| runs-on: ubuntu-latest | |
| # Only run this job if the merge group event fails, skip on forks | |
| if: ${{ github.event_name == 'merge_group' && failure() }} | |
| needs: | |
| - check-all-jobs-pass | |
| steps: | |
| - name: Log merge group failure to Google Sheets | |
| uses: MetaMask/github-tools/.github/actions/log-merge-group-failure@v1 | |
| with: | |
| google-application-credentials: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} | |
| google-service-account: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }} | |
| spreadsheet-id: ${{ secrets.GOOGLE_MERGE_QUEUE_SPREADSHEET_ID }} | |
| sheet-name: ${{ secrets.GOOGLE_MERGE_QUEUE_SHEET_NAME }} |