zig-mobile-runner 0.1.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 +484 -0
- package/CONTRIBUTING.md +42 -0
- package/FEATURES.md +112 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/SECURITY.md +34 -0
- package/build.zig +38 -0
- package/build.zig.zon +7 -0
- package/clients/README.md +144 -0
- package/clients/go/README.md +24 -0
- package/clients/go/examples/fake-session/main.go +93 -0
- package/clients/go/go.mod +3 -0
- package/clients/go/zmr/client.go +432 -0
- package/clients/kotlin/README.md +35 -0
- package/clients/kotlin/build.gradle.kts +35 -0
- package/clients/kotlin/settings.gradle.kts +15 -0
- package/clients/kotlin/src/main/kotlin/dev/zmr/FakeSession.kt +86 -0
- package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +67 -0
- package/clients/python/README.md +29 -0
- package/clients/python/examples/fake_session.py +48 -0
- package/clients/python/pyproject.toml +13 -0
- package/clients/python/zmr_client.py +202 -0
- package/clients/rust/Cargo.lock +107 -0
- package/clients/rust/Cargo.toml +10 -0
- package/clients/rust/README.md +19 -0
- package/clients/rust/examples/fake_session.rs +70 -0
- package/clients/rust/src/lib.rs +461 -0
- package/clients/swift/Package.swift +16 -0
- package/clients/swift/README.md +36 -0
- package/clients/swift/Sources/ZMRClient/ZMRClient.swift +114 -0
- package/clients/swift/Sources/ZMRFakeSession/main.swift +86 -0
- package/clients/typescript/README.md +34 -0
- package/clients/typescript/examples/fake-session.mjs +36 -0
- package/clients/typescript/index.d.ts +144 -0
- package/clients/typescript/index.mjs +192 -0
- package/clients/typescript/package.json +8 -0
- package/docs/adr/0001-agent-native-runner-boundary.md +31 -0
- package/docs/adr/0002-app-local-zmr-contract.md +39 -0
- package/docs/adr/0003-ios-simulator-xctest-shim.md +41 -0
- package/docs/adr/0004-benchmark-claims-and-baseline-collection.md +37 -0
- package/docs/adr/README.md +12 -0
- package/docs/ai-agents.md +156 -0
- package/docs/app-integration.md +316 -0
- package/docs/benchmarking.md +275 -0
- package/docs/client-installation.md +141 -0
- package/docs/clients.md +98 -0
- package/docs/config.md +175 -0
- package/docs/demo.md +259 -0
- package/docs/dsl.md +57 -0
- package/docs/install.md +233 -0
- package/docs/market-positioning.md +70 -0
- package/docs/npm.md +359 -0
- package/docs/protocol-fixtures/README.md +8 -0
- package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
- package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
- package/docs/protocol-versioning.md +65 -0
- package/docs/protocol.md +560 -0
- package/docs/publication.md +77 -0
- package/docs/release-audit.md +99 -0
- package/docs/release-candidate.md +111 -0
- package/docs/release-evidence.md +188 -0
- package/docs/release-notes-template.md +58 -0
- package/docs/roadmap.md +334 -0
- package/docs/scenario-authoring.md +88 -0
- package/docs/shipping.md +170 -0
- package/docs/trace-privacy.md +88 -0
- package/docs/troubleshooting.md +256 -0
- package/examples/android-app-auth-probe.json +89 -0
- package/examples/android-app-error-state.json +13 -0
- package/examples/android-app-login-smoke.json +192 -0
- package/examples/android-app-onboarding.json +12 -0
- package/examples/android-app-referral-deep-link.json +12 -0
- package/examples/android-shim-smoke.json +19 -0
- package/examples/demo-failure.json +12 -0
- package/examples/demo-fake.json +14 -0
- package/examples/ios-dev-client-open-link.json +26 -0
- package/examples/ios-dev-client-route-snapshot.json +24 -0
- package/examples/ios-shim-smoke.json +23 -0
- package/examples/ios-smoke.json +9 -0
- package/go.work +3 -0
- package/npm/agents.mjs +183 -0
- package/npm/app-config.mjs +95 -0
- package/npm/build-zmr.mjs +21 -0
- package/npm/commands.mjs +104 -0
- package/npm/generated-files.mjs +50 -0
- package/npm/index.mjs +75 -0
- package/npm/init-app.mjs +80 -0
- package/npm/package-scripts.mjs +72 -0
- package/npm/postinstall.mjs +21 -0
- package/npm/scaffold.mjs +179 -0
- package/npm/scenarios.mjs +93 -0
- package/npm/setup.mjs +69 -0
- package/npm/wizard.mjs +117 -0
- package/npm/zmr.mjs +23 -0
- package/package.json +114 -0
- package/prebuilds/darwin-arm64/zmr +0 -0
- package/prebuilds/darwin-x64/zmr +0 -0
- package/prebuilds/linux-arm64/zmr +0 -0
- package/prebuilds/linux-x64/zmr +0 -0
- package/schemas/README.md +26 -0
- package/schemas/action-result.schema.json +27 -0
- package/schemas/capabilities-output.schema.json +98 -0
- package/schemas/devices-output.schema.json +25 -0
- package/schemas/doctor-output.schema.json +51 -0
- package/schemas/explain-output.schema.json +51 -0
- package/schemas/import-output.schema.json +23 -0
- package/schemas/init-output.schema.json +71 -0
- package/schemas/json-rpc.schema.json +55 -0
- package/schemas/release-manifest.schema.json +43 -0
- package/schemas/release-readiness-output.schema.json +127 -0
- package/schemas/run-output.schema.json +43 -0
- package/schemas/scenario.schema.json +128 -0
- package/schemas/schemas-output.schema.json +26 -0
- package/schemas/semantic-snapshot.schema.json +116 -0
- package/schemas/snapshot.schema.json +60 -0
- package/schemas/trace-event.schema.json +14 -0
- package/schemas/trace-manifest.schema.json +59 -0
- package/schemas/validate-output.schema.json +42 -0
- package/schemas/version-output.schema.json +23 -0
- package/schemas/zmr-config.schema.json +75 -0
- package/scripts/android-emulator.sh +126 -0
- package/scripts/assert-ios-physical-ready.sh +213 -0
- package/scripts/benchmark-command.sh +307 -0
- package/scripts/benchmark.sh +359 -0
- package/scripts/benchmark_gate.py +117 -0
- package/scripts/benchmark_result_row.py +88 -0
- package/scripts/compare-benchmarks.py +288 -0
- package/scripts/create-android-demo-app.sh +342 -0
- package/scripts/create-ios-demo-app.sh +261 -0
- package/scripts/demo-android-real.sh +232 -0
- package/scripts/demo-ios-real.sh +270 -0
- package/scripts/demo.sh +464 -0
- package/scripts/device-matrix.sh +338 -0
- package/scripts/ensure-ios-shim-target.rb +237 -0
- package/scripts/install-android-shim.sh +281 -0
- package/scripts/install-ios-shim.sh +589 -0
- package/scripts/pilot-gate.sh +560 -0
- package/scripts/release-readiness.py +838 -0
- package/scripts/release-readiness.sh +91 -0
- package/scripts/run-android-pilot.sh +561 -0
- package/scripts/run-ios-pilot.sh +509 -0
- package/shims/android/README.md +21 -0
- package/shims/android/ZMRShimInstrumentedTest.java +152 -0
- package/shims/android/protocol.md +18 -0
- package/shims/ios/README.md +50 -0
- package/shims/ios/ZMRShim.swift +110 -0
- package/shims/ios/ZMRShimUITestCase.swift +475 -0
- package/shims/ios/protocol.md +74 -0
- package/skills/zmr-mobile-testing/SKILL.md +127 -0
- package/src/android.zig +344 -0
- package/src/android_device_info.zig +99 -0
- package/src/android_emulator.zig +154 -0
- package/src/android_screen_recording.zig +112 -0
- package/src/android_shell.zig +112 -0
- package/src/bundle.zig +124 -0
- package/src/bundle_redaction.zig +272 -0
- package/src/bundle_tar.zig +123 -0
- package/src/cli_devices.zig +97 -0
- package/src/cli_doctor.zig +114 -0
- package/src/cli_import.zig +70 -0
- package/src/cli_info.zig +39 -0
- package/src/cli_init.zig +72 -0
- package/src/cli_output.zig +467 -0
- package/src/cli_run.zig +259 -0
- package/src/cli_serve.zig +287 -0
- package/src/cli_trace.zig +111 -0
- package/src/cli_validate.zig +41 -0
- package/src/command.zig +211 -0
- package/src/config.zig +305 -0
- package/src/config_diagnostics.zig +212 -0
- package/src/config_paths.zig +49 -0
- package/src/device_registry.zig +37 -0
- package/src/doctor.zig +412 -0
- package/src/doctor_hints.zig +52 -0
- package/src/errors.zig +55 -0
- package/src/fake_device.zig +163 -0
- package/src/health.zig +28 -0
- package/src/importer.zig +343 -0
- package/src/importer_json.zig +100 -0
- package/src/importer_model.zig +103 -0
- package/src/ios.zig +399 -0
- package/src/ios_devices.zig +219 -0
- package/src/ios_lifecycle.zig +72 -0
- package/src/ios_shim.zig +242 -0
- package/src/ios_snapshot.zig +20 -0
- package/src/json_fields.zig +80 -0
- package/src/json_rpc.zig +150 -0
- package/src/json_rpc_methods.zig +318 -0
- package/src/json_rpc_observation.zig +31 -0
- package/src/json_rpc_params.zig +52 -0
- package/src/json_rpc_protocol.zig +110 -0
- package/src/json_rpc_trace.zig +73 -0
- package/src/main.zig +135 -0
- package/src/mcp.zig +234 -0
- package/src/mcp_protocol.zig +64 -0
- package/src/mcp_trace.zig +83 -0
- package/src/report.zig +346 -0
- package/src/report_html.zig +63 -0
- package/src/report_values.zig +27 -0
- package/src/run_options.zig +152 -0
- package/src/runner.zig +280 -0
- package/src/runner_actions.zig +109 -0
- package/src/runner_config.zig +6 -0
- package/src/runner_diagnostics.zig +268 -0
- package/src/runner_events.zig +170 -0
- package/src/runner_native.zig +88 -0
- package/src/runner_waits.zig +300 -0
- package/src/scaffold.zig +472 -0
- package/src/scenario.zig +346 -0
- package/src/scenario_fields.zig +50 -0
- package/src/schema_registry.zig +53 -0
- package/src/selector.zig +84 -0
- package/src/semantic.zig +171 -0
- package/src/trace.zig +315 -0
- package/src/trace_json.zig +340 -0
- package/src/trace_summary.zig +218 -0
- package/src/trace_summary_diagnostic.zig +202 -0
- package/src/types.zig +120 -0
- package/src/uiautomator.zig +164 -0
- package/src/validation.zig +187 -0
- package/src/version.zig +22 -0
- package/viewer/app.js +373 -0
- package/viewer/index.html +126 -0
- package/viewer/parser.js +233 -0
- package/viewer/styles.css +585 -0
package/docs/protocol.md
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# ZMR JSON-RPC Protocol
|
|
2
|
+
|
|
3
|
+
ZMR exposes newline-delimited JSON-RPC 2.0 over stdio or localhost TCP in v1. Each request is one JSON object followed by `\n`. Each response is one JSON object followed by `\n`.
|
|
4
|
+
|
|
5
|
+
Current runner version: `0.1.0`.
|
|
6
|
+
|
|
7
|
+
Current protocol version: `2026-04-28`.
|
|
8
|
+
|
|
9
|
+
Public schemas:
|
|
10
|
+
|
|
11
|
+
- `schemas/json-rpc.schema.json`
|
|
12
|
+
- `schemas/scenario.schema.json`
|
|
13
|
+
- `schemas/snapshot.schema.json`
|
|
14
|
+
- `schemas/semantic-snapshot.schema.json`
|
|
15
|
+
- `schemas/action-result.schema.json`
|
|
16
|
+
- `schemas/trace-event.schema.json`
|
|
17
|
+
- `schemas/trace-manifest.schema.json`
|
|
18
|
+
- `schemas/doctor-output.schema.json`
|
|
19
|
+
- `schemas/init-output.schema.json`
|
|
20
|
+
- `schemas/import-output.schema.json`
|
|
21
|
+
- `schemas/devices-output.schema.json`
|
|
22
|
+
- `schemas/validate-output.schema.json`
|
|
23
|
+
- `schemas/version-output.schema.json`
|
|
24
|
+
- `schemas/capabilities-output.schema.json`
|
|
25
|
+
- `schemas/explain-output.schema.json`
|
|
26
|
+
- `schemas/run-output.schema.json`
|
|
27
|
+
- `schemas/release-manifest.schema.json`
|
|
28
|
+
- `schemas/release-readiness-output.schema.json`
|
|
29
|
+
- `schemas/schemas-output.schema.json`
|
|
30
|
+
|
|
31
|
+
Compatibility fixtures live under `docs/protocol-fixtures/`. The Zig unit suite
|
|
32
|
+
loads these JSONL requests and asserts exact response shapes for stable core
|
|
33
|
+
methods.
|
|
34
|
+
|
|
35
|
+
`zmr schemas --json` returns the same public schema index in machine-readable
|
|
36
|
+
form for setup scripts, generated clients, and editor integrations. The
|
|
37
|
+
response is covered by `schemas/schemas-output.schema.json`.
|
|
38
|
+
|
|
39
|
+
`zmr-release-readiness --json` checks one or more release/pilot evidence files
|
|
40
|
+
for dev-preview, production, or market-claim gates. The output is covered by
|
|
41
|
+
`schemas/release-readiness-output.schema.json`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{"ok":false,"target":"market-claim","status":"blocked","evidence":"traces/release-candidate/run/evidence.jsonl","evidenceFiles":["traces/release-candidate/run/evidence.jsonl"],"passed":["local release gate","public Android emulator demo","public iOS simulator demo"],"satisfied":["local release gate","public Android demo","public iOS simulator demo"],"failed":[],"planned":[],"missing":["physical iOS readiness","Android hardware pilot","iOS simulator hardware pilot","iOS physical hardware pilot","competitive benchmark comparison"],"insufficient":[],"blocked":["physical iOS readiness","Android hardware pilot","iOS simulator hardware pilot","iOS physical hardware pilot","competitive benchmark comparison"],"requirements":[{"name":"local release gate","status":"satisfied","evidenceName":"local release gate"},{"name":"public Android demo","status":"satisfied","evidenceName":"public Android emulator demo"},{"name":"public iOS simulator demo","status":"satisfied","evidenceName":"public iOS simulator demo"},{"name":"physical iOS readiness","status":"missing","reason":"no matching passed evidence row"},{"name":"Android hardware pilot","status":"missing","reason":"no matching passed evidence row"},{"name":"iOS simulator hardware pilot","status":"missing","reason":"no matching passed evidence row"},{"name":"iOS physical hardware pilot","status":"missing","reason":"no matching passed evidence row"},{"name":"competitive benchmark comparison","status":"missing","reason":"no matching passed evidence row"}],"nextSteps":[{"requirement":"Android hardware pilot + iOS simulator hardware pilot","command":"zmr-pilot-gate --android --ios --android-app-root /path/to/mobile-app --android-app-id <android-app-id> --android-device <android-serial> --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Debug-iphonesimulator/Sample.app --ios-app-id <ios-app-id> --ios-device booted --ios-shim /path/to/mobile-app/.zmr/ios-shim --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out /path/to/mobile-app/traces/zmr-pilots/evidence.jsonl","commands":["zmr-pilot-gate --android --ios --android-app-root /path/to/mobile-app --android-app-id <android-app-id> --android-device <android-serial> --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Debug-iphonesimulator/Sample.app --ios-app-id <ios-app-id> --ios-device booted --ios-shim /path/to/mobile-app/.zmr/ios-shim --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out /path/to/mobile-app/traces/zmr-pilots/evidence.jsonl"],"covers":["Android hardware pilot","iOS simulator hardware pilot"]},{"requirement":"physical iOS readiness + iOS physical hardware pilot","command":"zmr-pilot-gate --ios --ios-device-type physical --ios-device <physical-device-id> --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Release-iphoneos/Sample.ipa --ios-app-id <ios-app-id> --ios-shim /path/to/mobile-app/.zmr/ios-shim --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out /path/to/mobile-app/traces/zmr-pilots/evidence.jsonl","commands":["zmr-pilot-gate --ios --ios-device-type physical --ios-device <physical-device-id> --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Release-iphoneos/Sample.ipa --ios-app-id <ios-app-id> --ios-shim /path/to/mobile-app/.zmr/ios-shim --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out /path/to/mobile-app/traces/zmr-pilots/evidence.jsonl"],"covers":["physical iOS readiness","iOS physical hardware pilot"]},{"requirement":"competitive benchmark comparison","command":"zmr-benchmark --zmr .zmr/android-smoke.json --platform <platform> --device <device-id> --app-id <app-id> --app-build <build-id-or-artifact> --runs 20 --trace-root traces/bench-comparison/zmr --results traces/bench-comparison/results.jsonl --replace --min-pass-rate 100 --max-failures 0 && zmr-benchmark-command --tool <baseline-name> --platform <platform> --device <device-id> --app-id <app-id> --scenario .zmr/android-smoke.json --app-build <build-id-or-artifact> --runs 20 --trace-root traces/bench-comparison/baseline --results traces/bench-comparison/results.jsonl -- <baseline command> && zmr-compare-benchmarks --results traces/bench-comparison/results.jsonl --candidate zmr --baseline <baseline-name> --min-candidate-pass-rate 100 --max-candidate-failures 0 --min-mean-speedup 1.25 --min-p95-speedup 1.25 --out traces/bench-comparison/report.md --evidence-out traces/bench-comparison/evidence.jsonl","commands":["zmr-benchmark --zmr .zmr/android-smoke.json --platform <platform> --device <device-id> --app-id <app-id> --app-build <build-id-or-artifact> --runs 20 --trace-root traces/bench-comparison/zmr --results traces/bench-comparison/results.jsonl --replace --min-pass-rate 100 --max-failures 0","zmr-benchmark-command --tool <baseline-name> --platform <platform> --device <device-id> --app-id <app-id> --scenario .zmr/android-smoke.json --app-build <build-id-or-artifact> --runs 20 --trace-root traces/bench-comparison/baseline --results traces/bench-comparison/results.jsonl -- <baseline command>","zmr-compare-benchmarks --results traces/bench-comparison/results.jsonl --candidate zmr --baseline <baseline-name> --min-candidate-pass-rate 100 --max-candidate-failures 0 --min-mean-speedup 1.25 --min-p95-speedup 1.25 --out traces/bench-comparison/report.md --evidence-out traces/bench-comparison/evidence.jsonl"],"covers":["competitive benchmark comparison"]}],"recommendedWording":"Do not publish the market claim yet. Missing evidence: physical iOS readiness, Android hardware pilot, iOS simulator hardware pilot, iOS physical hardware pilot, competitive benchmark comparison.","claimLimitations":["missing evidence"]}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Zero-dependency TypeScript, standard-library Python, standard-library Go, and
|
|
48
|
+
Rust reference clients live under `clients/typescript/`, `clients/python/`,
|
|
49
|
+
`clients/go/`, and `clients/rust/`; all are exercised by the no-device demo.
|
|
50
|
+
|
|
51
|
+
## Trace Event Contract
|
|
52
|
+
|
|
53
|
+
`zmr run ... --trace-dir <dir>` writes a run manifest to `<dir>/trace.json` and newline-delimited events to `<dir>/events.jsonl`. Each event has `seq`, `timestampMs`, `kind`, and `payload`.
|
|
54
|
+
|
|
55
|
+
`trace.json` is the stable bundle entrypoint for agents and viewers. It includes `schemaVersion`, `runnerVersion`, `protocolVersion`, `scenarioName`, `appId`, `status`, start/end/duration timestamps, failure metadata, `eventsPath`, `artifactsDir`, event/snapshot counts, partial failure count, and `reportPath` when `zmr report` has generated a single-trace HTML report.
|
|
56
|
+
|
|
57
|
+
`zmr export <trace-dir> --out <bundle.zmrtrace>` writes a deterministic tar archive with relative paths only. V1 bundles include `trace.json`, `events.jsonl`, optional `report.html`, and every regular file under `artifacts/`.
|
|
58
|
+
|
|
59
|
+
`zmr export <trace-dir> --out <bundle.zmrtrace> --redact` writes a shareable bundle without mutating the local trace directory. Redacted bundles replace PNG screenshots with placeholder frames, omit screen recording artifacts, scrub text artifacts for emails/tokens/sensitive JSON values, and add `redaction` metadata to the bundled `trace.json`. Add `--omit-screenshots` when the bundle should contain no screenshot artifacts at all.
|
|
60
|
+
|
|
61
|
+
The local static viewer lives at `viewer/index.html` and opens `.zmrtrace` bundles directly in the browser. It uses `trace.json` as the entrypoint, then renders the event timeline, replay controls for snapshot-linked frames, event payloads, side-by-side screenshots and UI trees, selectable node details, snapshot JSON, and artifact links.
|
|
62
|
+
|
|
63
|
+
Scenario traces start with `scenario.start` and terminate with `scenario.end`.
|
|
64
|
+
|
|
65
|
+
Successful terminal event:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{"kind":"scenario.end","payload":{"value":"flow name","status":"passed"}}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Failed terminal event:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{"kind":"step.error","payload":{"index":3,"error":"WaitTimeout"}}
|
|
75
|
+
{"kind":"scenario.end","payload":{"value":"flow name","status":"failed","failedStepIndex":3,"error":"WaitTimeout"}}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Clients should read the last `scenario.end` event as the authoritative trace outcome. The CLI still exits non-zero on failed runs.
|
|
79
|
+
|
|
80
|
+
`zmr run <scenario.json> --json` returns a terminal run summary after the
|
|
81
|
+
scenario completes. For traced runs it mirrors the authoritative `trace.json`
|
|
82
|
+
terminal fields, including trace paths, event/snapshot counts, failed step, and
|
|
83
|
+
stable error name. Traced summaries also include `nextCommands` so agents can
|
|
84
|
+
immediately render an HTML report, explain the failure, or export a redacted
|
|
85
|
+
trace bundle. Failed scenarios still exit non-zero after writing the JSON
|
|
86
|
+
summary. The response is covered by `schemas/run-output.schema.json`:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{"ok":false,"status":"failed","scenario":"login smoke","appId":"com.example.mobiletest","traceDir":"traces/login-smoke","eventsPath":"events.jsonl","artifactsDir":"artifacts","durationMs":100,"eventCount":4,"snapshotCount":1,"failedStepIndex":2,"error":"WaitTimeout","nextCommands":["zmr report traces/login-smoke --out traces/login-smoke/report.html","zmr explain traces/login-smoke --json","zmr export traces/login-smoke --out traces/login-smoke.zmrtrace --redact"]}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When a run captures useful artifacts but a non-terminal enrichment fails, such
|
|
93
|
+
as an iOS screenshot succeeding while XCTest hierarchy extraction fails, the
|
|
94
|
+
terminal status is `partial` and `partialFailureCount` is greater than zero.
|
|
95
|
+
The event stream includes a structured event such as
|
|
96
|
+
`observe.snapshot.semanticExtraction` with `artifactStatus: "captured"` and
|
|
97
|
+
`semanticStatus: "failed"`. `zmr run --json` also includes the last
|
|
98
|
+
`partialFailure` object so agents can report that visual proof exists while
|
|
99
|
+
semantic extraction failed:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{"ok":false,"status":"partial","scenario":"ios screenshot smoke","appId":"com.example.mobiletest","traceDir":"traces/ios-smoke","eventsPath":"events.jsonl","artifactsDir":"artifacts","durationMs":120,"eventCount":5,"snapshotCount":1,"partialFailureCount":1,"partialFailure":{"kind":"observe.snapshot.semanticExtraction","status":"failed","artifactStatus":"captured","semanticStatus":"failed","error":"CommandFailed","screenshotArtifact":"traces/ios-smoke/artifacts/snapshot-1.png","source":"ios-xctest-shim"}}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Selector miss and wait timeout payloads include diagnostic fields intended for agents and humans: `visibleTexts`, `hiddenCandidates`, `disabledCandidates`, `offscreenCandidates`, and `nearestTextMatches`. Native selector waits, including iOS XCTest-shim waits, capture one final snapshot on timeout and include the same diagnostic fields with `strategy: "nativeSelector"` when snapshot capture is available. Tap actions only target nodes that are visible, enabled, and inside the viewport; disabled or offscreen exact selector matches are reported as diagnostics instead of being tapped.
|
|
106
|
+
|
|
107
|
+
For terminal triage without opening the HTML report or static viewer, run:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
zmr explain traces/android-app-auth-probe
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The text summary includes the terminal status, failed step, stable error, last diagnostic event, snapshot id or screenshot artifact, active app context, visible text, and nearest text matches.
|
|
114
|
+
|
|
115
|
+
`zmr explain <trace-dir> --json` or `zmr explain --json <trace-dir>` returns
|
|
116
|
+
the same failure triage fields in a stable machine-readable shape for agents
|
|
117
|
+
and CI. It also includes `traceDir` and `nextCommands` for rendering an HTML
|
|
118
|
+
report or exporting a redacted bundle. The response is covered by
|
|
119
|
+
`schemas/explain-output.schema.json`:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{"ok":true,"traceDir":"traces/login-smoke","scenario":"login smoke","status":"failed","appId":"com.example.mobiletest","durationMs":100,"eventCount":4,"snapshotCount":1,"failedStepIndex":2,"error":"WaitTimeout","diagnostic":{"kind":"wait.visible","status":"timeout","snapshotId":"snapshot-7","activePackage":"com.example.mobiletest","activeActivity":".MainActivity","visibleTexts":["Sign in","Try again"],"nearestTextMatches":["Dashboards (score 1)"]},"lastEvent":"scenario.end","nextCommands":["zmr report traces/login-smoke --out traces/login-smoke/report.html","zmr export traces/login-smoke --out traces/login-smoke.zmrtrace --redact"]}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For partial visual captures, `diagnostic.kind` is
|
|
126
|
+
`observe.snapshot.semanticExtraction` and includes `artifactStatus`,
|
|
127
|
+
`semanticStatus`, `error`, `screenshotArtifact`, and `source`.
|
|
128
|
+
|
|
129
|
+
## Version Output Contract
|
|
130
|
+
|
|
131
|
+
`zmr version --json` returns runner and protocol compatibility metadata for
|
|
132
|
+
installers, setup scripts, and generated clients. The response is covered by
|
|
133
|
+
`schemas/version-output.schema.json`:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{"name":"zmr","version":"0.1.0","protocolVersion":"2026-04-28","minimumCompatibleProtocolVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Capabilities Output Contract
|
|
140
|
+
|
|
141
|
+
`runner.capabilities` returns the live protocol, platform support, transport,
|
|
142
|
+
and method inventory for JSON-RPC clients. The result object is covered by
|
|
143
|
+
`schemas/capabilities-output.schema.json`. Clients should use
|
|
144
|
+
`platformSupport` rather than parsing prose docs when deciding whether Android,
|
|
145
|
+
iOS simulator, or physical iOS workflows are available.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{"name":"zmr","version":"0.1.0","protocolVersion":"2026-04-28","protocol":{"version":"2026-04-28","minimumCompatibleVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"},"platforms":["android","ios"],"platformSupport":{"android":{"status":"supported","deviceTypes":["emulator","physical"],"automation":["adb","uiautomator","android-shim"]},"ios":{"status":"supported","deviceTypes":["simulator","physical"],"automation":["simctl","devicectl","xctest-shim"],"physicalDevices":true}},"iosPreview":false,"transports":["stdio","tcp"],"methods":["runner.capabilities","device.list","observe.snapshot","observe.semanticSnapshot"]}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Doctor Output Contract
|
|
152
|
+
|
|
153
|
+
`zmr doctor --json` returns setup diagnostics for local tooling. With
|
|
154
|
+
`--config`, it also validates configured Android/iOS smoke scenario files. The
|
|
155
|
+
response is covered by `schemas/doctor-output.schema.json`:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{"ok":false,"checks":[{"name":"android-shim","status":"missing","errorCode":"setup.android_shim.not_found","detail":"./.zmr/android-shim: FileNotFound","hint":"Run npx zmr-install-android-shim in the app repo or update tools.androidShimPath in .zmr/config.json."}]}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{"ok":false,"checks":[{"name":"config","status":"warning","errorCode":"config.empty_string","detail":".zmr/config.json: ConfigFieldMustBeNonEmptyString","fieldPath":"$.scripts.android","hint":"Fix the config file or regenerate it with npx zmr-wizard, then run zmr doctor --strict --json --config .zmr/config.json."}]}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{"ok":false,"checks":[{"name":"ios-smoke-scenario","status":"missing","errorCode":"scenario.file_not_found","detail":"./missing-ios-smoke.json: FileNotFound","hint":"Run npx zmr-wizard, create the iOS smoke scenario, or update ios.smokeScenario in .zmr/config.json."}]}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Physical iOS readiness is reported independently from simulator readiness:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{"ok":false,"checks":[{"name":"ios-physical-devices","status":"warning","errorCode":"setup.ios.no_physical_devices","detail":"0 physical iOS device(s)","hint":"Connect and trust an iPhone, enable Developer Mode, confirm zmr devices --json --platform ios --ios-device-type physical reports ready:true, then pass --ios-device-type physical --device <physical-device-id>."}]}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
If physical devices are listed but none are connected/available, the same check
|
|
176
|
+
uses `setup.ios.no_ready_physical_devices` and includes the listed count plus a
|
|
177
|
+
state breakdown:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{"ok":false,"checks":[{"name":"ios-physical-devices","status":"warning","errorCode":"setup.ios.no_ready_physical_devices","detail":"0 ready physical iOS device(s); 2 listed (disconnected=1, unavailable=1)","hint":"Connect and trust an iPhone, enable Developer Mode, confirm zmr devices --json --platform ios --ios-device-type physical reports ready:true, then pass --ios-device-type physical --device <physical-device-id>."}]}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Healthy checks omit `hint` and `errorCode`. Missing or warning checks include a
|
|
184
|
+
remediation hint that agents and setup scripts can surface directly. Warning
|
|
185
|
+
and missing checks include `errorCode` when the failure has a stable public
|
|
186
|
+
code. Config checks also include `fieldPath` when ZMR can identify the invalid
|
|
187
|
+
app-local config field. `zmr doctor --strict --json` keeps the same output
|
|
188
|
+
shape, but exits non-zero when any check is not `ok`; use it for CI gates that
|
|
189
|
+
should fail before device orchestration starts.
|
|
190
|
+
|
|
191
|
+
## Init Output Contract
|
|
192
|
+
|
|
193
|
+
`zmr init --json`, `zmr-init --json`, and `zmr-wizard --json` return
|
|
194
|
+
machine-readable bootstrap output for setup scripts and AI agents. The response
|
|
195
|
+
is covered by `schemas/init-output.schema.json`. In app mode it lists every
|
|
196
|
+
generated app-local file, direct paths for the config/scenario/matrix/agent
|
|
197
|
+
files, first-run commands, and the generated script names:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{"ok":true,"mode":"app","dir":".","appId":"com.example.mobiletest","created":["./.zmr/config.json","./.zmr/android-smoke.json","./.zmr/ios-smoke.json","./.zmr/device-matrix.json","./.zmr/AGENTS.md"],"configPath":"./.zmr/config.json","androidScenarioPath":"./.zmr/android-smoke.json","iosScenarioPath":"./.zmr/ios-smoke.json","deviceMatrixPath":"./.zmr/device-matrix.json","agentInstructionsPath":"./.zmr/AGENTS.md","next":"zmr doctor --strict --json --config ./.zmr/config.json","nextCommands":["zmr doctor --strict --json --config ./.zmr/config.json","zmr schemas --json","zmr validate --json ./.zmr/android-smoke.json","zmr validate --json ./.zmr/ios-smoke.json"],"smokeCommands":["zmr run ./.zmr/android-smoke.json --device emulator-5554 --trace-dir ./traces/zmr-android","zmr run ./.zmr/ios-smoke.json --platform ios --device booted --trace-dir ./traces/zmr-ios"],"scriptCount":16,"scriptNames":["doctor","schemas","validate","android","androidReport","androidReliability","ios","iosReport","iosReliability","matrix","pilotGate","readiness","serve","mcp","explain","exportTrace"]}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Agents should read `agentInstructionsPath` for the app-local operating note and
|
|
204
|
+
run `nextCommands` in order before attempting longer smoke, matrix, or pilot
|
|
205
|
+
runs. After validation passes and devices are available, agents can run
|
|
206
|
+
`smokeCommands` for direct Android and iOS smoke runs.
|
|
207
|
+
|
|
208
|
+
When Expo dev-client scenarios are generated, app mode also includes
|
|
209
|
+
`androidDevClientScenarioPath` and `iosDevClientScenarioPath` and includes those
|
|
210
|
+
files in the generated validation command.
|
|
211
|
+
|
|
212
|
+
Single-scenario mode reports the created scenario and next validation command:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{"ok":true,"mode":"scenario","appId":"com.example.mobiletest","created":["zmr-scenario.json"],"next":"zmr validate zmr-scenario.json","nextCommands":["zmr validate --json zmr-scenario.json","zmr run zmr-scenario.json --json --trace-dir traces/zmr-run"]}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Import Output Contract
|
|
219
|
+
|
|
220
|
+
`zmr import flow-yaml <flow.yaml> --out .zmr/imported.json --json` converts a
|
|
221
|
+
supported subset of mobile-flow YAML into native ZMR scenario JSON. The
|
|
222
|
+
response is covered by `schemas/import-output.schema.json`:
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{"ok":true,"format":"flow-yaml","source":"flows/login.yaml","out":".zmr/login-smoke.json","name":"Imported login smoke","appId":"com.example.mobiletest","stepCount":10,"next":"zmr validate .zmr/login-smoke.json","nextCommands":["zmr validate --json .zmr/login-smoke.json","zmr run .zmr/login-smoke.json --json --trace-dir traces/zmr-run"]}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The importer is intentionally a migration helper, not a runtime dependency.
|
|
229
|
+
After import, agents and CI should treat the generated `.zmr/*.json` file as
|
|
230
|
+
the source of truth and run `nextCommands` in order.
|
|
231
|
+
|
|
232
|
+
## Validate Output Contract
|
|
233
|
+
|
|
234
|
+
`zmr validate <scenario.json> --json` returns machine-readable scenario
|
|
235
|
+
preflight diagnostics without touching a device. The response is covered by
|
|
236
|
+
`schemas/validate-output.schema.json`:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{"ok":true,"path":"examples/demo-fake.json","name":"ZMR fake Android auth probe demo","appId":"com.example.mobiletest","stepCount":4,"nextCommands":["zmr run examples/demo-fake.json --json --trace-dir traces/zmr-run"]}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Invalid scenarios exit non-zero after writing the JSON object:
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
{"ok":false,"path":"bad.json","errorCode":"scenario.invalid","message":"scenario is invalid","fieldPath":"$.steps"}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Devices Output Contract
|
|
249
|
+
|
|
250
|
+
`zmr devices --json` returns a stable platform, count, and device list for setup
|
|
251
|
+
scripts that need to choose a target before starting `zmr run` or `zmr serve`.
|
|
252
|
+
Each device includes `ready`, a portable boolean for agent setup logic. The
|
|
253
|
+
response is covered by `schemas/devices-output.schema.json`:
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{"platform":"android","count":1,"devices":[{"serial":"emulator-5554","state":"device","ready":true}]}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
For iOS simulators, use `zmr devices --json --platform ios`; states come from
|
|
260
|
+
`simctl`, for example `Booted`. For physical iOS devices, use:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
zmr devices --json --platform ios --ios-device-type physical
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Physical-device states come from `devicectl` connection data, for example
|
|
267
|
+
`connected`, `disconnected`, or `paired`. For iOS physical devices, `ready` is
|
|
268
|
+
true only for `connected` or `available`.
|
|
269
|
+
The `serial` field is the commandable CoreDevice identifier to pass back to
|
|
270
|
+
`--device`.
|
|
271
|
+
|
|
272
|
+
## Start
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
zmr serve --transport stdio --device emulator-5554 --app-id com.example.mobiletest --trace-dir traces/agent-session
|
|
276
|
+
zmr serve --transport tcp --port 8765 --device emulator-5554 --app-id com.example.mobiletest --trace-dir traces/agent-session
|
|
277
|
+
zmr serve --transport stdio --platform ios --device <sim-udid> --app-id com.example.mobiletest --trace-dir traces/ios-agent-session
|
|
278
|
+
zmr serve --transport stdio --platform ios --ios-device-type physical --device <physical-device-id> --app-id com.example.mobiletest --trace-dir traces/ios-physical-agent-session
|
|
279
|
+
zmr mcp --config .zmr/config.json --trace-dir traces/mcp-agent-session
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`runner.capabilities` reports `platforms: ["android","ios"]`, `platformSupport.ios.status: "supported"`, and legacy `iosPreview: false`. Android supports emulators and connected devices. iOS simulators use `simctl` for discovery, install, launch, stop, clear-state-by-uninstall, deep links, screenshots, logs, and snapshots. Physical iOS devices use `devicectl` for discovery, install, launch, deep-link launch, clear-state-by-uninstall, and best-effort stop. Selector-grade `ui.*` methods on iOS require a configured XCTest/XCUIAutomation shim command; without one they return `IosXCTestShimRequired`. With the shim configured, single-field `ui.tap`, selector-scoped `ui.type`, selector-scoped `ui.eraseText`, `wait.*`, and `assert.*` can execute directly through XCTest. Compound selectors continue to use the portable snapshot-matching fallback. iOS snapshot responses are bounded to common XCTest element families so traces stay usable on large apps. Physical iOS screenshot artifacts use the XCTest shim; physical-device log capture is intentionally limited in this release.
|
|
283
|
+
|
|
284
|
+
## Core Methods
|
|
285
|
+
|
|
286
|
+
- `runner.capabilities`
|
|
287
|
+
- `device.list`
|
|
288
|
+
- `session.create`
|
|
289
|
+
- `session.close`
|
|
290
|
+
- `app.install` with `{ "path": "/path/app.apk" }` on Android or `{ "path": "/path/App.app" }` on iOS
|
|
291
|
+
- `app.launch`
|
|
292
|
+
- `app.stop`
|
|
293
|
+
- `app.openLink` with `{ "url": "exampleapp://e2e-auth?probe=1" }`
|
|
294
|
+
- `app.clearState`
|
|
295
|
+
- `observe.snapshot`
|
|
296
|
+
- `observe.semanticSnapshot`
|
|
297
|
+
- `ui.tap` with `{ "selector": { "text": "Sign in" } }`
|
|
298
|
+
- `ui.type` with `{ "text": "hello" }`, optionally with `selector`
|
|
299
|
+
- `ui.eraseText` with `{ "maxChars": 80 }`, optionally with `selector`
|
|
300
|
+
- `ui.hideKeyboard`
|
|
301
|
+
- `ui.swipe` with `{ "x1": 500, "y1": 1600, "x2": 500, "y2": 400, "durationMs": 300 }`
|
|
302
|
+
- `ui.pressBack`
|
|
303
|
+
- `ui.scrollUntilVisible` with `{ "selector": { "id": "invite-card" }, "direction": "down", "timeoutMs": 20000 }`
|
|
304
|
+
- `wait.until` with `{ "visible": { "textContains": "Home" }, "timeoutMs": 10000 }`
|
|
305
|
+
- `wait.any` with `{ "selectors": [{ "text": "A" }, { "textContains": "B" }], "timeoutMs": 10000 }`
|
|
306
|
+
- `wait.gone` with `{ "selector": { "textContains": "Loading" }, "timeoutMs": 10000 }`
|
|
307
|
+
- `assert.visible`
|
|
308
|
+
- `assert.notVisible`
|
|
309
|
+
- `assert.healthy`
|
|
310
|
+
- `trace.events`
|
|
311
|
+
- `trace.export`
|
|
312
|
+
|
|
313
|
+
`runner.capabilities` returns both legacy `protocolVersion` and a structured `protocol` object. Clients should treat `protocol.version` as the compatibility key for method and payload shape, and should reject servers older than `protocol.minimumCompatibleVersion` unless they intentionally support that older shape. Before `v1.0.0`, `protocol.stability` is `dev-preview` and breaking changes require both a protocol version bump and changelog entry.
|
|
314
|
+
|
|
315
|
+
## Request And Response Shape
|
|
316
|
+
|
|
317
|
+
Every request is newline-delimited JSON:
|
|
318
|
+
|
|
319
|
+
```json
|
|
320
|
+
{"jsonrpc":"2.0","id":1,"method":"app.launch","params":{}}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Successful responses use `result`:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{"jsonrpc":"2.0","id":1,"result":true}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Errors use JSON-RPC numeric codes plus an optional stable `publicCode` for agent/client handling:
|
|
330
|
+
|
|
331
|
+
```json
|
|
332
|
+
{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"MissingParam","publicCode":"cli.missing_param"}}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Parse and malformed-request errors may omit `publicCode` because the request was not valid enough to classify at the method layer.
|
|
336
|
+
|
|
337
|
+
## Method Examples
|
|
338
|
+
|
|
339
|
+
### `runner.capabilities`
|
|
340
|
+
|
|
341
|
+
Request:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{"jsonrpc":"2.0","id":1,"method":"runner.capabilities","params":{}}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Response:
|
|
348
|
+
|
|
349
|
+
```json
|
|
350
|
+
{"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.1.0","protocolVersion":"2026-04-28","protocol":{"version":"2026-04-28","minimumCompatibleVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"},"platforms":["android","ios"],"platformSupport":{"android":{"status":"supported","deviceTypes":["emulator","physical"],"automation":["adb","uiautomator","android-shim"]},"ios":{"status":"supported","deviceTypes":["simulator","physical"],"automation":["simctl","devicectl","xctest-shim"],"physicalDevices":true}},"iosPreview":false,"transports":["stdio","tcp"],"methods":["runner.capabilities","device.list","session.create","session.close","app.install","app.launch","app.stop","app.openLink","app.clearState","observe.snapshot","observe.semanticSnapshot","ui.tap","ui.type","ui.eraseText","ui.hideKeyboard","ui.swipe","ui.pressBack","ui.scrollUntilVisible","wait.until","wait.any","wait.gone","assert.visible","assert.notVisible","assert.healthy","trace.events","trace.export"]}}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### `trace.events`
|
|
354
|
+
|
|
355
|
+
Returns live trace events from a `zmr serve --trace-dir <dir>` session after a
|
|
356
|
+
sequence cursor. This is the event-streaming surface for long-running agents:
|
|
357
|
+
poll with the returned `nextSeq` value to receive only new events. Without a
|
|
358
|
+
live trace directory it returns an empty stream with `traceDir: null`.
|
|
359
|
+
|
|
360
|
+
Request:
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{"jsonrpc":"2.0","id":24,"method":"trace.events","params":{"afterSeq":0,"limit":100}}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Response:
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{"jsonrpc":"2.0","id":24,"result":{"traceDir":"traces/agent-session","afterSeq":0,"nextSeq":2,"latestSeq":2,"events":[{"seq":1,"timestampMs":1777794787560,"kind":"rpc.request","payload":{"method":"session.create","id":1}},{"seq":2,"timestampMs":1777794787561,"kind":"rpc.response","payload":{"method":"session.create","id":1}}]}}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
`limit` defaults to `100` and is capped at `1000`. `latestSeq` is the current
|
|
373
|
+
server-side event counter; `nextSeq` is the last returned event and can be
|
|
374
|
+
passed back as `afterSeq`.
|
|
375
|
+
|
|
376
|
+
### `device.list`
|
|
377
|
+
|
|
378
|
+
Response:
|
|
379
|
+
|
|
380
|
+
```json
|
|
381
|
+
{"jsonrpc":"2.0","id":2,"result":[{"serial":"emulator-5554","state":"device","ready":true}]}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
For `--platform ios`, states come from `simctl`, for example `Booted`.
|
|
385
|
+
|
|
386
|
+
### `observe.snapshot`
|
|
387
|
+
|
|
388
|
+
Response shape:
|
|
389
|
+
|
|
390
|
+
```json
|
|
391
|
+
{
|
|
392
|
+
"jsonrpc": "2.0",
|
|
393
|
+
"id": 3,
|
|
394
|
+
"result": {
|
|
395
|
+
"id": "snapshot-1",
|
|
396
|
+
"timestampMs": 1777367889379,
|
|
397
|
+
"viewport": { "width": 720, "height": 1280 },
|
|
398
|
+
"displayDensityDpi": 420,
|
|
399
|
+
"activePackage": "com.example.mobiletest",
|
|
400
|
+
"activeActivity": ".MainActivity",
|
|
401
|
+
"screenshotArtifact": null,
|
|
402
|
+
"treeArtifact": null,
|
|
403
|
+
"focusedNodeId": null,
|
|
404
|
+
"logDelta": null,
|
|
405
|
+
"nodes": []
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### `observe.semanticSnapshot`
|
|
411
|
+
|
|
412
|
+
Returns an agent-optimized view of the same observation. It normalizes
|
|
413
|
+
platform-specific UI classes into roles, emits the best selector for each node,
|
|
414
|
+
adds center points, and marks the recommended action when a node is actionable.
|
|
415
|
+
The result is covered by `schemas/semantic-snapshot.schema.json`.
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{"jsonrpc":"2.0","id":3,"method":"observe.semanticSnapshot","params":{}}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```json
|
|
422
|
+
{"jsonrpc":"2.0","id":3,"result":{"id":"snapshot-1","timestampMs":1777367889379,"viewport":{"width":720,"height":1280},"activePackage":"com.example.mobiletest","activeActivity":".MainActivity","focusedNodeId":null,"nodes":[{"id":"submit","role":"button","name":"Sign in","selector":{"resourceId":"submit"},"source":{"className":"android.widget.Button","resourceId":"submit","text":"Sign in","contentDesc":null},"bounds":{"x":80,"y":470,"width":560,"height":70,"centerX":360,"centerY":505},"enabled":true,"visible":true,"selected":false,"interactive":true,"recommendedAction":"tap"}],"summary":{"nodeCount":1,"interactiveCount":1,"visibleText":["Sign in"]}}}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## MCP Tool Surface
|
|
426
|
+
|
|
427
|
+
`zmr mcp` speaks the Model Context Protocol over stdio. It uses the same app
|
|
428
|
+
config, device adapters, selectors, waits, and trace writer as `zmr serve`, but
|
|
429
|
+
exposes tool calls for agent runtimes that prefer MCP over raw JSON-RPC.
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
zmr mcp --config .zmr/config.json --trace-dir traces/mcp-agent
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Core tools are `snapshot`, `semantic_snapshot`, `tap`, `type`, `press_back`,
|
|
436
|
+
`open_link`, `wait_visible`, `trace_events`, and `trace_export`. The MCP
|
|
437
|
+
protocol handshake is intentionally standard, while the tool names and payloads
|
|
438
|
+
are versioned with the ZMR runner and public schemas.
|
|
439
|
+
|
|
440
|
+
### `ui.tap`
|
|
441
|
+
|
|
442
|
+
Request:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
{"jsonrpc":"2.0","id":4,"method":"ui.tap","params":{"selector":{"text":"Sign in"}}}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Response:
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{"jsonrpc":"2.0","id":4,"result":true}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### `ui.type`
|
|
455
|
+
|
|
456
|
+
Request:
|
|
457
|
+
|
|
458
|
+
```json
|
|
459
|
+
{"jsonrpc":"2.0","id":5,"method":"ui.type","params":{"selector":{"id":"email-login-email-input"},"text":"person@example.com"}}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Waits And Assertions
|
|
463
|
+
|
|
464
|
+
```json
|
|
465
|
+
{"jsonrpc":"2.0","id":6,"method":"wait.until","params":{"visible":{"textContains":"Home"},"timeoutMs":10000}}
|
|
466
|
+
{"jsonrpc":"2.0","id":7,"method":"assert.notVisible","params":{"selector":{"textContains":"Loading"},"timeoutMs":5000}}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### `trace.export`
|
|
470
|
+
|
|
471
|
+
When `zmr serve` is started with `--trace-dir`, live JSON-RPC sessions use the
|
|
472
|
+
same trace manifest, event stream, snapshot artifacts, and `.zmrtrace` bundle
|
|
473
|
+
format as scenario runs. Each request records `rpc.request` and `rpc.response`
|
|
474
|
+
or `rpc.error`; snapshot, wait, and UI methods also record their domain events.
|
|
475
|
+
|
|
476
|
+
Request:
|
|
477
|
+
|
|
478
|
+
```json
|
|
479
|
+
{"jsonrpc":"2.0","id":8,"method":"trace.export","params":{"out":"traces/agent-session-redacted.zmrtrace","redact":true,"omitScreenshots":true}}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Response:
|
|
483
|
+
|
|
484
|
+
```json
|
|
485
|
+
{"jsonrpc":"2.0","id":8,"result":{"traceDir":"traces/agent-session","out":"traces/agent-session-redacted.zmrtrace","redacted":true,"omitScreenshots":true}}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
`omitScreenshots` implies redacted export semantics, even when `redact` is
|
|
489
|
+
omitted or false.
|
|
490
|
+
|
|
491
|
+
If the server was not started with `--trace-dir`, `trace.export` returns a
|
|
492
|
+
result with `traceDir: null` and a message explaining how to enable live traces.
|
|
493
|
+
|
|
494
|
+
## Public Error Codes
|
|
495
|
+
|
|
496
|
+
Current stable public codes:
|
|
497
|
+
|
|
498
|
+
- `cli.missing_scenario`
|
|
499
|
+
- `cli.missing_device`
|
|
500
|
+
- `cli.missing_trace_dir`
|
|
501
|
+
- `cli.missing_app_id`
|
|
502
|
+
- `cli.missing_adb_path`
|
|
503
|
+
- `cli.missing_xcrun_path`
|
|
504
|
+
- `cli.missing_zig_path`
|
|
505
|
+
- `cli.missing_platform`
|
|
506
|
+
- `cli.unknown_flag`
|
|
507
|
+
- `cli.missing_param`
|
|
508
|
+
- `cli.unsupported_platform`
|
|
509
|
+
- `cli.unsupported_transport`
|
|
510
|
+
- `scenario.file_not_found`
|
|
511
|
+
- `scenario.invalid`
|
|
512
|
+
- `selector.invalid`
|
|
513
|
+
- `runner.wait_timeout`
|
|
514
|
+
- `runner.assertion_failed`
|
|
515
|
+
- `runner.selector_not_found`
|
|
516
|
+
- `device.command_failed`
|
|
517
|
+
- `ios.xctest_shim_required`
|
|
518
|
+
- `internal.error`
|
|
519
|
+
|
|
520
|
+
## Selectors
|
|
521
|
+
|
|
522
|
+
Selectors can combine fields. All provided fields must match the same visible node.
|
|
523
|
+
|
|
524
|
+
```json
|
|
525
|
+
{
|
|
526
|
+
"id": "email-login-submit-button",
|
|
527
|
+
"resourceId": "email-login-submit-button",
|
|
528
|
+
"text": "Sign in",
|
|
529
|
+
"textContains": "Sign",
|
|
530
|
+
"contentDesc": "Account",
|
|
531
|
+
"contentDescContains": "Acc",
|
|
532
|
+
"className": "android.widget.TextView"
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## Example
|
|
537
|
+
|
|
538
|
+
```json
|
|
539
|
+
{"jsonrpc":"2.0","id":1,"method":"runner.capabilities","params":{}}
|
|
540
|
+
{"jsonrpc":"2.0","id":2,"method":"app.openLink","params":{"url":"exampleapp://e2e-auth?probe=1"}}
|
|
541
|
+
{"jsonrpc":"2.0","id":3,"method":"wait.until","params":{"visible":{"text":"E2E auth probe"},"timeoutMs":30000}}
|
|
542
|
+
{"jsonrpc":"2.0","id":4,"method":"observe.snapshot","params":{}}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Scenario-Only Flow Primitives
|
|
546
|
+
|
|
547
|
+
Scenario JSON supports additional orchestration primitives for agent-grade mobile flows:
|
|
548
|
+
|
|
549
|
+
- `waitAny`
|
|
550
|
+
- `waitNotVisible`
|
|
551
|
+
- `whenVisible`
|
|
552
|
+
- `repeat`
|
|
553
|
+
- `scrollUntilVisible`
|
|
554
|
+
- `assertNoneVisible`
|
|
555
|
+
- `assertHealthy`
|
|
556
|
+
- `eraseText`
|
|
557
|
+
- `hideKeyboard`
|
|
558
|
+
- `"optional": true` on any step
|
|
559
|
+
|
|
560
|
+
These are intentionally explicit JSON structures instead of YAML conditionals, so agents can generate, validate, and mutate flows without parsing a second language.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Public GitHub Publication
|
|
2
|
+
|
|
3
|
+
This is the maintainer checklist for uploading ZMR to a public GitHub repository
|
|
4
|
+
and making it usable by other mobile app codebases.
|
|
5
|
+
|
|
6
|
+
## Public Repo State
|
|
7
|
+
|
|
8
|
+
Before pushing a branch or tag:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
git status --short
|
|
12
|
+
bash tests/public-safety-test.sh
|
|
13
|
+
./scripts/release-gate.sh
|
|
14
|
+
npm pack --dry-run
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Expected evidence:
|
|
18
|
+
|
|
19
|
+
- `git status --short` shows only intentional source, docs, workflow, schema,
|
|
20
|
+
shim, client, and test files.
|
|
21
|
+
- `bash tests/public-safety-test.sh` passes.
|
|
22
|
+
- `./scripts/release-gate.sh` passes locally.
|
|
23
|
+
- `npm pack --dry-run` lists only public package contents.
|
|
24
|
+
|
|
25
|
+
Do not commit generated traces, release archives, npm tarballs, local build
|
|
26
|
+
outputs, app credentials, private app identifiers, simulator logs, or raw visual
|
|
27
|
+
artifacts. `.gitignore` excludes `traces/`, `dist/`, `zig-out/`, Zig caches,
|
|
28
|
+
`prebuilds/`, `node_modules/`, and generated tarballs by default.
|
|
29
|
+
|
|
30
|
+
## Repository Setup
|
|
31
|
+
|
|
32
|
+
1. Create the public GitHub repository.
|
|
33
|
+
2. Push the source branch.
|
|
34
|
+
3. Confirm CI runs `./scripts/release-gate.sh`.
|
|
35
|
+
4. Configure branch protection for `main`.
|
|
36
|
+
5. Add `NPM_TOKEN` only when npm publish should be automated.
|
|
37
|
+
6. For the current dev-preview package, create and push tag `v0.1.0`.
|
|
38
|
+
|
|
39
|
+
The release workflow builds release archives, generates checksums, verifies
|
|
40
|
+
packaged binaries, builds the npm tarball with prebuilt binaries, uploads GitHub
|
|
41
|
+
release assets, publishes artifact attestations, and publishes to npm when
|
|
42
|
+
`NPM_TOKEN` is configured.
|
|
43
|
+
|
|
44
|
+
## App Integration Smoke
|
|
45
|
+
|
|
46
|
+
In a separate mobile app checkout:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install --save-dev zig-mobile-runner
|
|
50
|
+
npx zmr-wizard --app-id com.example.mobiletest --package-json
|
|
51
|
+
npx zmr doctor --strict --json --config .zmr/config.json
|
|
52
|
+
npm run zmr:android
|
|
53
|
+
npm run zmr:ios
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use `zmr-pilot-gate` for maintainer release evidence on machines with real app
|
|
57
|
+
builds and devices:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx zmr-pilot-gate \
|
|
61
|
+
--android \
|
|
62
|
+
--ios \
|
|
63
|
+
--android-app-root . \
|
|
64
|
+
--android-app-id com.example.mobiletest \
|
|
65
|
+
--android-device emulator-5554 \
|
|
66
|
+
--ios-app-root . --ios-app-path ./build/Debug-iphonesimulator/Sample.app \
|
|
67
|
+
--ios-app-id com.example.mobiletest \
|
|
68
|
+
--ios-device booted \
|
|
69
|
+
--ios-shim ./.zmr/ios-shim \
|
|
70
|
+
--runs 20 \
|
|
71
|
+
--min-pass-rate 100 \
|
|
72
|
+
--max-failures 0 \
|
|
73
|
+
--evidence-out traces/zmr-pilots/evidence.jsonl
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Publish reliability claims only from sanitized, app-agnostic summaries. Keep
|
|
77
|
+
private scenarios and raw traces in the private app repository.
|