From f9896f660c87926752937b4b4c0944a1b4c4f89b Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 30 Apr 2026 13:13:52 +0100 Subject: [PATCH 01/34] introduce initial maestro flow --- maestro/e2e-tests.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 maestro/e2e-tests.yml diff --git a/maestro/e2e-tests.yml b/maestro/e2e-tests.yml new file mode 100644 index 0000000..313b527 --- /dev/null +++ b/maestro/e2e-tests.yml @@ -0,0 +1,9 @@ +appId: com.comapeo.core.testing +--- +- launchApp: + clearState: true +- tapOn: "Run tests" +- extendedWaitUntil: + visible: + id: "all-tests-passed" + timeout: 10000 From 4fa45288592c64e3a0199afced3a65b5c323c721 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 30 Apr 2026 14:25:00 +0100 Subject: [PATCH 02/34] record test --- .gitignore | 3 +++ maestro/e2e-tests.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index cdfe0ae..e3687e8 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,6 @@ build/ # eslint .eslintcache + +# Maestro artifacts +maestro/recordings/ diff --git a/maestro/e2e-tests.yml b/maestro/e2e-tests.yml index 313b527..7ba9af5 100644 --- a/maestro/e2e-tests.yml +++ b/maestro/e2e-tests.yml @@ -2,8 +2,11 @@ appId: com.comapeo.core.testing --- - launchApp: clearState: true +- startRecording: + path: "maestro/recordings/e2e-tests" - tapOn: "Run tests" - extendedWaitUntil: visible: id: "all-tests-passed" timeout: 10000 +- stopRecording From b4ea31bc570e2d79cfeaa48575e4b9262f7d5773 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Mon, 4 May 2026 12:47:00 +0100 Subject: [PATCH 03/34] update app id used in test --- maestro/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/e2e-tests.yml b/maestro/e2e-tests.yml index 7ba9af5..3621198 100644 --- a/maestro/e2e-tests.yml +++ b/maestro/e2e-tests.yml @@ -1,4 +1,4 @@ -appId: com.comapeo.core.testing +appId: com.comapeo.core.e2e --- - launchApp: clearState: true From b4ddadc45b91b729f5a147f15a4a945ff609e6fc Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Mon, 4 May 2026 15:50:14 +0100 Subject: [PATCH 04/34] rename maestro flow --- maestro/{e2e-tests.yml => e2e.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename maestro/{e2e-tests.yml => e2e.yml} (83%) diff --git a/maestro/e2e-tests.yml b/maestro/e2e.yml similarity index 83% rename from maestro/e2e-tests.yml rename to maestro/e2e.yml index 3621198..47c27f8 100644 --- a/maestro/e2e-tests.yml +++ b/maestro/e2e.yml @@ -3,7 +3,7 @@ appId: com.comapeo.core.e2e - launchApp: clearState: true - startRecording: - path: "maestro/recordings/e2e-tests" + path: "maestro/recordings/e2e" - tapOn: "Run tests" - extendedWaitUntil: visible: From 9ca36bed1ec62c67b7e572687dd70e4a5449a4e5 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Tue, 5 May 2026 13:23:28 +0100 Subject: [PATCH 05/34] wip workflow still need to wait and poll test run statuses --- .github/workflows/e2e-tests.yml | 289 ++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 .github/workflows/e2e-tests.yml diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..2751c99 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,289 @@ +name: E2E Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + NODEJS_MOBILE_VERSION: v18.20.4 + +jobs: + build-android: + if: ${{ github.event.pull_request.draft == false }} + name: Build (Android) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + outputs: + app_url: ${{ steps.upload.outputs.app_url }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-node@v6 + with: + node-version-file: package.json + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Cache nodejs-mobile binaries + id: cache-libnode + uses: actions/cache@v4 + with: + path: android/libnode + # hashFiles(...) folds the BASE_URL (defined inside the + # download script) into the cache key, so any swap of the + # source repo automatically invalidates stale caches. + key: nodejs-mobile-${{ env.NODEJS_MOBILE_VERSION }}-android-${{ hashFiles('scripts/download-nodejs-mobile.sh') }} + + - name: Download nodejs-mobile binaries + if: steps.cache-libnode.outputs.cache-hit != 'true' + run: ./scripts/download-nodejs-mobile.sh + + - name: Install npm dependencies + run: | + npm install --ignore-scripts + npx patch-package + + - name: Expo prebuild (generates apps/e2e/android) + working-directory: apps/e2e + run: npx expo prebuild --platform android --no-install + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Set up E2E app + working-directory: apps/e2e + run: | + npm install --ignore-scripts + npx patch-package + npx expo prebuild --platform android --no-install + + - name: Build APK + working-directory: apps/e2e/android + run: | + ./gradlew assembleRelease --no-daemon + + - name: Upload APK + id: upload + env: + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + run: | + APK_PATH="$(pwd)/apps/e2e/android/app/build/outputs/apk/release/app-release.apk" + + APP_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ + -F "file=@$APK_PATH" | jq -r '.app_url') + + echo "app_url=$APP_URL" >> $GITHUB_OUTPUT + + build-ios: + if: ${{ github.event.pull_request.draft == false }} + name: Upload test suite + runs-on: macos-15 + timeout-minutes: 15 + permissions: + contents: read + outputs: + app_url: ${{ steps.upload.outputs.app_url }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Select Xcode + run: sudo xcode-select -s /Applications/Xcode_26.3.app/Contents/Developer + + - uses: actions/setup-node@v6 + with: + node-version-file: package.json + + - name: Cache nodejs-mobile binaries + id: cache-libnode + uses: actions/cache@v4 + with: + path: android/libnode + # hashFiles(...) folds the BASE_URL (defined inside the + # download script) into the cache key, so any swap of the + # source repo automatically invalidates stale caches. + key: nodejs-mobile-${{ env.NODEJS_MOBILE_VERSION }}-android-${{ hashFiles('scripts/download-nodejs-mobile.sh') }} + + - name: Download nodejs-mobile binaries + if: steps.cache-libnode.outputs.cache-hit != 'true' + run: ./scripts/download-nodejs-mobile.sh + + - name: Build backend bundle + run: npm run backend:build + + - name: Expo prebuild + working-directory: apps/e2e + run: npx expo prebuild --platform ios + + - name: Build .ipa + working-directory: apps/e2e/ios + run: | + xcodebuild archive \ + -workspace corereactnativee2e.xcworkspace \ + -scheme corereactnativee2e \ + -sdk iphoneos \ + -destination 'generic/platform=iOS' \ + -archivePath ./build/corereactnativee2e.xcarchive \ + CODE_SIGNING_ALLOWED='NO' + + mkdir -p Payload + cp -r ./build/corereactnativee2e.xcarchive/Products/Applications/corereactnativee2e.app Payload/ + zip -r corereactnativee2e.ipa Payload/ + + - name: Upload + id: upload + run: | + IPA_PATH="$(pwd)/apps/e2e/ios/corereactnativee2e.ipa" + + APP_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ + -F "file=@$IPA_PATH" | jq -r '.app_url') + + echo "app_url=$APP_URL" >> $GITHUB_OUTPUT + + upload-test-suite: + name: Upload test suite + needs: [build-android, build-ios] + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + test_suite_url: ${{ steps.upload.outputs.test_suite_url }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: | + maestro + + - name: Upload test suite to Browserstack + id: upload + env: + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + run: | + ZIP_NAME="maestro_tests.zip" + + zip -r "$ZIP_NAME" maestro/e2e.yml + + ZIP_PATH="$(pwd)/$ZIP_NAME" + + TEST_SUITE_URL=$(curl -u "BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/test-suite" \ + -F "file=@$ZIP_PATH" | jq -r '.test_suite_url') + + echo "test_suite_url=$TEST_SUITE_URL" >> $GITHUB_ENV + + test-android: + name: Run test suite on Android + needs: [build-android, upload-test-suite] + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Run tests + id: run + env: + APP_URL: ${{ needs.android-build.outputs.app_url }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + BROWSERSTACK_PROJECT_NAME: ${{ vars.BROWSERSTACK_PROJECT_NAME }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + TEST_SUITE_URL: ${{ needs.upload-test-suite.outputs.test_suite_url }} + run: | + DEVICES='[ + "Google Pixel 5-11.0", + "Google Pixel 9-16.0", + "Huawei P30-9.0", + "Huawei Nova 11 SE-12.0", + "Motorola Moto G9 Play-10.0", + "Motorola Moto G71 5G-11.0", + "OnePlus 9-11.0", + "OnePlus 11R-13.0", + "OnePlus 12R-14.0", + "OnePlus 13R-15.0", + "Oppo Reno 3 Pro-10.0", + "Oppo Reno 6-11.0", + "Samsung Galaxy Note 9-8.1" + "Samsung Galaxy S10-9.0", + "Samsung Galaxy A51-10.0", + "Samsung Galaxy S23-13.0", + "Vivo V21-11.0", + "Vivo Y21-11.0", + "Xiaomi Redmi Note 9-10.0" + "Xiaomi Redmi Note 11-11.0", + ]' + + BUILD_ID=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/build" \ + -d '{ + "app": "$APP_URL", + "testSuite": "$TEST_SUITE_URL", + "devices": $DEVICES, + "project": "$BROWSERSTACK_PROJECT_NAME", + "deviceLogs": true }' \ + -H "Content-Type: application/json" | jq -r '.build_id') + + echo "build_id=$BUILD_ID" >> $GITHUB_ENV + + test-ios: + name: Run test suite on iOS + needs: [build-ios, upload-test-suite] + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Run tests + id: run + env: + APP_URL: ${{ needs.build-ios.outputs.app_url }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + BROWSERSTACK_PROJECT_NAME: ${{ vars.BROWSERSTACK_PROJECT_NAME }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + TEST_SUITE_URL: ${{ needs.upload-test-suite.outputs.test_suite_url }} + run: | + DEVICES='[ + "iPhone 13-15", + "iPhone 15-17", + "iPhone 17-26" + ]' + BUILD_ID=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/build" \ + -d '{ + "app": "$APP_URL", + "testSuite": "$TEST_SUITE_URL", + "devices": $DEVICES, + "project": "$BROWSERSTACK_PROJECT_NAME", + "deviceLogs": true }' \ + -H "Content-Type: application/json" | jq -r '.build_id') + + echo "build_id=$BUILD_ID" >> $GITHUB_ENV From bafab2b71149f211971682a42cc2f55d3c21ae9e Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Tue, 5 May 2026 13:37:34 +0100 Subject: [PATCH 06/34] specify maestro version to use --- .github/workflows/e2e-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 2751c99..161cbcb 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -245,7 +245,8 @@ jobs: "testSuite": "$TEST_SUITE_URL", "devices": $DEVICES, "project": "$BROWSERSTACK_PROJECT_NAME", - "deviceLogs": true }' \ + "deviceLogs": true, + "maestroVersion": "latest" }' \ -H "Content-Type: application/json" | jq -r '.build_id') echo "build_id=$BUILD_ID" >> $GITHUB_ENV @@ -283,7 +284,8 @@ jobs: "testSuite": "$TEST_SUITE_URL", "devices": $DEVICES, "project": "$BROWSERSTACK_PROJECT_NAME", - "deviceLogs": true }' \ + "deviceLogs": true, + "maestroVersion": "latest" }' \ -H "Content-Type: application/json" | jq -r '.build_id') echo "build_id=$BUILD_ID" >> $GITHUB_ENV From d38fb7a43b479c7a36b2cefb2cecba5e8fcd96ca Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Tue, 5 May 2026 13:46:32 +0100 Subject: [PATCH 07/34] remove stopRecording command Browserstack's Maestro support does not seem to support this command (https://www.browserstack.com/docs/app-automate/maestro/references/supported-commands) --- maestro/e2e.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/maestro/e2e.yml b/maestro/e2e.yml index 47c27f8..e864a84 100644 --- a/maestro/e2e.yml +++ b/maestro/e2e.yml @@ -9,4 +9,3 @@ appId: com.comapeo.core.e2e visible: id: "all-tests-passed" timeout: 10000 -- stopRecording From ab12dde708c87f29c041adaad9a4208fc156eeea Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Tue, 5 May 2026 13:51:40 +0100 Subject: [PATCH 08/34] simplify test jobs --- .github/workflows/e2e-tests.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 161cbcb..18f5fe5 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -198,14 +198,10 @@ jobs: name: Run test suite on Android needs: [build-android, upload-test-suite] runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 permissions: contents: read steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Run tests id: run env: @@ -255,14 +251,10 @@ jobs: name: Run test suite on iOS needs: [build-ios, upload-test-suite] runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 permissions: contents: read steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Run tests id: run env: From 2571dfaa4330806e44c9bb4e6da208864c8b8462 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Tue, 5 May 2026 13:53:32 +0100 Subject: [PATCH 09/34] allow test suite upload to happen immediately --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 18f5fe5..a135b1b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -162,8 +162,8 @@ jobs: echo "app_url=$APP_URL" >> $GITHUB_OUTPUT upload-test-suite: + if: ${{ github.event.pull_request.draft == false }} name: Upload test suite - needs: [build-android, build-ios] runs-on: ubuntu-latest permissions: contents: read From 1135e42cab964b605715d3e195c383a5778c6ad1 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Tue, 5 May 2026 14:08:10 +0100 Subject: [PATCH 10/34] minor cleanup --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index a135b1b..95e7be5 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -239,7 +239,7 @@ jobs: -d '{ "app": "$APP_URL", "testSuite": "$TEST_SUITE_URL", - "devices": $DEVICES, + "devices": "$DEVICES", "project": "$BROWSERSTACK_PROJECT_NAME", "deviceLogs": true, "maestroVersion": "latest" }' \ @@ -274,7 +274,7 @@ jobs: -d '{ "app": "$APP_URL", "testSuite": "$TEST_SUITE_URL", - "devices": $DEVICES, + "devices": "$DEVICES", "project": "$BROWSERSTACK_PROJECT_NAME", "deviceLogs": true, "maestroVersion": "latest" }' \ From a8ffdcd648da7a905a2e08f1bffb10813286b721 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 10:07:51 +0100 Subject: [PATCH 11/34] fix test-android env --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 95e7be5..5c01996 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -205,7 +205,7 @@ jobs: - name: Run tests id: run env: - APP_URL: ${{ needs.android-build.outputs.app_url }} + APP_URL: ${{ needs.build-android.outputs.app_url }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} BROWSERSTACK_PROJECT_NAME: ${{ vars.BROWSERSTACK_PROJECT_NAME }} BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} From afadca4cfbd45ac8a8d55afd229b7e715c05f46f Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 11:57:25 +0100 Subject: [PATCH 12/34] add polling for result step --- .../run-browserstack-maestro/action.yml | 95 ++++++++++++ .github/workflows/e2e-tests.yml | 146 ++++++++---------- 2 files changed, 162 insertions(+), 79 deletions(-) create mode 100644 .github/actions/run-browserstack-maestro/action.yml diff --git a/.github/actions/run-browserstack-maestro/action.yml b/.github/actions/run-browserstack-maestro/action.yml new file mode 100644 index 0000000..a39db49 --- /dev/null +++ b/.github/actions/run-browserstack-maestro/action.yml @@ -0,0 +1,95 @@ +name: Run BrowserStack Maestro Tests + +description: Triggers a BrowserStack Maestro build and polls until completion + +inputs: + app_url: + description: BrowserStack app URL (bs://...) + required: true + test_suite_url: + description: BrowserStack test suite URL (bs://...) + required: true + devices: + description: JSON array of device strings + required: true + project_name: + description: BrowserStack project name + required: true + browserstack_username: + description: BrowserStack username + required: true + browserstack_access_key: + description: BrowserStack access key + required: true + timeout: + description: Test run timeout in seconds + required: false + default: "600" + +runs: + using: composite + steps: + - name: Validate inputs + if: ${{ fromJson(inputs.timeout) < 30 }} + shell: bash + run: | + echo "timeout must be at least 30 seconds" + exit 1 + + - name: Trigger build + id: trigger + env: + APP_URL: ${{ inputs.app_url }} + TEST_SUITE_URL: ${{ inputs.test_suite_url }} + DEVICES: ${{ inputs.devices }} + PROJECT_NAME: ${{ inputs.project_name }} + BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} + BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} + shell: bash + run: | + PAYLOAD=$(jq -n \ + --arg app "$APP_URL" \ + --arg suite "$TEST_SUITE_URL" \ + --argjson devices "$DEVICES" \ + --arg project "$PROJECT_NAME" \ + '{app: $app, testSuite: $suite, devices: $devices, project: $project, deviceLogs: true, maestroVersion: "latest"}') + + BUILD_ID=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/build" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + | jq -r '.build_id') + + echo "Triggered build: $BUILD_ID" + echo "build_id=$BUILD_ID" >> $GITHUB_OUTPUT + + - name: Poll for result + env: + BUILD_ID: ${{ steps.trigger.outputs.build_id }} + BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} + BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} + TIMEOUT: ${{ fromJson(inputs.timeout) }} + shell: bash + run: | + MAX_WAIT=$TIMEOUT + POLL_INTERVAL=30 + ELAPSED=0 + + while [ "$ELAPSED" -lt "$MAX_WAIT" ]; do + RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + "https://api-cloud.browserstack.com/app-automate/maestro/v2/builds/$BUILD_ID") + + STATUS=$(echo "$RESPONSE" | jq -r '.status') + echo "[${ELAPSED}s] Build $BUILD_ID status: $STATUS" + + case "$STATUS" in + passed|completed) echo "Tests passed"; exit 0 ;; + failed) echo "Tests failed"; exit 1 ;; + esac + + sleep "$POLL_INTERVAL" + ELAPSED=$((ELAPSED + POLL_INTERVAL)) + done + + echo "Timed out after ${MAX_WAIT}s waiting for build $BUILD_ID" + exit 1 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 5c01996..3368571 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -95,7 +95,7 @@ jobs: build-ios: if: ${{ github.event.pull_request.draft == false }} - name: Upload test suite + name: Build (iOS) runs-on: macos-15 timeout-minutes: 15 permissions: @@ -173,8 +173,7 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - sparse-checkout: | - maestro + sparse-checkout: maestro - name: Upload test suite to Browserstack id: upload @@ -188,96 +187,85 @@ jobs: ZIP_PATH="$(pwd)/$ZIP_NAME" - TEST_SUITE_URL=$(curl -u "BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + TEST_SUITE_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/test-suite" \ -F "file=@$ZIP_PATH" | jq -r '.test_suite_url') - echo "test_suite_url=$TEST_SUITE_URL" >> $GITHUB_ENV + echo "test_suite_url=$TEST_SUITE_URL" >> $GITHUB_OUTPUT test-android: - name: Run test suite on Android + name: Run tests (Android) needs: [build-android, upload-test-suite] runs-on: ubuntu-latest - timeout-minutes: 5 + # TODO: Adjust based on actual timing + timeout-minutes: 60 permissions: contents: read steps: - - name: Run tests - id: run - env: - APP_URL: ${{ needs.build-android.outputs.app_url }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - BROWSERSTACK_PROJECT_NAME: ${{ vars.BROWSERSTACK_PROJECT_NAME }} - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} - TEST_SUITE_URL: ${{ needs.upload-test-suite.outputs.test_suite_url }} - run: | - DEVICES='[ - "Google Pixel 5-11.0", - "Google Pixel 9-16.0", - "Huawei P30-9.0", - "Huawei Nova 11 SE-12.0", - "Motorola Moto G9 Play-10.0", - "Motorola Moto G71 5G-11.0", - "OnePlus 9-11.0", - "OnePlus 11R-13.0", - "OnePlus 12R-14.0", - "OnePlus 13R-15.0", - "Oppo Reno 3 Pro-10.0", - "Oppo Reno 6-11.0", - "Samsung Galaxy Note 9-8.1" - "Samsung Galaxy S10-9.0", - "Samsung Galaxy A51-10.0", - "Samsung Galaxy S23-13.0", - "Vivo V21-11.0", - "Vivo Y21-11.0", - "Xiaomi Redmi Note 9-10.0" - "Xiaomi Redmi Note 11-11.0", - ]' - - BUILD_ID=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ - -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/build" \ - -d '{ - "app": "$APP_URL", - "testSuite": "$TEST_SUITE_URL", - "devices": "$DEVICES", - "project": "$BROWSERSTACK_PROJECT_NAME", - "deviceLogs": true, - "maestroVersion": "latest" }' \ - -H "Content-Type: application/json" | jq -r '.build_id') - - echo "build_id=$BUILD_ID" >> $GITHUB_ENV + - uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: .github/actions + + - uses: ./.github/actions/run-browserstack-maestro + with: + app_url: ${{ needs.build-android.outputs.app_url }} + test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} + project_name: ${{ vars.BROWSERSTACK_PROJECT_NAME }} + browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME }} + browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + # TODO: Adjust based on actual timing + timeout: 1800 + devices: | + [ + "Google Pixel 5-11.0", + "Google Pixel 9-16.0", + "Huawei P30-9.0", + "Huawei Nova 11 SE-12.0", + "Motorola Moto G9 Play-10.0", + "Motorola Moto G71 5G-11.0", + "OnePlus 9-11.0", + "OnePlus 11R-13.0", + "OnePlus 12R-14.0", + "OnePlus 13R-15.0", + "Oppo Reno 3 Pro-10.0", + "Oppo Reno 6-11.0", + "Samsung Galaxy Note 9-8.1", + "Samsung Galaxy S10-9.0", + "Samsung Galaxy A51-10.0", + "Samsung Galaxy S23-13.0", + "Vivo V21-11.0", + "Vivo Y21-11.0", + "Xiaomi Redmi Note 9-10.0", + "Xiaomi Redmi Note 11-11.0" + ] test-ios: - name: Run test suite on iOS + name: Run tests (iOS) needs: [build-ios, upload-test-suite] runs-on: ubuntu-latest - timeout-minutes: 5 + # TODO: Adjust based on actual timing + timeout-minutes: 60 permissions: contents: read steps: - - name: Run tests - id: run - env: - APP_URL: ${{ needs.build-ios.outputs.app_url }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - BROWSERSTACK_PROJECT_NAME: ${{ vars.BROWSERSTACK_PROJECT_NAME }} - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} - TEST_SUITE_URL: ${{ needs.upload-test-suite.outputs.test_suite_url }} - run: | - DEVICES='[ - "iPhone 13-15", - "iPhone 15-17", - "iPhone 17-26" - ]' - BUILD_ID=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ - -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/build" \ - -d '{ - "app": "$APP_URL", - "testSuite": "$TEST_SUITE_URL", - "devices": "$DEVICES", - "project": "$BROWSERSTACK_PROJECT_NAME", - "deviceLogs": true, - "maestroVersion": "latest" }' \ - -H "Content-Type: application/json" | jq -r '.build_id') - - echo "build_id=$BUILD_ID" >> $GITHUB_ENV + - uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: .github/actions + + - uses: ./.github/actions/run-browserstack-maestro + with: + app_url: ${{ needs.build-ios.outputs.app_url }} + test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} + project_name: ${{ vars.BROWSERSTACK_PROJECT_NAME }} + browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME }} + browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + # TODO: Adjust based on actual timing + timeout: 1800 + devices: | + [ + "iPhone 13-15", + "iPhone 15-17", + "iPhone 17-26" + ] From eb5cf3c3407768b17121bd8e08bb0242929c60b1 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 12:10:10 +0100 Subject: [PATCH 13/34] update secrets and vars names --- .github/workflows/e2e-tests.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3368571..67549db 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -82,8 +82,8 @@ jobs: - name: Upload APK id: upload env: - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} run: | APK_PATH="$(pwd)/apps/e2e/android/app/build/outputs/apk/release/app-release.apk" @@ -178,8 +178,8 @@ jobs: - name: Upload test suite to Browserstack id: upload env: - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} run: | ZIP_NAME="maestro_tests.zip" @@ -211,9 +211,9 @@ jobs: with: app_url: ${{ needs.build-android.outputs.app_url }} test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} - project_name: ${{ vars.BROWSERSTACK_PROJECT_NAME }} - browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME }} - browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + project_name: ${{ vars.BROWSERSTACK_PROJECT_NAME_TESTS }} + browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} + browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} # TODO: Adjust based on actual timing timeout: 1800 devices: | @@ -258,9 +258,9 @@ jobs: with: app_url: ${{ needs.build-ios.outputs.app_url }} test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} - project_name: ${{ vars.BROWSERSTACK_PROJECT_NAME }} - browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME }} - browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + project_name: ${{ vars.BROWSERSTACK_PROJECT_NAME_TESTS }} + browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} + browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} # TODO: Adjust based on actual timing timeout: 1800 devices: | From ea4303c98743991dbf770e4b016b577f877b5317 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 12:31:16 +0100 Subject: [PATCH 14/34] fix missing npm install step for build-ios --- .github/workflows/e2e-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 67549db..c575889 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -128,6 +128,11 @@ jobs: if: steps.cache-libnode.outputs.cache-hit != 'true' run: ./scripts/download-nodejs-mobile.sh + - name: Install npm dependencies + run: | + npm install --ignore-scripts + npx patch-package + - name: Build backend bundle run: npm run backend:build From 2ff9c2a0b443b0a4e82867b3e15c1d03c0e4ba85 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 12:33:05 +0100 Subject: [PATCH 15/34] fix e2e app setup step for build-ios --- .github/workflows/e2e-tests.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index c575889..b92c22b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -136,9 +136,12 @@ jobs: - name: Build backend bundle run: npm run backend:build - - name: Expo prebuild + - name: Set up E2E app working-directory: apps/e2e - run: npx expo prebuild --platform ios + run: | + npm install --ignore-scripts + npx patch-package + npx expo prebuild --platform ios - name: Build .ipa working-directory: apps/e2e/ios From e8ab704d28b9b2387fdf023514d1b82adf8b5122 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 12:35:24 +0100 Subject: [PATCH 16/34] rename flow file --- .github/workflows/e2e-tests.yml | 2 +- maestro/{e2e.yml => e2e.yaml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename maestro/{e2e.yml => e2e.yaml} (100%) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index b92c22b..4d287ea 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -191,7 +191,7 @@ jobs: run: | ZIP_NAME="maestro_tests.zip" - zip -r "$ZIP_NAME" maestro/e2e.yml + zip -r "$ZIP_NAME" maestro/e2e.yaml ZIP_PATH="$(pwd)/$ZIP_NAME" diff --git a/maestro/e2e.yml b/maestro/e2e.yaml similarity index 100% rename from maestro/e2e.yml rename to maestro/e2e.yaml From 1762251db93fa107cdc3a6285a2c26718bda24d4 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 12:43:10 +0100 Subject: [PATCH 17/34] maybe fix -F curl usage --- .github/workflows/e2e-tests.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 4d287ea..ac7cd2d 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -85,11 +85,11 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} run: | - APK_PATH="$(pwd)/apps/e2e/android/app/build/outputs/apk/release/app-release.apk" + APK_RELATIVE_PATH="apps/e2e/android/app/build/outputs/apk/release/app-release.apk" APP_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ - -F "file=@$APK_PATH" | jq -r '.app_url') + -F "file=@$APK_RELATIVE_PATH" | jq -r '.app_url') echo "app_url=$APP_URL" >> $GITHUB_OUTPUT @@ -161,11 +161,11 @@ jobs: - name: Upload id: upload run: | - IPA_PATH="$(pwd)/apps/e2e/ios/corereactnativee2e.ipa" + IPA_RELATIVE_PATH="apps/e2e/ios/corereactnativee2e.ipa" APP_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ - -F "file=@$IPA_PATH" | jq -r '.app_url') + -F "file=@$IPA_RELATIVE_PATH" | jq -r '.app_url') echo "app_url=$APP_URL" >> $GITHUB_OUTPUT @@ -193,11 +193,9 @@ jobs: zip -r "$ZIP_NAME" maestro/e2e.yaml - ZIP_PATH="$(pwd)/$ZIP_NAME" - TEST_SUITE_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/test-suite" \ - -F "file=@$ZIP_PATH" | jq -r '.test_suite_url') + -F "file=@$ZIP_NAME" | jq -r '.test_suite_url') echo "test_suite_url=$TEST_SUITE_URL" >> $GITHUB_OUTPUT From 4c6bcd18be84d63fb8d4928c60037448b9b2b3d0 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 12:50:23 +0100 Subject: [PATCH 18/34] fix build-android --- .github/workflows/e2e-tests.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index ac7cd2d..f14355d 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -55,17 +55,16 @@ jobs: if: steps.cache-libnode.outputs.cache-hit != 'true' run: ./scripts/download-nodejs-mobile.sh + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Install npm dependencies run: | npm install --ignore-scripts npx patch-package - - name: Expo prebuild (generates apps/e2e/android) - working-directory: apps/e2e - run: npx expo prebuild --platform android --no-install - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 + - name: Build backend bundle + run: npm run backend:build - name: Set up E2E app working-directory: apps/e2e From e5fe3ff6edb90f88048c22318dc6b413fff4035a Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 12:56:36 +0100 Subject: [PATCH 19/34] improve curl calls --- .github/workflows/e2e-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index f14355d..8f90c4f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -86,7 +86,7 @@ jobs: run: | APK_RELATIVE_PATH="apps/e2e/android/app/build/outputs/apk/release/app-release.apk" - APP_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + APP_URL=$(curl --fail --show-error -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ -F "file=@$APK_RELATIVE_PATH" | jq -r '.app_url') @@ -162,7 +162,7 @@ jobs: run: | IPA_RELATIVE_PATH="apps/e2e/ios/corereactnativee2e.ipa" - APP_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + APP_URL=$(curl --fail --show-error -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ -F "file=@$IPA_RELATIVE_PATH" | jq -r '.app_url') @@ -192,7 +192,7 @@ jobs: zip -r "$ZIP_NAME" maestro/e2e.yaml - TEST_SUITE_URL=$(curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + TEST_SUITE_URL=$(curl --fail --show-error -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/test-suite" \ -F "file=@$ZIP_NAME" | jq -r '.test_suite_url') From 101e0ce600ee265a5e676f57fa8206f3c03430b5 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 13:01:35 +0100 Subject: [PATCH 20/34] fix upload step in build-ios --- .github/workflows/e2e-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 8f90c4f..efd244b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -159,6 +159,9 @@ jobs: - name: Upload id: upload + env: + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} run: | IPA_RELATIVE_PATH="apps/e2e/ios/corereactnativee2e.ipa" From 6254152e35e08d4676089ce7471b719fb8be74e3 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 13:02:50 +0100 Subject: [PATCH 21/34] add commented out needs spec to upload-test-suite --- .github/workflows/e2e-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index efd244b..967ac1f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -174,6 +174,8 @@ jobs: upload-test-suite: if: ${{ github.event.pull_request.draft == false }} name: Upload test suite + # TODO: Enable when we know this job works + # needs: [build-android, build-ios] runs-on: ubuntu-latest permissions: contents: read From 3dbcd6c20088cea0cc33ca900f06799fe8b9261d Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 13:09:41 +0100 Subject: [PATCH 22/34] rename zip file used for upload --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 967ac1f..fc50f59 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -193,7 +193,7 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} run: | - ZIP_NAME="maestro_tests.zip" + ZIP_NAME="flows.zip" zip -r "$ZIP_NAME" maestro/e2e.yaml From 93c6f6183d73750f29536d3f34486354b19cecdc Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 13:58:45 +0100 Subject: [PATCH 23/34] curl fixes --- .github/actions/run-browserstack-maestro/action.yml | 4 ++-- .github/workflows/e2e-tests.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/run-browserstack-maestro/action.yml b/.github/actions/run-browserstack-maestro/action.yml index a39db49..511fe72 100644 --- a/.github/actions/run-browserstack-maestro/action.yml +++ b/.github/actions/run-browserstack-maestro/action.yml @@ -54,7 +54,7 @@ runs: --arg project "$PROJECT_NAME" \ '{app: $app, testSuite: $suite, devices: $devices, project: $project, deviceLogs: true, maestroVersion: "latest"}') - BUILD_ID=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + BUILD_ID=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/build" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" \ @@ -76,7 +76,7 @@ runs: ELAPSED=0 while [ "$ELAPSED" -lt "$MAX_WAIT" ]; do - RESPONSE=$(curl -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + RESPONSE=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ "https://api-cloud.browserstack.com/app-automate/maestro/v2/builds/$BUILD_ID") STATUS=$(echo "$RESPONSE" | jq -r '.status') diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index fc50f59..eaf77e4 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -86,7 +86,7 @@ jobs: run: | APK_RELATIVE_PATH="apps/e2e/android/app/build/outputs/apk/release/app-release.apk" - APP_URL=$(curl --fail --show-error -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + APP_URL=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ -F "file=@$APK_RELATIVE_PATH" | jq -r '.app_url') @@ -165,7 +165,7 @@ jobs: run: | IPA_RELATIVE_PATH="apps/e2e/ios/corereactnativee2e.ipa" - APP_URL=$(curl --fail --show-error -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + APP_URL=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ -F "file=@$IPA_RELATIVE_PATH" | jq -r '.app_url') @@ -197,7 +197,7 @@ jobs: zip -r "$ZIP_NAME" maestro/e2e.yaml - TEST_SUITE_URL=$(curl --fail --show-error -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ + TEST_SUITE_URL=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/test-suite" \ -F "file=@$ZIP_NAME" | jq -r '.test_suite_url') From b01826bfd364bac1a80eac71dc989c24d1fd8f9c Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 13:59:24 +0100 Subject: [PATCH 24/34] fix caching of nodejs-mobile for ios --- .github/workflows/e2e-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index eaf77e4..ea2c974 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -114,14 +114,14 @@ jobs: node-version-file: package.json - name: Cache nodejs-mobile binaries - id: cache-libnode + id: cache-ios-framework uses: actions/cache@v4 with: - path: android/libnode + path: ios/NodeMobile.xcframework # hashFiles(...) folds the BASE_URL (defined inside the # download script) into the cache key, so any swap of the # source repo automatically invalidates stale caches. - key: nodejs-mobile-${{ env.NODEJS_MOBILE_VERSION }}-android-${{ hashFiles('scripts/download-nodejs-mobile.sh') }} + key: nodejs-mobile-${{ env.NODEJS_MOBILE_VERSION }}-ios-${{ hashFiles('scripts/download-nodejs-mobile.sh') }} - name: Download nodejs-mobile binaries if: steps.cache-libnode.outputs.cache-hit != 'true' From 0d72bc1568efb3fa635540c120de05c70c3c899d Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 14:09:02 +0100 Subject: [PATCH 25/34] minor fixup --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index ea2c974..c3e0e6c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -84,7 +84,7 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} run: | - APK_RELATIVE_PATH="apps/e2e/android/app/build/outputs/apk/release/app-release.apk" + APK_RELATIVE_PATH="./apps/e2e/android/app/build/outputs/apk/release/app-release.apk" APP_URL=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ @@ -163,7 +163,7 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} run: | - IPA_RELATIVE_PATH="apps/e2e/ios/corereactnativee2e.ipa" + IPA_RELATIVE_PATH="./apps/e2e/ios/corereactnativee2e.ipa" APP_URL=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ From 44335e7df19f530e01a62fe7019d0158cbf14e12 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 14:28:46 +0100 Subject: [PATCH 26/34] limit devices used due to browserstack plan limitations --- .github/workflows/e2e-tests.yml | 37 +++++++++++---------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index c3e0e6c..98b8100 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -226,28 +226,16 @@ jobs: browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} # TODO: Adjust based on actual timing timeout: 1800 - devices: | + devices: >- [ - "Google Pixel 5-11.0", - "Google Pixel 9-16.0", - "Huawei P30-9.0", - "Huawei Nova 11 SE-12.0", - "Motorola Moto G9 Play-10.0", - "Motorola Moto G71 5G-11.0", - "OnePlus 9-11.0", - "OnePlus 11R-13.0", - "OnePlus 12R-14.0", - "OnePlus 13R-15.0", - "Oppo Reno 3 Pro-10.0", - "Oppo Reno 6-11.0", - "Samsung Galaxy Note 9-8.1", - "Samsung Galaxy S10-9.0", - "Samsung Galaxy A51-10.0", - "Samsung Galaxy S23-13.0", - "Vivo V21-11.0", - "Vivo Y21-11.0", - "Xiaomi Redmi Note 9-10.0", - "Xiaomi Redmi Note 11-11.0" + "Google Pixel 9-16.0", + "Huawei Nova 11 SE-12.0", + "Motorola Moto G71 5G-11.0", + "OnePlus 13R-15.0", + "Oppo Reno 6-11.0", + "Samsung Galaxy Note 9-8.1", + "Vivo Y21-11.0", + "Xiaomi Redmi Note 11-11.0" ] test-ios: @@ -273,9 +261,8 @@ jobs: browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} # TODO: Adjust based on actual timing timeout: 1800 - devices: | + devices: >- [ - "iPhone 13-15", - "iPhone 15-17", - "iPhone 17-26" + "iPhone 13-15", + "iPhone 17-26" ] From f9cebcf4a96cfad0abbeb8d3519a5581a5090e11 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 14:46:21 +0100 Subject: [PATCH 27/34] browserstack project adjustements --- .github/actions/run-browserstack-maestro/action.yml | 8 ++++---- .github/workflows/e2e-tests.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/run-browserstack-maestro/action.yml b/.github/actions/run-browserstack-maestro/action.yml index 511fe72..aa2c68f 100644 --- a/.github/actions/run-browserstack-maestro/action.yml +++ b/.github/actions/run-browserstack-maestro/action.yml @@ -12,8 +12,8 @@ inputs: devices: description: JSON array of device strings required: true - project_name: - description: BrowserStack project name + browserstack_project: + description: BrowserStack project required: true browserstack_username: description: BrowserStack username @@ -42,7 +42,7 @@ runs: APP_URL: ${{ inputs.app_url }} TEST_SUITE_URL: ${{ inputs.test_suite_url }} DEVICES: ${{ inputs.devices }} - PROJECT_NAME: ${{ inputs.project_name }} + BROWSERSTACK_PROJECT: ${{ inputs.browserstack_project }} BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} shell: bash @@ -51,7 +51,7 @@ runs: --arg app "$APP_URL" \ --arg suite "$TEST_SUITE_URL" \ --argjson devices "$DEVICES" \ - --arg project "$PROJECT_NAME" \ + --arg project "$BROWSERSTACK_PROJECT" \ '{app: $app, testSuite: $suite, devices: $devices, project: $project, deviceLogs: true, maestroVersion: "latest"}') BUILD_ID=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 98b8100..702c3d9 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -221,7 +221,7 @@ jobs: with: app_url: ${{ needs.build-android.outputs.app_url }} test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} - project_name: ${{ vars.BROWSERSTACK_PROJECT_NAME_TESTS }} + browserstack_project: ${{ vars.BROWSERSTACK_PROJECT_TESTS }} browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} # TODO: Adjust based on actual timing @@ -256,7 +256,7 @@ jobs: with: app_url: ${{ needs.build-ios.outputs.app_url }} test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} - project_name: ${{ vars.BROWSERSTACK_PROJECT_NAME_TESTS }} + browserstack_project: ${{ vars.BROWSERSTACK_PROJECT_TESTS }} browserstack_username: ${{ secrets.BROWSERSTACK_USERNAME_TESTS }} browserstack_access_key: ${{ secrets.BROWSERSTACK_ACCESS_KEY_TESTS }} # TODO: Adjust based on actual timing From 490d6f7ffa323a40710e12f90a06b5f67330e75f Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 15:26:18 +0100 Subject: [PATCH 28/34] fix url used when executing build --- .../run-browserstack-maestro/action.yml | 21 +++++++++++++++---- .github/workflows/e2e-tests.yml | 2 ++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/actions/run-browserstack-maestro/action.yml b/.github/actions/run-browserstack-maestro/action.yml index aa2c68f..b8b5868 100644 --- a/.github/actions/run-browserstack-maestro/action.yml +++ b/.github/actions/run-browserstack-maestro/action.yml @@ -3,6 +3,9 @@ name: Run BrowserStack Maestro Tests description: Triggers a BrowserStack Maestro build and polls until completion inputs: + platform: + description: Platform to run tests on (android or ios) + required: true app_url: description: BrowserStack app URL (bs://...) required: true @@ -30,11 +33,20 @@ runs: using: composite steps: - name: Validate inputs - if: ${{ fromJson(inputs.timeout) < 30 }} + env: + PLATFORM: ${{ inputs.platform }} + TIMEOUT: ${{ fromJson(inputs.timeout) }} shell: bash run: | - echo "timeout must be at least 30 seconds" - exit 1 + if [[ "$PLATFORM" != "android" && "$PLATFORM" != "ios" ]]; then + echo "platform must be android or ios" + exit 1 + fi + + if [[ "$TIMEOUT" -lt 30 ]]; then + echo "timeout must be at least 30 seconds" + exit 1 + fi - name: Trigger build id: trigger @@ -45,6 +57,7 @@ runs: BROWSERSTACK_PROJECT: ${{ inputs.browserstack_project }} BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} + PLATFORM: ${{ inputs.platform }} shell: bash run: | PAYLOAD=$(jq -n \ @@ -55,7 +68,7 @@ runs: '{app: $app, testSuite: $suite, devices: $devices, project: $project, deviceLogs: true, maestroVersion: "latest"}') BUILD_ID=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ - -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/build" \ + -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/$PLATFORM/build" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" \ | jq -r '.build_id') diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 702c3d9..90057cd 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -219,6 +219,7 @@ jobs: - uses: ./.github/actions/run-browserstack-maestro with: + platform: android app_url: ${{ needs.build-android.outputs.app_url }} test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} browserstack_project: ${{ vars.BROWSERSTACK_PROJECT_TESTS }} @@ -254,6 +255,7 @@ jobs: - uses: ./.github/actions/run-browserstack-maestro with: + platform: android app_url: ${{ needs.build-ios.outputs.app_url }} test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} browserstack_project: ${{ vars.BROWSERSTACK_PROJECT_TESTS }} From ec186be85c9d7f5c32eb19b8494d0067618b3c9f Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 15:41:04 +0100 Subject: [PATCH 29/34] fix dumb mistake --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 90057cd..4de4978 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -255,7 +255,7 @@ jobs: - uses: ./.github/actions/run-browserstack-maestro with: - platform: android + platform: ios app_url: ${{ needs.build-ios.outputs.app_url }} test_suite_url: ${{ needs.upload-test-suite.outputs.test_suite_url }} browserstack_project: ${{ vars.BROWSERSTACK_PROJECT_TESTS }} From fd2ee6948bdaacaa1571f02a0ea26674224d3559 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 15:44:05 +0100 Subject: [PATCH 30/34] update devices --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 4de4978..95618e7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -265,6 +265,6 @@ jobs: timeout: 1800 devices: >- [ - "iPhone 13-15", + "iPhone 15-17", "iPhone 17-26" ] From 557529eee759a141af57a255a16c074c99b7b66a Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 15:58:01 +0100 Subject: [PATCH 31/34] increase timeout for flow in CI --- .github/actions/run-browserstack-maestro/action.yml | 12 +++++++++++- maestro/e2e.yaml | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/actions/run-browserstack-maestro/action.yml b/.github/actions/run-browserstack-maestro/action.yml index b8b5868..c35e41e 100644 --- a/.github/actions/run-browserstack-maestro/action.yml +++ b/.github/actions/run-browserstack-maestro/action.yml @@ -58,6 +58,7 @@ runs: BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} PLATFORM: ${{ inputs.platform }} + TIMEOUT: 60000 shell: bash run: | PAYLOAD=$(jq -n \ @@ -65,7 +66,16 @@ runs: --arg suite "$TEST_SUITE_URL" \ --argjson devices "$DEVICES" \ --arg project "$BROWSERSTACK_PROJECT" \ - '{app: $app, testSuite: $suite, devices: $devices, project: $project, deviceLogs: true, maestroVersion: "latest"}') + '{ + app: $app, + testSuite: $suite, + devices: $devices, + project: $project, + setEnvVariables: { "TIMEOUT": $TIMEOUT }, + deviceLogs: true, + maestroVersion: "latest" + }' + ) BUILD_ID=$(curl --fail --show-error -s -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/maestro/v2/$PLATFORM/build" \ diff --git a/maestro/e2e.yaml b/maestro/e2e.yaml index e864a84..8f50998 100644 --- a/maestro/e2e.yaml +++ b/maestro/e2e.yaml @@ -1,4 +1,6 @@ appId: com.comapeo.core.e2e +env: + TIMEOUT: ${TIMEOUT || 10000} --- - launchApp: clearState: true @@ -8,4 +10,4 @@ appId: com.comapeo.core.e2e - extendedWaitUntil: visible: id: "all-tests-passed" - timeout: 10000 + timeout: ${TIMEOUT} From f9a6b4765badd30151c0f1296798e0a5aba52502 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 16:14:22 +0100 Subject: [PATCH 32/34] fix timeout interpolation --- .github/actions/run-browserstack-maestro/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/run-browserstack-maestro/action.yml b/.github/actions/run-browserstack-maestro/action.yml index c35e41e..fb04d56 100644 --- a/.github/actions/run-browserstack-maestro/action.yml +++ b/.github/actions/run-browserstack-maestro/action.yml @@ -66,12 +66,13 @@ runs: --arg suite "$TEST_SUITE_URL" \ --argjson devices "$DEVICES" \ --arg project "$BROWSERSTACK_PROJECT" \ + --argjson timeout "$TIMEOUT" \ '{ app: $app, testSuite: $suite, devices: $devices, project: $project, - setEnvVariables: { "TIMEOUT": $TIMEOUT }, + setEnvVariables: { "TIMEOUT": $timeout }, deviceLogs: true, maestroVersion: "latest" }' From 4b7fe32983c452693f2b25e20277e06564c7ab76 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 17:29:30 +0100 Subject: [PATCH 33/34] stick with (larger) hardcoded timeout for now --- .github/actions/run-browserstack-maestro/action.yml | 3 --- maestro/e2e.yaml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/actions/run-browserstack-maestro/action.yml b/.github/actions/run-browserstack-maestro/action.yml index fb04d56..8725865 100644 --- a/.github/actions/run-browserstack-maestro/action.yml +++ b/.github/actions/run-browserstack-maestro/action.yml @@ -58,7 +58,6 @@ runs: BROWSERSTACK_USERNAME: ${{ inputs.browserstack_username }} BROWSERSTACK_ACCESS_KEY: ${{ inputs.browserstack_access_key }} PLATFORM: ${{ inputs.platform }} - TIMEOUT: 60000 shell: bash run: | PAYLOAD=$(jq -n \ @@ -66,13 +65,11 @@ runs: --arg suite "$TEST_SUITE_URL" \ --argjson devices "$DEVICES" \ --arg project "$BROWSERSTACK_PROJECT" \ - --argjson timeout "$TIMEOUT" \ '{ app: $app, testSuite: $suite, devices: $devices, project: $project, - setEnvVariables: { "TIMEOUT": $timeout }, deviceLogs: true, maestroVersion: "latest" }' diff --git a/maestro/e2e.yaml b/maestro/e2e.yaml index 8f50998..9343edb 100644 --- a/maestro/e2e.yaml +++ b/maestro/e2e.yaml @@ -1,6 +1,6 @@ appId: com.comapeo.core.e2e env: - TIMEOUT: ${TIMEOUT || 10000} + TIMEOUT: 30000 --- - launchApp: clearState: true From 132f4146bbd9da6eff8ba46d39391d77cc9abcc1 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 7 May 2026 17:53:25 +0100 Subject: [PATCH 34/34] remove startRecording command --- .gitignore | 3 --- maestro/e2e.yaml | 2 -- 2 files changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index e3687e8..cdfe0ae 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,3 @@ build/ # eslint .eslintcache - -# Maestro artifacts -maestro/recordings/ diff --git a/maestro/e2e.yaml b/maestro/e2e.yaml index 9343edb..9ac2754 100644 --- a/maestro/e2e.yaml +++ b/maestro/e2e.yaml @@ -4,8 +4,6 @@ env: --- - launchApp: clearState: true -- startRecording: - path: "maestro/recordings/e2e" - tapOn: "Run tests" - extendedWaitUntil: visible: