zeno-mobile-runner 0.1.2 → 0.1.8
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 +162 -3
- package/FEATURES.md +50 -7
- package/README.md +133 -7
- package/build.zig.zon +3 -3
- package/clients/README.md +60 -3
- package/clients/go/README.md +12 -0
- package/clients/go/zmr/client.go +142 -0
- package/clients/kotlin/README.md +18 -1
- package/clients/kotlin/build.gradle.kts +1 -1
- package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +76 -1
- package/clients/python/README.md +19 -0
- package/clients/python/pyproject.toml +1 -1
- package/clients/python/zmr_client.py +33 -0
- package/clients/rust/Cargo.lock +1 -1
- package/clients/rust/Cargo.toml +1 -1
- package/clients/rust/README.md +25 -1
- package/clients/rust/src/lib.rs +201 -0
- package/clients/swift/README.md +18 -0
- package/clients/swift/Sources/ZMRClient/ZMRClient.swift +82 -0
- package/clients/typescript/README.md +16 -0
- package/clients/typescript/index.d.ts +12 -0
- package/clients/typescript/index.mjs +16 -0
- package/clients/typescript/package.json +1 -1
- package/docs/agent-discovery.md +202 -0
- package/docs/ai-agents.md +87 -6
- package/docs/benchmarking.md +10 -3
- package/docs/clients.md +10 -6
- package/docs/demo.md +4 -0
- package/docs/expo-smoke.md +79 -0
- package/docs/install.md +3 -2
- package/docs/npm.md +58 -4
- package/docs/production-readiness.md +123 -0
- package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
- package/docs/protocol.md +215 -16
- package/docs/scenario-authoring.md +3 -0
- package/docs/troubleshooting.md +1 -1
- package/npm/agents.mjs +16 -0
- package/npm/build-zmr.mjs +1 -1
- package/npm/commands.mjs +9 -5
- package/npm/postinstall.mjs +28 -2
- package/npm/verify-publish.mjs +36 -0
- package/package.json +2 -1
- 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 +4 -0
- package/schemas/discover-output.schema.json +83 -0
- package/schemas/draft-output.schema.json +58 -0
- package/schemas/explore-output.schema.json +94 -0
- package/schemas/inspect-output.schema.json +88 -0
- package/schemas/run-output.schema.json +2 -0
- package/scripts/install-ios-shim.sh +79 -14
- package/scripts/release-readiness.py +43 -0
- package/scripts/run-android-pilot.sh +35 -9
- package/scripts/run-ios-pilot.sh +11 -4
- package/shims/ios/ZMRShim.swift +3 -0
- package/shims/ios/ZMRShimUITestCase.swift +41 -11
- package/skills/zmr-mobile-testing/SKILL.md +28 -3
- package/src/cli_discover.zig +239 -0
- package/src/cli_draft.zig +924 -0
- package/src/cli_explore.zig +136 -0
- package/src/cli_inspect.zig +310 -0
- package/src/cli_output.zig +26 -2
- package/src/cli_run.zig +28 -0
- package/src/cli_trace.zig +8 -0
- package/src/errors.zig +9 -0
- package/src/ios.zig +11 -4
- package/src/ios_lifecycle.zig +36 -0
- package/src/ios_shim.zig +42 -0
- package/src/json_rpc_methods.zig +85 -11
- package/src/json_rpc_params.zig +8 -0
- package/src/json_rpc_protocol.zig +1 -1
- package/src/json_rpc_trace.zig +112 -0
- package/src/main.zig +24 -2
- package/src/mcp.zig +209 -6
- package/src/mcp_protocol.zig +29 -1
- package/src/mcp_trace.zig +126 -4
- package/src/report.zig +186 -0
- package/src/runner.zig +26 -4
- package/src/runner_actions.zig +10 -0
- package/src/runner_diagnostics.zig +31 -1
- package/src/runner_events.zig +70 -7
- package/src/runner_native.zig +17 -1
- package/src/runner_waits.zig +82 -19
- package/src/scaffold.zig +28 -12
- package/src/scenario.zig +32 -4
- package/src/schema_registry.zig +4 -0
- package/src/version.zig +1 -1
package/docs/protocol.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
-
Current runner version: `0.1.
|
|
5
|
+
Current runner version: `0.1.8`.
|
|
6
6
|
|
|
7
7
|
Current protocol version: `2026-04-28`.
|
|
8
8
|
|
|
@@ -24,6 +24,10 @@ Public schemas:
|
|
|
24
24
|
- `schemas/capabilities-output.schema.json`
|
|
25
25
|
- `schemas/explain-output.schema.json`
|
|
26
26
|
- `schemas/run-output.schema.json`
|
|
27
|
+
- `schemas/inspect-output.schema.json`
|
|
28
|
+
- `schemas/discover-output.schema.json`
|
|
29
|
+
- `schemas/explore-output.schema.json`
|
|
30
|
+
- `schemas/draft-output.schema.json`
|
|
27
31
|
- `schemas/release-manifest.schema.json`
|
|
28
32
|
- `schemas/release-readiness-output.schema.json`
|
|
29
33
|
- `schemas/schemas-output.schema.json`
|
|
@@ -36,12 +40,74 @@ methods.
|
|
|
36
40
|
form for setup scripts, generated clients, and editor integrations. The
|
|
37
41
|
response is covered by `schemas/schemas-output.schema.json`.
|
|
38
42
|
|
|
43
|
+
`zmr inspect --json` returns a read-only app handoff for humans and agents. It
|
|
44
|
+
reports the app root, config and `.zmr/AGENTS.md` presence, configured Android
|
|
45
|
+
and iOS smoke scenarios, safe next commands, claim limits, and current runner
|
|
46
|
+
and protocol versions. The response is covered by
|
|
47
|
+
`schemas/inspect-output.schema.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{"ok":true,"status":"ready","schemaVersion":1,"runnerVersion":"0.1.8","protocolVersion":"2026-04-28","dir":".","configPath":".zmr/config.json","configExists":true,"agentInstructionsPath":".zmr/AGENTS.md","agentInstructionsExists":true,"platforms":[{"name":"android","enabled":true,"defaultDevice":"emulator-5554","smokeScenario":".zmr/android-smoke.json","smokeScenarioExists":true,"traceDir":"traces/zmr-android"},{"name":"ios","enabled":true,"defaultDevice":"booted","smokeScenario":".zmr/ios-smoke.json","smokeScenarioExists":true,"traceDir":"traces/zmr-ios"}],"recommendedCommands":["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","zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent","zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent"],"claimsPolicy":["verify runs with trace evidence before making readiness claims","do not claim Flutter widget-tree inspection"],"limitations":["inspect is read-only and does not launch devices","autonomous crawling is not shipped; generate or edit scenarios for human review"]}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`zmr discover --from-trace <trace-dir> --out <scenario.json> --validate --json`
|
|
54
|
+
is the public trace-to-test handoff for agents. It reads the trace manifest and
|
|
55
|
+
latest snapshot artifact, writes a reviewable scenario, and optionally validates
|
|
56
|
+
that generated scenario before returning. With `--include-actions`, it replays
|
|
57
|
+
only successful supported trace actions that contain stable replay data. It is
|
|
58
|
+
offline and review-first: it does not start a device session, crawl the app,
|
|
59
|
+
invent credentials, or commit files. The response is covered by
|
|
60
|
+
`schemas/discover-output.schema.json`:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{"ok":true,"mode":"discover","schemaVersion":1,"runnerVersion":"0.1.8","protocolVersion":"2026-04-28","out":".zmr/discovered/replay-smoke.json","traceDir":"traces/zmr-agent","sourceSnapshot":"traces/zmr-agent/artifacts/snapshot-2.json","name":"draft from login smoke","appId":"com.example.mobiletest","selectorCount":2,"stepCount":6,"replay":{"enabled":true,"eventCount":4,"stepCount":3,"skippedEventCount":1},"warnings":["draft requires human review before commit"],"validated":true,"validation":{"ok":true,"path":".zmr/discovered/replay-smoke.json","name":"draft from login smoke","appId":"com.example.mobiletest","stepCount":6},"nextCommands":["zmr validate --json .zmr/discovered/replay-smoke.json","zmr run .zmr/discovered/replay-smoke.json --json --trace-dir traces/zmr-agent"]}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`zmr explore --from-trace <trace-dir> --out <scenario.json> --goal <goal>
|
|
67
|
+
--include-actions --validate --json` is the review-first exploration handoff
|
|
68
|
+
for CLI agents. It uses the same trace-backed scenario writer as `discover`,
|
|
69
|
+
but carries the agent goal and explicit guardrails in the response. It does not
|
|
70
|
+
launch devices, crawl the app, invent missing actions, discover credentials, or
|
|
71
|
+
commit files. The response is covered by `schemas/explore-output.schema.json`:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{"ok":true,"mode":"explore","schemaVersion":1,"runnerVersion":"0.1.8","protocolVersion":"2026-04-28","goal":"find a stable login smoke","autonomous":false,"reviewRequired":true,"guardrails":["writes from existing trace evidence only","does not crawl the app","does not discover credentials or secrets","requires human review before commit"],"out":".zmr/discovered/login-smoke.json","traceDir":"traces/zmr-agent","sourceSnapshot":"traces/zmr-agent/artifacts/snapshot-2.json","name":"draft from login smoke","appId":"com.example.mobiletest","selectorCount":2,"stepCount":6,"replay":{"enabled":true,"eventCount":4,"stepCount":3,"skippedEventCount":1},"warnings":["draft requires human review before commit"],"validated":true,"validation":{"ok":true,"path":".zmr/discovered/login-smoke.json","name":"draft from login smoke","appId":"com.example.mobiletest","stepCount":6},"nextCommands":["zmr validate --json .zmr/discovered/login-smoke.json","zmr run .zmr/discovered/login-smoke.json --json --trace-dir traces/zmr-agent"]}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`zmr draft --from-trace <trace-dir> --out <scenario.json> --json` is the lower
|
|
78
|
+
level scenario-writing primitive used by `discover`. It reads the
|
|
79
|
+
trace manifest and latest semantic snapshot artifact, then writes a reviewable
|
|
80
|
+
surface-smoke scenario. The generated scenario starts with `launch` and
|
|
81
|
+
`snapshot`, then adds `assertVisible` steps for a small set of visible stable
|
|
82
|
+
selectors. It does not start a device session, crawl the app, tap controls, type
|
|
83
|
+
into fields, or commit files. The response is covered by
|
|
84
|
+
`schemas/draft-output.schema.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{"ok":true,"mode":"draft","schemaVersion":1,"runnerVersion":"0.1.8","protocolVersion":"2026-04-28","out":".zmr/discovered/surface-smoke.json","traceDir":"traces/zmr-agent","sourceSnapshot":"traces/zmr-agent/artifacts/snapshot-2.json","name":"draft from login smoke","appId":"com.example.mobiletest","selectorCount":2,"stepCount":4,"replay":{"enabled":false,"eventCount":0,"stepCount":0,"skippedEventCount":0},"warnings":["draft requires human review before commit"],"nextCommands":["zmr validate --json .zmr/discovered/surface-smoke.json","zmr run .zmr/discovered/surface-smoke.json --json --trace-dir traces/zmr-agent"]}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`zmr draft --include-actions` additionally parses `events.jsonl` and prepends
|
|
91
|
+
successful supported replay steps before the final snapshot assertions. Replay
|
|
92
|
+
drafts currently include app launch, deep links, selector taps, selector text
|
|
93
|
+
entry, selector erase, selector/timeout-preserving visible/not-visible waits,
|
|
94
|
+
matched wait-any events as `waitVisible`, back, keyboard hiding,
|
|
95
|
+
coordinate-complete swipes, direction/timeout-preserving selector scrolls, and
|
|
96
|
+
selector/timeout-preserving `assertVisible` and `assertNotVisible`,
|
|
97
|
+
`assertNoneVisible` selector arrays plus timed `assertHealthy` checks. Events
|
|
98
|
+
without enough replay data, failed events, diagnostics, underspecified swipes,
|
|
99
|
+
and control events are skipped with warnings rather than guessed. Text entry
|
|
100
|
+
events redacted from the trace are skipped.
|
|
101
|
+
The `replay` object in draft and discover JSON reports whether action replay
|
|
102
|
+
was enabled, how many trace action events were considered, how many replay
|
|
103
|
+
steps were generated, and how many events were skipped.
|
|
104
|
+
|
|
39
105
|
`zmr-release-readiness --json` checks one or more repeated app/device evidence
|
|
40
106
|
files and emits a machine-readable readiness summary. The output is covered by
|
|
41
107
|
`schemas/release-readiness-output.schema.json`:
|
|
42
108
|
|
|
43
109
|
```json
|
|
44
|
-
{"ok":false,"target":"production","status":"blocked","evidence":"traces/zmr-pilots/evidence.jsonl","evidenceFiles":["traces/zmr-pilots/evidence.jsonl"],"passed":["Android emulator
|
|
110
|
+
{"ok":false,"target":"production","status":"blocked","evidence":"traces/zmr-pilots/evidence.jsonl","evidenceFiles":["traces/zmr-pilots/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","agent workflow smoke"],"failed":[],"planned":[],"missing":["physical iOS readiness","Android hardware pilot","iOS simulator hardware pilot","iOS physical hardware pilot"],"insufficient":[],"blocked":["physical iOS readiness","Android hardware pilot","iOS simulator hardware pilot","iOS physical hardware pilot"],"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":"agent workflow smoke","status":"satisfied","evidenceName":"local release gate"},{"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"}],"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"]}],"recommendedWording":"Do not publish the production claim yet. Missing evidence: physical iOS readiness, Android hardware pilot, iOS simulator hardware pilot, iOS physical hardware pilot.","claimLimitations":["missing evidence"]}
|
|
45
111
|
```
|
|
46
112
|
|
|
47
113
|
Zero-dependency TypeScript, standard-library Python, standard-library Go, and
|
|
@@ -54,6 +120,12 @@ Rust reference clients live under `clients/typescript/`, `clients/python/`,
|
|
|
54
120
|
|
|
55
121
|
`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
122
|
|
|
123
|
+
`zmr report <trace-or-benchmark-dir> --out <report.html> --junit <report.xml>`
|
|
124
|
+
writes the local HTML report and a CI-friendly JUnit XML file. For trace
|
|
125
|
+
directories, the XML contains one testcase for the scenario. For benchmark
|
|
126
|
+
directories with `results.jsonl`, the XML contains one testcase per benchmark
|
|
127
|
+
row.
|
|
128
|
+
|
|
57
129
|
`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
130
|
|
|
59
131
|
`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.
|
|
@@ -81,14 +153,23 @@ Clients should read the last `scenario.end` event as the authoritative trace out
|
|
|
81
153
|
scenario completes. For traced runs it mirrors the authoritative `trace.json`
|
|
82
154
|
terminal fields, including trace paths, event/snapshot counts, failed step, and
|
|
83
155
|
stable error name. Traced summaries also include `nextCommands` so agents can
|
|
84
|
-
immediately render
|
|
85
|
-
|
|
86
|
-
summary. The response is
|
|
156
|
+
immediately render HTML and JUnit reports, explain the failure, generate a
|
|
157
|
+
reviewable scenario from the trace, or export a redacted trace bundle. Failed
|
|
158
|
+
scenarios still exit non-zero after writing the JSON summary. The response is
|
|
159
|
+
covered by `schemas/run-output.schema.json`:
|
|
87
160
|
|
|
88
161
|
```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"]}
|
|
162
|
+
{"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 --junit traces/login-smoke/junit.xml","zmr explain traces/login-smoke --json","zmr discover --from-trace traces/login-smoke --out .zmr/discovered/replay-smoke.json --include-actions --validate --force --json","zmr export traces/login-smoke --out traces/login-smoke.zmrtrace --redact"]}
|
|
90
163
|
```
|
|
91
164
|
|
|
165
|
+
`zmr run <scenario.json> --trace-dir <trace-dir> --discover-out
|
|
166
|
+
<scenario.json> --json` runs the same trace-backed discover engine before
|
|
167
|
+
printing the run summary. The response includes `discovery` with the
|
|
168
|
+
`zmr discover --json` payload, including validation results and next commands.
|
|
169
|
+
If discovery cannot run, the run summary remains authoritative and includes
|
|
170
|
+
`discoveryError` with the stable error name. `--discover-out` requires an
|
|
171
|
+
effective `--trace-dir`, either passed directly or resolved from `.zmr/config.json`.
|
|
172
|
+
|
|
92
173
|
When a run captures useful artifacts but a non-terminal enrichment fails, such
|
|
93
174
|
as an iOS screenshot succeeding while XCTest hierarchy extraction fails, the
|
|
94
175
|
terminal status is `partial` and `partialFailureCount` is greater than zero.
|
|
@@ -114,12 +195,12 @@ The text summary includes the terminal status, failed step, stable error, last d
|
|
|
114
195
|
|
|
115
196
|
`zmr explain <trace-dir> --json` or `zmr explain --json <trace-dir>` returns
|
|
116
197
|
the same failure triage fields in a stable machine-readable shape for agents
|
|
117
|
-
and CI. It also includes `traceDir` and `nextCommands` for rendering
|
|
118
|
-
|
|
198
|
+
and CI. It also includes `traceDir` and `nextCommands` for rendering HTML and
|
|
199
|
+
JUnit reports or exporting a redacted bundle. The response is covered by
|
|
119
200
|
`schemas/explain-output.schema.json`:
|
|
120
201
|
|
|
121
202
|
```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"]}
|
|
203
|
+
{"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 --junit traces/login-smoke/junit.xml","zmr export traces/login-smoke --out traces/login-smoke.zmrtrace --redact"]}
|
|
123
204
|
```
|
|
124
205
|
|
|
125
206
|
For partial visual captures, `diagnostic.kind` is
|
|
@@ -133,7 +214,7 @@ installers, setup scripts, and generated clients. The response is covered by
|
|
|
133
214
|
`schemas/version-output.schema.json`:
|
|
134
215
|
|
|
135
216
|
```json
|
|
136
|
-
{"name":"zmr","version":"0.1.
|
|
217
|
+
{"name":"zmr","version":"0.1.8","protocolVersion":"2026-04-28","minimumCompatibleProtocolVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"}
|
|
137
218
|
```
|
|
138
219
|
|
|
139
220
|
## Capabilities Output Contract
|
|
@@ -145,7 +226,7 @@ and method inventory for JSON-RPC clients. The result object is covered by
|
|
|
145
226
|
iOS simulator, or physical iOS workflows are available.
|
|
146
227
|
|
|
147
228
|
```json
|
|
148
|
-
{"name":"zmr","version":"0.1.
|
|
229
|
+
{"name":"zmr","version":"0.1.8","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","scenario.validate","trace.events","trace.explore","trace.discover","trace.explain","trace.export"]}
|
|
149
230
|
```
|
|
150
231
|
|
|
151
232
|
## Doctor Output Contract
|
|
@@ -307,7 +388,11 @@ zmr mcp --config .zmr/config.json --trace-dir traces/mcp-agent-session
|
|
|
307
388
|
- `assert.visible`
|
|
308
389
|
- `assert.notVisible`
|
|
309
390
|
- `assert.healthy`
|
|
391
|
+
- `scenario.validate`
|
|
310
392
|
- `trace.events`
|
|
393
|
+
- `trace.explore`
|
|
394
|
+
- `trace.discover`
|
|
395
|
+
- `trace.explain`
|
|
311
396
|
- `trace.export`
|
|
312
397
|
|
|
313
398
|
`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.
|
|
@@ -347,7 +432,7 @@ Request:
|
|
|
347
432
|
Response:
|
|
348
433
|
|
|
349
434
|
```json
|
|
350
|
-
{"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.1.
|
|
435
|
+
{"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.1.8","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","scenario.validate","trace.events","trace.explore","trace.discover","trace.explain","trace.export"]}}
|
|
351
436
|
```
|
|
352
437
|
|
|
353
438
|
### `trace.events`
|
|
@@ -373,6 +458,90 @@ Response:
|
|
|
373
458
|
server-side event counter; `nextSeq` is the last returned event and can be
|
|
374
459
|
passed back as `afterSeq`.
|
|
375
460
|
|
|
461
|
+
### `trace.explain`
|
|
462
|
+
|
|
463
|
+
Summarizes the active live trace with the same JSON shape as
|
|
464
|
+
`zmr explain <trace-dir> --json`. Use it when an agent needs the current trace
|
|
465
|
+
status, failure diagnostic, last event, and next commands without leaving the
|
|
466
|
+
JSON-RPC session.
|
|
467
|
+
|
|
468
|
+
Request:
|
|
469
|
+
|
|
470
|
+
```json
|
|
471
|
+
{"jsonrpc":"2.0","id":26,"method":"trace.explain","params":{}}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Response:
|
|
475
|
+
|
|
476
|
+
```json
|
|
477
|
+
{"jsonrpc":"2.0","id":26,"result":{"ok":true,"traceDir":"traces/agent-session","scenario":"json-rpc session","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","visibleTexts":["Sign in","Try again"]},"lastEvent":"scenario.end","nextCommands":["zmr report traces/agent-session --out traces/agent-session/report.html --junit traces/agent-session/junit.xml","zmr export traces/agent-session --out traces/agent-session.zmrtrace --redact"]}}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Without `--trace-dir`, it returns a result with `traceDir: null` and a message
|
|
481
|
+
explaining how to enable live trace explanation.
|
|
482
|
+
|
|
483
|
+
### `scenario.validate`
|
|
484
|
+
|
|
485
|
+
Validates a ZMR scenario file and returns the same structured payload as
|
|
486
|
+
`zmr validate --json`, including field paths and source locations for invalid
|
|
487
|
+
files.
|
|
488
|
+
|
|
489
|
+
Request:
|
|
490
|
+
|
|
491
|
+
```json
|
|
492
|
+
{"jsonrpc":"2.0","id":23,"method":"scenario.validate","params":{"path":".zmr/discovered/agent-smoke.json"}}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Response:
|
|
496
|
+
|
|
497
|
+
```json
|
|
498
|
+
{"jsonrpc":"2.0","id":23,"result":{"ok":true,"path":".zmr/discovered/agent-smoke.json","name":"agent smoke","appId":"com.example.mobiletest","stepCount":4,"nextCommands":["zmr run .zmr/discovered/agent-smoke.json --json --trace-dir traces/zmr-run"]}}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### `trace.discover`
|
|
502
|
+
|
|
503
|
+
Generates a reviewable scenario candidate from the active live trace. It uses
|
|
504
|
+
the same engine as `zmr discover --from-trace`, so it can include supported
|
|
505
|
+
successful replay actions, add final snapshot assertions, and optionally
|
|
506
|
+
validate the generated scenario before returning.
|
|
507
|
+
|
|
508
|
+
Request:
|
|
509
|
+
|
|
510
|
+
```json
|
|
511
|
+
{"jsonrpc":"2.0","id":25,"method":"trace.discover","params":{"out":".zmr/discovered/agent-smoke.json","includeActions":true,"validate":true,"force":true,"name":"agent smoke"}}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Response:
|
|
515
|
+
|
|
516
|
+
```json
|
|
517
|
+
{"jsonrpc":"2.0","id":25,"result":{"ok":true,"mode":"discover","schemaVersion":1,"runnerVersion":"0.1.8","protocolVersion":"2026-04-28","out":".zmr/discovered/agent-smoke.json","traceDir":"traces/agent-session","sourceSnapshot":"traces/agent-session/artifacts/snapshot-1.json","name":"agent smoke","appId":"com.example.mobiletest","selectorCount":1,"stepCount":4,"replay":{"enabled":true,"eventCount":2,"stepCount":1,"skippedEventCount":1},"warnings":["draft requires human review before commit"],"validated":true,"validation":{"ok":true,"path":".zmr/discovered/agent-smoke.json","name":"agent smoke","appId":"com.example.mobiletest","stepCount":4},"nextCommands":["zmr validate --json .zmr/discovered/agent-smoke.json","zmr run .zmr/discovered/agent-smoke.json --json --trace-dir traces/agent-session"]}}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Without `--trace-dir`, it returns `ok: false` with `traceDir: null`. Generated
|
|
521
|
+
scenarios remain review-first: ZMR does not crawl, invent credentials, or
|
|
522
|
+
commit tests.
|
|
523
|
+
|
|
524
|
+
### `trace.explore`
|
|
525
|
+
|
|
526
|
+
Generates a review-required scenario candidate from the active live trace for a
|
|
527
|
+
stated goal. It uses the same trace-backed engine as `zmr explore --from-trace`:
|
|
528
|
+
the runner writes from existing trace evidence only, does not crawl the app,
|
|
529
|
+
does not discover credentials or secrets, and returns `reviewRequired: true`.
|
|
530
|
+
|
|
531
|
+
Request:
|
|
532
|
+
|
|
533
|
+
```json
|
|
534
|
+
{"jsonrpc":"2.0","id":27,"method":"trace.explore","params":{"out":".zmr/discovered/agent-goal.json","goal":"find a stable login smoke","includeActions":true,"validate":true,"force":true,"name":"agent goal smoke"}}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Response:
|
|
538
|
+
|
|
539
|
+
```json
|
|
540
|
+
{"jsonrpc":"2.0","id":27,"result":{"ok":true,"mode":"explore","schemaVersion":1,"runnerVersion":"0.1.8","protocolVersion":"2026-04-28","out":".zmr/discovered/agent-goal.json","traceDir":"traces/agent-session","sourceSnapshot":"traces/agent-session/artifacts/snapshot-1.json","name":"agent goal smoke","appId":"com.example.mobiletest","selectorCount":1,"stepCount":4,"replay":{"enabled":true,"eventCount":2,"stepCount":1,"skippedEventCount":1},"warnings":["draft requires human review before commit"],"validated":true,"validation":{"ok":true,"path":".zmr/discovered/agent-goal.json","name":"agent goal smoke","appId":"com.example.mobiletest","stepCount":4},"nextCommands":["zmr validate --json .zmr/discovered/agent-goal.json","zmr run .zmr/discovered/agent-goal.json --json --trace-dir traces/agent-session"],"goal":"find a stable login smoke","autonomous":false,"reviewRequired":true,"guardrails":["writes from existing trace evidence only","does not crawl the app","does not discover credentials or secrets","requires human review before commit"]}}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Without `--trace-dir`, it returns `ok: false` with `traceDir: null`.
|
|
544
|
+
|
|
376
545
|
### `device.list`
|
|
377
546
|
|
|
378
547
|
Response:
|
|
@@ -432,10 +601,14 @@ exposes tool calls for agent runtimes that prefer MCP over raw JSON-RPC.
|
|
|
432
601
|
zmr mcp --config .zmr/config.json --trace-dir traces/mcp-agent
|
|
433
602
|
```
|
|
434
603
|
|
|
435
|
-
Core tools are `snapshot`, `semantic_snapshot`, `
|
|
436
|
-
`
|
|
437
|
-
|
|
438
|
-
|
|
604
|
+
Core tools are `snapshot`, `semantic_snapshot`, `install_app`, `launch_app`,
|
|
605
|
+
`stop_app`, `clear_state`, `tap`, `type`, `erase_text`, `hide_keyboard`,
|
|
606
|
+
`swipe`, `press_back`, `open_link`, `wait_visible`, `wait_not_visible`,
|
|
607
|
+
`wait_any`, `scroll_until_visible`, `assert_visible`, `assert_not_visible`,
|
|
608
|
+
`assert_healthy`, `scenario_validate`, `trace_events`, `trace_explain`,
|
|
609
|
+
`trace_explore`, `trace_discover`, and `trace_export`. The MCP protocol handshake is
|
|
610
|
+
intentionally standard, while the tool names and payloads are versioned with
|
|
611
|
+
the ZMR runner and public schemas.
|
|
439
612
|
|
|
440
613
|
### `ui.tap`
|
|
441
614
|
|
|
@@ -466,6 +639,32 @@ Request:
|
|
|
466
639
|
{"jsonrpc":"2.0","id":7,"method":"assert.notVisible","params":{"selector":{"textContains":"Loading"},"timeoutMs":5000}}
|
|
467
640
|
```
|
|
468
641
|
|
|
642
|
+
### MCP `trace_discover`
|
|
643
|
+
|
|
644
|
+
`trace_discover` mirrors JSON-RPC `trace.discover` for MCP agents. Required
|
|
645
|
+
argument: `out`. Optional arguments: `includeActions`, `validate`, `force`,
|
|
646
|
+
`name`, and `appId`. The tool response text is the same discover JSON payload.
|
|
647
|
+
|
|
648
|
+
### MCP `trace_explore`
|
|
649
|
+
|
|
650
|
+
`trace_explore` mirrors JSON-RPC `trace.explore` for MCP agents. Required
|
|
651
|
+
arguments: `out` and `goal`. Optional arguments: `includeActions`, `validate`,
|
|
652
|
+
`force`, `name`, and `appId`. The tool response text is the same guarded
|
|
653
|
+
explore JSON payload, including `autonomous:false`, `reviewRequired:true`, and
|
|
654
|
+
`guardrails`.
|
|
655
|
+
|
|
656
|
+
### MCP `trace_explain`
|
|
657
|
+
|
|
658
|
+
`trace_explain` mirrors JSON-RPC `trace.explain` for MCP agents. It takes no
|
|
659
|
+
arguments. The tool response text is the same explanation JSON payload returned
|
|
660
|
+
by `zmr explain --json`.
|
|
661
|
+
|
|
662
|
+
### MCP `scenario_validate`
|
|
663
|
+
|
|
664
|
+
`scenario_validate` mirrors JSON-RPC `scenario.validate` for MCP agents.
|
|
665
|
+
Required argument: `path`. The tool response text is the same validation JSON
|
|
666
|
+
payload returned by `zmr validate --json`.
|
|
667
|
+
|
|
469
668
|
### `trace.export`
|
|
470
669
|
|
|
471
670
|
When `zmr serve` is started with `--trace-dir`, live JSON-RPC sessions use the
|
|
@@ -71,6 +71,9 @@ The importer supports the common subset needed for smoke scenarios:
|
|
|
71
71
|
generated JSON before committing it; native `.zmr/*.json` scenarios remain the
|
|
72
72
|
runtime contract for agents and CI.
|
|
73
73
|
|
|
74
|
+
`assertVisible` and `assertNotVisible` accept the same `timeoutMs` field as
|
|
75
|
+
waits when a scenario needs assertion-specific timing.
|
|
76
|
+
|
|
74
77
|
## Example Templates
|
|
75
78
|
|
|
76
79
|
The example directory includes templates for common app flows:
|
package/docs/troubleshooting.md
CHANGED
|
@@ -228,7 +228,7 @@ When a run fails, do not rerun blindly. Inspect the recorded failure:
|
|
|
228
228
|
|
|
229
229
|
```bash
|
|
230
230
|
zmr explain traces/zmr-android
|
|
231
|
-
zmr report traces/zmr-android --out traces/zmr-android/report.html
|
|
231
|
+
zmr report traces/zmr-android --out traces/zmr-android/report.html --junit traces/zmr-android/junit.xml
|
|
232
232
|
```
|
|
233
233
|
|
|
234
234
|
Timeout diagnostics include the active package/activity, visible text, hidden or
|
package/npm/agents.mjs
CHANGED
|
@@ -47,6 +47,7 @@ export function nextStepCommands(config, { android = true, ios = true, packageSc
|
|
|
47
47
|
export function agentInstructions(appId, { android = true, ios = true, packageScripts = false, scripts = {} } = {}) {
|
|
48
48
|
const command = (name, fallback) => scripts[name] ?? fallback;
|
|
49
49
|
const appCommand = (scriptName, directCommand) => packageScripts ? `npm run ${scriptName}` : directCommand;
|
|
50
|
+
const inspectCommand = "zmr inspect --json --dir .";
|
|
50
51
|
const doctorCommand = appCommand("zmr:doctor", command("doctor", "zmr doctor --strict --json --config .zmr/config.json"));
|
|
51
52
|
const schemasCommand = appCommand("zmr:schemas", command("schemas", "zmr schemas --json"));
|
|
52
53
|
const validateDirectCommand = command("validate", validateCommand({ android, ios }));
|
|
@@ -56,6 +57,7 @@ export function agentInstructions(appId, { android = true, ios = true, packageSc
|
|
|
56
57
|
const explainCommand = appCommand("zmr:explain", command("explain", "zmr explain traces/zmr-agent --json"));
|
|
57
58
|
const exportCommand = appCommand("zmr:export", command("exportTrace", "zmr export traces/zmr-agent --out traces/zmr-agent-redacted.zmrtrace --redact"));
|
|
58
59
|
const setupChecks = [
|
|
60
|
+
inspectCommand,
|
|
59
61
|
doctorCommand,
|
|
60
62
|
schemasCommand,
|
|
61
63
|
validateAppCommand,
|
|
@@ -119,6 +121,7 @@ export function agentInstructions(appId, { android = true, ios = true, packageSc
|
|
|
119
121
|
const appSectionTitle = packageScripts ? "App Scripts" : "App Commands";
|
|
120
122
|
const appSectionCommands = nextStepCommands({ scripts: nextStepScripts }, { android, ios, packageScripts })
|
|
121
123
|
.map((step) => step.command);
|
|
124
|
+
appSectionCommands.unshift(inspectCommand);
|
|
122
125
|
const releaseClaims = android && ios
|
|
123
126
|
? `\`\`\`bash
|
|
124
127
|
${readinessCommandText}
|
|
@@ -148,6 +151,19 @@ ${mcpCommand}
|
|
|
148
151
|
|
|
149
152
|
Use \`semantic_snapshot\` before choosing tap or type actions. Prefer selectors from accessibility identifiers, resource ids, labels, or exact text before coordinates. Export redacted traces before sharing artifacts.
|
|
150
153
|
|
|
154
|
+
## Discover From Trace
|
|
155
|
+
|
|
156
|
+
\`\`\`bash
|
|
157
|
+
zmr explore --from-trace traces/zmr-agent --out .zmr/discovered/login-smoke.json --goal "find a stable login smoke" --include-actions --validate --json
|
|
158
|
+
zmr discover --from-trace traces/zmr-agent --out .zmr/discovered/replay-smoke.json --include-actions --validate --json
|
|
159
|
+
zmr draft --from-trace traces/zmr-agent --out .zmr/discovered/surface-smoke.json --json
|
|
160
|
+
zmr validate --json .zmr/discovered/surface-smoke.json
|
|
161
|
+
zmr draft --from-trace traces/zmr-agent --out .zmr/discovered/replay-smoke.json --include-actions --json
|
|
162
|
+
zmr validate --json .zmr/discovered/replay-smoke.json
|
|
163
|
+
\`\`\`
|
|
164
|
+
|
|
165
|
+
Prefer \`zmr explore\` for CLI agent loops when the goal should travel with the generated candidate. Its JSON includes \`autonomous:false\`, \`reviewRequired:true\`, \`guardrails\`, replay coverage, validation, and deterministic next commands. Treat discover output as a lower-level reviewable starting point. It writes from trace evidence and validates the generated file when \`--validate\` is present, but it does not crawl, invent missing actions, discover credentials, or commit tests. Treat draft output as a reviewable starting point when using the lower-level split workflow. The default draft contains only \`launch\`, \`snapshot\`, and conservative \`assertVisible\` checks. Use \`--include-actions\` only when the trace came from a reviewed agent session; unsupported events are skipped with warnings instead of guessed. Do not commit a discovered or drafted scenario until a human has reviewed and rerun it.
|
|
166
|
+
|
|
151
167
|
## Failure Triage
|
|
152
168
|
|
|
153
169
|
\`\`\`bash
|
package/npm/build-zmr.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
-
const out = path.join(root, "zig-out", "bin", process.platform === "win32" ? "zmr.exe" : "zmr");
|
|
8
|
+
const out = process.env.ZMR_BUILD_OUT || path.join(root, "zig-out", "bin", process.platform === "win32" ? "zmr.exe" : "zmr");
|
|
9
9
|
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
10
10
|
|
|
11
11
|
const args = ["build-exe", "src/main.zig", "-O", "ReleaseSafe", `-femit-bin=${out}`];
|
package/npm/commands.mjs
CHANGED
|
@@ -13,8 +13,8 @@ export function smokeRunCommand({ platform, androidShim = "", iosShim = "" }) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function smokeReportCommand({ platform }) {
|
|
16
|
-
if (platform === "android") return "zmr
|
|
17
|
-
if (platform === "ios") return "zmr
|
|
16
|
+
if (platform === "android") return reportCommand("zmr", "traces/zmr-android");
|
|
17
|
+
if (platform === "ios") return reportCommand("zmr", "traces/zmr-ios");
|
|
18
18
|
throw new Error(`unsupported smoke report platform: ${platform}`);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -71,7 +71,7 @@ export function reliabilityCommand({ scenario, platform = "", device, appId, xcr
|
|
|
71
71
|
"--max-p95-ms",
|
|
72
72
|
String(maxP95Ms),
|
|
73
73
|
);
|
|
74
|
-
return `export ZMR_BIN="\${ZMR_BIN:-zmr}"; ${shellJoin(args)} && "$ZMR_BIN"
|
|
74
|
+
return `export ZMR_BIN="\${ZMR_BIN:-zmr}"; ${shellJoin(args)} && ${reportCommand('"$ZMR_BIN"', traceRoot)}`;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
export function devClientRunCommand({ platform }) {
|
|
@@ -85,11 +85,15 @@ export function devClientRunCommand({ platform }) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export function devClientReportCommand({ platform }) {
|
|
88
|
-
if (platform === "android") return "zmr
|
|
89
|
-
if (platform === "ios") return "zmr
|
|
88
|
+
if (platform === "android") return reportCommand("zmr", "traces/zmr-android-dev-client");
|
|
89
|
+
if (platform === "ios") return reportCommand("zmr", "traces/zmr-ios-dev-client");
|
|
90
90
|
throw new Error(`unsupported dev-client report platform: ${platform}`);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
function reportCommand(bin, traceRoot) {
|
|
94
|
+
return `${bin} report ${shellQuote(traceRoot)} --out ${shellQuote(`${traceRoot}/report.html`)} --junit ${shellQuote(`${traceRoot}/junit.xml`)}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
export function shellJoin(args) {
|
|
94
98
|
return args.map((arg, index) => {
|
|
95
99
|
if (index === 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(arg)) return arg;
|
package/npm/postinstall.mjs
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
3
4
|
import { resolveBinary } from "./index.mjs";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
7
|
+
const binary = resolveBinary();
|
|
8
|
+
|
|
9
|
+
if (binary && binaryMatchesPackageVersion(binary, pkg.version)) process.exit(0);
|
|
10
|
+
if (binary && process.env.ZMR_BIN) {
|
|
11
|
+
console.warn(`zeno-mobile-runner: ZMR_BIN points to a zmr binary that does not report package version ${pkg.version}: ${binary}`);
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
6
14
|
if (process.env.ZMR_SKIP_POSTINSTALL_BUILD === "1") process.exit(0);
|
|
7
15
|
|
|
8
16
|
const hasZig = spawnSync("zig", ["version"], { stdio: "ignore" }).status === 0;
|
|
9
17
|
if (!hasZig) {
|
|
10
|
-
|
|
18
|
+
if (binary) {
|
|
19
|
+
console.warn(`zeno-mobile-runner: prebuilt zmr binary does not report package version ${pkg.version}: ${binary}`);
|
|
20
|
+
} else {
|
|
21
|
+
console.warn("zeno-mobile-runner: no prebuilt zmr binary found and Zig is not installed.");
|
|
22
|
+
}
|
|
11
23
|
console.warn("zeno-mobile-runner: install a release package with prebuilds, install Zig and run `npm run build:zmr`, or set ZMR_BIN.");
|
|
12
24
|
process.exit(0);
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
const result = spawnSync(process.execPath, [new URL("./build-zmr.mjs", import.meta.url).pathname], {
|
|
16
28
|
stdio: "inherit",
|
|
29
|
+
env: {
|
|
30
|
+
...process.env,
|
|
31
|
+
...(binary ? { ZMR_BUILD_OUT: binary } : {}),
|
|
32
|
+
},
|
|
17
33
|
});
|
|
18
34
|
|
|
19
35
|
if (result.status !== 0) {
|
|
20
36
|
console.warn("zeno-mobile-runner: postinstall build failed; set ZMR_BIN or run `npm run build:zmr` after fixing Zig.");
|
|
21
37
|
}
|
|
38
|
+
|
|
39
|
+
function binaryMatchesPackageVersion(binaryPath, expectedVersion) {
|
|
40
|
+
const result = spawnSync(binaryPath, ["version", "--json"], { encoding: "utf8" });
|
|
41
|
+
if (result.status !== 0) return false;
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(result.stdout).version === expectedVersion;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
9
|
+
const exe = process.platform === "win32" ? "zmr.exe" : "zmr";
|
|
10
|
+
const hostPrebuild = path.join(root, "prebuilds", `${process.platform}-${process.arch}`, exe);
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(hostPrebuild)) {
|
|
13
|
+
fail(`missing host prebuild for ${process.platform}-${process.arch}: ${hostPrebuild}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const version = spawnSync(hostPrebuild, ["version", "--json"], { encoding: "utf8" });
|
|
17
|
+
if (version.status !== 0) {
|
|
18
|
+
fail(`host prebuild is not runnable: ${hostPrebuild}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let actual;
|
|
22
|
+
try {
|
|
23
|
+
actual = JSON.parse(version.stdout).version;
|
|
24
|
+
} catch {
|
|
25
|
+
fail(`host prebuild did not return valid version JSON: ${hostPrebuild}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (actual !== pkg.version) {
|
|
29
|
+
fail(`host prebuild reports ${actual}, expected ${pkg.version}: ${hostPrebuild}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fail(message) {
|
|
33
|
+
console.error(`zeno-mobile-runner: ${message}`);
|
|
34
|
+
console.error("zeno-mobile-runner: run `npm run pack:npm` before publishing so prebuilds match package.json.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zeno-mobile-runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Agent-native mobile app test runner for React Native, Expo, Flutter, and native Android/iOS.",
|
|
5
5
|
"main": "npm/index.mjs",
|
|
6
6
|
"repository": {
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
],
|
|
93
93
|
"scripts": {
|
|
94
94
|
"postinstall": "node npm/postinstall.mjs",
|
|
95
|
+
"prepublishOnly": "node npm/verify-publish.mjs",
|
|
95
96
|
"build:zmr": "node npm/build-zmr.mjs",
|
|
96
97
|
"pack:npm": "bash scripts/build-npm-package.sh",
|
|
97
98
|
"zmr:demo": "node npm/zmr.mjs validate examples/demo-fake.json",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/schemas/README.md
CHANGED
|
@@ -19,6 +19,10 @@ This directory contains draft 2020-12 JSON Schemas for public ZMR file and proto
|
|
|
19
19
|
- `capabilities-output.schema.json`: machine-readable `runner.capabilities` JSON-RPC result for protocol, platform support, transport, and method discovery
|
|
20
20
|
- `explain-output.schema.json`: machine-readable `zmr explain --json` failure triage output for agents and CI
|
|
21
21
|
- `run-output.schema.json`: machine-readable `zmr run --json` terminal run summary output
|
|
22
|
+
- `inspect-output.schema.json`: machine-readable `zmr inspect --json` app and agent handoff output
|
|
23
|
+
- `discover-output.schema.json`: machine-readable `zmr discover --json` trace-backed scenario discovery output with replay coverage metadata
|
|
24
|
+
- `explore-output.schema.json`: machine-readable `zmr explore --json` review-first trace exploration output with goal and guardrail metadata
|
|
25
|
+
- `draft-output.schema.json`: machine-readable `zmr draft --json` trace-backed scenario draft output with replay coverage metadata
|
|
22
26
|
- `release-manifest.schema.json`: machine-readable `RELEASE_MANIFEST.json` emitted with release archives
|
|
23
27
|
- `release-readiness-output.schema.json`: machine-readable `zmr-release-readiness --json` release evidence gate output
|
|
24
28
|
- `schemas-output.schema.json`: machine-readable `zmr schemas --json` index of public schema names, paths, ids, and descriptions
|