diff --git a/.github/actions/android-emulator/action.yml b/.github/actions/android-emulator/action.yml new file mode 100644 index 0000000000..e73d5a29d7 --- /dev/null +++ b/.github/actions/android-emulator/action.yml @@ -0,0 +1,86 @@ +name: 'Android Emulator' +description: 'Boot an Android emulator using reactivecircus/android-emulator-runner and run a script while it is active.' +inputs: + api-level: + description: 'Android API level to use.' + required: false + default: '30' + target: + description: 'System image target (for example: default or google_apis).' + required: false + default: 'default' + arch: + description: 'CPU architecture of the system image.' + required: false + default: 'x86_64' + profile: + description: 'Emulator profile to use.' + required: false + default: 'pixel_5' + avd-name: + description: 'Name for the AVD.' + required: false + default: 'ci_avd' + force-avd-creation: + description: 'Force AVD creation even if it already exists.' + required: false + default: 'true' + disable-animations: + description: 'Disable emulator animations to improve test stability.' + required: false + default: 'true' + emulator-options: + description: 'Additional emulator launch options.' + required: false + default: '-no-snapshot -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -memory 4096' + emulator-boot-timeout: + description: 'Timeout in seconds for emulator boot.' + required: false + default: '600' + pre-emulator-launch-script: + description: 'Commands to run before launching the emulator.' + required: false + default: '' + post-emulator-launch-script: + description: 'Commands to run after the emulator has been launched but before the main script.' + required: false + default: 'adb wait-for-device shell "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"' + working-directory: + description: 'Working directory for the script.' + required: false + default: '.' + script: + description: 'Shell commands to execute while the emulator is running.' + required: true +runs: + using: 'composite' + steps: + - name: Enable KVM group perms + if: runner.os == 'Linux' + shell: bash + run: | + set -euxo pipefail + if [[ -e /dev/kvm ]]; then + echo 'Configuring KVM permissions for hardware acceleration.' + 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 + else + echo '::warning::/dev/kvm not found. Emulator will run without hardware acceleration.' + fi + - name: Run commands on Android emulator + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ inputs.api-level }} + target: ${{ inputs.target }} + arch: ${{ inputs.arch }} + profile: ${{ inputs.profile }} + avd-name: ${{ inputs.avd-name }} + force-avd-creation: ${{ inputs.force-avd-creation }} + disable-animations: ${{ inputs.disable-animations }} + emulator-options: ${{ inputs.emulator-options }} + emulator-boot-timeout: ${{ inputs.emulator-boot-timeout }} + pre-emulator-launch-script: ${{ inputs.pre-emulator-launch-script }} + post-emulator-launch-script: ${{ inputs.post-emulator-launch-script }} + working-directory: ${{ inputs.working-directory }} + script: ${{ inputs.script }} diff --git a/.github/workflows/ci-graphite.yml b/.github/workflows/ci-graphite.yml index d34580bc46..c5e8982228 100644 --- a/.github/workflows/ci-graphite.yml +++ b/.github/workflows/ci-graphite.yml @@ -174,7 +174,7 @@ jobs: test-android-graphite: needs: build-android-graphite - runs-on: macos-latest-large + runs-on: ubuntu-latest timeout-minutes: 60 env: TURBO_CACHE_DIR: .turbo/android @@ -196,23 +196,6 @@ jobs: with: path: apps/example/android/app/build/outputs/apk/debug/app-debug.apk key: apk-${{ github.sha }} - - - name: SKDs - download required images - run: $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-30;default;x86_64" - - - name: Emulator - Create - run: $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n Pixel_API_30 --device 'Nexus 5X' --package "system-images;android-30;default;x86_64" --sdcard 512M - - - name: Emulator - Boot - run: $ANDROID_HOME/emulator/emulator -memory 4096 -avd Pixel_API_30 -wipe-data -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim & - - - name: ADB Wait For Device - run: adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;' - timeout-minutes: 10 - - - name: Start Package Manager - working-directory: apps/example/ - run: E2E=true yarn start & - name: Check APK existence uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 @@ -220,22 +203,41 @@ jobs: files: apps/example/android/app/build/outputs/apk/debug/app-debug.apk fail: true - - name: Install APK - run: adb install -r apps/example/android/app/build/outputs/apk/debug/app-debug.apk - - # - name: Set up environment - # run: echo "PACKAGE_NAME=${{ env.PACKAGE_NAME }}" >> $GITHUB_ENV - - name: Launch APK - env: - PACKAGE_NAME: 'com.microsoft.reacttestapp' - run: adb shell monkey -p ${{ env.PACKAGE_NAME }} 1 - - # On fabric, the system fonts are slightly different - # so wont run the paragraph tests there for now - - name: Run e2e Tests - working-directory: packages/skia - run: | - CI=true E2E=true yarn test -i Paths --testPathIgnorePatterns Paragraphs + - name: Run Android tests + uses: ./.github/actions/android-emulator + with: + api-level: '30' + target: 'default' + arch: 'x86_64' + profile: 'Nexus 5X' + avd-name: 'Pixel_API_30' + emulator-options: '-no-snapshot -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -memory 4096' + script: | + set -euxo pipefail + + PACKAGE_NAME='com.microsoft.reacttestapp' + APK_PATH='apps/example/android/app/build/outputs/apk/debug/app-debug.apk' + + test -f "${APK_PATH}" + + pushd apps/example + E2E=true yarn start & + METRO_PID=$! + popd + + cleanup() { + kill "${METRO_PID}" 2>/dev/null || true + } + trap cleanup EXIT + + sleep 25 + + adb install -r "${APK_PATH}" + adb shell monkey -p "${PACKAGE_NAME}" 1 + + pushd packages/skia + CI=true E2E=true yarn test -i Paths --testPathIgnorePatterns Paragraphs + popd - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: failure() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8bae814f3b..d8ab95df20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -266,7 +266,7 @@ jobs: test-android: needs: build-android - runs-on: macos-latest-large + runs-on: ubuntu-latest timeout-minutes: 60 env: TURBO_CACHE_DIR: .turbo/android @@ -287,23 +287,6 @@ jobs: with: path: apps/example/android/app/build/outputs/apk/debug/app-debug.apk key: apk-${{ github.sha }} - - - name: SKDs - download required images - run: $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-30;default;x86_64" - - - name: Emulator - Create - run: $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n Pixel_API_30 --device 'Nexus 5X' --package "system-images;android-30;default;x86_64" --sdcard 512M - - - name: Emulator - Boot - run: $ANDROID_HOME/emulator/emulator -memory 4096 -avd Pixel_API_30 -wipe-data -no-window -gpu angle_indirect -no-snapshot -noaudio -no-boot-anim & - - - name: ADB Wait For Device - run: adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;' - timeout-minutes: 10 - - - name: Start Package Manager - working-directory: apps/example/ - run: E2E=true yarn start & - name: Check APK existence uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 @@ -311,22 +294,43 @@ jobs: files: apps/example/android/app/build/outputs/apk/debug/app-debug.apk fail: true - - name: Install APK - run: adb install -r apps/example/android/app/build/outputs/apk/debug/app-debug.apk + - name: Run Android tests + uses: ./.github/actions/android-emulator + with: + api-level: '30' + target: 'default' + arch: 'x86_64' + profile: 'Nexus 5X' + avd-name: 'Pixel_API_30' + emulator-options: '-no-snapshot -no-window -gpu angle_indirect -noaudio -no-boot-anim -memory 4096' + script: | + set -euxo pipefail - # - name: Set up environment - # run: echo "PACKAGE_NAME=${{ env.PACKAGE_NAME }}" >> $GITHUB_ENV - - name: Launch APK - env: - PACKAGE_NAME: 'com.microsoft.reacttestapp' - run: adb shell monkey -p ${{ env.PACKAGE_NAME }} 1 + PACKAGE_NAME='com.microsoft.reacttestapp' + APK_PATH='apps/example/android/app/build/outputs/apk/debug/app-debug.apk' - # On fabric, the system fonts are slightly different - # so wont run the paragraph tests there for now - - name: Run e2e Tests - working-directory: packages/skia - run: | - CI=true yarn e2e --testPathIgnorePatterns Paragraphs + test -f "${APK_PATH}" + + # Start the Metro bundler for E2E tests. + pushd apps/example + E2E=true yarn start & + METRO_PID=$! + popd + + cleanup() { + kill "${METRO_PID}" 2>/dev/null || true + } + trap cleanup EXIT + + # Give Metro a moment to start accepting connections. + sleep 25 + + adb install -r "${APK_PATH}" + adb shell monkey -p "${PACKAGE_NAME}" 1 + + pushd packages/skia + CI=true yarn e2e --testPathIgnorePatterns Paragraphs + popd - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: failure() diff --git a/.github/workflows/test-skia-package.yml b/.github/workflows/test-skia-package.yml index 88b2b9be6c..fe7f2bc0e2 100644 --- a/.github/workflows/test-skia-package.yml +++ b/.github/workflows/test-skia-package.yml @@ -197,8 +197,11 @@ jobs: test-skia-android: if: inputs.test_android - runs-on: macos-latest-large + runs-on: ubuntu-latest timeout-minutes: 60 + env: + SKIA_TEST_DIR: ${{ runner.temp }}/skia-test-app + ANDROID_SCREENSHOT_PATH: ${{ runner.temp }}/skia-test-screenshot-android.png steps: - name: Checkout repository @@ -225,18 +228,10 @@ jobs: distribution: 'zulu' java-version: '17' - - name: Download Android system image - run: $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-${{ inputs.android_api_level }};default;x86_64" - - - name: Create Android emulator - run: | - echo "Creating Android emulator..." - $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n test_avd --device '${{ inputs.android_device }}' --package "system-images;android-${{ inputs.android_api_level }};default;x86_64" --sdcard 512M --force - - name: Create test directory run: | - mkdir -p /Users/runner/skia-test-app - cd /Users/runner/skia-test-app + mkdir -p "$SKIA_TEST_DIR" + cd "$SKIA_TEST_DIR" pwd - name: Create Expo app with Skia template @@ -246,7 +241,7 @@ jobs: max_attempts: 3 retry_wait_seconds: 120 command: | - cd /Users/runner/skia-test-app + cd "$SKIA_TEST_DIR" echo "Creating Expo app with Skia template..." yarn create expo-app my-app -e with-skia @@ -260,7 +255,7 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Install React Native Skia - working-directory: /Users/runner/skia-test-app/my-app + working-directory: ${{ env.SKIA_TEST_DIR }}/my-app run: | VERSION="${{ inputs.skia_version }}" if [ "$VERSION" == "latest" ]; then @@ -278,72 +273,68 @@ jobs: fi - name: Show installed Skia version - working-directory: /Users/runner/skia-test-app/my-app + working-directory: ${{ env.SKIA_TEST_DIR }}/my-app run: | echo "Installed React Native Skia version:" yarn list @shopify/react-native-skia --depth=0 - name: Install TypeScript dependencies - working-directory: /Users/runner/skia-test-app/my-app + working-directory: ${{ env.SKIA_TEST_DIR }}/my-app run: | echo "Installing TypeScript dependencies..." npx expo install typescript @types/react - name: Install Expo Dev Client - working-directory: /Users/runner/skia-test-app/my-app + working-directory: ${{ env.SKIA_TEST_DIR }}/my-app run: | echo "Installing expo-dev-client..." npx expo install expo-dev-client - name: Prebuild native directories - working-directory: /Users/runner/skia-test-app/my-app + working-directory: ${{ env.SKIA_TEST_DIR }}/my-app run: | echo "Generating native Android directory..." npx expo prebuild --platform android - - name: Start Android emulator - run: | - echo "Starting Android emulator in background..." - $ANDROID_HOME/emulator/emulator -memory 4096 -avd test_avd -wipe-data -no-window -gpu angle_indirect -no-snapshot -noaudio -no-boot-anim & + - name: Build and run Android app on emulator + uses: ./.github/actions/android-emulator + with: + api-level: ${{ inputs.android_api_level }} + target: 'default' + arch: 'x86_64' + profile: ${{ inputs.android_device }} + avd-name: 'test_avd' + emulator-options: '-no-snapshot -no-window -gpu angle_indirect -noaudio -no-boot-anim -memory 4096' + script: | + set -euxo pipefail - - name: Wait for emulator to boot - run: | - echo "Waiting for Android emulator to boot..." - adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;' - echo "Emulator is ready" - adb devices - timeout-minutes: 10 + APP_DIR="${SKIA_TEST_DIR}/my-app" + SCREENSHOT_PATH="${ANDROID_SCREENSHOT_PATH}" - - name: Start Metro bundler - working-directory: /Users/runner/skia-test-app/my-app - run: | - echo "Starting Metro bundler in background..." - npx expo start --dev-client & - sleep 10 + cd "${APP_DIR}" - - name: Build and run Android app - working-directory: /Users/runner/skia-test-app/my-app - run: | - echo "Building and running Android app on emulator..." - npx expo run:android + npx expo start --dev-client & + METRO_PID=$! - - name: Wait for app to launch - run: | - echo "Waiting for app to launch and stabilize..." - sleep 120 + cleanup() { + kill "${METRO_PID}" 2>/dev/null || true + } + trap cleanup EXIT - - name: Capture emulator screenshot - if: always() - run: | - echo "Capturing screenshot from Android emulator..." - adb exec-out screencap -p > /Users/runner/skia-test-screenshot-android.png || echo "Failed to capture screenshot" + sleep 25 + + npx expo run:android + + sleep 120 + + adb exec-out screencap -p > "${SCREENSHOT_PATH}" || echo "Failed to capture screenshot" - name: Upload screenshot if: always() uses: actions/upload-artifact@v5 with: name: android-emulator-screenshot-${{ github.run_id }} - path: /Users/runner/skia-test-screenshot-android.png + path: ${{ env.ANDROID_SCREENSHOT_PATH }} if-no-files-found: ignore - name: Print test summary