From e89bafcbfb53631c942170f525c840235f528e66 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 16:56:58 +0200 Subject: [PATCH 1/5] Use ATD Android emulator in CI --- .github/workflows/test.yml | 18 ++- example-app/scripts/run-maestro-android-ci.sh | 153 ------------------ 2 files changed, 15 insertions(+), 156 deletions(-) delete mode 100755 example-app/scripts/run-maestro-android-ci.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b1ebad..0bc2827 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,15 +94,27 @@ jobs: 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: Setup Android SDK tools - uses: android-actions/setup-android@v3 - name: Install Maestro CLI run: | export MAESTRO_VERSION=2.4.0 curl -Ls "https://get.maestro.mobile.dev" | bash echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" - name: Run Android Maestro flows - run: cd example-app && ./scripts/run-maestro-android-ci.sh + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 34 + target: google_atd + arch: x86_64 + profile: pixel_6 + emulator-boot-timeout: 900 + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none + disable-animations: true + script: | + cd example-app/android + ./gradlew assembleDebug + adb install -r app/build/outputs/apk/debug/app-debug.apk + cd .. + bun run maestro:android - name: Upload Android Maestro artifacts if: always() uses: actions/upload-artifact@v4 diff --git a/example-app/scripts/run-maestro-android-ci.sh b/example-app/scripts/run-maestro-android-ci.sh deleted file mode 100755 index c18a2ad..0000000 --- a/example-app/scripts/run-maestro-android-ci.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -ANDROID_DIR="$ROOT_DIR/android" -OUTPUT_DIR="$ROOT_DIR/build/maestro" -EMULATOR_LOG="$OUTPUT_DIR/android-emulator.log" - -ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-/usr/local/lib/android/sdk}}" - -ANDROID_API_LEVEL="${ANDROID_API_LEVEL:-34}" -ANDROID_EMULATOR_ARCH="${ANDROID_EMULATOR_ARCH:-x86_64}" -ANDROID_EMULATOR_DEVICE="${ANDROID_EMULATOR_DEVICE:-pixel_6}" -ANDROID_EMULATOR_NAME="${ANDROID_EMULATOR_NAME:-maestro-ci}" -ANDROID_EMULATOR_PORT="${ANDROID_EMULATOR_PORT:-5554}" -ANDROID_SERIAL="emulator-${ANDROID_EMULATOR_PORT}" -ANDROID_BOOT_TIMEOUT_SECONDS="${ANDROID_BOOT_TIMEOUT_SECONDS:-420}" - -mkdir -p "$OUTPUT_DIR" - -find_sdk_tool() { - local tool="$1" - local candidate - - if candidate="$(command -v "$tool" 2>/dev/null)"; then - echo "$candidate" - return 0 - fi - - for candidate in \ - "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/$tool" \ - "$ANDROID_SDK_ROOT/cmdline-tools"/*/bin/"$tool" \ - "$ANDROID_SDK_ROOT/emulator/$tool" \ - "$ANDROID_SDK_ROOT/platform-tools/$tool"; do - if [[ -x "$candidate" ]]; then - echo "$candidate" - return 0 - fi - done - - return 1 -} - -SDKMANAGER="$(find_sdk_tool sdkmanager || true)" -AVDMANAGER="$(find_sdk_tool avdmanager || true)" -EMULATOR_BIN="$(find_sdk_tool emulator || true)" -ADB_BIN="$(find_sdk_tool adb || true)" - -if [[ -z "$SDKMANAGER" || -z "$AVDMANAGER" || -z "$EMULATOR_BIN" || -z "$ADB_BIN" ]]; then - echo "Android SDK tools are missing under $ANDROID_SDK_ROOT." >&2 - exit 1 -fi - -install_system_image() { - local candidate - - yes | "$SDKMANAGER" --licenses >/dev/null 2>&1 || true - - for candidate in \ - "system-images;android-${ANDROID_API_LEVEL};google_atd;${ANDROID_EMULATOR_ARCH}" \ - "system-images;android-${ANDROID_API_LEVEL};google_apis;${ANDROID_EMULATOR_ARCH}"; do - if yes | "$SDKMANAGER" --install \ - "platform-tools" \ - "emulator" \ - "platforms;android-${ANDROID_API_LEVEL}" \ - "$candidate"; then - echo "$candidate" - return 0 - fi - done - - echo "Failed to install a compatible Android system image." >&2 - return 1 -} - -wait_for_boot() { - local deadline boot_completed boot_anim - deadline=$((SECONDS + ANDROID_BOOT_TIMEOUT_SECONDS)) - - "$ADB_BIN" start-server >/dev/null - "$ADB_BIN" -s "$ANDROID_SERIAL" wait-for-device - - while ((SECONDS < deadline)); do - boot_completed="$("$ADB_BIN" -s "$ANDROID_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" - boot_anim="$("$ADB_BIN" -s "$ANDROID_SERIAL" shell getprop init.svc.bootanim 2>/dev/null | tr -d '\r')" - - if [[ "$boot_completed" == "1" && "$boot_anim" == "stopped" ]]; then - return 0 - fi - - sleep 5 - done - - echo "Android emulator did not boot within ${ANDROID_BOOT_TIMEOUT_SECONDS}s." >&2 - return 1 -} - -wait_for_package_manager() { - local deadline - deadline=$((SECONDS + 120)) - - while ((SECONDS < deadline)); do - if "$ADB_BIN" -s "$ANDROID_SERIAL" shell pm path android >/dev/null 2>&1; then - return 0 - fi - sleep 2 - done - - echo "Android package manager did not become ready in time." >&2 - return 1 -} - -cleanup() { - "$ADB_BIN" -s "$ANDROID_SERIAL" emu kill >/dev/null 2>&1 || true -} -trap cleanup EXIT - -SYSTEM_IMAGE_PACKAGE="$(install_system_image)" -rm -rf "$HOME/.android/avd/${ANDROID_EMULATOR_NAME}.avd" "$HOME/.android/avd/${ANDROID_EMULATOR_NAME}.ini" -echo "no" | "$AVDMANAGER" create avd \ - --force \ - --name "$ANDROID_EMULATOR_NAME" \ - --package "$SYSTEM_IMAGE_PACKAGE" \ - --device "$ANDROID_EMULATOR_DEVICE" >/dev/null - -: >"$EMULATOR_LOG" -"$EMULATOR_BIN" \ - -avd "$ANDROID_EMULATOR_NAME" \ - -port "$ANDROID_EMULATOR_PORT" \ - -no-window \ - -gpu swiftshader_indirect \ - -no-snapshot \ - -noaudio \ - -no-boot-anim \ - -camera-back none \ - -camera-front none >"$EMULATOR_LOG" 2>&1 & - -wait_for_boot -wait_for_package_manager - -"$ADB_BIN" -s "$ANDROID_SERIAL" shell settings put global window_animation_scale 0 >/dev/null 2>&1 || true -"$ADB_BIN" -s "$ANDROID_SERIAL" shell settings put global transition_animation_scale 0 >/dev/null 2>&1 || true -"$ADB_BIN" -s "$ANDROID_SERIAL" shell settings put global animator_duration_scale 0 >/dev/null 2>&1 || true -"$ADB_BIN" -s "$ANDROID_SERIAL" shell wm dismiss-keyguard >/dev/null 2>&1 || true -"$ADB_BIN" -s "$ANDROID_SERIAL" shell input keyevent 82 >/dev/null 2>&1 || true - -cd "$ANDROID_DIR" -./gradlew assembleDebug -"$ADB_BIN" -s "$ANDROID_SERIAL" install -r app/build/outputs/apk/debug/app-debug.apk - -cd "$ROOT_DIR" -export ANDROID_SERIAL -bun run maestro:android From 0fdb790b32e7f2a59805ff9517c5f67f4bab0285 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 17:00:12 +0200 Subject: [PATCH 2/5] Retrigger PR CI From 989df4a51ecef6439d31b9323b684e58651a636b Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 17:01:45 +0200 Subject: [PATCH 3/5] Allow manual CI dispatch --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0bc2827..78a837d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,7 @@ on: - renovate/** pull_request: branches: [main] + workflow_dispatch: workflow_call: # Allow this workflow to be called by other workflows jobs: From c73b54951077bbf68d34ed1ca85c95e29a8e7d2f Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 17:10:29 +0200 Subject: [PATCH 4/5] Fix Android Maestro CI working directory --- .github/workflows/test.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78a837d..3d75355 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -111,11 +111,9 @@ jobs: emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none disable-animations: true script: | - cd example-app/android - ./gradlew assembleDebug - adb install -r app/build/outputs/apk/debug/app-debug.apk - cd .. - bun run maestro:android + ./example-app/android/gradlew -p ./example-app/android assembleDebug + adb install -r example-app/android/app/build/outputs/apk/debug/app-debug.apk + cd example-app && bun run maestro:android - name: Upload Android Maestro artifacts if: always() uses: actions/upload-artifact@v4 From 8c6026effec5d682f54798fa73c04598f9dd4e7c Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 17:22:25 +0200 Subject: [PATCH 5/5] Avoid Maestro Android port-forward conflict --- example-app/README.md | 2 +- example-app/scripts/run-maestro-android.sh | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/example-app/README.md b/example-app/README.md index 49a2c3b..1890874 100644 --- a/example-app/README.md +++ b/example-app/README.md @@ -41,7 +41,7 @@ bun run maestro:ios bun run maestro:android ``` -The Android script bootstraps Maestro's embedded driver APKs explicitly, starts the instrumentation runner, and forwards the gRPC bridge before running the flows. That makes local Android runs match CI instead of depending on Maestro's flaky auto-bootstrap behavior. +The Android script bootstraps Maestro's embedded driver APKs explicitly and starts the instrumentation runner before running the flows. The CLI still owns the ADB port-forward setup so local runs match CI without fighting Maestro's session setup. ### Native sync diff --git a/example-app/scripts/run-maestro-android.sh b/example-app/scripts/run-maestro-android.sh index 783fe05..3483333 100755 --- a/example-app/scripts/run-maestro-android.sh +++ b/example-app/scripts/run-maestro-android.sh @@ -63,10 +63,6 @@ if [[ "${DRIVER_READY:-0}" -ne 1 ]]; then exit 1 fi -adb -s "$DEVICE_ID" forward --remove tcp:7001 >/dev/null 2>&1 || true -adb -s "$DEVICE_ID" forward tcp:7001 tcp:7001 >/dev/null -sleep 2 - JAVA_TOOL_OPTIONS=-Djava.net.preferIPv4Stack=true maestro test \ --no-reinstall-driver \ -p android \