diff --git a/.github/actions/android-sdk-packages/action.yml b/.github/actions/android-sdk-packages/action.yml new file mode 100644 index 0000000000..a65482a565 --- /dev/null +++ b/.github/actions/android-sdk-packages/action.yml @@ -0,0 +1,29 @@ +name: Android SDK packages +description: >- + Installs the SDK packages reactivecircus/android-emulator-runner would download, with retries. + The action downloads the emulator and the system image on every run and doesn't retry: a + corrupted download ("Error reading Zip content from a SeekableByteChannel") fails the job before + the emulator boots. sdkmanager skips packages that are installed and current, so the action's own + install becomes a no-op. +inputs: + api-level: + description: The system image API level, as passed to android-emulator-runner + required: true +runs: + using: composite + steps: + - name: Install the emulator and the system image + shell: bash + env: + API_LEVEL: ${{ inputs.api-level }} + run: | + sdkmanager="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" + packages=(emulator platform-tools "platforms;android-$API_LEVEL" "system-images;android-$API_LEVEL;default;x86_64") + yes | "$sdkmanager" --licenses > /dev/null || true + for attempt in 1 2 3; do + "$sdkmanager" --install "${packages[@]}" --channel=0 > /dev/null && exit 0 + echo "::warning::sdkmanager failed (attempt $attempt of 3); retrying in 30 s" + sleep 30 + done + echo "::error::Could not install ${packages[*]}" + exit 1 diff --git a/.github/workflows/e2e-test-cron.yml b/.github/workflows/e2e-test-cron.yml index 8af4ead87a..bf6ce67f9b 100644 --- a/.github/workflows/e2e-test-cron.yml +++ b/.github/workflows/e2e-test-cron.yml @@ -352,6 +352,10 @@ jobs: working-directory: examples/SampleApp run: bundle exec fastlane allure_launch cron:true continue-on-error: true + # The emulator runner downloads these without retrying; a corrupted download fails the job. + - uses: ./.github/actions/android-sdk-packages + with: + api-level: ${{ matrix.api-level }} - name: Run e2e tests (Android emulator) id: tests uses: reactivecircus/android-emulator-runner@v2 @@ -362,7 +366,10 @@ jobs: emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} disable-animations: true working-directory: examples/SampleApp - script: bundle exec fastlane test_e2e platform:android device:att skip_build:true mock_server_branch:"$MOCK_SERVER_BRANCH" coverage:${{ matrix.coverage == true }} batch:${{ matrix.batch }} batch_count:${{ matrix.batch_count }} + # Logcat is written on the runner, so it survives the emulator dying mid-run (RN-513). + script: | + nohup "$ANDROID_HOME/platform-tools/adb" -s emulator-5554 logcat -b all -v threadtime > "$RUNNER_TEMP/logcat.txt" 2>&1 < /dev/null & + bundle exec fastlane test_e2e platform:android device:att skip_build:true mock_server_branch:"$MOCK_SERVER_BRANCH" coverage:${{ matrix.coverage == true }} batch:${{ matrix.batch }} batch_count:${{ matrix.batch_count }} # Also when tests failed: those keep their coverage from the previous map. Not when they never # ran: a batch without its failed-tests file keeps the whole previous map. @@ -407,6 +414,7 @@ jobs: examples/E2ETestApp/stream-chat-test-mock-server/logs examples/E2ETestApp/artifacts /tmp/android-runner + ${{ runner.temp }}/logcat.txt coverage_map: name: Build e2e coverage map diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 4a7af02577..398fe3e59b 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -386,6 +386,10 @@ jobs: with: name: e2e-selection path: examples/E2ETestApp + # The emulator runner downloads these without retrying; a corrupted download fails the job. + - uses: ./.github/actions/android-sdk-packages + with: + api-level: ${{ env.ANDROID_API_LEVEL }} - name: Run e2e tests (Android emulator) uses: reactivecircus/android-emulator-runner@v2 with: diff --git a/examples/E2ETestApp/e2e/robots/UserRobot.ts b/examples/E2ETestApp/e2e/robots/UserRobot.ts index 3f1a115e4c..26b206445f 100644 --- a/examples/E2ETestApp/e2e/robots/UserRobot.ts +++ b/examples/E2ETestApp/e2e/robots/UserRobot.ts @@ -57,9 +57,10 @@ const tapInContextMenu = async (selector: Detox.NativeMatcher) => { }; /** - * Long-presses a message until its menu opens. iOS runs without Detox's idle sync, so a long press - * that lands while the list is still settling after a scroll is cancelled by the movement, and the - * menu never opens. `target` is re-resolved on each attempt, since the frames may have moved. + * Long-presses a message until its menu opens. A long press that lands while the list is still + * settling after a scroll is cancelled by the movement, and the menu never opens: on iOS, which runs + * without Detox's idle sync, and now and then on Android too. `target` is re-resolved on each + * attempt, since the frames may have moved. */ const longPressUntilMenuOpens = async ( target: () => Promise<{ index: number; selector: Detox.NativeMatcher }>, @@ -68,11 +69,7 @@ const longPressUntilMenuOpens = async ( for (let attempt = 1; ; attempt++) { const { index, selector } = await target(); await waitToAppearAndLongPress(selector, { index }); - if ( - device.getPlatform() !== 'ios' || - attempt >= attempts || - (await waitForExistence(ContextMenu.backdrop, { timeout: 3000 })) - ) { + if (attempt >= attempts || (await waitForExistence(ContextMenu.backdrop, { timeout: 3000 }))) { return; } }