Commit Graph
283 Commits
Author SHA1 Message Date
pj 6b1c17328c fix(sample-app): make it standalone, no repo_root assumptions (#8)
* fix(cli): fall back to node_modules for @uatu/spec resolution

Drop the hard failure when the uatu source tree is not reachable from
the spec file. Users integrating uatu in their own app have @uatu/spec
installed via npm; esbuild now resolves it from node_modules.

* build(gradle): drop :sample-app include from root settings

The sample now has its own Gradle project in examples/sample-app/android.

* build(sample-app): vendor gradle wrapper

Users running the sample build the APK via ./gradlew from inside the
sample-app's own android/ directory, no repo-root wrapper required.

* build(sample-app): make gradle project standalone

Drop the project(':sdk-android') dependency in favor of the Maven
Central coordinate io.github.priyanshujain:sdk-android. The sample now
owns its settings.gradle.kts and gradle.properties, so it builds
without any pieces of the uatu source tree.

* chore(sample-app): declare @uatu/spec npm dependency

Mirrors what a downstream user would put in their own package.json.
Uses file: for pre-release development; becomes a normal semver pin
once @uatu/spec ships to npm.

* docs(sample-app): rewrite justfile and add README

Justfile drops repo_root; all recipes run against the local gradle
wrapper and uatu from PATH. README is scoped to what a user needs to
run the sample against their own device.

* docs(manual): update sample install steps to standalone layout

./gradlew :sample-app:installDebug no longer exists; the sample owns
its own wrapper under android/.

* feat(sdk): log when Uatu.start succeeds

Silent SDK start makes the "SDK didn't connect" failure mode
impossible to debug. One INFO line at start time is enough.

* fix(sidecar): launch via am start -W instead of monkey

monkey -p <pkg> -c LAUNCHER 1 is unreliable on API 36+: it reports no
error but silently fails to start the activity, so the SDK never runs
and the CLI times out on the SDK-accept handshake.

Resolve the launcher activity via `cmd package resolve-activity
--brief` and launch it with `am start -W -n`. -W makes the call block
until the activity is up, which also makes the subsequent SDK
accept timing deterministic.

* feat(cli): auto-resolve Android device; boot AVD if none connected

--avd becomes optional. Resolution order:
 - use any already-connected adb device;
 - else if --avd names an existing AVD, boot it and wait for boot;
 - else if --avd is missing or names no AVD, error with a clear message.

Falls back to $ANDROID_HOME/emulator/emulator when the binary is not on
PATH, so a standard Android SDK install works without extra shell setup.

* docs(sample-app): AVD is optional; document both paths

just test runs against any connected device. If none, pass AVD=<name>
to have uatu boot the emulator for you.

* feat(cli): auto-discover Android SDK; auto-pick the lone AVD

adb and emulator are looked up via PATH, then $ANDROID_HOME,
$ANDROID_SDK_ROOT, ~/Library/Android/sdk, ~/Android/Sdk, and the
Homebrew cask path. The discovered platform-tools directory is
prepended to the sidecar's PATH so its adb subprocess calls work too.

When --avd isn't passed and no device is connected, the CLI picks the
sole local AVD and boots it. Multiple AVDs → error listing them.

* build(make): add `make install` that go-installs uatu onto PATH

Puts `uatu` into $GOBIN (or $GOPATH/bin) so the sample and any local
dev flow can call it without PATH= prefixes.

* docs(sample-app): zero-config just test; dotenv-load for persistence

Drop the expectation that users prefix commands with PATH=, ANDROID_HOME=,
or AVD=. `just test` now works as-is; optional knobs can be pinned in a
.env file alongside the justfile.

* fix(sample-app): auto-detect ANDROID_HOME for Gradle tasks

The Go CLI finds the SDK itself, but AGP still needs ANDROID_HOME to
resolve `sdk.dir`. The justfile now resolves it from env or canonical
install paths before invoking ./gradlew, so `just install` works out
of the box on a standard Android SDK setup.

* gitignore runs directory for sample app
2026-04-18 15:31:47 +07:00
pj bf733f4f93 Revise doc links in README
Updated documentation links to point to the new site.
2026-04-18 14:18:35 +07:00
pj ae595526da fix(agent): race in readWithDeadline clobbers conn deadline (#7)
* refactor(docs): inline pandoc build into Makefile, drop scripts dir

* fix(agent): wait for deadline watcher before returning

readWithDeadline's watcher goroutine could clobber the conn's read
deadline with time.Unix(1, 0) after the main function reset it to zero.
When the Accept ctx was canceled shortly after Accept returned, the
watcher raced with close(done) in select and sometimes picked ctx.Done()
even though we were already done reading, leaving the conn unusable for
the next read (instant i/o timeout on step 1 snapshot).

Synchronize on the watcher's exit before resetting the deadline so it
can never override the reset.

* test(agent): cover readWithDeadline race on Accept ctx cancel

Drives Accept with a short-timeout ctx, cancels it right after Accept
returns, then does a Snapshot. Reliably fails without the readWithDeadline
synchronization fix (watcher goroutine overwrites the deadline to past).
2026-04-18 14:16:15 +07:00
pj 55ef13e0ed refactor(docs): inline pandoc build into Makefile (#6)
* refactor(docs): inline pandoc build into Makefile, drop scripts dir

* ci(docs): use make target in workflow, drop scripts path trigger
2026-04-18 14:06:53 +07:00
pj e62319e916 docs: pandoc-based site and v0.1.0 groundwork (#5)
* chore(prose): remove em-dashes from config files

* chore(prose): remove em-dashes from android sdk config

* docs(spec-api): remove em-dash from README

* fix(doctor): reword sidecar-jar error without em-dash

* test(sidecar): reword assertion message without em-dash

* docs: add CLAUDE.md with project conventions

* build: add docs target for pandoc site

* docs(site): add pandoc template and stylesheet

* docs(site): add pandoc build script

* docs(site): add landing pages

* docs(manual): add getting-started

* docs(manual): add writing-specs

* docs(manual): add runs

* docs(manual): add cli reference

* docs(dev): add design principles

* docs(dev): add architecture

* ci: deploy docs site to github pages

* docs: rewrite README as entry point to docs site
2026-04-18 14:00:57 +07:00
pj a74d9fbeae build(examples): add justfile for sample-app
Targets: install/uninstall the APK, build the uatu CLI, run 'uatu test'
against a named AVD, run the Go verify tests, and clean. Keeps the
example self-contained so users can 'just test AVD=pixel_7' after cloning.
2026-04-18 10:33:53 +07:00
pj ec3764ad85 chore(examples): remove merchant-ledger spec
No longer referenced anywhere — sample-app replaces it as the
in-tree example.
2026-04-18 10:33:49 +07:00
pj 40c92d4569 test(verifier): rewrite integration tests for sample-app spec
The old tests loaded merchant-android uiautomator dumps from /tmp and
skipped when absent. Replace with two hermetic tests that bundle
examples/sample-app/spec.ts against a synthetic hierarchy: one checks
tapClickMe fires, the other drives the three properties through a
holds/holds/violated snapshot sequence.
2026-04-18 10:33:43 +07:00
pj 0c775c199d feat(examples): add sample-app spec
Introduce examples/sample-app/spec.ts — a minimal property-based spec that
taps the sample app's "Click me" button and asserts click_count is
monotonic. Bundle-check and the trace writer test now reference the new
path.
2026-04-18 10:33:35 +07:00
pj c12a3e242f refactor(android): move sample app under examples/sample-app/
Rename the gradle sample project from :sdk-android-sample to :sample-app
so the isolated example for users lives beside its spec in examples/.
2026-04-18 10:33:31 +07:00
pj f57634809e build(sidecar): pin shadowJar output name to sidecar-all.jar (#3) v0.0.1-rc2 2026-04-18 09:05:00 +07:00
pj 2b006aabd8 fix(spec-api): use tsx loader so tests run on Node 20 (#2) v0.0.1-rc1 2026-04-18 08:56:27 +07:00
pj 0570719e6f Publish pipeline: goreleaser + Maven Central + npm (#1)
* feat(cli): add Version var and version subcommand

* build(gradle): introduce uatu.version property for lockstep releases

* build(sdk-android): swap GitHub Packages for vanniktech Maven Central plugin

* build(spec-api): make package publish-ready for npm

* ci(release): add goreleaser config for cross-platform uatu CLI builds

* ci: add ci and release GitHub Actions workflows

* ci: restrict ci.yml to PR + workflow_dispatch (no direct push to master)

* docs(release): add local release targets, env example, and install docs

* build(sdk-android): make signAllPublications conditional on signing key

* ci(release): stage sidecar JAR at embed path before go build

* chore(spec-api): regenerate package-lock for updated package.json

* ci: install protoc-gen-go plugins before buf generate

* ci: bump Node to 22 (required for --experimental-strip-types)
2026-04-18 08:50:57 +07:00
pj 6e4c832678 chore: stop tracking sidecar JAR (build artifact)
make uatu copies the real fat JAR into assets/ before
go build -tags withsidecar. Keeping that path tracked was
the root cause of the 130 MB push rejection.
2026-04-18 06:46:32 +07:00
pj ebb9389754 build(make): pass -tags withsidecar when embedding real JAR
Also mkdir the assets dir so fresh clones work once the JAR
path is gitignored in the next commit.
2026-04-18 06:46:28 +07:00
pj ae10354ff0 test(sidecar): split tests across stub and withsidecar builds
Default !withsidecar build: assert IsPlaceholder, empty JAR,
Extract errors. withsidecar build: existing extract/checksum
coverage.
2026-04-18 06:46:24 +07:00
pj 4744736dc5 refactor(sidecar): gate JAR embed behind withsidecar build tag
Splits embed.go so the go:embed directive only fires under
-tags withsidecar. Default builds get a stub with a nil JAR
and IsPlaceholder()=true. This removes the landmine where
make uatu overwrote a tracked placeholder file, making any
git add silently stage 130 MB.
2026-04-18 06:46:20 +07:00
pj 6a07284e24 fix(spec): screen-aware sign convention + leaveLedger generator to cycle counterparties 2026-04-18 06:39:00 +07:00
pj b19d83fd54 fix(spec): state-aware enterMobile + globalise properties for evaluator pickup 2026-04-18 06:39:00 +07:00
pj f2fe54debc test(verifier): verify dismissMultiDevice fires against real confirm dialog 2026-04-18 06:39:00 +07:00
pj 4b43b9a22a chore(runner): log screen + tag hits per step for debuggability 2026-04-18 06:39:00 +07:00
pj cefb398437 feat(verifier): retry NextAction up to 16x so gated generators eventually fire 2026-04-18 06:39:00 +07:00
pj 6c7ea35a7d feat(hierarchy): expose checked/focused/selected on element objects 2026-04-18 06:39:00 +07:00
pj b248ad2e5a test(verifier): integration tests against live uiautomator dumps 2026-04-18 01:59:54 +07:00
pj 6ce4eabe86 feat(spec): drive merchant onboarding (English, phone, OTP, multi-device, home) 2026-04-18 01:59:51 +07:00
pj eea9a0067a feat(hierarchy): descPrefix selector for Compose testTag + UUID suffixes 2026-04-18 01:59:46 +07:00
pj fb6c3ca517 fix(runner): fetch hierarchy before SDK pause to avoid stale uiautomator dumps 2026-04-18 01:59:41 +07:00
pj c4cf0ff530 feat(driver): optional launcher_activity on Launch RPC for multi-alias apps 2026-04-18 01:59:36 +07:00
pj 6d81e24e71 feat(sidecar): real hierarchy via adb uiautomator dump 2026-04-18 01:24:04 +07:00
pj 447fd39363 feat(runner): fetch hierarchy per step and resolve Tap coords 2026-04-18 01:24:00 +07:00
pj 5f2a2d52ab feat(verifier): wire hierarchy into state.ax and carry element coords in Action 2026-04-18 01:23:55 +07:00
pj e539e89438 feat(hierarchy): XML parser and selector resolver for uiautomator dumps 2026-04-18 01:23:51 +07:00
pj e99c85c363 feat(cli): orchestrate full uatu test pipeline
runTestPipeline assembles the v0.1 stack end to end:
  1. Bundle the spec (with @uatu/spec alias resolution)
  2. Extract the embedded sidecar JAR
  3. Spawn java -jar sidecar --port <free>
  4. Wait for sidecar Health
  5. Listen on a host TCP port + adb reverse to the device's
     localabstract:uatu-agent socket
  6. Launch the app via the maestro driver
  7. Accept the SDK HELLO
  8. Load the bundle into the verifier
  9. Open the trace writer + write meta.json
  10. runner.Run for the requested duration
  11. Terminate the app + clean up adb reverse

Test subcommand now prints a bundle error for a missing spec,
verifying the flag surface reaches the pipeline.
2026-04-18 00:51:44 +07:00
pj e7b3e2ba9c refactor(runner): caller manages app launch/terminate
Removes Launch + Terminate from runner.Run so the CLI can launch
the app first, wait for the SDK to connect, then start the loop.
The previous shape forced runner to launch internally which fought
with the SDK-must-be-connected-first ordering.

BundleID/ClearState fields go away too since runner no longer
launches; the CLI keeps them on its testOptions struct.
2026-04-18 00:51:39 +07:00
pj 0c68e72341 feat(sdk-android): publish to GitHub Packages at maven.pkg.github.com
Coordinates: dev.uatu:sdk-android:0.0.1. Credentials read from
GH_TOKEN/GH_USERNAME (or GITHUB_TOKEN/GITHUB_ACTOR for CI).
.env is gitignored so local tokens stay out of git.

Consumers add the maven repo + debugImplementation in their
build.gradle and we're done.
2026-04-18 00:16:33 +07:00
pj fad4f7db38 test(proto): include TapSelector in v0.1 RPC list 2026-04-18 00:09:27 +07:00
pj 4dbad9073e feat(spec): merchant-ledger property + login/navigation generators
Property ledgerBalanceMatchesTxns asserts displayed balance equals
sum(Given) - sum(Received) on either ledger screen. Catches the
class of bug where the server-fed CustomerModel.balance diverges
from the local-DB-fed CoreDatabaseDao sums (stale cache, partial
sync, deleted-txn handling glitch, etc.).

Generators are gated by screen state and weighted to push the run
through login -> home -> ledger quickly:
  enterPhone (100), enterOtp (100), openCustomerOrSupplier (80),
  taps (10), swipes (2).

Phone/OTP read from process.env via esbuild defines so credentials
never land in source. cmd/internal-tools/bundle-check is a quick
sanity tool to confirm the spec bundles before running uatu test.
2026-04-18 00:08:40 +07:00
pj 7aa75f3d9f feat(bundler): add Aliases option for spec import resolution
Specs import from "@uatu/spec"; the bundler maps that to the
vendored pkg/spec-api/src/index.ts via esbuild's Alias map.
2026-04-18 00:08:31 +07:00
pj 8c6a3171d7 feat(cli): doctor check that surfaces placeholder sidecar JAR
Doctor flags a shipped binary that's still carrying the build-time
placeholder so `uatu test` won't silently fail trying to launch a
nonexistent sidecar. Makefile uatu target now copies the freshly
built fat JAR into the embed directory before `go build`.
2026-04-18 00:06:30 +07:00
pj 6a9fac7ec0 feat(sidecar): embed sidecar JAR via go:embed
Ships with a 24-byte placeholder so fresh clones build without
requiring a sidecar build first. `make uatu` copies the real
fat JAR into internal/sidecar/assets before `go build`, so
shipping binaries carry the full sidecar (~130 MB).

Extract writes the JAR to a temp dir alongside a SHA-256 file
and skips rewrite when the checksum already matches.
2026-04-18 00:06:27 +07:00
pj 69d0800ea3 feat(driver/maestro): gRPC client implementing driver.Driver
Wraps each v0.1 RPC, plus a WaitForHealth helper that polls until
the sidecar reports Ready=true (used at startup before any other
calls happen). Tests stub the gRPC server in-process so they don't
need a real sidecar JAR.
2026-04-18 00:04:24 +07:00
pj f6efee1a9c feat(sidecar): wire tapSelector through DriverBackend + DriverService 2026-04-18 00:04:22 +07:00
pj b24463fc81 feat(proto): add TapSelector RPC and Selector message
Driver interface in Go already exposes TapSelector for selector-based
taps; mirror that in the proto so maestro can resolve selectors
sidecar-side rather than forcing Go to walk the hierarchy first.
2026-04-18 00:04:20 +07:00
pj 6020a52adb test(sidecar): cover DriverService RPC delegation via in-process gRPC 2026-04-18 00:02:11 +07:00
pj fc209aaeb2 feat(sidecar): Main entry point binding the gRPC server to 127.0.0.1 2026-04-18 00:02:10 +07:00
pj 125a060a69 feat(sidecar): DriverService implementing v0.1 RPCs
DriverService delegates each RPC to a pluggable DriverBackend so
real Maestro integration can land in a follow-up without changing
the gRPC layer. StubDriverBackend satisfies the contract and lets
the Go side talk to a running sidecar today.
2026-04-18 00:02:08 +07:00
pj d9800b648f chore(sidecar): wire grpc + protobuf-gradle-plugin + maestro
Generates Java stubs from proto/driverpb/driver.proto via the
protobuf plugin (sourceSets points at the shared root proto dir).
Pulls in dev.mobile:maestro-client:1.40.0, grpc-netty-shaded,
slf4j-simple, and the JUnit vintage engine so the gRPC test rules
work alongside JUnit Jupiter.
2026-04-18 00:02:06 +07:00
pj d856c40d7c feat(permissions): grant dangerous permissions via aapt + adb
Inspector parses uses-permission entries from `aapt dump permissions`
and the granter shells out to `adb shell pm grant`. Both are pluggable
so tests can drive logic without aapt or a device. Granter failures
become warnings rather than errors — Android refuses non-runtime
permissions and we'd rather keep going than abort the run.
2026-04-17 23:57:17 +07:00
pj b6b18c3681 feat(cli): real doctor subcommand with adb/emulator/java checks
Each check is a value so tests can swap in fakes. Java check parses
both legacy (1.8) and modern (17+) version strings. Emulator check
falls back to ANDROID_HOME/emulator/emulator since the brew cask
ships it without putting it on PATH.
2026-04-17 23:56:00 +07:00
pj 63796fbd42 chore(build): add Makefile with bootstrap, sidecar, sdk, uatu, test targets 2026-04-17 23:54:41 +07:00