Skip to content

Commit 10b104f

Browse files
committed
Switch Android UI test to instrumentation emulator run
1 parent 34401e9 commit 10b104f

File tree

3 files changed

+553
-399
lines changed

3 files changed

+553
-399
lines changed

scripts/build-android-app.sh

Lines changed: 224 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ if [ -z "$GRADLE_PROJECT_DIR" ]; then
265265
exit 1
266266
fi
267267

268-
# --- Inject Robolectric UI test into Gradle project ---
268+
# --- Inject instrumentation UI test into Gradle project ---
269269
APP_MODULE_DIR=$(find "$GRADLE_PROJECT_DIR" -maxdepth 1 -type d -name "app" | head -n 1 || true)
270270
if [ -z "$APP_MODULE_DIR" ]; then
271271
ba_log "Unable to locate Gradle app module inside $GRADLE_PROJECT_DIR" >&2
@@ -278,14 +278,14 @@ if [ ! -f "$UI_TEST_TEMPLATE" ]; then
278278
exit 1
279279
fi
280280

281-
UI_TEST_DIR="$APP_MODULE_DIR/src/test/java/${PACKAGE_PATH}"
281+
UI_TEST_DIR="$APP_MODULE_DIR/src/androidTest/java/${PACKAGE_PATH}"
282282
mkdir -p "$UI_TEST_DIR"
283283
UI_TEST_FILE="$UI_TEST_DIR/${MAIN_NAME}UiTest.java"
284284

285285
sed -e "s|@PACKAGE@|$PACKAGE_NAME|g" \
286286
-e "s|@MAIN_NAME@|$MAIN_NAME|g" \
287287
"$UI_TEST_TEMPLATE" > "$UI_TEST_FILE"
288-
ba_log "Created Robolectric UI test at $UI_TEST_FILE"
288+
ba_log "Created instrumentation UI test at $UI_TEST_FILE"
289289

290290
APP_BUILD_GRADLE="$APP_MODULE_DIR/build.gradle"
291291
if [ ! -f "$APP_BUILD_GRADLE" ]; then
@@ -295,12 +295,6 @@ fi
295295

296296
"$SCRIPT_DIR/update_android_ui_test_gradle.py" "$APP_BUILD_GRADLE"
297297

