adb-ready 0.6.0 → 0.8.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.
@@ -198,6 +198,20 @@ Preview the complete project plan before a device is allocated:
198
198
  adb-ready run --preset expo --dry-run --json -- npm run test:e2e
199
199
  ```
200
200
 
201
+ ### Fan out over an explicit pool
202
+
203
+ Named project pools repeat the same bounded verifier without weakening the
204
+ single-target run contract:
205
+
206
+ ```bash
207
+ adb-ready run --pool smoke --max-concurrency 2 -- npm run test:e2e
208
+ ```
209
+
210
+ Each member keeps its own target lease, session, result envelope, evidence, and
211
+ cleanup. Required failures fail the aggregate; optional failures remain
212
+ visible. Queueing is bounded and host-local, never an inferred distributed
213
+ lock. See [Target pools and fan-out](./target-pools.md).
214
+
201
215
  ### Start an existing AVD and deploy the intended build
202
216
 
203
217
  For a complete local or CI-owned emulator job, name one existing AVD and either
@@ -265,3 +279,78 @@ fragile parsing of human log messages.
265
279
  USB passthrough, emulators, remote ADB servers, and device labs remain the CI
266
280
  environment's responsibility. ADB Ready reports the observed boundary instead
267
281
  of simulating a connected device.
282
+
283
+ ### GitHub-hosted emulator
284
+
285
+ The repository continuously runs a complete finite ADB Ready job on a real
286
+ headless Linux emulator. The runner layer creates and owns the AVD; ADB Ready
287
+ binds to its exact serial, verifies boot and unlock state, executes the bounded
288
+ check, writes normalized NDJSON and evidence, and leaves emulator teardown to
289
+ the owning layer.
290
+
291
+ Use the maintained, commit-pinned workflow and fixture as the starting point:
292
+
293
+ - [Android emulator workflow](../.github/workflows/android-emulator.yml)
294
+ - [CI emulator fixture](../examples/ci-emulator/)
295
+
296
+ The evidence upload uses `if: always()` so a product assertion or verifier
297
+ failure retains the same manifest, result, JUnit, native files, and GitHub step
298
+ summary as a successful run. A failure before ADB Ready starts cannot produce
299
+ an ADB Ready bundle and is reported by the provisioning step instead.
300
+
301
+ ### Self-hosted physical target
302
+
303
+ Use the maintained [physical-device recipe](../examples/ci-physical/) when a
304
+ dedicated self-hosted runner owns one pre-authorized Android phone or tablet.
305
+ The workflow is manual-only, routes through an explicit `android-device`
306
+ runner label, queues competing GitHub jobs, and still relies on ADB Ready's
307
+ host-local target lease as the final ownership boundary.
308
+
309
+ The configured serial must match one `adb devices -l` row exactly. Missing,
310
+ `unauthorized`, `offline`, replaced, and concurrently owned targets fail before
311
+ the verifier can mutate another device. ADB Ready does not restart the shared
312
+ ADB server, approve Android's RSA dialog, or claim a distributed lock across
313
+ runner machines.
314
+
315
+ Do not expose a physical self-hosted runner to untrusted pull requests. Keep
316
+ the runner and its custom label scoped to trusted repositories or runner
317
+ groups, and keep the phone unlocked only when the project's assertions require
318
+ it. The example verifier is read-only and intentionally captures no screenshot
319
+ from a personal device.
320
+
321
+ ### Gradle-owned virtual devices
322
+
323
+ When the Android build declares Gradle Managed Devices, keep lifecycle
324
+ ownership with Gradle instead of treating its transient emulator as an ADB
325
+ Ready target:
326
+
327
+ ```bash
328
+ adb-ready test gradle
329
+ adb-ready test gradle :app:pixel2api35DebugAndroidTest --dry-run
330
+ adb-ready test gradle :app:pixel2api35DebugAndroidTest --software-rendering
331
+ ```
332
+
333
+ ADB Ready preflights the exact task and normalizes fresh JUnit, HTML, shard,
334
+ and Gradle failure evidence without replacing the build's device definitions
335
+ or test DSL. See [Gradle Managed Devices](./gradle-managed-devices.md) for task
336
+ groups, sharding, outcomes, and the ownership boundary.
337
+
338
+ ### Firebase-owned remote devices
339
+
340
+ Use Firebase Test Lab when the provider should own remote physical or virtual
341
+ targets while ADB Ready owns the deterministic invocation, outcome language,
342
+ and bounded evidence handoff:
343
+
344
+ ```bash
345
+ adb-ready test firebase devices --project my-project
346
+ adb-ready test firebase instrumentation \
347
+ --project my-project \
348
+ --app app-debug.apk \
349
+ --test-apk app-debug-androidTest.apk \
350
+ --test-device model=Pixel2.arm,version=35 \
351
+ --dry-run --json
352
+ ```
353
+
354
+ See [Firebase Test Lab](./firebase-test-lab.md) for live catalog policy,
355
+ provider-owned lifecycle, result buckets, normalized outcomes, and explicit
356
+ remote cancellation.
@@ -0,0 +1,61 @@
1
+ # Shell completion
2
+
3
+ ADB Ready generates static tab-completion scripts for both `adb-ready` and its
4
+ short alias, `adbr`. Generation is instant and offline: it does not load project
5
+ configuration, start ADB, or inspect a device.
6
+
7
+ ## Bash
8
+
9
+ ```bash
10
+ mkdir -p ~/.local/share/bash-completion/completions
11
+ adb-ready completion bash > ~/.local/share/bash-completion/completions/adb-ready
12
+ ```
13
+
14
+ Start a new shell. If the user completion directory is not loaded by your Bash
15
+ installation, source the generated file from `~/.bashrc`.
16
+
17
+ ## zsh
18
+
19
+ ```zsh
20
+ mkdir -p ~/.zfunc
21
+ adb-ready completion zsh > ~/.zfunc/_adb-ready
22
+ ```
23
+
24
+ Add the directory before `compinit` in `~/.zshrc`:
25
+
26
+ ```zsh
27
+ fpath=(~/.zfunc $fpath)
28
+ autoload -Uz compinit && compinit
29
+ ```
30
+
31
+ ## fish
32
+
33
+ ```fish
34
+ mkdir -p ~/.config/fish/completions
35
+ adb-ready completion fish > ~/.config/fish/completions/adb-ready.fish
36
+ ```
37
+
38
+ fish loads the file automatically in new shell sessions.
39
+
40
+ ## PowerShell
41
+
42
+ ```powershell
43
+ New-Item -ItemType Directory -Force (Split-Path $PROFILE) | Out-Null
44
+ adb-ready completion powershell | Out-File -Append -Encoding utf8 $PROFILE
45
+ ```
46
+
47
+ Reload the profile with `. $PROFILE` or open a new PowerShell session.
48
+
49
+ ## Nushell
50
+
51
+ ```nu
52
+ mkdir ($nu.data-dir | path join "vendor" "autoload")
53
+ adb-ready completion nushell | save --force ($nu.data-dir | path join "vendor" "autoload" "adb-ready.nu")
54
+ ```
55
+
56
+ Nushell loads vendor autoload files in new sessions. The generated declarations
57
+ complete public commands while preserving ordinary file completion for command
58
+ arguments.
59
+
60
+ Regenerate the script after upgrading ADB Ready so newly added commands become
61
+ available to the shell.
@@ -58,6 +58,7 @@ existing file unless `--force` is explicit. Use `--dry-run` first.
58
58
  | `timeoutMs` | Default bounded operation timeout |
59
59
  | `output` | Interactive, animation, color, and Unicode preferences |
60
60
  | `targets.aliases` | Friendly name to exact ADB serial mapping |
61
+ | `targets.pools` | Named explicit target sets with bounded concurrency and queue policy |
61
62
  | `dev` | Development-session configuration |
62
63
  | `profiles` | Named, optionally inherited overrides |
63
64
 
@@ -81,6 +82,11 @@ existing file unless `--force` is explicit. Use `--dry-run` first.
81
82
  Unknown keys and invalid nested values fail validation rather than being
82
83
  silently ignored.
83
84
 
85
+ Target pools accept typed `adb`, `avd`, `remote-adb`, or `firebase` members.
86
+ Every pool requires an explicit `maxConcurrency`; member IDs and resource
87
+ identities must be unique. See [Target pools and fan-out](./target-pools.md) for
88
+ the complete schema, execution semantics, and trust boundaries.
89
+
84
90
  `init` detects and writes `packageManager` only for Expo, React Native, and
85
91
  Capacitor presets, where it selects the project CLI. Flutter and native Gradle
86
92
  use their own launchers, so an unrelated package-manager executable available
@@ -0,0 +1,126 @@
1
+ # Firebase Test Lab
2
+
3
+ Use this workflow when Firebase should own the remote Android targets. ADB
4
+ Ready validates exact dimensions against the live catalog, starts one bounded
5
+ instrumentation or Robo matrix through `gcloud`, normalizes its outcome, and
6
+ keeps local evidence. It does not create projects, manage billing, provision
7
+ credentials, or replace Firebase's test formats.
8
+
9
+ ## Prerequisites
10
+
11
+ Install the current [Google Cloud CLI](https://cloud.google.com/sdk/docs/install),
12
+ authenticate it in the surrounding developer or CI environment, and enable
13
+ Firebase Test Lab for the selected Google Cloud project. Credentials remain in
14
+ Google's credential store; ADB Ready never writes access tokens into config,
15
+ output, or evidence.
16
+
17
+ Inspect the authenticated live catalog before choosing a matrix:
18
+
19
+ ```bash
20
+ adb-ready test firebase devices --project my-project
21
+ adb-ready test firebase devices --project my-project --json
22
+ ```
23
+
24
+ Catalog output joins each model to its supported Android versions and reports
25
+ form, form factor, capacity, and provider tags. A requested unavailable,
26
+ inaccessible, deprecated, reduced-stability, zero-capacity, or low-capacity
27
+ dimension fails before artifact upload. Deprecated, reduced-stability, and
28
+ low-capacity choices require their matching explicit `--allow-*` policy.
29
+
30
+ ## Instrumentation
31
+
32
+ Preview the exact direct `gcloud` invocation first:
33
+
34
+ ```bash
35
+ adb-ready test firebase instrumentation \
36
+ --project my-project \
37
+ --app app/build/outputs/apk/debug/app-debug.apk \
38
+ --test-apk app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk \
39
+ --test-device model=Pixel2.arm,version=35,locale=en,orientation=portrait \
40
+ --dry-run --json
41
+ ```
42
+
43
+ Remove `--dry-run` to create the matrix. Repeat `--test-device` for additional
44
+ explicit dimensions. Local paths and private `gs://bucket/object` artifact
45
+ references are supported; signed URLs are rejected so credentials cannot be
46
+ retained accidentally.
47
+
48
+ ## Robo
49
+
50
+ Robo needs the app artifact but no instrumentation APK:
51
+
52
+ ```bash
53
+ adb-ready test firebase robo \
54
+ --project my-project \
55
+ --app app-debug.apk \
56
+ --test-device model=Pixel2.arm,version=35
57
+ ```
58
+
59
+ `--test-timeout 10m` controls the provider-side timeout for each execution.
60
+ `--run-timeout 30m` bounds only ADB Ready's local observation.
61
+
62
+ ## Results and evidence
63
+
64
+ For deterministic CI retention, use a bucket your job can read and write:
65
+
66
+ ```bash
67
+ adb-ready test firebase instrumentation \
68
+ --project my-project \
69
+ --app app-debug.apk \
70
+ --test-apk app-debug-androidTest.apk \
71
+ --test-device model=Pixel2.arm,version=35 \
72
+ --results-bucket gs://my-test-results \
73
+ --results-dir ci/$GITHUB_RUN_ID
74
+ ```
75
+
76
+ ADB Ready always retains redacted `gcloud` output and the structured result
77
+ under `.adb-ready/artifacts/firebase-<run-id>/`. With an explicit result bucket,
78
+ it also lists only that exact prefix and downloads a bounded allowlist of native
79
+ JUnit, HTML, JSON, log, screenshot, video, coverage, and protobuf artifacts:
80
+
81
+ - at most 100 provider files;
82
+ - at most 20 MiB per file;
83
+ - at most 50 MiB total;
84
+ - exact post-download size verification; and
85
+ - a local `provider-files.json` source manifest.
86
+
87
+ The structured result reports `complete`, `unavailable`, `remote-running`, or
88
+ `not-configured` evidence collection. Failure to copy requested provider
89
+ evidence is a warning alongside the original test outcome; it never rewrites a
90
+ test assertion into a false pass or a different provider outcome.
91
+
92
+ Without an explicit bucket, the Firebase console URL remains the provider
93
+ result reference and only local normalized evidence is copied.
94
+
95
+ ## Outcomes and cancellation
96
+
97
+ ADB Ready preserves the documented `gcloud firebase test android run` result
98
+ classes instead of flattening them:
99
+
100
+ - `passed`;
101
+ - `flaky` — provider roll-up succeeded but at least one test was flaky;
102
+ - `assertion-failed`;
103
+ - `inconclusive`;
104
+ - `unsupported`;
105
+ - `cancelled`;
106
+ - `infrastructure-failed`;
107
+ - `auth-failed`; and
108
+ - local `observation-timed-out` or `observation-stopped`.
109
+
110
+ Stopping or timing out local observation intentionally leaves an identified
111
+ remote matrix running. Cancel exactly one matrix with a separate explicit
112
+ action:
113
+
114
+ ```bash
115
+ adb-ready test firebase cancel MATRIX_ID --project my-project --dry-run
116
+ adb-ready test firebase cancel MATRIX_ID --project my-project
117
+ ```
118
+
119
+ Cancellation obtains a short-lived access token from the authenticated
120
+ `gcloud` process in memory and calls the official Testing API. The token is not
121
+ stored or printed.
122
+
123
+ See Firebase's current [Android CLI guide](https://firebase.google.com/docs/test-lab/android/command-line),
124
+ [available-device guidance](https://firebase.google.com/docs/test-lab/android/available-testing-devices),
125
+ and [result documentation](https://firebase.google.com/docs/test-lab/android/analyzing-results)
126
+ for provider setup, billing, quotas, and native result semantics.
@@ -0,0 +1,105 @@
1
+ # Gradle Managed Devices
2
+
3
+ Use this workflow when the Android build already declares virtual devices or
4
+ device groups in Gradle. Gradle remains the lifecycle owner: it provisions the
5
+ emulator, installs the app and tests, applies sharding, runs instrumentation,
6
+ and tears the target down. ADB Ready does not select, lease, or send commands to
7
+ that transient emulator.
8
+
9
+ ## Discover declared tasks
10
+
11
+ Run discovery from the Gradle project root:
12
+
13
+ ```bash
14
+ adb-ready test gradle
15
+ ```
16
+
17
+ ADB Ready invokes the checked-in `gradlew` or `android/gradlew` Wrapper and
18
+ lists only task descriptions that identify a managed device or device group.
19
+ If the Wrapper lives elsewhere, pass it explicitly:
20
+
21
+ ```bash
22
+ adb-ready test gradle --gradle tools/gradlew
23
+ ```
24
+
25
+ Task names belong to the project. Typical Android Gradle Plugin names resemble
26
+ `pixel2api35DebugAndroidTest` and
27
+ `phoneAndTabletGroupDebugAndroidTest`; ADB Ready does not invent a device,
28
+ variant, or group name.
29
+
30
+ ## Preview and run one task
31
+
32
+ Preview the exact direct process invocation without starting Gradle:
33
+
34
+ ```bash
35
+ adb-ready test gradle :app:pixel2api35DebugAndroidTest --dry-run --json
36
+ ```
37
+
38
+ Then execute the same bounded task:
39
+
40
+ ```bash
41
+ adb-ready test gradle :app:pixel2api35DebugAndroidTest --run-timeout 20m
42
+ ```
43
+
44
+ Before execution, ADB Ready asks Gradle to resolve that exact task. A missing
45
+ task fails before test infrastructure starts. The task is passed directly to
46
+ the Wrapper without a shell.
47
+
48
+ ## Groups, sharding, and server rendering
49
+
50
+ A declared group task runs through the same boundary:
51
+
52
+ ```bash
53
+ adb-ready test gradle :app:phoneAndTabletGroupDebugAndroidTest
54
+ ```
55
+
56
+ Request Gradle's managed-device sharding property when the build and Android
57
+ Gradle Plugin support it:
58
+
59
+ ```bash
60
+ adb-ready test gradle :app:pixel2api35DebugAndroidTest --shards 4
61
+ ```
62
+
63
+ On a headless host, opt into Gradle's documented SwiftShader property:
64
+
65
+ ```bash
66
+ adb-ready test gradle :app:pixel2api35DebugAndroidTest --software-rendering
67
+ ```
68
+
69
+ ADB Ready passes these properties for the current invocation. It never edits
70
+ Gradle files, creates SDK components, or changes a project's declared devices.
71
+
72
+ ## Results and evidence
73
+
74
+ After execution, ADB Ready reads native files from the Android Gradle Plugin
75
+ managed-device result and report directories. It retains bounded XML,
76
+ HTML, JSON, logs, text, screenshots, and video under:
77
+
78
+ ```text
79
+ .adb-ready/artifacts/gradle-managed-<run-id>/
80
+ ```
81
+
82
+ Machine output keeps these outcomes distinct:
83
+
84
+ - `passed` — Gradle exited successfully and retained JUnit has no failures;
85
+ - `assertion-failed` — fresh JUnit reports a failed or errored test;
86
+ - `infrastructure-failed` — Gradle could not complete provisioning or execution
87
+ and no test assertion explains the failure;
88
+ - `timed-out` — the configured finite timeout expired;
89
+ - `cancelled` — the caller interrupted the task;
90
+ - `GRADLE_MANAGED_TASK_NOT_FOUND` — exact task preflight failed.
91
+
92
+ The original Gradle result and report trees remain the source artifacts. The
93
+ ADB Ready copy is bounded, redacted where textual, and convenient for CI upload
94
+ or an agent. Failed tasks can only classify assertions from files written by
95
+ that execution. A successful up-to-date Gradle task may retain the output that
96
+ Gradle validated from its cache, explicitly marked with `gradle-cache`
97
+ provenance.
98
+
99
+ ## Responsibility boundary
100
+
101
+ Use `adb-ready test gradle` for Gradle-owned virtual devices. Use
102
+ `adb-ready run` when ADB Ready must own one already visible ADB target, prepare
103
+ ports and a development service, run a finite verifier, and clean only its own
104
+ resources. Connected-device tasks therefore remain under `run`; they are not
105
+ misrepresented as Gradle Managed Devices.
@@ -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.
@@ -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` |
@@ -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
+ }