The Maps omnibox hint rotates: it read "Search here" in the Phase 5 run and "Try petrol stations, ATMs" in the AUD-128 re-verification, so the recipe's text match on it failed. The resource id does not rotate. - internal/ui: add Find/FindID and the "id:" selector; Find dispatches an id: selector to the resource id and everything else to text as before. - internal/recipe: wait/tap/wait_gone/tap_if and the tap helper use Find, so a recipe may name a resource id. Guards stay text-only. - recipes/maps_navigate.recipe: wait/tap id:search_omnibox_text_box. - tests for the id selector; docs and README updated. Co-Authored-By: Paperclip <noreply@paperclip.ing>
114 lines
5.4 KiB
Markdown
114 lines
5.4 KiB
Markdown
# Artemis Control
|
||
|
||
Artemis is the phone's **hands**. It drives the Pixel 8a screen from `.13` over
|
||
Tailscale using **ADB wireless**, so the Hub can do things no deep link allows.
|
||
|
||
The build spec is [`AGENTS.md`](AGENTS.md). Follow the phases in section 11 in
|
||
order.
|
||
|
||
## Status
|
||
|
||
| Phase | State |
|
||
|---|---|
|
||
| 1 — Prove the ADB link | ✅ proven — see [`docs/phase-1-adb-link.md`](docs/phase-1-adb-link.md) |
|
||
| 2 — Act primitives | ✅ done — tap by text, type, scroll, back, home, launch |
|
||
| 3 — Spotify recipe | ✅ done — `spotify_play` plays the right song end to end (13 steps, 35s), see [`docs/phase-3-spotify-recipe.md`](docs/phase-3-spotify-recipe.md) |
|
||
| 4 — Song radio | ✅ done — `spotify_radio` starts a real radio (19 steps, 57s); the run log is now bounded at ~1 MB × 5, see [`docs/phase-4-song-radio.md`](docs/phase-4-song-radio.md) |
|
||
| 5 — Maps recipe | ✅ done — `maps_navigate` opens a real search and the result list names the place (8 steps, 28s), see [`docs/phase-5-maps-recipe.md`](docs/phase-5-maps-recipe.md) |
|
||
| 6 — Action API | not started |
|
||
| 7 — Resilience | not started |
|
||
| 8 — Documentation | not started |
|
||
|
||
## Layout
|
||
|
||
```
|
||
cmd/artemis/ command line entry point
|
||
internal/adb/ timeout-bounded wrapper around the adb binary
|
||
internal/ui/ read the screen (uiautomator dump) and act on it (input)
|
||
internal/recipe/ the recipes file format and its runner
|
||
recipes/ one hand-editable file per task
|
||
logs/ run log (JSON lines, not committed)
|
||
docs/ phase evidence and notes
|
||
```
|
||
|
||
## Build and test
|
||
|
||
Go is not installed system-wide on `.13`; use a one-off nix shell. Nothing is
|
||
added to `nixos-rebuild` and no system module changes.
|
||
|
||
```sh
|
||
nix shell nixpkgs#go -c go build -o bin/artemis ./cmd/artemis
|
||
nix shell nixpkgs#go -c go vet ./...
|
||
nix shell nixpkgs#go -c go test ./...
|
||
```
|
||
|
||
## Use
|
||
|
||
The phone address defaults to `100.101.49.17`. Override with `ARTEMIS_PHONE`,
|
||
pin a serial with `ARTEMIS_ADB_SERIAL`, point at a different adb with
|
||
`ARTEMIS_ADB`, point at another recipes directory with `ARTEMIS_RECIPES`, and
|
||
move the run log with `ARTEMIS_LOG`.
|
||
|
||
```sh
|
||
./bin/artemis connect # scan for the wireless-debugging port, then connect
|
||
./bin/artemis devices
|
||
./bin/artemis dump # text nodes and bounds
|
||
./bin/artemis keyguard # lock-screen state; exits 1 when locked
|
||
./bin/artemis launch com.spotify.music
|
||
./bin/artemis tap "Search" # find by text, tap its centre (FR-4)
|
||
./bin/artemis type "Rolling Stones Honky Tonk"
|
||
./bin/artemis scroll down
|
||
./bin/artemis back
|
||
./bin/artemis home
|
||
./bin/artemis demo # Phase 2 DoD: launch an app, tap a named element
|
||
```
|
||
|
||
## Recipes
|
||
|
||
A recipe is a plain-text file in `recipes/` that Sam edits by hand. Steps name
|
||
what to find on screen; there are no coordinates anywhere.
|
||
|
||
```sh
|
||
./bin/artemis recipes # list them
|
||
./bin/artemis run spotify_play query="Rolling Stones Honky Tonk"
|
||
./bin/artemis run spotify_radio query="Rolling Stones Honky Tonk"
|
||
./bin/artemis run maps_navigate query="Melbourne Museum"
|
||
```
|
||
|
||
Read [`docs/phase-3-spotify-recipe.md`](docs/phase-3-spotify-recipe.md) for the
|
||
file format, the verbs, and why list rows need their own step,
|
||
[`docs/phase-4-song-radio.md`](docs/phase-4-song-radio.md) for the context menu,
|
||
`menu_row`, and the two ways a radio run fails on purpose, and
|
||
[`docs/phase-5-maps-recipe.md`](docs/phase-5-maps-recipe.md) for the Maps
|
||
labels, the `expect_change` check, and the one ordering rule it depends on.
|
||
|
||
## Rules that the code enforces
|
||
|
||
- **Find by text, never fixed coordinates.** `tap` resolves a match to
|
||
bounds and taps the centre; `tap_row` scores result rows against the query
|
||
and taps the winning row's centre; `menu_row` opens the action menu of that
|
||
same row, from the button inside it. Where a visible label rotates (the Maps
|
||
search box), a step names the stable resource id with the `id:` selector —
|
||
still a named match, still no coordinates.
|
||
- **Never tap blindly.** A missing or disabled element is a loud failure, not a
|
||
guessed coordinate. A named bad screen (`guard`) fails with its own reason,
|
||
and a step may name its own failure with `else "..."`.
|
||
- **Detect a wedged app.** An app can stop consuming input while it keeps
|
||
drawing: Android delivers the tap, the app never acts on it. `expect_change`
|
||
fails with `the app stopped consuming input` when the screen does not change
|
||
after the previous step, instead of blaming the label the next step waited for.
|
||
- **Never touch a locked phone.** A secure keyguard stops Artemis with
|
||
`phone is locked; unlock it and retry`. No PIN is stored, read or typed
|
||
(AGENTS.md section 13 Q2).
|
||
- **Confirm playback from the media session, not the screen.** Spotify's
|
||
transport button keeps the label "Play" while music plays; the `playing` step
|
||
asks Android what is actually playing and checks the track title.
|
||
- **Wake first.** Every acting command wakes the screen before it acts (FR-7),
|
||
and it checks the keyguard only after the screen is on.
|
||
- **Bound every command.** No adb call runs without a timeout, and a recipe has
|
||
a per-step and a whole-run timeout (FR-11).
|
||
- **Log every run.** Each run appends a JSON line to `logs/runs.jsonl` (FR-10).
|
||
The file rotates at about 1 MB, keeping the recent runs (FR-10 is an audit
|
||
trail, so history is bounded, never deleted wholesale). Tune it with
|
||
`ARTEMIS_LOG_MAX_BYTES` and `ARTEMIS_LOG_KEEP`.
|