|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +set -euo pipefail |
| 4 | + |
| 5 | +readonly iterations="${1:-5000}" |
| 6 | +readonly emulator_serial="${ANDROID_SERIAL:-emulator-5554}" |
| 7 | +readonly crash_loop_api_key="${CRASH_LOOP_API_KEY:-}" |
| 8 | +readonly shared_prefs_path="shared_prefs/io.bitdrift.gradletestapp_preferences.xml" |
| 9 | +readonly apk_path="platform/jvm/gradle-test-app/build/outputs/apk/debug/gradle-test-app-debug.apk" |
| 10 | +readonly maestro_flow="tools/maestro/native-crash-loop.yaml" |
| 11 | +readonly maestro_retries=3 |
| 12 | + |
| 13 | +if ! [[ "$iterations" =~ ^[0-9]+$ ]] || [[ "$iterations" -lt 1 ]]; then |
| 14 | + echo "iterations must be a positive integer" |
| 15 | + exit 1 |
| 16 | +fi |
| 17 | + |
| 18 | +if [[ -z "$crash_loop_api_key" ]]; then |
| 19 | + echo "CRASH_LOOP_API_KEY must be set" |
| 20 | + exit 1 |
| 21 | +fi |
| 22 | + |
| 23 | +if [[ ! -f "$apk_path" ]]; then |
| 24 | + echo "Expected APK not found at $apk_path" |
| 25 | + exit 1 |
| 26 | +fi |
| 27 | + |
| 28 | +wait_for_emulator_ready() { |
| 29 | + adb -s "$emulator_serial" wait-for-device |
| 30 | + |
| 31 | + for _ in $(seq 1 45); do |
| 32 | + local sys_boot_completed |
| 33 | + local dev_boot_completed |
| 34 | + local boot_anim |
| 35 | + |
| 36 | + sys_boot_completed="$(adb -s "$emulator_serial" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" |
| 37 | + dev_boot_completed="$(adb -s "$emulator_serial" shell getprop dev.bootcomplete 2>/dev/null | tr -d '\r')" |
| 38 | + boot_anim="$(adb -s "$emulator_serial" shell getprop init.svc.bootanim 2>/dev/null | tr -d '\r')" |
| 39 | + |
| 40 | + if [[ "$sys_boot_completed" == "1" ]] && |
| 41 | + [[ "$dev_boot_completed" == "1" ]] && |
| 42 | + [[ "$boot_anim" == "stopped" ]] && |
| 43 | + adb -s "$emulator_serial" shell cmd package list packages >/dev/null 2>&1; then |
| 44 | + return 0 |
| 45 | + fi |
| 46 | + |
| 47 | + adb reconnect offline >/dev/null 2>&1 || true |
| 48 | + sleep 2 |
| 49 | + done |
| 50 | + |
| 51 | + echo "Timed out waiting for emulator package manager readiness" |
| 52 | + return 1 |
| 53 | +} |
| 54 | + |
| 55 | +seed_gradle_test_app_settings() { |
| 56 | + local escaped_api_key |
| 57 | + local prefs_xml |
| 58 | + |
| 59 | + escaped_api_key="$(printf '%s' "$crash_loop_api_key" | sed -e 's/&/\&/g' -e 's/"/\"/g' -e "s/'/\\'/g" -e 's/</\</g' -e 's/>/\>/g')" |
| 60 | + prefs_xml="$(cat <<EOF |
| 61 | +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> |
| 62 | +<map> |
| 63 | + <string name='apiUrl'>https://api.bitdrift.dev</string> |
| 64 | + <string name='api_key'>$escaped_api_key</string> |
| 65 | +</map> |
| 66 | +EOF |
| 67 | +)" |
| 68 | + |
| 69 | + printf '%s' "$prefs_xml" | adb -s "$emulator_serial" shell "run-as io.bitdrift.gradletestapp sh -c 'mkdir -p shared_prefs && cat > $shared_prefs_path'" |
| 70 | +} |
| 71 | + |
| 72 | +curl -Ls "https://get.maestro.mobile.dev" | bash |
| 73 | +export PATH="$PATH:$HOME/.maestro/bin" |
| 74 | +export MAESTRO_CLI_NO_ANALYTICS=1 |
| 75 | +export ANDROID_SERIAL="$emulator_serial" |
| 76 | + |
| 77 | +adb start-server |
| 78 | +wait_for_emulator_ready |
| 79 | +adb -s "$emulator_serial" install -r "$apk_path" |
| 80 | +adb -s "$emulator_serial" shell input keyevent 82 >/dev/null 2>&1 || true |
| 81 | +seed_gradle_test_app_settings |
| 82 | + |
| 83 | +for attempt in $(seq 1 "$maestro_retries"); do |
| 84 | + echo "Running Maestro attempt $attempt/$maestro_retries" |
| 85 | + |
| 86 | + if maestro test -e ITERATIONS="$iterations" "$maestro_flow"; then |
| 87 | + exit 0 |
| 88 | + fi |
| 89 | + |
| 90 | + if [[ "$attempt" -eq "$maestro_retries" ]]; then |
| 91 | + exit 1 |
| 92 | + fi |
| 93 | + |
| 94 | + echo "Maestro failed, restarting adb before retry" |
| 95 | + adb kill-server || true |
| 96 | + adb start-server |
| 97 | + wait_for_emulator_ready |
| 98 | + seed_gradle_test_app_settings |
| 99 | +done |
0 commit comments