adb-ready 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -1
- package/COMPATIBILITY.md +31 -0
- package/README.md +33 -11
- package/dist/cli.js +5381 -564
- package/docs/agent-integration.md +64 -17
- package/docs/apps-and-evidence.md +14 -1
- package/docs/automation.md +120 -7
- package/docs/configuration.md +6 -0
- package/docs/firebase-test-lab.md +126 -0
- package/docs/gradle-managed-devices.md +105 -0
- package/docs/logs-and-context.md +34 -0
- package/docs/target-pools.md +151 -0
- package/docs/targets-and-wireless.md +21 -0
- package/docs/threat-model.md +33 -0
- package/docs/troubleshooting.md +39 -0
- package/docs/ui-automation.md +130 -6
- package/examples/README.md +6 -0
- package/examples/ci-emulator/README.md +32 -0
- package/examples/ci-emulator/adb-ready.config.json +20 -0
- package/examples/ci-emulator/dev-service.mjs +14 -0
- package/examples/ci-emulator/verify-emulator.mjs +108 -0
- package/examples/ci-physical/README.md +51 -0
- package/examples/ci-physical/adb-ready.config.json +20 -0
- package/examples/ci-physical/dev-service.mjs +14 -0
- package/examples/ci-physical/github-actions.yml +56 -0
- package/examples/ci-physical/run-job.mjs +77 -0
- package/examples/ci-physical/verify-device.mjs +90 -0
- package/examples/target-pools/README.md +22 -0
- package/examples/target-pools/adb-ready.config.json +29 -0
- package/llms.txt +8 -4
- package/package.json +4 -1
- package/schema/agent-tools-v1.json +1080 -81
- package/schema/config-v1.schema.json +86 -0
- package/skills/adb-ready/SKILL.md +54 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Target pools and fan-out
|
|
2
|
+
|
|
3
|
+
Use a target pool when the same bounded verification must run against an
|
|
4
|
+
explicit set of Android targets. Pool mode is opt-in. A normal `adb-ready run`
|
|
5
|
+
still selects exactly one target.
|
|
6
|
+
|
|
7
|
+
Every acquired member receives its own single-target session, target lease,
|
|
8
|
+
verifier result, cleanup, and evidence bundle. The pool result aggregates those
|
|
9
|
+
unchanged member envelopes in declaration order.
|
|
10
|
+
|
|
11
|
+
## Declare a local pool
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"$schema": "./node_modules/adb-ready/schema/config-v1.schema.json",
|
|
16
|
+
"version": 1,
|
|
17
|
+
"targets": {
|
|
18
|
+
"pools": {
|
|
19
|
+
"smoke": {
|
|
20
|
+
"maxConcurrency": 2,
|
|
21
|
+
"failFast": false,
|
|
22
|
+
"leaseWaitMs": 30000,
|
|
23
|
+
"members": [
|
|
24
|
+
{ "id": "desk", "kind": "adb", "serial": "R3CT..." },
|
|
25
|
+
{ "id": "api36", "kind": "avd", "name": "Pixel_9_API_36" },
|
|
26
|
+
{
|
|
27
|
+
"id": "lab",
|
|
28
|
+
"kind": "remote-adb",
|
|
29
|
+
"host": "android-lab.internal",
|
|
30
|
+
"port": 5037,
|
|
31
|
+
"serial": "10.20.0.15:5555"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Run the same verifier on every member:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
adb-ready run --pool smoke -- npm run test:e2e
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Preview every member without inspecting ADB, starting an AVD, or running the
|
|
47
|
+
verifier:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
adb-ready run --pool smoke --dry-run --json -- npm run test:e2e
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The pool owns concurrency policy; it never infers an unbounded worker count.
|
|
54
|
+
Override it for one invocation only when the CI runner has known capacity:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
adb-ready run --pool smoke --max-concurrency 1 --fail-fast \
|
|
58
|
+
--lease-wait 45s -- npm run test:e2e
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Member kinds
|
|
62
|
+
|
|
63
|
+
| Kind | Required fields | Ownership |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `adb` | `id`, exact `serial` | Uses an already ADB-visible target. |
|
|
66
|
+
| `avd` | `id`, exact existing AVD `name` | Reuses or starts that AVD; stops only an emulator process started by this run. |
|
|
67
|
+
| `remote-adb` | `id`, `host`, exact `serial`; optional `port` | Uses the explicit ADB server without changing the global local server. |
|
|
68
|
+
| `firebase` | `id`, `model`, `version`; optional `locale`, `orientation` | Firebase owns the remote target and lifecycle. Use only with `test firebase`. |
|
|
69
|
+
|
|
70
|
+
Member IDs and resource identities must be unique inside a pool. Members are
|
|
71
|
+
required by default. Set `"required": false` only when that dimension is truly
|
|
72
|
+
informational; an optional failure remains visible but does not fail the
|
|
73
|
+
aggregate.
|
|
74
|
+
|
|
75
|
+
## Firebase dimensions
|
|
76
|
+
|
|
77
|
+
A Firebase-only pool submits one independently observable matrix per declared
|
|
78
|
+
dimension. This gives every dimension a stable member result and lets ADB Ready
|
|
79
|
+
bound submission concurrency.
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"version": 1,
|
|
84
|
+
"targets": {
|
|
85
|
+
"pools": {
|
|
86
|
+
"firebase-smoke": {
|
|
87
|
+
"maxConcurrency": 2,
|
|
88
|
+
"failFast": true,
|
|
89
|
+
"members": [
|
|
90
|
+
{ "id": "pixel-api35", "kind": "firebase", "model": "akita", "version": "35" },
|
|
91
|
+
{ "id": "tablet-api34", "kind": "firebase", "model": "tangorpro", "version": "34" }
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
adb-ready test firebase instrumentation \
|
|
101
|
+
--pool firebase-smoke \
|
|
102
|
+
--project my-project \
|
|
103
|
+
--app app-debug.apk \
|
|
104
|
+
--test-apk app-debug-androidTest.apk
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Dimensions are checked against the live Firebase catalog before upload. With
|
|
108
|
+
`failFast`, ADB Ready stops submitting queued dimensions after a required
|
|
109
|
+
failure. It does not cancel matrices already running remotely. Use
|
|
110
|
+
`adb-ready test firebase cancel` for an intentional provider cancellation.
|
|
111
|
+
|
|
112
|
+
## Queueing and leases
|
|
113
|
+
|
|
114
|
+
ADB Ready serializes contending work with an owner-recorded, heartbeating lease.
|
|
115
|
+
`leaseWaitMs` bounds how long a pool member waits; cancellation exits the queue
|
|
116
|
+
without taking ownership. Expired leases and leases owned by a dead process on
|
|
117
|
+
the same host are recovered conservatively.
|
|
118
|
+
|
|
119
|
+
The built-in coordination backend is a per-user filesystem on one host. It is
|
|
120
|
+
not a distributed lock. CI jobs on different machines need runner-level
|
|
121
|
+
routing or another explicit external coordination system. ADB Ready never
|
|
122
|
+
infers cross-host ownership.
|
|
123
|
+
|
|
124
|
+
An explicit remote ADB server is also a trust boundary: its socket is not
|
|
125
|
+
encrypted by ADB Ready. Protect it with a trusted private network, VPN, SSH
|
|
126
|
+
tunnel, or equivalent transport; never expose port 5037 to an untrusted
|
|
127
|
+
network.
|
|
128
|
+
|
|
129
|
+
## Aggregate result
|
|
130
|
+
|
|
131
|
+
Human output shows the status of every member. JSON and NDJSON preserve every
|
|
132
|
+
nested result envelope, including its command ID, structured problems,
|
|
133
|
+
timestamps, and evidence path. The aggregate cannot report success when any
|
|
134
|
+
required member failed, disappeared, was cancelled, or never started.
|
|
135
|
+
|
|
136
|
+
Useful automation fields:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
data.pool
|
|
140
|
+
data.maxConcurrency
|
|
141
|
+
data.failFast
|
|
142
|
+
data.coordination
|
|
143
|
+
data.members[].id
|
|
144
|
+
data.members[].status
|
|
145
|
+
data.members[].exitCode
|
|
146
|
+
data.members[].result
|
|
147
|
+
data.summary
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Pool configuration is declarative data. It cannot add executable hooks or
|
|
151
|
+
shell fragments, and every verifier continues to use direct argv execution.
|
|
@@ -96,6 +96,21 @@ This is useful for WSL, containers, VMs, SSH-forwarded workstations, and device
|
|
|
96
96
|
labs. ADB Ready does not start, kill, or reconfigure that shared server
|
|
97
97
|
implicitly.
|
|
98
98
|
|
|
99
|
+
Before every command sent through an explicit `--adb-host` or `--adb-port`, ADB
|
|
100
|
+
Ready opens a bounded read-only connection and requests only `host:version`.
|
|
101
|
+
The real `adb` process starts only when the server protocol matches the local
|
|
102
|
+
client. This matters because the upstream ADB client automatically sends
|
|
103
|
+
`host:kill` when it encounters a mismatched server version (see the
|
|
104
|
+
[AOSP ADB client implementation](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/client/adb_client.cpp)).
|
|
105
|
+
ADB Ready instead returns `ADB_REMOTE_SERVER_PROTOCOL_MISMATCH` and leaves the
|
|
106
|
+
shared server untouched.
|
|
107
|
+
|
|
108
|
+
The preflight also distinguishes an unreachable route, timeout, cancellation,
|
|
109
|
+
and a non-ADB response. It is repeated before each command so replacement of a
|
|
110
|
+
server during a long session fails closed. This is a narrow safety handshake,
|
|
111
|
+
not a replacement ADB transport; all device operations still use the selected
|
|
112
|
+
`adb` executable.
|
|
113
|
+
|
|
99
114
|
## Network limitations
|
|
100
115
|
|
|
101
116
|
Wireless discovery and connection can be blocked by guest Wi-Fi, client
|
|
@@ -110,6 +125,12 @@ adb-ready doctor --verbose
|
|
|
110
125
|
adb-ready devices
|
|
111
126
|
```
|
|
112
127
|
|
|
128
|
+
For WSL, a container, VM, or remote runner, first verify that its network
|
|
129
|
+
namespace can reach the exact protected ADB host and port. A successful DNS
|
|
130
|
+
lookup or mDNS discovery does not prove that the TCP route is usable. ADB Ready
|
|
131
|
+
reports this as `ADB_REMOTE_SERVER_UNREACHABLE` rather than an empty target
|
|
132
|
+
list.
|
|
133
|
+
|
|
113
134
|
If discovery is unavailable but the device shows a reachable connection
|
|
114
135
|
endpoint, pass it explicitly. Do not assume port `5555`; modern Wireless
|
|
115
136
|
debugging ports are dynamic.
|
package/docs/threat-model.md
CHANGED
|
@@ -39,6 +39,12 @@ selection fails. An MCP connection binds one target and refuses a silent
|
|
|
39
39
|
switch. UI references include the current hierarchy digest and are revalidated
|
|
40
40
|
immediately before mutation.
|
|
41
41
|
|
|
42
|
+
MCP profiles narrow discovery for a workflow and reduce accidental tool
|
|
43
|
+
selection, but they are not an authorization boundary. Every exposed operation
|
|
44
|
+
still applies its own target, path, input, ownership, and destructive-action
|
|
45
|
+
checks. Tool annotations and namespaced sensitivity metadata help clients choose
|
|
46
|
+
approval policy; they are hints, not security enforcement.
|
|
47
|
+
|
|
42
48
|
### Unsafe file mutation
|
|
43
49
|
|
|
44
50
|
Capture and setup paths are constrained to the project, checked for unsafe
|
|
@@ -52,6 +58,20 @@ redacted. Pairing codes use protected input or stdin and never enter process
|
|
|
52
58
|
arguments. Screenshots and UI hierarchies require explicit calls and never join
|
|
53
59
|
AI context automatically.
|
|
54
60
|
|
|
61
|
+
Screenshot capture rejects malformed PNG structure, invalid checksums,
|
|
62
|
+
unsupported encodings, and excessive source dimensions before publishing a
|
|
63
|
+
file. MCP image results are cropped or proportionally bounded by default and
|
|
64
|
+
must fit an encoded payload budget; full-resolution delivery is an explicit
|
|
65
|
+
client choice. These controls bound transport and context size, not the visual
|
|
66
|
+
sensitivity of the selected pixels.
|
|
67
|
+
|
|
68
|
+
Failure inspection accepts only a verified installed package, binds every
|
|
69
|
+
probe to the same target, uses package-scoped exit history and log filtering,
|
|
70
|
+
and retains DropBox blocks only when an exact package or package-process line
|
|
71
|
+
matches. Unavailable or permission-limited sources are reported rather than
|
|
72
|
+
replaced by target-wide evidence. Source bytes, time windows, record counts,
|
|
73
|
+
and returned excerpts are bounded and redacted.
|
|
74
|
+
|
|
55
75
|
### Unbounded or misleading automation
|
|
56
76
|
|
|
57
77
|
Process output, recordings, UI trees, session storage, context, retries, and
|
|
@@ -59,6 +79,12 @@ waits are bounded. Results distinguish process acceptance (`ok`) from observed
|
|
|
59
79
|
postconditions (`verified`). An unchanged UI is reported as a verification gap,
|
|
60
80
|
not silently upgraded to verified success.
|
|
61
81
|
|
|
82
|
+
Keyboard dismissal requires two independent Android visibility signals to
|
|
83
|
+
agree before Back is sent and after it completes. Runtime-permission responses
|
|
84
|
+
require one recognized PermissionController dialog and one exact resource ID;
|
|
85
|
+
localized labels, generic system dialogs, biometric prompts, notification
|
|
86
|
+
setup, and ambiguous OEM states are never accepted speculatively.
|
|
87
|
+
|
|
62
88
|
### Network exposure
|
|
63
89
|
|
|
64
90
|
The 0.2 MCP server uses stdio and opens no listener. ADB itself may connect to a
|
|
@@ -73,6 +99,13 @@ ADB Ready's isolation boundary.
|
|
|
73
99
|
- autonomous destructive recovery;
|
|
74
100
|
- automatic screenshot or UI-text upload.
|
|
75
101
|
|
|
102
|
+
Protected UI text is accepted from CLI stdin or by environment-variable name
|
|
103
|
+
through MCP. It is not placed in host argv, result envelopes, plans, journals,
|
|
104
|
+
screenshots created by the action, or AI context. It still exists transiently
|
|
105
|
+
in ADB Ready memory and the child-process pipe, and the selected Android target,
|
|
106
|
+
input method, app, or OEM auditing may observe it. ADB Ready does not claim to
|
|
107
|
+
turn a normal visible field or an untrusted target into a secure channel.
|
|
108
|
+
|
|
76
109
|
## Residual risk
|
|
77
110
|
|
|
78
111
|
A trusted ADB server can control connected Android targets, and an approved AI
|
package/docs/troubleshooting.md
CHANGED
|
@@ -21,6 +21,9 @@ adb-ready context --since 5m --only problems,recovery,logs
|
|
|
21
21
|
| `ADB_NOT_FOUND` | No usable ADB executable was resolved | Install Platform-Tools or pass `--adb PATH` |
|
|
22
22
|
| `ADB_SERVER_UNAVAILABLE` | The selected local or remote server could not be queried | Check the configured host/port and run `adb start-server` explicitly if appropriate |
|
|
23
23
|
| `ADB_VERSION_MISMATCH` | Client and server report incompatible versions | Align Platform-Tools versions; review before restarting the shared server |
|
|
24
|
+
| `ADB_REMOTE_SERVER_UNREACHABLE` | The explicit server socket has no usable route | Check WSL/container/VM/VPN/firewall/tunnel routing; do not expose port 5037 publicly |
|
|
25
|
+
| `ADB_REMOTE_SERVER_PROTOCOL_MISMATCH` | Remote and local ADB protocols differ | Align Platform-Tools deliberately; ADB Ready will not let upstream ADB kill the shared server |
|
|
26
|
+
| `ADB_REMOTE_SERVER_INVALID_RESPONSE` | The endpoint or local client could not complete the read-only protocol preflight | Verify the host, port, tunnel destination, and local Platform-Tools installation |
|
|
24
27
|
| `TARGET_UNAUTHORIZED` | Android has not authorized this host | Unlock the device and accept its RSA debugging prompt |
|
|
25
28
|
| `TARGET_NO_PERMISSIONS` | The host cannot access the USB device | Fix host USB permissions or rules, then reconnect |
|
|
26
29
|
| `TARGET_OFFLINE` | ADB knows the transport but it is not ready | Check cable/network state and retry `devices` |
|
|
@@ -34,6 +37,11 @@ adb-ready context --since 5m --only problems,recovery,logs
|
|
|
34
37
|
| `UI_HIERARCHY_BUSY` | Another process held the selected target's single UI Automation service through the UI timeout | Let that capture finish or increase the UI timeout, then retry |
|
|
35
38
|
| `UI_HIERARCHY_LOCK_UNAVAILABLE` | The per-user coordination directory is not writable | Restore write access to the ADB Ready state directory, then retry |
|
|
36
39
|
| `UI_NOT_IDLE` | Android UI Automator could not observe a quiet accessibility window | Pause continuous UI changes or navigate to a stable screen, then retry |
|
|
40
|
+
| `UI_UNICODE_INPUT_UNAVAILABLE` | Unicode/multiline input was requested but ADBKeyBoard is not installed and enabled | Install a compatible official ADBKeyBoard release, enable it in Android settings, and retry; ADB Ready will not enable an IME silently |
|
|
41
|
+
| `UI_INPUT_METHOD_UNKNOWN` | Android did not expose the current IME, so it cannot be restored safely | Select a keyboard on the target before retrying Unicode input |
|
|
42
|
+
| `UI_KEYBOARD_STATE_AMBIGUOUS` | InputMethodManager and WindowInsets disagree about keyboard visibility | Wait for the transition to settle, then inspect again; ADB Ready will not press Back while state is uncertain |
|
|
43
|
+
| `UI_KEYBOARD_STATE_UNSUPPORTED` | The Android build omitted one of the two required keyboard signals | Dismiss the keyboard in the app or use a target that exposes both dumpsys states |
|
|
44
|
+
| `UI_PERMISSION_DIALOG_UNSUPPORTED` | The current PermissionController or OEM dialog is not one exact supported runtime-permission prompt | Handle the dialog manually; notification, biometric, Settings, and unknown OEM dialogs are not auto-accepted |
|
|
37
45
|
| `SESSION_RECOVERY_FAILED` | The bounded target/port recovery budget was exhausted | Inspect `problems`, network state, and saved recovery events |
|
|
38
46
|
| `SESSION_PERSISTENCE_FAILED` | The private session record could not be written | Check user-state directory permissions and capacity |
|
|
39
47
|
| `FRAMEWORK_LAUNCHER_NOT_FOUND` | The detected Flutter or Gradle project has no runnable launcher | Install Flutter and expose `flutter` on PATH, or restore the project's checked-in Gradle Wrapper |
|
|
@@ -135,6 +143,37 @@ Resolve the user-action problem, then start a new session. Increase the recovery
|
|
|
135
143
|
budget only when the environment is known to need more time; do not use an
|
|
136
144
|
unbounded timeout.
|
|
137
145
|
|
|
146
|
+
## Screenshot capture is rejected
|
|
147
|
+
|
|
148
|
+
`SCREENSHOT_BOUNDS_INVALID` means the requested `--crop X,Y,WIDTH,HEIGHT` lies
|
|
149
|
+
outside the decoded source image. Capture once without a crop and read the
|
|
150
|
+
reported `image.source` dimensions, then retry with coordinates inside them.
|
|
151
|
+
|
|
152
|
+
`SCREENSHOT_PAYLOAD_TOO_LARGE` protects an MCP client from an unexpectedly
|
|
153
|
+
large image. Crop the relevant region or lower `maxWidth`/`maxHeight`; use
|
|
154
|
+
`fullResolution: true` only when the client can deliberately accept it.
|
|
155
|
+
|
|
156
|
+
`SCREENSHOT_INVALID` or `SCREENSHOT_ENCODING_UNSUPPORTED` means ADB did not
|
|
157
|
+
produce a complete standard non-interlaced 8-bit PNG. Confirm that the selected
|
|
158
|
+
display is available and not blocked by a secure surface, then retry without
|
|
159
|
+
assuming that a PNG signature alone proves usable evidence.
|
|
160
|
+
|
|
161
|
+
## Failure inspection has unavailable sources
|
|
162
|
+
|
|
163
|
+
`adb-ready inspect failures APP_ID` succeeds when the app and target are
|
|
164
|
+
verified even if an optional Android evidence source is absent. Read the
|
|
165
|
+
`sources` list: each entry distinguishes available, truncated,
|
|
166
|
+
permission-limited, and unsupported evidence.
|
|
167
|
+
The top-level `truncated` flag means the requested incident limit bounded the
|
|
168
|
+
combined result; a source-level flag means Android output or log retention was
|
|
169
|
+
bounded before correlation.
|
|
170
|
+
|
|
171
|
+
Application exit history requires Android 11/API 30 or a compatible OEM
|
|
172
|
+
backport. DropBox visibility varies by Android build and shell permissions.
|
|
173
|
+
ADB Ready does not substitute unrelated target-wide crashes. Use a recent
|
|
174
|
+
`--since` window, reproduce the failure, and retry; use focused `adb-ready logs`
|
|
175
|
+
when the app is still running and live output is required.
|
|
176
|
+
|
|
138
177
|
## Report a reproducible issue
|
|
139
178
|
|
|
140
179
|
For ordinary bugs, open a GitHub issue with the ADB Ready version, host/runtime,
|
package/docs/ui-automation.md
CHANGED
|
@@ -30,6 +30,33 @@ parallel, and selector processing or input does not hold the capture lock. If
|
|
|
30
30
|
another process does not finish within the configured UI timeout, the command
|
|
31
31
|
returns `UI_HIERARCHY_BUSY` instead of misreporting an inaccessible screen.
|
|
32
32
|
|
|
33
|
+
Choose an acquisition profile when the default one-shot observation is not the
|
|
34
|
+
right tradeoff:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
adb-ready inspect ui --acquisition balanced
|
|
38
|
+
adb-ready inspect ui --acquisition fast --interactive-only
|
|
39
|
+
adb-ready inspect ui --acquisition strict
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `balanced` takes one fresh platform-idle snapshot within the normal bounded
|
|
43
|
+
UI timeout and is the default.
|
|
44
|
+
- `fast` keeps the same safe target-scoped platform backend but limits the
|
|
45
|
+
attempt to three seconds. It reports a timeout or non-idle screen instead of
|
|
46
|
+
pretending that an empty result is a valid screen.
|
|
47
|
+
- `strict` requires two consecutive matching hierarchy digests within at most
|
|
48
|
+
three captures. A continuously changing screen returns
|
|
49
|
+
`UI_HIERARCHY_UNSTABLE`.
|
|
50
|
+
|
|
51
|
+
Every successful structured snapshot reports its observation time, duration,
|
|
52
|
+
attempt count, stability status, source and idle strategy, requested filters,
|
|
53
|
+
node limits, truncation, observed display bounds and rotation, and detected
|
|
54
|
+
semantic limitations. An empty hierarchy is an explicit `UI_HIERARCHY_EMPTY`
|
|
55
|
+
failure. WebView, Compose, and Flutter
|
|
56
|
+
markers are reported only when their corresponding platform view is actually
|
|
57
|
+
observed; they describe framework accessibility boundaries rather than guessing
|
|
58
|
+
why an otherwise unreadable window failed.
|
|
59
|
+
|
|
33
60
|
## Audit one screen for people and agents
|
|
34
61
|
|
|
35
62
|
```bash
|
|
@@ -92,6 +119,7 @@ field happens to be focused:
|
|
|
92
119
|
adb-ready ui get 'id=com.example:id/email' --json
|
|
93
120
|
adb-ready ui fill 'id=com.example:id/email' 'person@example.com'
|
|
94
121
|
adb-ready ui fill 'id=com.example:id/search' 'pixel' --submit
|
|
122
|
+
adb-ready ui fill 'id=com.example:id/name' 'Příliš žluťoučký 🦊' --input-mode unicode
|
|
95
123
|
adb-ready ui clear 'id=com.example:id/search'
|
|
96
124
|
```
|
|
97
125
|
|
|
@@ -128,9 +156,84 @@ node whose `scrollable` property is true; without a selector it uses the screen.
|
|
|
128
156
|
Supported keys are `back`, `home`, `enter`, `menu`, `volume-up`, and
|
|
129
157
|
`volume-down`.
|
|
130
158
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
Text input is bounded to 1–256 Unicode code points and 4096 UTF-8 bytes. The
|
|
160
|
+
default `--input-mode auto` uses Android's built-in `input text` only for the
|
|
161
|
+
conservative ASCII set of letters, numbers, spaces, and `._@+,:/-`. Use
|
|
162
|
+
`--input-mode ascii` to require that path explicitly.
|
|
163
|
+
|
|
164
|
+
Unicode, emoji, RTL, CJK, and multiline input use the open-source
|
|
165
|
+
[ADBKeyBoard](https://github.com/senzhk/ADBKeyBoard) broadcast contract because
|
|
166
|
+
Android's built-in input command does not reliably represent those values. The
|
|
167
|
+
IME must already be installed and enabled on the selected target. ADB Ready
|
|
168
|
+
checks that contract before touching the screen, temporarily selects the IME,
|
|
169
|
+
and restores the exact previous IME in a `finally` path. It never downloads,
|
|
170
|
+
installs, enables, or leaves a keyboard selected silently. If the helper or a
|
|
171
|
+
restorable current IME is unavailable, the action fails before typing.
|
|
172
|
+
|
|
173
|
+
Some apps drop text delivered too quickly. Pace either backend by Unicode code
|
|
174
|
+
point with a whole-millisecond delay from 0 to 2000:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
adb-ready ui type 'مرحبا بالعالم' --input-mode unicode --typing-delay 40
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Protected input
|
|
181
|
+
|
|
182
|
+
Do not put passwords or tokens in a command argument. Pipe one value to stdin:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
printf '%s' "$TEST_PASSWORD" |
|
|
186
|
+
adb-ready ui fill 'id=com.example:id/password' --secret-stdin --submit
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`--secret-stdin` reads at most 4096 bytes to EOF, removes one final line ending,
|
|
190
|
+
and preserves embedded newlines. Plaintext is excluded from host process
|
|
191
|
+
arguments, operation plans, terminal output, structured results, event
|
|
192
|
+
journals, screenshots created by this action, and AI context. The value exists
|
|
193
|
+
only in process memory and the pipe used to deliver it to the selected target.
|
|
194
|
+
For Unicode, the on-device shell receives base64 rather than plaintext.
|
|
195
|
+
|
|
196
|
+
This is a bounded host-side guarantee, not a secure-input claim about Android
|
|
197
|
+
or the app: the selected IME and target receive the value, a normal visible
|
|
198
|
+
field may display it, and device/OEM auditing can observe shell activity. Use a
|
|
199
|
+
password field and assert a non-secret postcondition. A protected field cannot
|
|
200
|
+
expose its value for exact verification, so ADB Ready returns
|
|
201
|
+
`text-not-observable` rather than inventing success.
|
|
202
|
+
|
|
203
|
+
## Keyboard and runtime-permission dialogs
|
|
204
|
+
|
|
205
|
+
Inspect or dismiss the software keyboard without sending a blind Back action:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
adb-ready ui keyboard status --json
|
|
209
|
+
adb-ready ui keyboard dismiss
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
ADB Ready requires Android's InputMethodManager `mInputShown` state and its
|
|
213
|
+
WindowInsets `type=ime` visibility to both exist and agree. `dismiss` sends Back
|
|
214
|
+
only after both report visible, then reads both services again and succeeds only
|
|
215
|
+
after both report hidden. An already hidden keyboard is a verified no-op.
|
|
216
|
+
Missing or conflicting signals return `UI_KEYBOARD_STATE_UNSUPPORTED` or
|
|
217
|
+
`UI_KEYBOARD_STATE_AMBIGUOUS`; ADB Ready does not guess from inset height or
|
|
218
|
+
press Back against an unknown screen.
|
|
219
|
+
|
|
220
|
+
Inspect one standard Android runtime-permission prompt before choosing an exact
|
|
221
|
+
decision:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
adb-ready ui permission inspect --json
|
|
225
|
+
adb-ready ui permission respond allow-while-using
|
|
226
|
+
adb-ready ui permission respond allow-once --dry-run --json
|
|
227
|
+
adb-ready ui permission respond deny
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Inspection returns only the decisions actually present on the current dialog.
|
|
231
|
+
Responses match PermissionController package and resource IDs, never translated
|
|
232
|
+
button labels or coordinates copied from another device. A fresh hierarchy must
|
|
233
|
+
show that the specific prompt changed or disappeared after the tap. Multiple,
|
|
234
|
+
unknown, or OEM-specific states fail closed. Notification permission setup,
|
|
235
|
+
biometric prompts, Settings mutation, and generic system-dialog acceptance are
|
|
236
|
+
intentionally outside this workflow.
|
|
134
237
|
|
|
135
238
|
## Find, assert, compare, and wait
|
|
136
239
|
|
|
@@ -143,8 +246,9 @@ adb-ready ui assert 'text=Loading' --state gone
|
|
|
143
246
|
adb-ready ui compare 7c4a31b8d2ef0000000000000000000000000000000000000000000000000000
|
|
144
247
|
```
|
|
145
248
|
|
|
146
|
-
`compare` consumes the complete digest returned by inspection
|
|
147
|
-
action and reports whether the current hierarchy changed.
|
|
249
|
+
The CLI `compare` command consumes the complete digest returned by inspection
|
|
250
|
+
or another UI action and reports whether the current hierarchy changed. It
|
|
251
|
+
does not persist the earlier sensitive hierarchy.
|
|
148
252
|
|
|
149
253
|
Waits use exact, explicit selectors:
|
|
150
254
|
|
|
@@ -186,11 +290,31 @@ the next state is known.
|
|
|
186
290
|
## AI agents
|
|
187
291
|
|
|
188
292
|
The MCP server exposes the same intent-level workflow through `audit_ui`, `get_ui`,
|
|
189
|
-
`find_ui`, `fill_ui`, `clear_ui`, `scroll_ui`, `assert_ui`,
|
|
293
|
+
`find_ui`, `fill_ui`, `clear_ui`, `scroll_ui`, `assert_ui`, `compare_ui`,
|
|
294
|
+
`inspect_keyboard`, `dismiss_keyboard`, `inspect_permission_dialog`, and
|
|
295
|
+
`respond_to_permission_dialog`.
|
|
190
296
|
Its structured selectors can match exact values, prefixes, or substrings and
|
|
191
297
|
qualify enabled/actionable state. An optional one-based occurrence is accepted
|
|
192
298
|
only when repeated nodes are intentional. Arguments are schema-validated, each
|
|
193
299
|
MCP connection stays bound to one target, and no raw ADB or shell tool is
|
|
194
300
|
exposed.
|
|
195
301
|
|
|
302
|
+
`type_text_ui` and `fill_ui` accept `inputMode` and `typingDelayMs`. For a
|
|
303
|
+
secret, set `secretEnv` to the *name* of an environment variable already passed
|
|
304
|
+
to the local MCP server and omit `text`. ADB Ready reads the value locally; the
|
|
305
|
+
MCP request, tool result, and retained agent conversation contain only the
|
|
306
|
+
variable name. Supplying both fields, neither field, or a missing/empty variable
|
|
307
|
+
fails before device mutation.
|
|
308
|
+
|
|
309
|
+
`inspect_ui` retains at most eight sensitive snapshots in memory for that MCP
|
|
310
|
+
connection only. `compare_ui` accepts one of those complete digests and returns
|
|
311
|
+
a bounded semantic diff: added, removed, updated, and moved nodes with current
|
|
312
|
+
selector context. `changed` describes the requested filtered view, while
|
|
313
|
+
`digestChanged` also reveals changes outside that view. Unchanged screens return
|
|
314
|
+
empty change lists. Bases expire
|
|
315
|
+
after five minutes and are never written to disk; missing or expired digests
|
|
316
|
+
require a fresh `inspect_ui`. A diff is rejected rather than guessed when its
|
|
317
|
+
target, display bounds or rotation, hierarchy filters, acquisition contract, or
|
|
318
|
+
completeness differs from the base.
|
|
319
|
+
|
|
196
320
|
[Connect an agent →](./agent-integration.md)
|
package/examples/README.md
CHANGED
|
@@ -15,8 +15,14 @@ adb-ready dev --dry-run
|
|
|
15
15
|
- [`flutter/adb-ready.config.json`](./flutter/adb-ready.config.json)
|
|
16
16
|
- [`capacitor/adb-ready.config.json`](./capacitor/adb-ready.config.json)
|
|
17
17
|
- [`custom/adb-ready.config.json`](./custom/adb-ready.config.json)
|
|
18
|
+
- [`ci-emulator/`](./ci-emulator/) — maintained GitHub-hosted emulator
|
|
19
|
+
acceptance with a finite run and retained failure evidence
|
|
20
|
+
- [`ci-physical/`](./ci-physical/) — manual self-hosted physical-device job
|
|
21
|
+
with explicit serial selection, host-local leasing, and bounded evidence
|
|
18
22
|
- [`automation/README.md`](./automation/README.md) — one finite AVD, deployment,
|
|
19
23
|
verifier, evidence, and cleanup job
|
|
24
|
+
- [`target-pools/`](./target-pools/) — bounded fan-out across explicit local,
|
|
25
|
+
AVD, remote-ADB, or Firebase members
|
|
20
26
|
|
|
21
27
|
Prefer `adb-ready init` when starting from an existing detected project. Add
|
|
22
28
|
only the ports, hooks, and retention rules that the project genuinely needs.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# GitHub-hosted Android emulator
|
|
2
|
+
|
|
3
|
+
This fixture powers ADB Ready's maintained emulator acceptance workflow. It
|
|
4
|
+
demonstrates the ownership boundary used in ordinary CI:
|
|
5
|
+
|
|
6
|
+
1. the runner provisions and owns one Android emulator;
|
|
7
|
+
2. ADB Ready binds a finite run to its exact serial;
|
|
8
|
+
3. the fixture verifies Android boot and identity, opens Settings, and captures
|
|
9
|
+
native evidence; and
|
|
10
|
+
4. ADB Ready retains normalized NDJSON, JUnit, screenshot, window, and manifest
|
|
11
|
+
evidence before the runner tears its emulator down.
|
|
12
|
+
|
|
13
|
+
The workflow is [`android-emulator.yml`](../../.github/workflows/android-emulator.yml).
|
|
14
|
+
Every external Action is pinned to a full commit. The job uses no repository
|
|
15
|
+
secret and uploads evidence with `if: always()`.
|
|
16
|
+
|
|
17
|
+
Do not copy the fixture's custom development process into an application. Keep
|
|
18
|
+
your real project command and readiness assertions, then use the same bounded
|
|
19
|
+
shape:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx adb-ready run \
|
|
23
|
+
--device emulator-5554 \
|
|
24
|
+
--run-timeout 10m \
|
|
25
|
+
--json \
|
|
26
|
+
--non-interactive \
|
|
27
|
+
-- npm run test:e2e
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
ADB Ready does not implicitly create SDKs or AVDs. If another workflow layer
|
|
31
|
+
created the emulator, ADB Ready attaches without claiming ownership and leaves
|
|
32
|
+
emulator teardown to that layer.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../schema/config-v1.schema.json",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"dev": {
|
|
5
|
+
"preset": "custom",
|
|
6
|
+
"command": {
|
|
7
|
+
"executable": "node",
|
|
8
|
+
"args": ["examples/ci-emulator/dev-service.mjs"]
|
|
9
|
+
},
|
|
10
|
+
"reversePorts": [],
|
|
11
|
+
"logs": false,
|
|
12
|
+
"cleanupPorts": true,
|
|
13
|
+
"watch": true,
|
|
14
|
+
"ready": {
|
|
15
|
+
"timeoutMs": 60000,
|
|
16
|
+
"pollIntervalMs": 1000,
|
|
17
|
+
"all": [{ "kind": "boot" }, { "kind": "unlocked" }]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
let stopping = false;
|
|
2
|
+
|
|
3
|
+
const keepAlive = setInterval(() => undefined, 60_000);
|
|
4
|
+
|
|
5
|
+
function stop() {
|
|
6
|
+
if (stopping) return;
|
|
7
|
+
stopping = true;
|
|
8
|
+
process.stderr.write("CI development service stopped.\n");
|
|
9
|
+
clearInterval(keepAlive);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
process.once("SIGINT", stop);
|
|
13
|
+
process.once("SIGTERM", stop);
|
|
14
|
+
process.stderr.write("CI development service ready.\n");
|