Skip to content

Build Apps and Run Performance E2E Tests #4563

Build Apps and Run Performance E2E Tests

Build Apps and Run Performance E2E Tests #4563

name: Build Apps and Run Performance E2E Tests
# This workflow runs performance E2E tests every 3 hours during weekdays (Monday-Friday)
# Schedule: Every 3 hours at the top of the hour (00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00)
# Days: Monday (1) through Friday (6)
on:
schedule:
- cron: '0 */3 * * 1-6'
push:
branches:
- main
- 'release/*'
workflow_dispatch:
inputs:
description:
description: 'Optional description for this test run'
required: false
type: string
sentry_target:
description: 'Sentry target for performance events (test or real)'
required: false
type: choice
options:
- test
- real
default: test
browserstack_app_url_android_onboarding:
description: 'BrowserStack Android Onboarding App URL (bs://...)'
required: false
type: string
browserstack_app_url_ios_onboarding:
description: 'BrowserStack iOS Onboarding App URL (bs://...)'
required: false
type: string
browserstack_app_url_android_imported_wallet:
description: 'BrowserStack Android Imported Wallet App URL (bs://...)'
required: false
type: string
browserstack_app_url_ios_imported_wallet:
description: 'BrowserStack iOS Imported Wallet App URL (bs://...)'
required: false
type: string
workflow_call:
inputs:
description:
description: 'Optional description for this test run'
required: false
type: string
sentry_target:
description: 'Sentry target for performance events (test or real)'
required: false
type: string
default: test
browserstack_app_url_android_onboarding:
description: 'BrowserStack Android Onboarding App URL (bs://...)'
required: false
type: string
browserstack_app_url_ios_onboarding:
description: 'BrowserStack iOS Onboarding App URL (bs://...)'
required: false
type: string
browserstack_app_url_android_imported_wallet:
description: 'BrowserStack Android Imported Wallet App URL (bs://...)'
required: false
type: string
browserstack_app_url_ios_imported_wallet:
description: 'BrowserStack iOS Imported Wallet App URL (bs://...)'
required: false
type: string
branch_name:
description: 'Branch name to use for build names (defaults to auto-detection)'
required: false
type: string
build_variant:
description: 'Build variant for Bitrise (rc = release, exp = experimental)'
required: false
type: string
default: 'rc'
permissions:
contents: read
id-token: write
actions: write
concurrency:
# Include build_variant so experimental (exp) and release/direct (rc) don't share the same
# queue — avoids "Build Apps" (direct/release) and experimental runs canceling each other.
# Note: GitHub allows at most 1 running + 1 pending per group; a 3rd run cancels the pending one.
group: performance-e2e-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build_variant || 'rc' }}
# Do not cancel in-progress runs; pending runs can still be replaced by a newer trigger.
cancel-in-progress: false
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
MM_TEST_ACCOUNT_SRP: ${{ secrets.MM_TEST_ACCOUNT_SRP }}
TEST_SRP_1: ${{ secrets.TEST_SRP_1 }}
TEST_SRP_2: ${{ secrets.TEST_SRP_2 }}
TEST_SRP_3: ${{ secrets.TEST_SRP_3 }}
E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }}
DISABLE_VIDEO_DOWNLOAD: true
jobs:
determine-branch-name:
name: Determine Branch Name for Bitrise
runs-on: ubuntu-latest
outputs:
branch_name: ${{ steps.get-branch.outputs.branch_name }}
steps:
- name: Get correct branch name
id: get-branch
env:
INPUT_BRANCH: ${{ inputs.branch_name }}
HEAD_REF: ${{ github.head_ref }}
REF_NAME: ${{ github.ref_name }}
EVENT_NAME: ${{ github.event_name }}
run: |
# Determine the correct branch name for all trigger scenarios:
# - workflow_call (from ci.yml): uses INPUT_BRANCH (github.head_ref for PRs, github.ref_name for push)
# - pull_request: uses HEAD_REF (source branch, e.g., "feature-branch")
# - schedule: uses REF_NAME (typically "main")
# - workflow_dispatch: uses INPUT_BRANCH if provided, otherwise REF_NAME
# Priority: explicit input > PR head_ref > ref_name
if [ -n "$INPUT_BRANCH" ]; then
BRANCH_NAME="$INPUT_BRANCH"
echo "Using explicit input: $BRANCH_NAME"
elif [ "$EVENT_NAME" = "pull_request" ] && [ -n "$HEAD_REF" ]; then
BRANCH_NAME="$HEAD_REF"
echo "Using PR source branch: $BRANCH_NAME"
else
BRANCH_NAME="$REF_NAME"
echo "Using ref_name: $BRANCH_NAME"
fi
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
echo "Branch for Bitrise: $BRANCH_NAME"
read-device-matrix:
name: Read Device Matrix
runs-on: ubuntu-latest
needs: [determine-branch-name]
outputs:
android_matrix: ${{ steps.read-matrix.outputs.android_matrix }}
ios_matrix: ${{ steps.read-matrix.outputs.ios_matrix }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Select Devices
id: read-matrix
env:
BRANCH_NAME: ${{ needs.determine-branch-name.outputs.branch_name }}
run: |
FILE="tests/performance/device-matrix.json"
# Push to main or release branches: Use only "low" category devices
# Schedule and workflow_dispatch: Use all devices
if [ "${{ github.event_name }}" = "push" ]; then
FILTER='[.[] | select(.category == "low")]'
echo "Limited mode: low category devices only (event: ${{ github.event_name }}, branch: $BRANCH_NAME)"
else
FILTER='.'
echo "Full mode: All devices (event: ${{ github.event_name }}, branch: $BRANCH_NAME)"
fi
ANDROID_MATRIX=$(jq ".android_devices | $FILTER" "$FILE")
IOS_MATRIX=$(jq ".ios_devices | $FILTER" "$FILE")
{
echo "android_matrix<<EOF"
echo "$ANDROID_MATRIX"
echo "EOF"
echo "ios_matrix<<EOF"
echo "$IOS_MATRIX"
echo "EOF"
} >> "$GITHUB_OUTPUT"
echo "Selected: $(echo "$ANDROID_MATRIX" | jq length) Android, $(echo "$IOS_MATRIX" | jq length) iOS"
set-build-names:
name: Set Unified BrowserStack Build Names
runs-on: ubuntu-latest
needs: [determine-branch-name]
outputs:
android_build_name: ${{ steps.set-builds.outputs.android_build_name }}
ios_build_name: ${{ steps.set-builds.outputs.ios_build_name }}
steps:
- name: Set unified build names
id: set-builds
run: |
BRANCH_NAME="${{ needs.determine-branch-name.outputs.branch_name }}"
echo "android_build_name=Android-Performance-$BRANCH_NAME-Branch" >> "$GITHUB_OUTPUT"
echo "ios_build_name=iOS-Performance-$BRANCH_NAME-Branch" >> "$GITHUB_OUTPUT"
echo "Set unified build names:"
echo " Android: Android-Performance-$BRANCH_NAME-Branch"
echo " iOS: iOS-Performance-$BRANCH_NAME-Branch"
trigger-android-dual-versions:
name: Trigger Android Dual Versions and Extract BrowserStack URLs
uses: ./.github/workflows/build-android-upload-to-browserstack.yml
needs: [determine-branch-name]
if: (!inputs.browserstack_app_url_android_onboarding && !inputs.browserstack_app_url_android_imported_wallet)
with:
branch_name: ${{ needs.determine-branch-name.outputs.branch_name }}
build_variant: ${{ inputs.build_variant || 'rc' }}
secrets: inherit
trigger-ios-dual-versions:
name: Trigger iOS Dual Versions and Extract BrowserStack URLs
uses: ./.github/workflows/build-ios-upload-to-browserstack.yml
needs: [determine-branch-name]
if: (!inputs.browserstack_app_url_ios_onboarding && !inputs.browserstack_app_url_ios_imported_wallet)
with:
branch_name: ${{ needs.determine-branch-name.outputs.branch_name }}
build_variant: ${{ inputs.build_variant || 'rc' }}
secrets: inherit
# =============================================================================
# PHASE 1: ONBOARDING TESTS (Run First)
# =============================================================================
run-android-onboarding-tests:
name: Run Android Onboarding Tests
uses: ./.github/workflows/performance-test-runner.yml
needs:
[
read-device-matrix,
trigger-android-dual-versions,
set-build-names,
determine-branch-name,
]
if: always() && !failure() && !cancelled() && (needs.trigger-android-dual-versions.result == 'skipped' || needs.trigger-android-dual-versions.result == 'success') && (inputs.browserstack_app_url_android_onboarding != '' || needs.trigger-android-dual-versions.outputs.without-srp-browserstack-url != '')
with:
platform: android
build_type: onboarding
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.android_matrix }}
browserstack_app_url: ${{ needs.trigger-android-dual-versions.outputs.without-srp-browserstack-url || inputs.browserstack_app_url_android_onboarding }}
app_version: ${{ needs.trigger-android-dual-versions.outputs.without-srp-version || 'Manual-Input' }}
branch_name: ${{ needs.determine-branch-name.outputs.branch_name }}
browserstack_build_name: ${{ needs.set-build-names.outputs.android_build_name }}
secrets: inherit
run-ios-onboarding-tests:
name: Run iOS Onboarding Tests
uses: ./.github/workflows/performance-test-runner.yml
needs:
[
read-device-matrix,
trigger-ios-dual-versions,
set-build-names,
determine-branch-name,
]
if: always() && !failure() && !cancelled() && (needs.trigger-ios-dual-versions.result == 'skipped' || needs.trigger-ios-dual-versions.result == 'success') && (inputs.browserstack_app_url_ios_onboarding != '' || needs.trigger-ios-dual-versions.outputs.without-srp-browserstack-url != '')
with:
platform: ios
build_type: onboarding
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.ios_matrix }}
browserstack_app_url: ${{ needs.trigger-ios-dual-versions.outputs.without-srp-browserstack-url || inputs.browserstack_app_url_ios_onboarding }}
app_version: ${{ needs.trigger-ios-dual-versions.outputs.without-srp-version || 'Manual-Input' }}
branch_name: ${{ needs.determine-branch-name.outputs.branch_name }}
browserstack_build_name: ${{ needs.set-build-names.outputs.ios_build_name }}
secrets: inherit
# =============================================================================
# PHASE 2: IMPORTED WALLET TESTS (Run After Onboarding Complete)
# =============================================================================
wait-for-onboarding-completion:
name: Wait for Onboarding Completion
runs-on: ubuntu-latest
needs: [run-android-onboarding-tests, run-ios-onboarding-tests]
if: always()
steps:
- name: Wait for onboarding tests to complete
run: |
echo "Onboarding tests have completed (success or failure)"
echo "Proceeding with imported wallet tests..."
run-android-imported-wallet-tests:
name: Run Android Imported Wallet Tests
uses: ./.github/workflows/performance-test-runner.yml
needs:
[
read-device-matrix,
trigger-android-dual-versions,
wait-for-onboarding-completion,
set-build-names,
determine-branch-name,
]
if: always() && !cancelled() && (needs.trigger-android-dual-versions.result == 'skipped' || needs.trigger-android-dual-versions.result == 'success') && (inputs.browserstack_app_url_android_imported_wallet != '' || needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url != '')
with:
platform: android
build_type: imported-wallet
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.android_matrix }}
browserstack_app_url: ${{ needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url || inputs.browserstack_app_url_android_imported_wallet }}
app_version: ${{ needs.trigger-android-dual-versions.outputs.with-srp-version || 'Manual-Input' }}
branch_name: ${{ needs.determine-branch-name.outputs.branch_name }}
browserstack_build_name: ${{ needs.set-build-names.outputs.android_build_name }}
secrets: inherit
run-ios-imported-wallet-tests:
name: Run iOS Imported Wallet Tests
uses: ./.github/workflows/performance-test-runner.yml
needs:
[
read-device-matrix,
trigger-ios-dual-versions,
wait-for-onboarding-completion,
set-build-names,
determine-branch-name,
]
if: always() && !cancelled() && (needs.trigger-ios-dual-versions.result == 'skipped' || needs.trigger-ios-dual-versions.result == 'success') && (inputs.browserstack_app_url_ios_imported_wallet != '' || needs.trigger-ios-dual-versions.outputs.with-srp-browserstack-url != '')
with:
platform: ios
build_type: imported-wallet
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.ios_matrix }}
browserstack_app_url: ${{ needs.trigger-ios-dual-versions.outputs.with-srp-browserstack-url || inputs.browserstack_app_url_ios_imported_wallet }}
app_version: ${{ needs.trigger-ios-dual-versions.outputs.with-srp-version || 'Manual-Input' }}
branch_name: ${{ needs.determine-branch-name.outputs.branch_name }}
browserstack_build_name: ${{ needs.set-build-names.outputs.ios_build_name }}
secrets: inherit
fetch-rn-playground-apk-upload-to-browserstack:
name: Fetch RN Playground APK and Upload to BrowserStack
runs-on: ubuntu-latest
needs: [wait-for-onboarding-completion]
if: always() && !cancelled()
outputs:
browserstack-playground-url: ${{ steps.upload-playground.outputs.browserstack-url }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Fetch playground APK from GitHub Releases
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RN_PLAYGROUND_APK_VERSION: ${{ vars.RN_PLAYGROUND_APK_VERSION || '' }}
run: ./scripts/fetch-rn-playground-apk.sh --output ./tmp/rn-playground.apk
- name: Upload playground APK to BrowserStack
id: upload-playground
run: |
echo "Uploading RN Playground APK to BrowserStack..."
RESPONSE=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "file=@./tmp/rn-playground.apk" \
-F "custom_id=RN-Playground-${{ github.run_id }}")
APP_URL=$(echo "$RESPONSE" | jq -r '.app_url')
if [ -z "$APP_URL" ] || [ "$APP_URL" = "null" ]; then
echo "Error: Failed to upload playground APK to BrowserStack"
echo "Response: $RESPONSE"
exit 1
fi
echo "browserstack-url=$APP_URL" >> "$GITHUB_OUTPUT"
echo "Playground APK uploaded: $APP_URL"
run-android-mm-connect-tests:
name: Run Android MM-Connect Tests
uses: ./.github/workflows/performance-test-runner.yml
needs:
[
read-device-matrix,
trigger-android-dual-versions,
fetch-rn-playground-apk-upload-to-browserstack,
set-build-names,
determine-branch-name,
]
if: always() && !cancelled() && (needs.trigger-android-dual-versions.result == 'skipped' || needs.trigger-android-dual-versions.result == 'success') && (inputs.browserstack_app_url_android_imported_wallet != '' || needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url != '') && needs.fetch-rn-playground-apk-upload-to-browserstack.result == 'success'
with:
platform: android
build_type: mm-connect
sentry_target: ${{ inputs.sentry_target || 'test' }}
build_variant: ${{ inputs.build_variant || 'rc' }}
device_matrix: ${{ needs.read-device-matrix.outputs.android_matrix }}
browserstack_app_url: ${{ needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url || inputs.browserstack_app_url_android_imported_wallet }}
app_version: ${{ needs.trigger-android-dual-versions.outputs.with-srp-version || 'Manual-Input' }}
branch_name: ${{ needs.determine-branch-name.outputs.branch_name }}
browserstack_build_name: ${{ needs.set-build-names.outputs.android_build_name }}
browserstack_playground_url: ${{ needs.fetch-rn-playground-apk-upload-to-browserstack.outputs.browserstack-playground-url }}
secrets: inherit
aggregate-results:
name: Aggregate All Test Results
runs-on: ubuntu-latest
needs:
[
run-android-imported-wallet-tests,
run-android-onboarding-tests,
run-android-mm-connect-tests,
run-ios-imported-wallet-tests,
run-ios-onboarding-tests,
wait-for-onboarding-completion,
determine-branch-name,
]
if: always()
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download All Test Results
uses: actions/download-artifact@v4
with:
pattern: '*-test-results-*'
path: ./test-results
merge-multiple: true
- name: Run aggregation script
env:
BRANCH_NAME: ${{ needs.determine-branch-name.outputs.branch_name }}
BUILD_VARIANT: ${{ inputs.build_variant || 'rc' }}
run: |
echo "Processing all test results..."
echo "Running aggregation script..."
node scripts/aggregate-performance-reports.mjs
echo "Aggregation completed"
- name: Upload Final Combined Results
uses: actions/upload-artifact@v4
with:
name: aggregated-reports
path: |
tests/aggregated-reports/
if-no-files-found: ignore
retention-days: 30
slack-notification:
name: Send Slack Notification
runs-on: ubuntu-latest
needs:
[
run-android-imported-wallet-tests,
run-android-onboarding-tests,
run-android-mm-connect-tests,
run-ios-imported-wallet-tests,
run-ios-onboarding-tests,
aggregate-results,
determine-branch-name,
]
if: always()
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download Aggregated Results
uses: actions/download-artifact@v4
with:
name: aggregated-reports
path: ./aggregated-reports
- name: Generate Test Summary
id: summary
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_NAME: ${{ needs.determine-branch-name.outputs.branch_name }}
run: |
{
echo "summary<<EOF"
./scripts/generate-slack-summary.sh aggregated-reports/summary.json
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Send Slack Notification
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a
with:
webhook: ${{ secrets.PERFORMANCE_E2E_SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": "${{ steps.summary.outputs.summary }}"
}