From b0ebe8c7d36f5308bbf7e2be7c3566ef26ac3e9c Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 12 Apr 2026 11:26:23 +0100 Subject: [PATCH] Switch Android emulator tests to emulator.wtf with OIDC auth Replace the local Android emulator runner (reactivecircus/android-emulator-runner) with emulator.wtf cloud-based testing. This removes the need for KVM setup, AVD caching, and local emulator management in CI. - Use emulator-wtf/configure-credentials@v1 with OIDC (no secrets needed) - Run tests for android-test, android-test-app, and regression-test separately - Add emulator.wtf CLI instructions to android-test README - Keep existing API level matrix (21, 23, 29, 34) --- .github/workflows/build.yml | 80 +++++++++++--------------- android-test/src/androidTest/README.md | 67 ++++++++++++--------- 2 files changed, 73 insertions(+), 74 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 420d94959687..17a368b47df4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -295,6 +295,9 @@ jobs: android: runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: read + id-token: write strategy: fail-fast: false @@ -315,62 +318,43 @@ jobs: distribution: 'temurin' java-version: 21 - - name: Enable KVM group perms - # https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: Verify KVM - run: | - sudo apt-get install -y cpu-checker - kvm-ok || echo "KVM is not accelerated" - kvm-ok || exit 1 - - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 - - name: Gradle cache - run: ./gradlew :android-test:test + - name: Build Android APKs + run: > + ./gradlew -PandroidBuild=true + :android-test:assembleDebugAndroidTest + :android-test-app:assembleDebug + :android-test-app:assembleDebugAndroidTest + :regression-test:assembleDebugAndroidTest - - name: AVD System Image Cache - uses: actions/cache@v5 - id: avd-cache + - name: Configure emulator.wtf credentials + uses: emulator-wtf/configure-credentials@v1 with: - key: avd-${{ runner.os }}-${{ matrix.api-level }}-${{ matrix.api-level >= 30 && 'x86_64' || 'x86' }} - path: | - ~/.android/avd/* - ~/.android/adb* - # Added the actual system image path - ${{ env.ANDROID_HOME }}/system-images/android-${{ matrix.api-level }} - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 + oidc-configuration-id: # TODO: add OIDC configuration ID from emulator.wtf + + - name: Run android-test instrumented tests + uses: emulator-wtf/run-tests@v0 with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - arch: ${{ matrix.api-level >= 30 && 'x86_64' || 'x86' }} - # No window, no audio, and use swiftshader for headless environments - emulator-options: > - -no-window - -gpu swiftshader_indirect - -noaudio - -no-boot-anim - -camera-back none - -memory 2048 - disable-animations: true - script: echo "Generated AVD snapshot for caching." + test: android-test/build/outputs/apk/androidTest/debug/android-test-debug-androidTest.apk + devices: version=${{ matrix.api-level }} + outputs-dir: build/test-results/android-test - - name: Run Tests - uses: reactivecircus/android-emulator-runner@v2 + - name: Run android-test-app instrumented tests + uses: emulator-wtf/run-tests@v0 with: - api-level: ${{ matrix.api-level }} - arch: ${{ matrix.api-level == '34' && 'x86_64' || 'x86' }} - script: ./gradlew -PandroidBuild=true connectedCheck - env: - API_LEVEL: ${{ matrix.api-level }} + app: android-test-app/build/outputs/apk/debug/android-test-app-debug.apk + test: android-test-app/build/outputs/apk/androidTest/debug/android-test-app-debug-androidTest.apk + devices: version=${{ matrix.api-level }} + outputs-dir: build/test-results/android-test-app + + - name: Run regression-test instrumented tests + uses: emulator-wtf/run-tests@v0 + with: + test: regression-test/build/outputs/apk/androidTest/debug/regression-test-debug-androidTest.apk + devices: version=${{ matrix.api-level }} + outputs-dir: build/test-results/regression-test - name: Build Release App run: ./gradlew android-test-app:lint android-test-app:assembleRelease diff --git a/android-test/src/androidTest/README.md b/android-test/src/androidTest/README.md index 8d485e22269a..31fc56ec580a 100644 --- a/android-test/src/androidTest/README.md +++ b/android-test/src/androidTest/README.md @@ -3,6 +3,45 @@ Android Test A gradle module for running Android instrumentation tests on a device or emulator. +## Running with emulator.wtf (recommended) + +[emulator.wtf](https://emulator.wtf) runs instrumented tests in the cloud without needing a local emulator. + +### Local CLI usage + +1. Install the ew-cli: + +``` +$ brew install emulator-wtf/tap/ew-cli +``` + +2. Authenticate (set your API token): + +``` +$ export EW_API_TOKEN=your-token-here +``` + +3. Build the test APK and run: + +``` +$ ./gradlew -PandroidBuild=true :android-test:assembleDebugAndroidTest +$ ew-cli \ + --test android-test/build/outputs/apk/androidTest/debug/android-test-debug-androidTest.apk \ + --devices version=29 \ + --outputs-dir build/test-results/android-test +``` + +You can test on multiple API levels by specifying additional `--devices`: + +``` +$ ew-cli \ + --test android-test/build/outputs/apk/androidTest/debug/android-test-debug-androidTest.apk \ + --devices version=21 --devices version=29 --devices version=34 \ + --outputs-dir build/test-results/android-test +``` + +## Running with a local emulator + 1. Add an Emulator named `pixel5`, if you don't already have one ``` @@ -16,40 +55,16 @@ $ echo "no" | avdmanager --verbose create avd --force --name "pixel5" --device " $ emulator -no-window -no-snapshot-load @pixel5 ``` -2. Turn on logs with logcat +3. Turn on logs with logcat ``` $ adb logcat '*:E' OkHttp:D Http2:D TestRunner:D TaskRunner:D OkHttpTest:D GnssHAL_GnssInterface:F DeviceStateChecker:F memtrack:F -... -01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] responseHeadersEnd: Response{protocol=h2, code=200, message=, url=https://1.1.1.1/dns-query?dns=AAABAAABAAAAAAAAA3d3dwhmYWNlYm9vawNjb20AABwAAQ} -01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] responseBodyStart -01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] responseBodyEnd: byteCount=128 -01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] connectionReleased -01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] callEnd -01-01 12:53:32.816 10999 11090 D OkHttp : [54 ms] responseHeadersStart -01-01 12:53:32.816 10999 11090 D OkHttp : [54 ms] responseHeadersEnd: Response{protocol=h2, code=200, message=, url=https://1.1.1.1/dns-query?dns=AAABAAABAAAAAAAAA3d3dwhmYWNlYm9vawNjb20AAAEAAQ} -01-01 12:53:32.817 10999 11090 D OkHttp : [55 ms] responseBodyStart -01-01 12:53:32.818 10999 11090 D OkHttp : [56 ms] responseBodyEnd: byteCount=128 -01-01 12:53:32.818 10999 11090 D OkHttp : [56 ms] connectionReleased -01-01 12:53:32.818 10999 11090 D OkHttp : [56 ms] callEnd ``` -3. Run tests using gradle +4. Run tests using gradle ``` $ ANDROID_SDK_ROOT=/Users/myusername/Library/Android/sdk ./gradlew :android-test:connectedCheck -PandroidBuild=true -... -> Task :android-test:connectedDebugAndroidTest -... -11:55:40 V/InstrumentationResultParser: Time: 13.271 -11:55:40 V/InstrumentationResultParser: -11:55:40 V/InstrumentationResultParser: OK (12 tests) -... -11:55:40 I/XmlResultReporter: XML test result file generated at /Users/myusername/workspace/okhttp/android-test/build/outputs/androidTest-results/connected/TEST-pixel3a-Q(AVD) - 10-android-test-.xml. Total tests 13, passed 11, assumption_failure 1, ignored 1, -... -BUILD SUCCESSFUL in 1m 30s -63 actionable tasks: 61 executed, 2 up-to-date - ``` n.b. use ANDROID_SERIAL=emulator-5554 or similar if you need to select between devices.