From 95a5338abdb5c040638eb6ae95f31e1f045f40ff Mon Sep 17 00:00:00 2001 From: Trey T Date: Sat, 18 Apr 2026 14:57:42 -0500 Subject: [PATCH] CI: Gradle Managed Devices + GitHub Actions workflow pixel7Api34 managed device runs instrumented tests headlessly on CI. Three test-filter profiles (ci/parallel/full) mirror iOS xctestplan variants. run_ui_tests.sh convenience wrapper. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/android-ui-tests.yml | 30 +++++++++++++++++++ composeApp/build.gradle.kts | 9 ++++++ .../testplans/ci.testfilter | 5 ++++ .../testplans/full.testfilter | 10 +++++++ .../testplans/parallel.testfilter | 7 +++++ scripts/run_ui_tests.sh | 30 +++++++++++++++++++ 6 files changed, 91 insertions(+) create mode 100644 .github/workflows/android-ui-tests.yml create mode 100644 composeApp/src/androidInstrumentedTest/testplans/ci.testfilter create mode 100644 composeApp/src/androidInstrumentedTest/testplans/full.testfilter create mode 100644 composeApp/src/androidInstrumentedTest/testplans/parallel.testfilter create mode 100755 scripts/run_ui_tests.sh diff --git a/.github/workflows/android-ui-tests.yml b/.github/workflows/android-ui-tests.yml new file mode 100644 index 0000000..0c484ae --- /dev/null +++ b/.github/workflows/android-ui-tests.yml @@ -0,0 +1,30 @@ +name: Android UI Tests +on: + pull_request: + branches: [main, master] + push: + branches: [main, master] +jobs: + ui-tests: + runs-on: macos-14 + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: { distribution: temurin, java-version: 17 } + - name: Accept Android licenses + run: yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true + - name: Verify test-tag parity + run: ./scripts/verify_test_tag_parity.sh + - name: Run unit tests + run: ./gradlew :composeApp:testDebugUnitTest + - name: Run instrumented tests (managed device) + run: ./gradlew :composeApp:pixel7Api34DebugAndroidTest + env: + GRADLE_OPTS: -Xmx4g -XX:+UseParallelGC + - name: Upload test reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: composeApp/build/reports/ diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 7308493..2d910fe 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -181,6 +181,15 @@ android { testOptions { unitTests.isIncludeAndroidResources = true unitTests.isReturnDefaultValues = true + managedDevices { + localDevices { + create("pixel7Api34") { + device = "Pixel 7" + apiLevel = 34 + systemImageSource = "aosp-atd" + } + } + } } } diff --git a/composeApp/src/androidInstrumentedTest/testplans/ci.testfilter b/composeApp/src/androidInstrumentedTest/testplans/ci.testfilter new file mode 100644 index 0000000..1197317 --- /dev/null +++ b/composeApp/src/androidInstrumentedTest/testplans/ci.testfilter @@ -0,0 +1,5 @@ +# Fast PR gating subset +com.tt.honeyDue.AAA_SeedTests +com.tt.honeyDue.Suite1_RegistrationTests +com.tt.honeyDue.Suite5_TaskTests +com.tt.honeyDue.SuiteZZ_CleanupTests diff --git a/composeApp/src/androidInstrumentedTest/testplans/full.testfilter b/composeApp/src/androidInstrumentedTest/testplans/full.testfilter new file mode 100644 index 0000000..d064a95 --- /dev/null +++ b/composeApp/src/androidInstrumentedTest/testplans/full.testfilter @@ -0,0 +1,10 @@ +# Full instrumented suite — all top-level suite classes (seed + per-feature suites + parallel Suite9/Suite10 + cleanup) +com.tt.honeyDue.AAA_SeedTests +com.tt.honeyDue.Suite1_RegistrationTests +com.tt.honeyDue.Suite4_ComprehensiveResidenceTests +com.tt.honeyDue.Suite5_TaskTests +com.tt.honeyDue.Suite7_ContractorTests +com.tt.honeyDue.Suite8_DocumentWarrantyTests +com.tt.honeyDue.Suite9_E2ERegressionTests +com.tt.honeyDue.Suite10_AccessibilityTests +com.tt.honeyDue.SuiteZZ_CleanupTests diff --git a/composeApp/src/androidInstrumentedTest/testplans/parallel.testfilter b/composeApp/src/androidInstrumentedTest/testplans/parallel.testfilter new file mode 100644 index 0000000..9f1686b --- /dev/null +++ b/composeApp/src/androidInstrumentedTest/testplans/parallel.testfilter @@ -0,0 +1,7 @@ +# Parallel-safe subset — excludes Suite9 E2E regression (sequential) and ordering-sensitive cleanup +com.tt.honeyDue.Suite1_RegistrationTests +com.tt.honeyDue.Suite4_ComprehensiveResidenceTests +com.tt.honeyDue.Suite5_TaskTests +com.tt.honeyDue.Suite7_ContractorTests +com.tt.honeyDue.Suite8_DocumentWarrantyTests +com.tt.honeyDue.Suite10_AccessibilityTests diff --git a/scripts/run_ui_tests.sh b/scripts/run_ui_tests.sh new file mode 100755 index 0000000..830587b --- /dev/null +++ b/scripts/run_ui_tests.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Convenience script for running Android UI tests locally via the Gradle +# Managed Device (pixel7Api34). Mirrors the iOS scripts/run_ui_tests.sh +# pattern but drives the Android instrumentation runner with one of the +# three test-filter profiles under: +# composeApp/src/androidInstrumentedTest/testplans/ +# +# Usage: ./scripts/run_ui_tests.sh [ci|parallel|full] +# ci - fast PR gating subset (seed + registration + tasks + cleanup) +# parallel - all non-E2E suites (safe to run in parallel shards) +# full - every suite (default) +set -euo pipefail + +profile="${1:-full}" +filter_file="composeApp/src/androidInstrumentedTest/testplans/${profile}.testfilter" + +if [[ ! -f "$filter_file" ]]; then + echo "Error: no test filter found at $filter_file" >&2 + echo "Valid profiles: ci | parallel | full" >&2 + exit 1 +fi + +# Flatten the file: strip comments/blank lines, comma-join the class names. +filter_arg="$(grep -v '^#' "$filter_file" | grep -v '^[[:space:]]*$' | tr '\n' ',' | sed 's/,$//')" + +echo "Running Android UI tests (profile: $profile)" +echo "Filter: $filter_arg" + +./gradlew :composeApp:pixel7Api34DebugAndroidTest \ + -Pandroid.testInstrumentationRunnerArguments.class="$filter_arg"