diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b1ebad..3d75355 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: @@ -94,15 +95,25 @@ 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: | + ./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 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-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 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 \