diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b1ebad..ceffafb 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: @@ -15,8 +16,6 @@ jobs: steps: - name: Check out uses: actions/checkout@v6 - with: - lfs: true - name: Enforce capacitor-swift-pm version run: | set -euo pipefail @@ -30,8 +29,6 @@ jobs: steps: - name: Check out uses: actions/checkout@v6 - with: - lfs: true - uses: oven-sh/setup-bun@v2 - name: Install dependencies id: install_code @@ -47,22 +44,33 @@ jobs: id: build_code run: bun run verify:android build_ios: - timeout-minutes: 30 + timeout-minutes: 90 runs-on: macOS-latest steps: - name: Check out uses: actions/checkout@v6 with: - lfs: true + submodules: recursive + - name: Setup Rust + uses: dtolnay/rust-toolchain@1.90.0 - uses: oven-sh/setup-bun@v2 - name: Install dependencies id: install_code run: bun i - name: Check plugin wiring run: bun run check:wiring + - name: Build native iOS core + run: cd rust && ./build_native_xcframework.sh - name: Build id: build_code run: bun run verify:ios && bun run test:macos + - name: Archive native iOS core artifact + run: tar -czf /tmp/native-ios-core-xcframework.tgz -C ios CapacitorFFmpegNativeCore.xcframework + - name: Upload native iOS core artifact + uses: actions/upload-artifact@v4 + with: + name: native-ios-core-xcframework + path: /tmp/native-ios-core-xcframework.tgz maestro_android: timeout-minutes: 60 runs-on: ubuntu-latest @@ -70,8 +78,6 @@ jobs: steps: - name: Check out uses: actions/checkout@v6 - with: - lfs: true - uses: oven-sh/setup-bun@v2 - uses: actions/setup-node@v4 with: @@ -89,20 +95,31 @@ jobs: with: distribution: 'zulu' java-version: '21' + - name: Build Android debug APK + run: ./example-app/android/gradlew -p ./example-app/android assembleDebug - name: Enable KVM access 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: 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: | + 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 @@ -122,8 +139,15 @@ jobs: steps: - name: Check out uses: actions/checkout@v6 + - name: Download native iOS core artifact + uses: actions/download-artifact@v4 with: - lfs: true + name: native-ios-core-xcframework + path: /tmp + - name: Restore native iOS core artifact + run: | + rm -rf ios/CapacitorFFmpegNativeCore.xcframework + tar -xzf /tmp/native-ios-core-xcframework.tgz -C ios - uses: oven-sh/setup-bun@v2 - uses: actions/setup-node@v4 with: @@ -183,8 +207,6 @@ jobs: steps: - name: Check out uses: actions/checkout@v6 - with: - lfs: true - uses: oven-sh/setup-bun@v2 - name: Install dependencies id: install_code diff --git a/example-app/.maestro/image-conversion.yaml b/example-app/.maestro/image-conversion.yaml index d6afa1c..42ba7f8 100644 --- a/example-app/.maestro/image-conversion.yaml +++ b/example-app/.maestro/image-conversion.yaml @@ -6,7 +6,9 @@ name: Convert image from picker - extendedWaitUntil: visible: "Load demo image" timeout: 60000 -- tapOn: "Load demo image" +- tapOn: + text: "Load demo image" + retryTapIfNoChange: true - extendedWaitUntil: visible: text: "sample-image.png staged in the app cache." @@ -20,10 +22,11 @@ name: Convert image from picker centerElement: true - tapOn: text: "Convert image" + retryTapIfNoChange: true - extendedWaitUntil: visible: - text: "Converted .* to (jpeg|webp).*" - timeout: 30000 + text: "Converted .* to (jpeg|png|webp).*" + timeout: 60000 - assertVisible: - text: "Converted .* to (jpeg|webp).*" + text: "Converted .* to (jpeg|png|webp).*" - takeScreenshot: "image-conversion-complete" diff --git a/example-app/.maestro/unsupported-reencode-android.yaml b/example-app/.maestro/unsupported-reencode-android.yaml index 9f39682..ef06c0d 100644 --- a/example-app/.maestro/unsupported-reencode-android.yaml +++ b/example-app/.maestro/unsupported-reencode-android.yaml @@ -4,18 +4,14 @@ name: Verify unsupported Android re-encode contract - stopApp - launchApp - extendedWaitUntil: - visible: "FFmpeg capability playground" + visible: "Run runtime checks" timeout: 30000 -- tapOn: "Show video workflow" +- tapOn: "Run runtime checks" - extendedWaitUntil: - visible: "Verify unsupported path" - timeout: 30000 -- tapOn: "Verify unsupported path" -- extendedWaitUntil: - visible: "Unsupported re-encode smoke test" + visible: "Unsupported re-encode contract" timeout: 30000 - assertVisible: - text: "Unsupported re-encode smoke test" + text: "Unsupported re-encode contract" - assertVisible: - text: "passed|Passed" + text: "reencodeVideo is currently only available on iOS." - takeScreenshot: "unsupported-reencode-android-complete" diff --git a/example-app/README.md b/example-app/README.md index 49a2c3b..bc4660d 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. Install Maestro CLI first before running `bun run maestro:android`; the script expects the local CLI artifacts under `$HOME/.maestro`. ### 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 \ diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ff100ed --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.90.0" diff --git a/rust/build_x264_ios_sim_arm64.sh b/rust/build_x264_ios_sim_arm64.sh index bdcaa53..b67f750 100755 --- a/rust/build_x264_ios_sim_arm64.sh +++ b/rust/build_x264_ios_sim_arm64.sh @@ -3,7 +3,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -X264_DIR="$SCRIPT_DIR/x264" +X264_DIR="$SCRIPT_DIR/rust/x264" OUTPUT_DIR="$SCRIPT_DIR/x264-build-ios-sim-arm64" echo "Building x264 for iOS Simulator ARM64..."