Project Setup¶
This page covers everything needed to integrate the AutoMobile JUnitRunner into an existing Android Gradle project: adding the dependency, configuring Gradle, installing prerequisites, and running your first test locally.
Dependency¶
The runner is published to Maven Central. Add it as a testImplementation dependency in any Android
module whose UI you want to test.
# gradle/libs.versions.toml
[versions]
auto-mobile-junit-runner = "0.0.13"
[libraries]
auto-mobile-junit-runner = { module = "dev.jasonpearson.auto-mobile:auto-mobile-junit-runner", version.ref = "auto-mobile-junit-runner" }
// app/build.gradle.kts
dependencies {
testImplementation(libs.auto.mobile.junit.runner)
}
// app/build.gradle.kts
dependencies {
testImplementation("dev.jasonpearson.auto-mobile:auto-mobile-junit-runner:0.0.13")
}
Using a SNAPSHOT build¶
If you need unreleased features or are iterating on the runner itself, publish the module locally and
reference it from mavenLocal().
# Inside the auto-mobile repo
cd android
./gradlew :junit-runner:publishToMavenLocal
./gradlew :test-plan-validation:publishToMavenLocal
Then add mavenLocal() before the other repositories in your project’s
settings.gradle.kts:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenLocal() // must come first for SNAPSHOT resolution
google()
mavenCentral()
}
}
And update the version:
# gradle/libs.versions.toml
auto-mobile-junit-runner = "0.0.13-SNAPSHOT"
SNAPSHOT transitive dependency
The auto-mobile-junit-runner POM references auto-mobile-test-plan-validation as a transitive
dependency. When using SNAPSHOTs both must be published to mavenLocal together. The Maven Central
release only carries stable versions — do not mix mavenLocal() versions with the Maven Central
release.
Gradle test task configuration¶
For most projects no Gradle configuration is needed — the runner auto-starts a daemon and auto-downloads CtrlProxy on first use. The block below is only needed if you want to point the daemon at a locally-built CtrlProxy APK (e.g., during active development of the accessibility service or on a network with no GitHub Releases access).
// app/build.gradle.kts
val autoMobileCtrlProxyApkPath =
providers.environmentVariable("AUTOMOBILE_CTRL_PROXY_APK_PATH")
tasks.withType<Test>().configureEach {
// Pass the CtrlProxy APK path to the test process when provided.
// The daemon uses this to install the accessibility service if it is missing.
autoMobileCtrlProxyApkPath.orNull?.let { apkPath ->
environment("AUTOMOBILE_CTRL_PROXY_APK_PATH", apkPath)
systemProperty("automobile.ctrl.proxy.apk.path", apkPath)
}
}
Configuration cache compatibility
Use providers.environmentVariable(...) instead of reading System.getenv() directly so the
Gradle configuration cache can store the task correctly. Calling .orNull at task execution time
is safe; calling it during configuration will cause a cache miss on every build.
Optional: pass additional tuning properties¶
tasks.withType<Test>().configureEach {
// Timeout for the daemon to start (ms). Default: 10 000 ms.
systemProperty("automobile.daemon.startup.timeout.ms", "15000")
// Ordering strategy: auto | duration-asc | duration-desc | none
systemProperty("automobile.junit.timing.ordering", "auto")
}
Prerequisites¶
ADB¶
adb must be on PATH or ANDROID_HOME must be set so the daemon can locate the SDK
platform-tools.
export ANDROID_HOME=$HOME/Library/Android/sdk # macOS typical path
export PATH=$PATH:$ANDROID_HOME/platform-tools
Verify connectivity:
adb devices
# List of devices attached
# emulator-5554 device
AutoMobile daemon¶
The JUnitRunner communicates with a locally running AutoMobile daemon over a Unix domain socket at
/tmp/auto-mobile-daemon-<uid>.sock. If no daemon is running when the first test starts, the runner
bootstraps one automatically using npx -y @kaeawc/auto-mobile@latest --daemon start.
For predictable local development, start the daemon yourself so you can control its lifetime and configuration:
npm install -g @kaeawc/auto-mobile --ignore-scripts
auto-mobile --daemon-mode &
Or if you have the auto-mobile repository checked out locally:
cd ~/path/to/auto-mobile
bun dist/src/index.js --daemon-mode &
Starting the daemon from the source directory
When using a local checkout, start the daemon from the repository root so that the daemon’s
working directory is the project root. This ensures schema files in schemas/ are resolved
correctly relative to process.cwd().
CtrlProxy (Accessibility Service)¶
The daemon needs the AutoMobile CtrlProxy APK installed on the device for view hierarchy access
during observe and interaction steps. The daemon auto-downloads it from GitHub Releases on first
use.
If the release APK is unavailable (e.g., during active development), build it locally:
cd ~/path/to/auto-mobile/android
./gradlew :control-proxy:assembleDebug
# Output: android/control-proxy/build/outputs/apk/debug/control-proxy-debug.apk
Then point the daemon to it:
export AUTOMOBILE_CTRL_PROXY_APK_PATH=/path/to/control-proxy-debug.apk
Pre-install the CtrlProxy before running tests using the CLI observe command:
auto-mobile --cli observe --platform android
This starts the daemon (if not already running) and installs the CtrlProxy on the connected device.
Running tests locally¶
Step 1 — Build and install the app APK¶
AutoMobile tests run against an already-installed app. Build the debug APK and install it:
./gradlew :app:assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
terminateApp and launchApp steps rely on the package being installed. If the APK is not
installed, terminateApp will fail even if launchApp appeared to succeed.
Step 2 — Start the daemon¶
auto-mobile --daemon-mode &
Wait for the socket to appear before proceeding:
until [ -S "/tmp/auto-mobile-daemon-$(id -u).sock" ]; do sleep 1; done
echo "Daemon ready: /tmp/auto-mobile-daemon-$(id -u).sock"
Step 3 — Run the tests¶
./gradlew :app:testDebugUnitTest --tests 'com.example.automobiletest.*'
Pass the CtrlProxy APK path when using a locally built APK:
AUTOMOBILE_CTRL_PROXY_APK_PATH=/path/to/control-proxy-debug.apk \
./gradlew :app:testDebugUnitTest --tests 'com.example.automobiletest.*'
Watching test results¶
Test XML reports are written to:
app/build/test-results/testDebugUnitTest/
HTML report:
app/build/reports/tests/testDebugUnitTest/index.html
Verbose log files for each test execution are written to:
app/scratch/test-logs/
Each log file is named <timestamp>_<TestClass>_<testMethod>.log and contains the full daemon
response, performance metrics, stdout, and stderr for the test run.
Multiple modules¶
If your project has more than one Android module with AutoMobile tests, add the dependency and
tasks.withType<Test> configuration to each module’s build.gradle.kts. The daemon is shared
across all test workers on the host — you do not need to run multiple daemon instances.
Troubleshooting¶
| Problem | Likely cause | Fix |
|---|---|---|
Could not resolve auto-mobile-junit-runner |
Missing mavenLocal() or wrong version |
Ensure mavenLocal() is first in settings.gradle.kts; republish both SNAPSHOT modules |
Could not find test-plan.schema.json |
Daemon running old binary with path bug | Restart daemon from the auto-mobile source directory; verify npm cache contains the latest binary |
Failed to terminate app: Command failed |
App APK not installed on device | Run adb install -r app-debug.apk before tests |
Daemon failed to start within Xms |
npx or bunx unavailable or slow |
Install auto-mobile globally and start the daemon manually before running tests |
No Android devices found — skipping |
adb devices shows no device |
Start your emulator or connect a device; check ANDROID_HOME is set |
See also¶
- Writing Tests — Annotation parameters, YAML plan authoring
- CI Integration — GitHub Actions, emulator.wtf ADB sessions
- CtrlProxy — Accessibility service setup and version management