Skip to content

Building and testing

Setup

You need:

  • the Android SDK with API 36 and build tools 36.0.0;
  • a JDK 17. Gradle finds one in ~/.gradle/jdks, an OS install or setup-java in CI, whatever JAVA_HOME points at;
  • Python 3.10+ for the Python client, tap-agent, tap-studio and tap-watcher;
  • Bun 1.3 for the tap-studio and tap-watcher pages;
  • optionally, GraalVM 21 (GRAALVM_HOME) for the native tap binary;
  • for device tests, one or more devices or emulators with API 26+ visible to adb.

Dependency and plugin versions live in gradle/libs.versions.toml, and the shared JVM module setup in the build-logic/ convention plugins.

git clone https://github.com/NoamCohen48/tap.git && cd tap
python -m venv .venv
.venv/bin/pip install -e "clients/python[dev]" -e clients/agent -e "clients/studio[dev]" \
  -e "clients/watcher[dev]"
(cd clients/studio/web && bun install && bun run build)    # the tap-studio page
(cd clients/watcher/web && bun install && bun run build)   # the tap-watcher page

Building

./gradlew :host:daemon:installDist     # JVM server: host/daemon/build/install/tap/bin/tap
./gradlew :host:daemon:nativeCompile   # native server: host/daemon/build/native/nativeCompile/tap
./gradlew :device:driver:assembleDebug :device:driver:assembleAndroidTest :fixture-app:assembleDebug

The server bundles the driver APKs, so a server you build yourself installs the matching driver on each device the first time a session opens.

Unit tests

No device needed. Run them before every pull request:

./gradlew :contracts:protocol:test :host:core:test :host:daemon:test \
  :device:driver:command-engine:test :device:driver:core:test
.venv/bin/pytest clients/agent/tests clients/studio/tests clients/watcher/tests
(cd clients/studio/web && bun run test)
(cd clients/watcher/web && bun run test)

Client suites

Both suites run against real devices. They start a server, run the fixture tests on every serial you pass (two-device tests are skipped with one serial), and stop the server again:

./gradlew :samples:fixture-tests:test -Ptap.serials=emulator-5554

TAP_BIN=$PWD/host/daemon/build/native/nativeCompile/tap TAP_MANAGE_DAEMON=1 \
  TAP_SERIALS=emulator-5554 .venv/bin/pytest clients/python/tests

Device validation

:host:validation:deviceTest is the fault-injection suite, one class per scenario: recovery, fencing, transport faults, cancellation, heartbeats, selectors, input, scrolling, permissions and multi-device isolation. Without -Ptap.serials every test is skipped.

./gradlew :host:validation:deviceTest -Ptap.serials=emulator-5554

It uses the validation build of the driver (which adds fault injection) and the fixture app by default; override them with -Ptap.driverApk=, -Ptap.driverTestApk= and -Ptap.fixtureApk=. Journals and device locks live in ~/.tap/sessions (or $TAP_STATE_DIR/sessions).

Reboots

-Ptap.reboot=true adds LateMutationQuarantineTest, which reboots every device you pass. Never use it on shared devices or for routine development. Without it, nothing is rebooted.

Product probe

tap-product-probe measures query latency and lists the accessibility inventory of screens in any installed app, without adding app-specific code to Tap. Each final argument is tap text|ready text|screen name, with - for the first screen:

./gradlew :host:validation:installDist
host/validation/build/install/tap-product-probe/bin/tap-product-probe emulator-5554 \
  device/driver/build/outputs/apk/product/debug/driver-product-debug.apk \
  device/driver/build/outputs/apk/androidTest/product/debug/driver-product-debug-androidTest.apk \
  /path/to/app.apk com.example.app .MainActivity '-|Home|home' 'Settings|Appearance|settings'

Changing the protocol

contracts/proto/**/*.proto is the only schema. After editing it:

  1. run ./gradlew :contracts:protocol:test :host:daemon:test (the wire golden bytes and the server tests);
  2. regenerate the committed Python stubs with clients/python/scripts/gen_stubs.py;
  3. update the guide and reference pages that describe the change.

Only add fields and values. CI runs buf lint and buf breaking and fails on anything else.

Native image

GRAALVM_HOME=/path/to/graalvm-21 ./gradlew :host:daemon:nativeCompile builds the native server. If a new dependency uses reflection, re-record the configuration in host/daemon/src/main/resources/META-INF/native-image by running the JVM server with the GraalVM tracing agent (JAVA_OPTS=-agentlib:native-image-agent=config-output-dir=...) through a smoke flow.

Documentation

docs/ is this site (MkDocs Material). scripts/build-docs.sh generates the Kotlin (Dokka), Python and gRPC references, builds the site into build/site, and a Markdown edition into build/docs-md. It needs JDK 17, buf, protoc-gen-doc and pip install -r docs/requirements.txt -e "clients/python[dev]". mkdocs serve previews the guide alone.

Continuous integration and releases

CI (.github/workflows/ci.yml) runs the proto checks, the JVM and Python unit tests, the native image, the tap-studio and tap-watcher pages and back ends, and both client suites plus the tap-agent and tap-studio smokes on an API 34 emulator. Tap has seven independently versioned artifact families: the engine (the tap server with the bundled driver, plus tap-schema/tap-api), the Kotlin client, the Python client, tap-agent, tap-studio, tap-watcher and sync-sdk. Versions are set in gradle.properties and the clients' pyproject.toml files; pushing a tag such as daemon/v0.0.2 on a commit with the matching version publishes that family. See Releases and versions.