Skip to content

test: fix permissions #2

test: fix permissions

test: fix permissions #2

# Nightly System Tests
#
# Runs the 18 performance test specs as functional system tests (no quality gates,
# no Sentry, no performance metrics). Validates that core user flows (login,
# onboarding, swaps, etc.) work on real devices via BrowserStack.
#
# Triggers:
# - Daily at 5 AM UTC (1 hour after nightly build starts)
# - Manually via workflow_dispatch (optionally with pre-built BrowserStack URLs)
name: Nightly System Tests
on:
# TODO: Remove push trigger before merging to main
push:
branches:
- run-system-tests
schedule:
- cron: '0 5 * * *' # 5 AM UTC daily
workflow_dispatch:
inputs:
browserstack_app_url_android:
description: 'BrowserStack Android App URL for login tests — with-SRP build (bs://...)'
required: false
type: string
browserstack_app_url_ios:
description: 'BrowserStack iOS App URL for login tests — with-SRP build (bs://...)'
required: false
type: string
browserstack_clean_app_url_android:
description: 'BrowserStack Android Clean App URL for onboarding tests (bs://...)'
required: false
type: string
browserstack_clean_app_url_ios:
description: 'BrowserStack iOS Clean App URL for onboarding tests (bs://...)'
required: false
type: string
permissions:
contents: write
id-token: write
concurrency:
group: system-tests-${{ github.ref }}
cancel-in-progress: true
jobs:
build-with-srp:
name: Build with-SRP (login tests)
if: >-
github.event_name != 'workflow_dispatch'
|| (!inputs.browserstack_app_url_android
&& !inputs.browserstack_app_url_ios
&& !inputs.browserstack_clean_app_url_android
&& !inputs.browserstack_clean_app_url_ios)
uses: ./.github/workflows/build.yml
with:
build_name: main-rc-with-srp
platform: both
skip_version_bump: true
source_branch: main
secrets: inherit
build-clean:
name: Build clean RC (onboarding tests)
if: >-
github.event_name != 'workflow_dispatch'
|| (!inputs.browserstack_app_url_android
&& !inputs.browserstack_app_url_ios
&& !inputs.browserstack_clean_app_url_android
&& !inputs.browserstack_clean_app_url_ios)
uses: ./.github/workflows/build.yml
with:
build_name: main-rc
platform: both
skip_version_bump: true
source_branch: main
secrets: inherit
upload-to-browserstack:
name: Upload builds to BrowserStack
needs: [build-with-srp, build-clean]
if: always() && !cancelled()
runs-on: ubuntu-latest
outputs:
android-login-url: ${{ steps.resolve.outputs.android-login-url }}
ios-login-url: ${{ steps.resolve.outputs.ios-login-url }}
android-onboarding-url: ${{ steps.resolve.outputs.android-onboarding-url }}
ios-onboarding-url: ${{ steps.resolve.outputs.ios-onboarding-url }}
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
steps:
- name: Download Android APK (with-SRP)
if: needs.build-with-srp.result == 'success'
uses: actions/download-artifact@v8
with:
name: android-apk-main-rc-with-srp
path: artifacts/android-with-srp
- name: Download iOS IPA (with-SRP)
if: needs.build-with-srp.result == 'success'
uses: actions/download-artifact@v8
with:
name: ios-ipa-main-rc-with-srp
path: artifacts/ios-with-srp
- name: Download Android APK (clean)
if: needs.build-clean.result == 'success'
uses: actions/download-artifact@v8
with:
name: android-apk-main-rc
path: artifacts/android-clean
- name: Download iOS IPA (clean)
if: needs.build-clean.result == 'success'
uses: actions/download-artifact@v8
with:
name: ios-ipa-main-rc
path: artifacts/ios-clean
- name: Upload artifacts to BrowserStack and resolve URLs
id: resolve
run: |
# Helper: resolve a single URL from manual input or build artifact.
# Outputs the URL to GITHUB_OUTPUT, or empty string if unavailable.
# Upload failures are logged but do not abort other resolutions.
resolve_url() {
local output_key="$1"
local manual_url="$2"
local artifact_dir="$3"
local extension="$4"
local custom_id="$5"
if [[ -n "$manual_url" ]]; then
echo "${output_key}=${manual_url}" >> "$GITHUB_OUTPUT"
echo "Using manual URL for ${output_key}: ${manual_url}"
return 0
fi
if [[ ! -d "$artifact_dir" ]]; then
echo "No build artifact for ${output_key} and no manual URL — test will be skipped"
echo "${output_key}=" >> "$GITHUB_OUTPUT"
return 0
fi
local file
file=$(find "$artifact_dir" -name "*.${extension}" | head -1)
if [[ -z "$file" ]]; then
echo "::warning::No .${extension} found in ${artifact_dir} — ${output_key} will be empty"
echo "${output_key}=" >> "$GITHUB_OUTPUT"
return 0
fi
echo "Uploading ${file} to BrowserStack..."
local response
response=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "file=@${file}" \
-F "custom_id=${custom_id}")
local app_url
app_url=$(echo "$response" | jq -r '.app_url')
if [[ -z "$app_url" || "$app_url" == "null" ]]; then
echo "::error::BrowserStack upload failed for ${output_key} (${file}): ${response}"
echo "${output_key}=" >> "$GITHUB_OUTPUT"
return 0
fi
echo "${output_key}=${app_url}" >> "$GITHUB_OUTPUT"
echo "${output_key}: ${app_url}"
}
resolve_url "android-login-url" \
"${{ inputs.browserstack_app_url_android }}" \
"artifacts/android-with-srp" "apk" \
"system-test-android-login-${{ github.run_id }}"
resolve_url "ios-login-url" \
"${{ inputs.browserstack_app_url_ios }}" \
"artifacts/ios-with-srp" "ipa" \
"system-test-ios-login-${{ github.run_id }}"
resolve_url "android-onboarding-url" \
"${{ inputs.browserstack_clean_app_url_android }}" \
"artifacts/android-clean" "apk" \
"system-test-android-onboarding-${{ github.run_id }}"
resolve_url "ios-onboarding-url" \
"${{ inputs.browserstack_clean_app_url_ios }}" \
"artifacts/ios-clean" "ipa" \
"system-test-ios-onboarding-${{ github.run_id }}"
echo "URL resolution complete"
run-android-login-tests:
name: Android Login Tests
needs: [upload-to-browserstack]
if: >-
always() && !cancelled()
&& needs.upload-to-browserstack.outputs.android-login-url != ''
runs-on: ubuntu-latest
env:
BROWSERSTACK_ANDROID_APP_URL: ${{ needs.upload-to-browserstack.outputs.android-login-url }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Restore node_modules cache
uses: actions/cache@v6
with:
path: |
node_modules
.yarn/cache
.yarn/install-state.gz
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: yarn --immutable
- name: Restore .metamask folder
id: restore-metamask
uses: actions/cache@v6
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
run: yarn setup:github-ci
- name: BrowserStack Env Setup
uses: browserstack/github-actions/setup-env@4478e16186f38e5be07721931642e65a028713c3
with:
username: ${{ secrets.BROWSERSTACK_USERNAME }}
access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
project-name: ${{ github.repository }}
- name: Compute BrowserStack Local Identifier
id: bs-local-id
run: echo "value=${{ github.run_id }}-android-login" >> "$GITHUB_OUTPUT"
- name: Setup BrowserStack Local
uses: browserstack/github-actions/setup-local@4478e16186f38e5be07721931642e65a028713c3
with:
local-testing: start
local-identifier: ${{ steps.bs-local-id.outputs.value }}
local-args: '--force-local --verbose'
- name: Wait for BrowserStack Local
run: sleep 10
- name: Set test environment
run: |
{
echo "BROWSERSTACK_LOCAL=true"
echo "BROWSERSTACK_LOCAL_IDENTIFIER=${{ steps.bs-local-id.outputs.value }}"
echo "BROWSERSTACK_BUILD_NAME=system-test-android-login-${{ github.run_id }}"
echo "MM_TEST_ACCOUNT_SRP=${{ secrets.MM_TEST_ACCOUNT_SRP }}"
echo "TEST_SRP_1=${{ secrets.TEST_SRP_1 }}"
echo "TEST_SRP_2=${{ secrets.TEST_SRP_2 }}"
echo "TEST_SRP_3=${{ secrets.TEST_SRP_3 }}"
echo "E2E_PASSWORD=${{ secrets.E2E_PASSWORD }}"
} >> "$GITHUB_ENV"
- name: Run Android login system tests
run: yarn run-system-tests:android-login
- name: Upload test results
uses: actions/upload-artifact@v8
if: always()
with:
name: system-test-report-android-login
path: tests/test-reports/system-test-report/
if-no-files-found: ignore
retention-days: 7
run-android-onboarding-tests:
name: Android Onboarding Tests
needs: [upload-to-browserstack]
if: >-
always() && !cancelled()
&& needs.upload-to-browserstack.outputs.android-onboarding-url != ''
runs-on: ubuntu-latest
env:
BROWSERSTACK_ANDROID_CLEAN_APP_URL: ${{ needs.upload-to-browserstack.outputs.android-onboarding-url }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Restore node_modules cache
uses: actions/cache@v6
with:
path: |
node_modules
.yarn/cache
.yarn/install-state.gz
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: yarn --immutable
- name: Restore .metamask folder
id: restore-metamask
uses: actions/cache@v6
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
run: yarn setup:github-ci
- name: BrowserStack Env Setup
uses: browserstack/github-actions/setup-env@4478e16186f38e5be07721931642e65a028713c3
with:
username: ${{ secrets.BROWSERSTACK_USERNAME }}
access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
project-name: ${{ github.repository }}
- name: Compute BrowserStack Local Identifier
id: bs-local-id
run: echo "value=${{ github.run_id }}-android-onboarding" >> "$GITHUB_OUTPUT"
- name: Setup BrowserStack Local
uses: browserstack/github-actions/setup-local@4478e16186f38e5be07721931642e65a028713c3
with:
local-testing: start
local-identifier: ${{ steps.bs-local-id.outputs.value }}
local-args: '--force-local --verbose'
- name: Wait for BrowserStack Local
run: sleep 10
- name: Set test environment
run: |
{
echo "BROWSERSTACK_LOCAL=true"
echo "BROWSERSTACK_LOCAL_IDENTIFIER=${{ steps.bs-local-id.outputs.value }}"
echo "BROWSERSTACK_BUILD_NAME=system-test-android-onboarding-${{ github.run_id }}"
echo "MM_TEST_ACCOUNT_SRP=${{ secrets.MM_TEST_ACCOUNT_SRP }}"
echo "TEST_SRP_1=${{ secrets.TEST_SRP_1 }}"
echo "TEST_SRP_2=${{ secrets.TEST_SRP_2 }}"
echo "TEST_SRP_3=${{ secrets.TEST_SRP_3 }}"
echo "E2E_PASSWORD=${{ secrets.E2E_PASSWORD }}"
} >> "$GITHUB_ENV"
- name: Run Android onboarding system tests
run: yarn run-system-tests:android-onboarding
- name: Upload test results
uses: actions/upload-artifact@v8
if: always()
with:
name: system-test-report-android-onboarding
path: tests/test-reports/system-test-report/
if-no-files-found: ignore
retention-days: 7
run-ios-login-tests:
name: iOS Login Tests
needs: [upload-to-browserstack]
if: >-
always() && !cancelled()
&& needs.upload-to-browserstack.outputs.ios-login-url != ''
runs-on: ubuntu-latest
env:
BROWSERSTACK_IOS_APP_URL: ${{ needs.upload-to-browserstack.outputs.ios-login-url }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Restore node_modules cache
uses: actions/cache@v6
with:
path: |
node_modules
.yarn/cache
.yarn/install-state.gz
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: yarn --immutable
- name: Restore .metamask folder
id: restore-metamask
uses: actions/cache@v6
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
run: yarn setup:github-ci
- name: BrowserStack Env Setup
uses: browserstack/github-actions/setup-env@4478e16186f38e5be07721931642e65a028713c3
with:
username: ${{ secrets.BROWSERSTACK_USERNAME }}
access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
project-name: ${{ github.repository }}
- name: Compute BrowserStack Local Identifier
id: bs-local-id
run: echo "value=${{ github.run_id }}-ios-login" >> "$GITHUB_OUTPUT"
- name: Setup BrowserStack Local
uses: browserstack/github-actions/setup-local@4478e16186f38e5be07721931642e65a028713c3
with:
local-testing: start
local-identifier: ${{ steps.bs-local-id.outputs.value }}
local-args: '--force-local --verbose'
- name: Wait for BrowserStack Local
run: sleep 10
- name: Set test environment
run: |
{
echo "BROWSERSTACK_LOCAL=true"
echo "BROWSERSTACK_LOCAL_IDENTIFIER=${{ steps.bs-local-id.outputs.value }}"
echo "BROWSERSTACK_BUILD_NAME=system-test-ios-login-${{ github.run_id }}"
echo "MM_TEST_ACCOUNT_SRP=${{ secrets.MM_TEST_ACCOUNT_SRP }}"
echo "TEST_SRP_1=${{ secrets.TEST_SRP_1 }}"
echo "TEST_SRP_2=${{ secrets.TEST_SRP_2 }}"
echo "TEST_SRP_3=${{ secrets.TEST_SRP_3 }}"
echo "E2E_PASSWORD=${{ secrets.E2E_PASSWORD }}"
} >> "$GITHUB_ENV"
- name: Run iOS login system tests
run: yarn run-system-tests:ios-login
- name: Upload test results
uses: actions/upload-artifact@v8
if: always()
with:
name: system-test-report-ios-login
path: tests/test-reports/system-test-report/
if-no-files-found: ignore
retention-days: 7
run-ios-onboarding-tests:
name: iOS Onboarding Tests
needs: [upload-to-browserstack]
if: >-
always() && !cancelled()
&& needs.upload-to-browserstack.outputs.ios-onboarding-url != ''
runs-on: ubuntu-latest
env:
BROWSERSTACK_IOS_CLEAN_APP_URL: ${{ needs.upload-to-browserstack.outputs.ios-onboarding-url }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Restore node_modules cache
uses: actions/cache@v6
with:
path: |
node_modules
.yarn/cache
.yarn/install-state.gz
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: yarn --immutable
- name: Restore .metamask folder
id: restore-metamask
uses: actions/cache@v6
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
run: yarn setup:github-ci
- name: BrowserStack Env Setup
uses: browserstack/github-actions/setup-env@4478e16186f38e5be07721931642e65a028713c3
with:
username: ${{ secrets.BROWSERSTACK_USERNAME }}
access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
project-name: ${{ github.repository }}
- name: Compute BrowserStack Local Identifier
id: bs-local-id
run: echo "value=${{ github.run_id }}-ios-onboarding" >> "$GITHUB_OUTPUT"
- name: Setup BrowserStack Local
uses: browserstack/github-actions/setup-local@4478e16186f38e5be07721931642e65a028713c3
with:
local-testing: start
local-identifier: ${{ steps.bs-local-id.outputs.value }}
local-args: '--force-local --verbose'
- name: Wait for BrowserStack Local
run: sleep 10
- name: Set test environment
run: |
{
echo "BROWSERSTACK_LOCAL=true"
echo "BROWSERSTACK_LOCAL_IDENTIFIER=${{ steps.bs-local-id.outputs.value }}"
echo "BROWSERSTACK_BUILD_NAME=system-test-ios-onboarding-${{ github.run_id }}"
echo "MM_TEST_ACCOUNT_SRP=${{ secrets.MM_TEST_ACCOUNT_SRP }}"
echo "TEST_SRP_1=${{ secrets.TEST_SRP_1 }}"
echo "TEST_SRP_2=${{ secrets.TEST_SRP_2 }}"
echo "TEST_SRP_3=${{ secrets.TEST_SRP_3 }}"
echo "E2E_PASSWORD=${{ secrets.E2E_PASSWORD }}"
} >> "$GITHUB_ENV"
- name: Run iOS onboarding system tests
run: yarn run-system-tests:ios-onboarding
- name: Upload test results
uses: actions/upload-artifact@v8
if: always()
with:
name: system-test-report-ios-onboarding
path: tests/test-reports/system-test-report/
if-no-files-found: ignore
retention-days: 7
report:
name: System Test Summary
needs:
- run-android-login-tests
- run-android-onboarding-tests
- run-ios-login-tests
- run-ios-onboarding-tests
if: always()
runs-on: ubuntu-latest
steps:
- name: Download all test reports
uses: actions/download-artifact@v8
with:
pattern: system-test-report-*
path: all-reports
merge-multiple: false
- name: Generate summary
run: |
STATUS_ANDROID_LOGIN="${{ needs.run-android-login-tests.result }}"
STATUS_ANDROID_ONBOARDING="${{ needs.run-android-onboarding-tests.result }}"
STATUS_IOS_LOGIN="${{ needs.run-ios-login-tests.result }}"
STATUS_IOS_ONBOARDING="${{ needs.run-ios-onboarding-tests.result }}"
format_status() {
case "$1" in
success) echo "passed" ;;
failure) echo "FAILED" ;;
skipped) echo "skipped" ;;
cancelled) echo "cancelled" ;;
*) echo "$1" ;;
esac
}
{
echo "## Nightly System Test Results"
echo ""
echo "| Project | Status |"
echo "|---------|--------|"
echo "| Android Login | $(format_status "$STATUS_ANDROID_LOGIN") |"
echo "| Android Onboarding | $(format_status "$STATUS_ANDROID_ONBOARDING") |"
echo "| iOS Login | $(format_status "$STATUS_IOS_LOGIN") |"
echo "| iOS Onboarding | $(format_status "$STATUS_IOS_ONBOARDING") |"
echo ""
echo "**Trigger:** ${{ github.event_name }}"
echo "**Branch:** ${{ github.ref_name }}"
echo "**Run:** [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
} >> "$GITHUB_STEP_SUMMARY"
# Fail the report job if any test job failed
if [[ "$STATUS_ANDROID_LOGIN" == "failure" || "$STATUS_ANDROID_ONBOARDING" == "failure" || "$STATUS_IOS_LOGIN" == "failure" || "$STATUS_IOS_ONBOARDING" == "failure" ]]; then
echo ""
echo "::error::One or more system test projects failed"
exit 1
fi