298-
# Capture UI test screenshots in a deterministic directory
299-
SCREENSHOT_OUTPUT_DIR="$GRADLE_PROJECT_DIR/test-artifacts/screenshots"
300-
rm -rf "$SCREENSHOT_OUTPUT_DIR"
301-
mkdir -p "$SCREENSHOT_OUTPUT_DIR"
302-
export CN1_TEST_SCREENSHOT_DIR="$SCREENSHOT_OUTPUT_DIR"
303-
304298
FINAL_ARTIFACT_DIR="${CN1_TEST_SCREENSHOT_EXPORT_DIR:-$REPO_ROOT/build-artifacts}"
305299
mkdir -p "$FINAL_ARTIFACT_DIR"
306300
if [ -n "${GITHUB_ENV:-}" ]; then
@@ -311,24 +305,156 @@ ba_log "Invoking Gradle build in $GRADLE_PROJECT_DIR"
311305
chmod +x "$GRADLE_PROJECT_DIR/gradlew"
312306
ORIGINAL_JAVA_HOME="$JAVA_HOME"
313307
export JAVA_HOME="$JAVA17_HOME"
314-
if command -v sdkmanager >/dev/null 2>&1; then
315-
yes | sdkmanager --licenses >/dev/null 2>&1 || true
316-
elif [ -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then
317-
yes | "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" --licenses >/dev/null 2>&1 || true
308+
export PATH="$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/emulator:$PATH"
309+
310+
SDKMANAGER_BIN=""
311+
if [ -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then
312+
SDKMANAGER_BIN="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager"
313+
elif [ -x "$ANDROID_SDK_ROOT/cmdline-tools/bin/sdkmanager" ]; then
314+
SDKMANAGER_BIN="$ANDROID_SDK_ROOT/cmdline-tools/bin/sdkmanager"
315+
elif command -v sdkmanager >/dev/null 2>&1; then
316+
SDKMANAGER_BIN="$(command -v sdkmanager)"
317+
fi
318+
319+
AVDMANAGER_BIN=""
320+
if [ -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/avdmanager" ]; then
321+
AVDMANAGER_BIN="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/avdmanager"
322+
elif [ -x "$ANDROID_SDK_ROOT/cmdline-tools/bin/avdmanager" ]; then
323+
AVDMANAGER_BIN="$ANDROID_SDK_ROOT/cmdline-tools/bin/avdmanager"
324+
elif command -v avdmanager >/dev/null 2>&1; then
325+
AVDMANAGER_BIN="$(command -v avdmanager)"
326+
fi
327+
328+
ADB_BIN="$ANDROID_SDK_ROOT/platform-tools/adb"
329+
if [ ! -x "$ADB_BIN" ]; then
330+
if command -v adb >/dev/null 2>&1; then
331+
ADB_BIN="$(command -v adb)"
332+
else
333+
ba_log "adb not found in Android SDK. Ensure platform-tools are installed." >&2
334+
exit 1
335+
fi
336+
fi
337+
338+
EMULATOR_BIN="$ANDROID_SDK_ROOT/emulator/emulator"
339+
if [ ! -x "$EMULATOR_BIN" ]; then
340+
if command -v emulator >/dev/null 2>&1; then
341+
EMULATOR_BIN="$(command -v emulator)"
342+
else
343+
ba_log "Android emulator binary not found" >&2
344+
exit 1
345+
fi
346+
fi
347+
348+
install_android_packages() {
349+
local manager="$1"
350+
if [ -z "$manager" ]; then
351+
ba_log "sdkmanager not available; cannot install system images" >&2
352+
exit 1
353+
fi
354+
yes | "$manager" --licenses >/dev/null 2>&1 || true
355+
"$manager" --install "platform-tools" "platforms;android-33" "system-images;android-33;google_apis;x86_64" >/dev/null 2>&1 || true
356+
}
357+
358+
create_avd() {
359+
local manager="$1"
360+
local name="$2"
361+
local image="$3"
362+
local avd_dir="$4"
363+
if [ -z "$manager" ]; then
364+
ba_log "avdmanager not available; cannot create emulator" >&2
365+
exit 1
366+
fi
367+
rm -rf "$avd_dir"
368+
mkdir -p "$avd_dir"
369+
ANDROID_AVD_HOME="$avd_dir" \
370+
printf 'no\n' | "$manager" create avd -n "$name" -k "$image" --device "pixel_6" --force >/dev/null
371+
}
372+
373+
wait_for_emulator() {
374+
local serial="$1"
375+
"$ADB_BIN" start-server >/dev/null
376+
"$ADB_BIN" -s "$serial" wait-for-device
377+
local booted="0"
378+
for _ in $(seq 1 120); do
379+
booted="$($ADB_BIN -s "$serial" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"
380+
if [ "$booted" = "1" ]; then
381+
break
382+
fi
383+
sleep 2
384+
done
385+
if [ "$booted" != "1" ]; then
386+
ba_log "Emulator $serial failed to boot within expected time" >&2
387+
return 1
388+
fi
389+
"$ADB_BIN" -s "$serial" shell settings put global window_animation_scale 0 >/dev/null 2>&1 || true
390+
"$ADB_BIN" -s "$serial" shell settings put global transition_animation_scale 0 >/dev/null 2>&1 || true
391+
"$ADB_BIN" -s "$serial" shell settings put global animator_duration_scale 0 >/dev/null 2>&1 || true
392+
"$ADB_BIN" -s "$serial" shell input keyevent 82 >/dev/null 2>&1 || true
393+
return 0
394+
}
395+
396+
get_emulator_serial() {
397+
"$ADB_BIN" devices | awk '/emulator-/{print $1; exit}'
398+
}
399+
400+
stop_emulator() {
401+
if [ -n "${EMULATOR_SERIAL:-}" ]; then
402+
"$ADB_BIN" -s "$EMULATOR_SERIAL" emu kill >/dev/null 2>&1 || true
403+
fi
404+
if [ -n "${EMULATOR_PID:-}" ]; then
405+
kill "$EMULATOR_PID" >/dev/null 2>&1 || true
406+
wait "$EMULATOR_PID" 2>/dev/null || true
407+
fi
408+
}
409+
410+
install_android_packages "$SDKMANAGER_BIN"
411+
412+
AVD_NAME="cn1UiTestAvd"
413+
SYSTEM_IMAGE="system-images;android-33;google_apis;x86_64"
414+
AVD_HOME="$WORK_DIR/android-avd"
415+
create_avd "$AVDMANAGER_BIN" "$AVD_NAME" "$SYSTEM_IMAGE" "$AVD_HOME"
416+
417+
ANDROID_AVD_HOME="$AVD_HOME" "$ADB_BIN" start-server >/dev/null
418+
419+
EMULATOR_LOG="$GRADLE_PROJECT_DIR/emulator.log"
420+
ba_log "Starting headless Android emulator $AVD_NAME"
421+
ANDROID_AVD_HOME="$AVD_HOME" "$EMULATOR_BIN" -avd "$AVD_NAME" -no-window -no-snapshot -gpu swiftshader_indirect -no-audio -no-boot-anim -accel off >"$EMULATOR_LOG" 2>&1 &
422+
EMULATOR_PID=$!
423+
trap stop_emulator EXIT
424+
425+
sleep 5
426+
EMULATOR_SERIAL=""
427+
for _ in $(seq 1 30); do
428+
EMULATOR_SERIAL="$(get_emulator_serial)"
429+
if [ -n "$EMULATOR_SERIAL" ]; then
430+
break
431+
fi
432+
sleep 2
433+
done
434+
if [ -z "$EMULATOR_SERIAL" ]; then
435+
ba_log "Failed to determine emulator serial" >&2
436+
stop_emulator
437+
exit 1
438+
fi
439+
ba_log "Using emulator serial $EMULATOR_SERIAL"
440+
441+
if ! wait_for_emulator "$EMULATOR_SERIAL"; then
442+
stop_emulator
443+
exit 1
318444
fi
319445

320-
UI_TEST_TIMEOUT_SECONDS="${UI_TEST_TIMEOUT_SECONDS:-600}"
446+
UI_TEST_TIMEOUT_SECONDS="${UI_TEST_TIMEOUT_SECONDS:-900}"
321447
if ! [[ "$UI_TEST_TIMEOUT_SECONDS" =~ ^[0-9]+$ ]] || [ "$UI_TEST_TIMEOUT_SECONDS" -le 0 ]; then
322-
ba_log "Invalid UI_TEST_TIMEOUT_SECONDS=$UI_TEST_TIMEOUT_SECONDS provided; falling back to 600"
323-
UI_TEST_TIMEOUT_SECONDS=600
448+
ba_log "Invalid UI_TEST_TIMEOUT_SECONDS=$UI_TEST_TIMEOUT_SECONDS provided; falling back to 900"
449+
UI_TEST_TIMEOUT_SECONDS=900
324450
fi
325451

326-
GRADLE_TEST_CMD=("./gradlew" "--no-daemon" "test")
452+
GRADLE_TEST_CMD=("./gradlew" "--no-daemon" "connectedDebugAndroidTest")
327453
if command -v timeout >/dev/null 2>&1; then
328-
ba_log "Running Gradle UI tests with external timeout of ${UI_TEST_TIMEOUT_SECONDS}s"
454+
ba_log "Running instrumentation UI tests with external timeout of ${UI_TEST_TIMEOUT_SECONDS}s"
329455
GRADLE_TEST_CMD=("timeout" "$UI_TEST_TIMEOUT_SECONDS" "${GRADLE_TEST_CMD[@]}")
330456
else
331-
ba_log "timeout command not found; running Gradle UI tests without external watchdog"
457+
ba_log "timeout command not found; running instrumentation tests without external watchdog"
332458
fi
333459

334460
GRADLE_UI_TEST_LOG="$GRADLE_PROJECT_DIR/gradle-ui-test.log"
@@ -349,9 +475,82 @@ if [ -f "$GRADLE_UI_TEST_LOG" ]; then
349475
fi
350476

351477
if [ "$TEST_EXIT_CODE" -eq 124 ]; then
352-
ba_log "Gradle UI tests exceeded ${UI_TEST_TIMEOUT_SECONDS}s timeout and were terminated"
478+
ba_log "Instrumentation tests exceeded ${UI_TEST_TIMEOUT_SECONDS}s timeout and were terminated"
353479
elif [ "$TEST_EXIT_CODE" -ne 0 ]; then
354-
ba_log "Gradle UI tests exited with status $TEST_EXIT_CODE"
480+
ba_log "Instrumentation tests exited with status $TEST_EXIT_CODE"
481+
fi
482+
483+
copy_device_file() {
484+
local src="$1"
485+
local dest="$2"
486+
if ! "$ADB_BIN" -s "$EMULATOR_SERIAL" shell run-as "$PACKAGE_NAME" ls "$src" >/dev/null 2>&1; then
487+
return 1
488+
fi
489+
if "$ADB_BIN" -s "$EMULATOR_SERIAL" exec-out run-as "$PACKAGE_NAME" cat "$src" >"$dest"; then
490+
return 0
491+
fi
492+
rm -f "$dest"
493+
return 1
494+
}
495+
496+
SCREENSHOT_STATUS=0
497+
ANDROID_SCREENSHOT=""
498+
CODENAMEONE_SCREENSHOT=""
499+
DEFAULT_SCREENSHOT=""
500+
501+
SCREENSHOT_DIR_ON_DEVICE="files/ui-test-screenshots"
502+
ANDROID_SCREENSHOT_NAME="${MAIN_NAME}-android-ui.png"
503+
CODENAMEONE_SCREENSHOT_NAME="${MAIN_NAME}-codenameone-ui.png"
504+
505+
ANDROID_SCREENSHOT_PATH_DEVICE="$SCREENSHOT_DIR_ON_DEVICE/$ANDROID_SCREENSHOT_NAME"
506+
CODENAMEONE_SCREENSHOT_PATH_DEVICE="$SCREENSHOT_DIR_ON_DEVICE/$CODENAMEONE_SCREENSHOT_NAME"
507+
508+
ANDROID_SCREENSHOT_DEST="$FINAL_ARTIFACT_DIR/$ANDROID_SCREENSHOT_NAME"
509+
CODENAMEONE_SCREENSHOT_DEST="$FINAL_ARTIFACT_DIR/$CODENAMEONE_SCREENSHOT_NAME"
510+
511+
if copy_device_file "$ANDROID_SCREENSHOT_PATH_DEVICE" "$ANDROID_SCREENSHOT_DEST"; then
512+
ba_log "Android screenshot copied to $ANDROID_SCREENSHOT_DEST"
513+
ANDROID_SCREENSHOT="$ANDROID_SCREENSHOT_DEST"
514+
DEFAULT_SCREENSHOT="$ANDROID_SCREENSHOT_DEST"
515+
else
516+
ba_log "Android screenshot not found at $ANDROID_SCREENSHOT_PATH_DEVICE" >&2
517+
SCREENSHOT_STATUS=1
518+
fi
519+
520+
if copy_device_file "$CODENAMEONE_SCREENSHOT_PATH_DEVICE" "$CODENAMEONE_SCREENSHOT_DEST"; then
521+
ba_log "Codename One screenshot copied to $CODENAMEONE_SCREENSHOT_DEST"
522+
CODENAMEONE_SCREENSHOT="$CODENAMEONE_SCREENSHOT_DEST"
523+
if [ -z "$DEFAULT_SCREENSHOT" ]; then
524+
DEFAULT_SCREENSHOT="$CODENAMEONE_SCREENSHOT_DEST"
525+
fi
526+
else
527+
ba_log "Codename One screenshot not found at $CODENAMEONE_SCREENSHOT_PATH_DEVICE" >&2
528+
SCREENSHOT_STATUS=1
529+
fi
530+
531+
if [ -f "$EMULATOR_LOG" ]; then
532+
cp "$EMULATOR_LOG" "$FINAL_ARTIFACT_DIR/emulator.log" || true
533+
fi
534+
535+
TEST_RESULT_DIR="$APP_MODULE_DIR/build/outputs/androidTest-results/connected"
536+
if [ -d "$TEST_RESULT_DIR" ]; then
537+
RESULT_DEST="$FINAL_ARTIFACT_DIR/androidTest-results"
538+
rm -rf "$RESULT_DEST"
539+
mkdir -p "$RESULT_DEST"
540+
cp -R "$TEST_RESULT_DIR/." "$RESULT_DEST/"
541+
ba_log "Android test results copied to $RESULT_DEST"
542+
fi
543+
544+
if [ -n "${GITHUB_ENV:-}" ]; then
545+
if [ -n "$DEFAULT_SCREENSHOT" ]; then
546+
printf 'CN1_UI_TEST_SCREENSHOT=%s\n' "$DEFAULT_SCREENSHOT" >> "$GITHUB_ENV"
547+
fi
548+
if [ -n "$ANDROID_SCREENSHOT" ]; then
549+
printf 'CN1_UI_TEST_ANDROID_SCREENSHOT=%s\n' "$ANDROID_SCREENSHOT" >> "$GITHUB_ENV"
550+
fi
551+
if [ -n "$CODENAMEONE_SCREENSHOT" ]; then
552+
printf 'CN1_UI_TEST_CODENAMEONE_SCREENSHOT=%s\n' "$CODENAMEONE_SCREENSHOT" >> "$GITHUB_ENV"
553+
fi
355554
fi
356555

357556
if [ "$TEST_EXIT_CODE" -eq 0 ]; then
@@ -360,55 +559,13 @@ if [ "$TEST_EXIT_CODE" -eq 0 ]; then
360559
./gradlew --no-daemon assembleDebug
361560
)
362561
else
363-
ba_log "UI tests failed (exit code $TEST_EXIT_CODE); skipping assembleDebug"
562+
ba_log "Instrumentation tests failed (exit code $TEST_EXIT_CODE); skipping assembleDebug"
364563
fi
564+
365565
export JAVA_HOME="$ORIGINAL_JAVA_HOME"
366566

367-
readarray -t SCREENSHOT_FILES < <(find "$SCREENSHOT_OUTPUT_DIR" -maxdepth 1 -type f -name '*.png' -print | sort)
368-
SCREENSHOT_STATUS=0
369-
if [ "${#SCREENSHOT_FILES[@]}" -eq 0 ]; then
370-
ba_log "UI test completed but no screenshot was produced in $SCREENSHOT_OUTPUT_DIR" >&2
371-
SCREENSHOT_STATUS=1
372-
else
373-
DEFAULT_SCREENSHOT=""
374-
ANDROID_SCREENSHOT=""
375-
CODENAMEONE_SCREENSHOT=""
376-
for src in "${SCREENSHOT_FILES[@]}"; do
377-
base=$(basename "$src")
378-
dest="$FINAL_ARTIFACT_DIR/$base"
379-
cp "$src" "$dest"
380-
label="UI test"
381-
case "$base" in
382-
*-android-*.png)
383-
ANDROID_SCREENSHOT="$dest"
384-
label="Android"
385-
;;
386-
*-codenameone-*.png)
387-
CODENAMEONE_SCREENSHOT="$dest"
388-
label="Codename One"
389-
;;
390-
esac
391-
if [ -z "$DEFAULT_SCREENSHOT" ]; then
392-
DEFAULT_SCREENSHOT="$dest"
393-
fi
394-
ba_log "$label screenshot copied to $dest"
395-
done
396-
if [ -n "$ANDROID_SCREENSHOT" ]; then
397-
DEFAULT_SCREENSHOT="$ANDROID_SCREENSHOT"
398-
fi
399-
if [ -n "${GITHUB_ENV:-}" ]; then
400-
if [ -n "$DEFAULT_SCREENSHOT" ]; then
401-
printf 'CN1_UI_TEST_SCREENSHOT=%s\n' "$DEFAULT_SCREENSHOT" >> "$GITHUB_ENV"
402-
fi
403-
if [ -n "$ANDROID_SCREENSHOT" ]; then
404-
printf 'CN1_UI_TEST_ANDROID_SCREENSHOT=%s\n' "$ANDROID_SCREENSHOT" >> "$GITHUB_ENV"
405-
fi
406-
if [ -n "$CODENAMEONE_SCREENSHOT" ]; then
407-
printf 'CN1_UI_TEST_CODENAMEONE_SCREENSHOT=%s\n' "$CODENAMEONE_SCREENSHOT" >> "$GITHUB_ENV"
408-
fi
409-
fi
410-
fi
411-
unset CN1_TEST_SCREENSHOT_DIR
567+
stop_emulator
568+
trap - EXIT
412569

413570
if [ "$TEST_EXIT_CODE" -ne 0 ]; then
414571
exit "$TEST_EXIT_CODE"

0 commit comments

Comments
 (0)