Skip to content

android workflow

android workflow #21

Workflow file for this run

name: Maestro E2E Android
on:
push:
branches:
- master
pull_request:
branches:
- master
types: [opened, synchronize, reopened]
workflow_dispatch:
jobs:
e2e-android:
runs-on: ubuntu-latest
timeout-minutes: 75
env:
ANDROID_HOME: /home/runner/androidsdk
ANDROID_SDK_ROOT: /home/runner/androidsdk
ANDROID_AVD_HOME: /mnt/avd
ANDROID_OS_IMAGE: system-images;android-32;google_apis;x86_64
ANDROID_PLATFORM: platforms;android-36
APP_ID: com.anonymous.client
steps:
- name: Enable KVM group perms
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: Checkout
uses: actions/checkout@v4
- name: Inject Google API key for Expo build
run: |
if [ -n "${{ secrets.EXPO_PUBLIC_GOOGLE_API_KEY }}" ]; then
echo "EXPO_PUBLIC_GOOGLE_API_KEY=${{ secrets.EXPO_PUBLIC_GOOGLE_API_KEY }}" >> "$GITHUB_ENV"
elif [ -n "${{ secrets.GOOGLE_API_KEY }}" ]; then
echo "EXPO_PUBLIC_GOOGLE_API_KEY=${{ secrets.GOOGLE_API_KEY }}" >> "$GITHUB_ENV"
else
echo "ERROR: Missing GitHub secret. Set EXPO_PUBLIC_GOOGLE_API_KEY (preferred) or GOOGLE_API_KEY." >&2
exit 1
fi
- name: Free disk space
run: |
df -h
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /opt/ghc || true
sudo rm -rf /usr/local/.ghcup || true
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
sudo docker system prune -af || true
sudo mkdir -p /mnt/avd
sudo chmod -R 777 /mnt/avd
df -h
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.19.4"
cache: npm
- name: Install JS dependencies
run: npm ci --ignore-scripts --prefer-offline --no-audit
- name: Ensure Android native project
run: |
if [ ! -d android ]; then
npx expo prebuild -p android --clean --no-install --non-interactive
fi
- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"
- name: Create Android release signing credentials
run: |
if [ ! -f android/app/eas-build.gradle ]; then
echo "ERROR: android/app/eas-build.gradle was not generated by Expo prebuild." >&2
exit 1
fi
keytool -genkeypair \
-keystore android/app/ci-release-key.keystore \
-storepass android \
-alias ci-release-key \
-keypass android \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-dname "CN=ConUMap CI, OU=CI, O=ConUMap, L=Montreal, S=QC, C=CA"
cat <<'EOF' > credentials.json
{
"android": {
"keystore": {
"keystorePath": "android/app/ci-release-key.keystore",
"keystorePassword": "android",
"keyAlias": "ci-release-key",
"keyPassword": "android"
}
}
}
EOF
- name: Install Maestro CLI
run: |
curl --proto '=https' --tlsv1.2 -fsSL "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.maestro/bin:$PATH"
"$HOME/.maestro/bin/maestro" --version
- name: Set up bartek-scripts
run: |
git clone --depth 1 https://github.com/mobile-dev-inc/bartek-scripts.git "$HOME/scripts"
echo "$HOME/scripts/bin" >> "$GITHUB_PATH"
- name: Set up Android command-line tools
run: |
export PATH="$HOME/scripts/bin:$PATH"
install_android_sdk https://dl.google.com/android/repository/commandlinetools-linux-12266719_latest.zip
echo "$ANDROID_HOME/cmdline-tools/latest/bin" >> "$GITHUB_PATH"
echo "$ANDROID_HOME/emulator" >> "$GITHUB_PATH"
echo "$ANDROID_HOME/platform-tools" >> "$GITHUB_PATH"
- name: Set up Android SDK components
run: |
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH"
yes | sdkmanager --licenses
sdkmanager --install \
"emulator" \
"platform-tools" \
"$ANDROID_PLATFORM" \
"build-tools;36.0.0" \
"$ANDROID_OS_IMAGE"
- name: Create local.properties
run: |
echo "sdk.dir=$ANDROID_HOME" > android/local.properties
cat android/local.properties
- name: Build Android release APK
working-directory: android
run: |
chmod +x ./gradlew
./gradlew assembleRelease \
-PreactNativeArchitectures=x86_64 \
--stacktrace \
--console=plain \
--no-daemon \
--max-workers=2
- name: Verify APK outputs
run: |
ls -lah android/app/build/outputs/apk/release || true
find android/app/build/outputs/apk/release -type f -name "*.apk" -print
- name: Create AVD
run: |
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH"
mkdir -p "$ANDROID_AVD_HOME"
echo "no" | avdmanager create avd \
--force \
--package "$ANDROID_OS_IMAGE" \
--name "MyAVD" \
--sdcard 512M
cat << EOF >> "$ANDROID_AVD_HOME/MyAVD.avd/config.ini"
hw.cpu.ncore=2
hw.gpu.enabled=yes
hw.gpu.mode=swiftshader_indirect
hw.ramSize=3072
vm.heapSize=576
disk.dataPartition.size=2048M
hw.lcd.density=440
hw.lcd.height=2220
hw.lcd.width=1080
fastboot.forceColdBoot=yes
showDeviceFrame=no
EOF
- name: Run AVD
run: |
mkdir -p .maestro/output .maestro/debug .maestro/artifacts .maestro/recordings
export PATH="$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH"
emulator @MyAVD \
-verbose \
-no-snapshot \
-no-window \
-no-audio \
-no-boot-anim \
-accel on \
-camera-back none \
-partition-size 2048 \
-qemu -m 3072 \
> .maestro/output/emulator-stdout.log \
2> .maestro/output/emulator-stderr.log &
- name: Wait for AVD to start up
run: |
export PATH="$ANDROID_HOME/platform-tools:$PATH"
found=""
for i in {1..120}; do
adb start-server || true
adb devices || true
if adb devices | grep -q "emulator-.*device"; then
found=1
echo "Emulator device online"
break
fi
sleep 2
done
if [ -z "$found" ]; then
echo "Emulator never became available"
echo "=== emulator stdout ==="
tail -200 .maestro/output/emulator-stdout.log || true
echo "=== emulator stderr ==="
tail -200 .maestro/output/emulator-stderr.log || true
exit 1
fi
DEVICE_SERIAL="$(adb devices | awk '/emulator-.*device$/ { print $1; exit }')"
if [ -z "$DEVICE_SERIAL" ]; then
echo "Failed to resolve Android emulator serial" >&2
adb devices
exit 1
fi
adb -s "$DEVICE_SERIAL" wait-for-device
echo "ANDROID_DEVICE_SERIAL=$DEVICE_SERIAL" >> "$GITHUB_ENV"
adb -s "$DEVICE_SERIAL" shell 'while [[ "$(getprop sys.boot_completed | tr -d "\r")" != "1" ]]; do sleep 1; done;' && echo "Emulator booted"
while true; do
adb -s "$DEVICE_SERIAL" shell service list | grep 'package' && echo 'service "package" is active!' && break
echo 'waiting for service "package" to start'
sleep 1
done
adb -s "$DEVICE_SERIAL" shell settings put global window_animation_scale 0 || true
adb -s "$DEVICE_SERIAL" shell settings put global transition_animation_scale 0 || true
adb -s "$DEVICE_SERIAL" shell settings put global animator_duration_scale 0 || true
- name: Install app
run: |
APK_PATH="$(find android/app/build/outputs/apk/release -type f -name '*release*.apk' ! -name '*unsigned*' | head -1)"
if [ -z "$APK_PATH" ]; then
echo "Signed release APK not found" >&2
find android/app/build/outputs/apk/release -type f -name "*.apk" -print || true
exit 1
fi
adb -s "$ANDROID_DEVICE_SERIAL" install -r "$APK_PATH"
- name: Verify installed app package
run: |
adb -s "$ANDROID_DEVICE_SERIAL" shell pm list packages | grep "$APP_ID"
- name: Run Maestro flow
timeout-minutes: 20
run: |
export PATH="$HOME/.maestro/bin:$PATH"
export MAESTRO_DRIVER_STARTUP_TIMEOUT=600000
mkdir -p .maestro/output .maestro/debug .maestro/artifacts .maestro/recordings
VIDEO_FILE=".maestro/recordings/maestro-android-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.mp4"
adb -s "$ANDROID_DEVICE_SERIAL" logcat -c || true
adb -s "$ANDROID_DEVICE_SERIAL" shell screenrecord /sdcard/maestro-android.mp4 >/tmp/android-screenrecord.log 2>&1 &
REC_PID=$!
set +e
maestro --verbose --device "$ANDROID_DEVICE_SERIAL" test .maestro/automated/suite.yaml \
--format html-detailed \
--output .maestro/output/report.html \
--debug-output .maestro/debug \
--test-output-dir .maestro/artifacts \
2>&1 | tee .maestro/output/maestro-console.log
FLOW_EXIT=${PIPESTATUS[0]}
set -e
if kill -0 "$REC_PID" 2>/dev/null; then
kill -INT "$REC_PID" 2>/dev/null || true
wait "$REC_PID" 2>/dev/null || true
fi
sleep 5
adb -s "$ANDROID_DEVICE_SERIAL" logcat -d > .maestro/output/logcat.txt || true
adb -s "$ANDROID_DEVICE_SERIAL" shell dumpsys window | grep -E 'mCurrentFocus|mFocusedApp' > .maestro/output/window-focus.txt || true
adb -s "$ANDROID_DEVICE_SERIAL" shell dumpsys activity activities | head -400 > .maestro/output/activity-dumpsys.txt || true
adb -s "$ANDROID_DEVICE_SERIAL" shell dumpsys package "$APP_ID" | head -250 > .maestro/output/package-dumpsys.txt || true
adb -s "$ANDROID_DEVICE_SERIAL" pull /sdcard/maestro-android.mp4 "$VIDEO_FILE" || true
exit $FLOW_EXIT
- name: Upload screen recording (mp4)
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-android-recording
path: .maestro/recordings/*.mp4
if-no-files-found: ignore
- name: Upload Maestro outputs
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-android-outputs
path: |
.maestro/output/**
.maestro/debug/**
.maestro/artifacts/**
.maestro/recordings/*.mp4
if-no-files-found: ignore
include-hidden-files: true