zeno-mobile-runner 0.1.3 → 0.2.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +192 -2
  2. package/FEATURES.md +50 -7
  3. package/README.md +168 -120
  4. package/build.zig.zon +3 -3
  5. package/clients/README.md +60 -3
  6. package/clients/go/README.md +12 -0
  7. package/clients/go/zmr/client.go +142 -0
  8. package/clients/kotlin/README.md +18 -1
  9. package/clients/kotlin/build.gradle.kts +1 -1
  10. package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +76 -1
  11. package/clients/python/README.md +19 -0
  12. package/clients/python/pyproject.toml +1 -1
  13. package/clients/python/zmr_client.py +33 -0
  14. package/clients/rust/Cargo.lock +1 -1
  15. package/clients/rust/Cargo.toml +1 -1
  16. package/clients/rust/README.md +25 -1
  17. package/clients/rust/src/lib.rs +201 -0
  18. package/clients/swift/README.md +18 -0
  19. package/clients/swift/Sources/ZMRClient/ZMRClient.swift +82 -0
  20. package/clients/typescript/README.md +16 -0
  21. package/clients/typescript/index.d.ts +12 -0
  22. package/clients/typescript/index.mjs +16 -0
  23. package/clients/typescript/package.json +1 -1
  24. package/docs/agent-discovery.md +151 -22
  25. package/docs/ai-agents.md +99 -11
  26. package/docs/benchmarking.md +49 -3
  27. package/docs/benchmarks/2026-06-09-android-workflow.md +73 -0
  28. package/docs/benchmarks/2026-06-09-android-workflow.results.jsonl +20 -0
  29. package/docs/benchmarks/2026-06-09-framework-baseline-status.md +32 -0
  30. package/docs/benchmarks/2026-06-09-ios-appium-comparison.md +115 -0
  31. package/docs/benchmarks/2026-06-09-ios-appium-comparison.results.jsonl +40 -0
  32. package/docs/benchmarks/2026-06-09-ios-demo.md +90 -0
  33. package/docs/benchmarks/2026-06-09-ios-demo.results.jsonl +20 -0
  34. package/docs/benchmarks/2026-06-09-ios-maestro-comparison.md +128 -0
  35. package/docs/benchmarks/2026-06-09-ios-maestro-comparison.results.jsonl +40 -0
  36. package/docs/benchmarks/2026-06-09-ios-workflow-comparison.md +143 -0
  37. package/docs/benchmarks/2026-06-09-ios-workflow-comparison.results.jsonl +40 -0
  38. package/docs/benchmarks/2026-06-09-ios-xctest-floor.md +106 -0
  39. package/docs/benchmarks/2026-06-09-ios-xctest-floor.results.jsonl +40 -0
  40. package/docs/benchmarks/README.md +36 -0
  41. package/docs/benchmarks/benchmark-lab-v1.json +155 -0
  42. package/docs/benchmarks/benchmark-lab-v1.md +95 -0
  43. package/docs/clients.md +26 -6
  44. package/docs/demo.md +40 -1
  45. package/docs/expo-smoke.md +8 -8
  46. package/docs/frameworks.md +10 -0
  47. package/docs/install.md +3 -2
  48. package/docs/npm.md +100 -4
  49. package/docs/production-readiness.md +123 -0
  50. package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
  51. package/docs/protocol.md +215 -16
  52. package/docs/scenario-authoring.md +18 -0
  53. package/docs/trace-privacy.md +9 -0
  54. package/docs/troubleshooting.md +7 -1
  55. package/examples/android-workflow.json +79 -0
  56. package/examples/ios-shim-workflow.json +79 -0
  57. package/examples/react-native-expo-workflow.json +75 -0
  58. package/npm/agents.mjs +16 -0
  59. package/npm/commands.mjs +9 -5
  60. package/package.json +6 -1
  61. package/prebuilds/darwin-arm64/zmr +0 -0
  62. package/prebuilds/darwin-x64/zmr +0 -0
  63. package/prebuilds/linux-arm64/zmr +0 -0
  64. package/prebuilds/linux-x64/zmr +0 -0
  65. package/schemas/README.md +4 -0
  66. package/schemas/discover-output.schema.json +83 -0
  67. package/schemas/draft-output.schema.json +58 -0
  68. package/schemas/explore-output.schema.json +94 -0
  69. package/schemas/inspect-output.schema.json +88 -0
  70. package/schemas/run-output.schema.json +2 -0
  71. package/scripts/benchmark-lab.py +253 -0
  72. package/scripts/create-android-demo-app.sh +324 -29
  73. package/scripts/create-ios-demo-app.sh +174 -7
  74. package/scripts/create-react-native-expo-demo-app.sh +727 -0
  75. package/scripts/demo.sh +3 -0
  76. package/scripts/install-ios-shim.sh +2 -2
  77. package/scripts/release-readiness.py +43 -0
  78. package/scripts/run-android-pilot.sh +35 -9
  79. package/scripts/run-ios-pilot.sh +11 -4
  80. package/shims/ios/ZMRShim.swift +10 -0
  81. package/shims/ios/ZMRShimUITestCase.swift +42 -0
  82. package/shims/ios/protocol.md +1 -0
  83. package/skills/zmr-mobile-testing/SKILL.md +28 -3
  84. package/src/cli_discover.zig +239 -0
  85. package/src/cli_draft.zig +924 -0
  86. package/src/cli_explore.zig +136 -0
  87. package/src/cli_import.zig +31 -15
  88. package/src/cli_inspect.zig +310 -0
  89. package/src/cli_output.zig +26 -2
  90. package/src/cli_run.zig +28 -0
  91. package/src/cli_trace.zig +45 -15
  92. package/src/cli_validate.zig +12 -6
  93. package/src/errors.zig +9 -0
  94. package/src/ios.zig +49 -12
  95. package/src/ios_shim.zig +36 -2
  96. package/src/json_rpc_methods.zig +85 -11
  97. package/src/json_rpc_params.zig +8 -0
  98. package/src/json_rpc_protocol.zig +1 -1
  99. package/src/json_rpc_trace.zig +112 -0
  100. package/src/main.zig +27 -2
  101. package/src/mcp.zig +209 -6
  102. package/src/mcp_protocol.zig +29 -1
  103. package/src/mcp_trace.zig +126 -4
  104. package/src/report.zig +186 -0
  105. package/src/runner.zig +26 -4
  106. package/src/runner_actions.zig +10 -0
  107. package/src/runner_diagnostics.zig +31 -1
  108. package/src/runner_events.zig +70 -7
  109. package/src/runner_native.zig +17 -1
  110. package/src/runner_waits.zig +82 -19
  111. package/src/scaffold.zig +28 -12
  112. package/src/scenario.zig +32 -4
  113. package/src/schema_registry.zig +4 -0
  114. package/src/version.zig +1 -1
  115. package/viewer/app.js +23 -3
package/README.md CHANGED
@@ -1,182 +1,230 @@
1
1
  # Zeno Mobile Runner
2
2
 
3
- > Agent-native mobile UI automation for React Native, Expo, Flutter, and native Android/iOS apps.
3
+ > The verification loop for AI coding agents building Expo, React Native,
4
+ > Flutter, and native Android/iOS apps.
4
5
 
5
6
  [![CI](https://github.com/johnmikel/zeno-mobile-runner/actions/workflows/ci.yml/badge.svg)](https://github.com/johnmikel/zeno-mobile-runner/actions/workflows/ci.yml)
6
7
  [![Release](https://img.shields.io/github/v/release/johnmikel/zeno-mobile-runner?include_prereleases)](https://github.com/johnmikel/zeno-mobile-runner/releases)
7
8
  [![npm](https://img.shields.io/npm/v/zeno-mobile-runner)](https://www.npmjs.com/package/zeno-mobile-runner)
8
9
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
10
 
10
- ZMR gives AI agents and test harnesses a typed mobile control plane. It can
11
- install and launch apps, observe the UI, choose an action, wait for the screen to
12
- settle, assert state, and export a replayable trace. The runner does not embed an
13
- LLM. Agents stay outside and use ZMR through CLI JSON, scenarios, JSON-RPC, MCP,
14
- or optional protocol clients.
11
+ Your coding agent can write mobile code, but it cannot see the phone. ZMR is
12
+ its eyes and hands: a typed mobile control plane that installs and launches
13
+ apps, observes the UI, taps and types, waits for the screen to settle, asserts
14
+ state, and exports a replayable trace as proof. The runner does not embed an
15
+ LLM. Agents stay outside and drive ZMR through MCP, JSON-RPC, CLI JSON, or
16
+ JSON scenarios.
17
+
18
+ ![ZMR trace viewer showing a passed iOS run with timeline, device screenshot, UI tree, and selector payload](docs/assets/viewer-hero.png)
19
+
20
+ <p align="center">
21
+ <img src="docs/assets/device-ios-demo.png" width="260" alt="iOS simulator screenshot captured by ZMR during a scenario run" />
22
+ &nbsp;&nbsp;
23
+ <img src="docs/assets/device-android-demo.png" width="260" alt="Android emulator screenshot captured by ZMR during a scenario run" />
24
+ </p>
25
+
26
+ <p align="center"><em>Real on-device screenshots from ZMR traces: the same demo flow
27
+ driven on an iOS simulator and an Android emulator.</em></p>
28
+
29
+ ## Why agents need this
30
+
31
+ - **Agents can't verify what they can't observe.** ZMR returns semantic UI
32
+ trees with stable selectors, screenshots, and typed action results an agent
33
+ can reason about — not raw pixels it has to guess at.
34
+ - **Evidence, not vibes.** Every session can write a deterministic trace:
35
+ events, screenshots, UI hierarchies, timings, assertion results, HTML and
36
+ JUnit reports, and a redacted shareable bundle.
37
+ - **Tests fall out for free.** After a live agent session, `zmr discover`
38
+ turns the trace into a reviewable JSON scenario that replays in CI without
39
+ an LLM in the loop.
40
+
41
+ ## How it works
42
+
43
+ ```mermaid
44
+ flowchart LR
45
+ A["AI coding agent<br/>Claude Code · Cursor · custom harness"]
46
+ subgraph zmr["ZMR — one small Zig binary"]
47
+ MCP["MCP server<br/><code>zmr mcp</code>"]
48
+ RPC["JSON-RPC stdio/TCP<br/><code>zmr serve</code>"]
49
+ CLI["CLI + JSON scenarios<br/><code>zmr run</code>"]
50
+ CORE["Core engine<br/>selectors · waits · assertions<br/>scenario runner · trace writer"]
51
+ MCP --> CORE
52
+ RPC --> CORE
53
+ CLI --> CORE
54
+ end
55
+ subgraph devices["Devices"]
56
+ AND["Android emulator/device<br/>ADB · UI Automator · optional shim"]
57
+ IOS["iOS simulator/device<br/>simctl · devicectl · XCTest shim"]
58
+ end
59
+ TRACE["Trace<br/>events.jsonl · screenshots · UI trees<br/>report.html · junit.xml · .zmrtrace"]
60
+ A -- "MCP tools" --> MCP
61
+ A -- "JSON-RPC" --> RPC
62
+ A -- "CLI JSON" --> CLI
63
+ CORE --> AND
64
+ CORE --> IOS
65
+ CORE --> TRACE
66
+ ```
67
+
68
+ No app instrumentation is required on Android. iOS selector actions use an
69
+ app-local XCTest shim that the wizard scaffolds. ZMR works below the
70
+ JavaScript/Dart layer, so React Native, Expo, Flutter, and fully native apps
71
+ are all driven the same way. See [docs/frameworks.md](docs/frameworks.md).
15
72
 
16
- ## Install
73
+ ## Five-minute start
17
74
 
18
75
  Inside a mobile app repo:
19
76
 
20
77
  ```bash
21
- npm install --save-dev zeno-mobile-runner
78
+ npm install --save-dev zeno-mobile-runner # bun add --dev zeno-mobile-runner
22
79
  npx zmr-wizard --app-id com.example.mobiletest --package-json
23
80
  npx zmr doctor --strict --json --config .zmr/config.json
24
81
  ```
25
82
 
26
- Run a generated smoke scenario:
83
+ Hook it up to your coding agent (Claude Code shown; any MCP client works):
27
84
 
28
85
  ```bash
29
- npm run zmr:validate
30
- npm run zmr:android
31
- npm run zmr:ios
86
+ claude mcp add zmr -- npx zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent
32
87
  ```
33
88
 
34
- ## React Native, Expo, and Flutter
35
-
36
- ZMR works below the JavaScript or Dart framework layer. It drives the installed
37
- Android or iOS app through platform lifecycle commands, deep links, accessibility
38
- semantics, screenshots, logs, selector actions, waits, assertions, and traces.
39
-
40
- - **React Native:** prefer `testID`, `accessibilityLabel`, stable text, and deep
41
- links for direct navigation into important states.
42
- - **Expo development builds:** pass `--expo-dev-client-scheme <scheme>` to the
43
- wizard so ZMR scaffolds dev-client open-link scenarios.
44
- - **Flutter:** ZMR supports Flutter apps at the Android and iOS app level when
45
- the app exposes stable semantics labels, text, deep links, or native ids. It is
46
- not a Flutter widget-tree driver and does not inspect Flutter internals.
47
- - **Native Android/iOS:** use resource ids, content descriptions, accessibility
48
- identifiers, XCTest labels, and app-owned deep links.
49
-
50
- See [docs/frameworks.md](docs/frameworks.md) and
51
- [docs/app-integration.md](docs/app-integration.md) for app-side setup guidance.
52
-
53
- ## Why ZMR
54
-
55
- - **Agent-native protocol:** structured snapshots, semantic mobile trees,
56
- actions, waits, assertions, live trace events, and redacted trace export over
57
- JSON-RPC or MCP.
58
- - **Trace-first debugging:** every run can produce screenshots, UI trees, logs,
59
- timings, action inputs, assertion results, and an HTML report.
60
- - **Fast local core:** Zig owns orchestration, subprocess control, selectors,
61
- waits, retries, scenario execution, and packaged binaries.
62
- - **App-local setup:** `.zmr/config.json`, smoke scenarios, shim commands, and
63
- traces live in the app repo.
64
- - **Android and iOS:** Android uses ADB/UI Automator plus an optional native
65
- shim. iOS simulators use `simctl`; physical iOS devices use `devicectl`;
66
- selector-grade iOS automation uses the XCTest/XCUIAutomation shim.
67
-
68
- ## Scenario Example
69
-
70
- ZMR scenarios are JSON so agents and build scripts can generate, validate, and
71
- mutate them without a second DSL.
89
+ Or in an `.mcp.json` / MCP client config:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "zmr": {
95
+ "command": "npx",
96
+ "args": ["zmr", "mcp", "--config", ".zmr/config.json", "--trace-dir", "traces/zmr-agent"]
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ Then ask the agent to verify its own work: *"launch the app, walk through
103
+ onboarding, and show me the trace."*
104
+
105
+ ## The agent verification loop
106
+
107
+ ```mermaid
108
+ sequenceDiagram
109
+ participant Agent as AI agent
110
+ participant ZMR
111
+ participant Device as Emulator / simulator
112
+ Agent->>ZMR: semantic_snapshot
113
+ ZMR->>Device: capture UI + screenshot
114
+ ZMR-->>Agent: roles, stable selectors, bounds
115
+ Agent->>ZMR: tap / type / swipe / open_link
116
+ ZMR->>Device: execute + settle
117
+ Agent->>ZMR: wait_visible / assert_visible
118
+ ZMR-->>Agent: typed result + trace events
119
+ Agent->>ZMR: trace_discover
120
+ ZMR-->>Agent: reviewable replay scenario
121
+ Agent->>ZMR: trace_export --redact
122
+ ZMR-->>Agent: .zmrtrace evidence bundle
123
+ ```
124
+
125
+ The MCP server exposes the full loop as mobile-native tools:
126
+
127
+ | Group | Tools |
128
+ | --- | --- |
129
+ | Observe | `snapshot`, `semantic_snapshot` |
130
+ | App lifecycle | `install_app`, `launch_app`, `stop_app`, `clear_state`, `open_link` |
131
+ | Act | `tap`, `type`, `erase_text`, `hide_keyboard`, `swipe`, `press_back` |
132
+ | Wait | `wait_visible`, `wait_not_visible`, `wait_any`, `scroll_until_visible` |
133
+ | Assert | `assert_visible`, `assert_not_visible`, `assert_healthy` |
134
+ | Evidence | `trace_events`, `trace_explain`, `trace_discover`, `trace_explore`, `trace_export`, `scenario_validate` |
135
+
136
+ The same surface is available over JSON-RPC for harnesses that embed ZMR
137
+ directly — see [docs/protocol.md](docs/protocol.md) and
138
+ [docs/ai-agents.md](docs/ai-agents.md). When a run fails, `zmr explain`
139
+ diagnoses the trace for humans and agents alike:
140
+
141
+ ![Terminal session showing a failed run, zmr explain diagnosing the failure with visible texts, and the fixed run passing](docs/assets/cli-run-explain.png)
142
+
143
+ ## Deterministic scenarios for CI
144
+
145
+ Scenarios are plain JSON — agents and build scripts generate, validate, and
146
+ mutate them without a second DSL, and they replay in CI with no LLM cost:
72
147
 
73
148
  ```json
74
149
  {
75
150
  "name": "Login smoke",
76
151
  "appId": "com.example.mobiletest",
77
152
  "steps": [
153
+ { "action": "clearState" },
78
154
  { "action": "launch" },
79
155
  { "action": "assertHealthy", "timeoutMs": 5000 },
80
156
  { "action": "tap", "selector": { "resourceId": "email" } },
81
157
  { "action": "typeText", "text": "user@example.com" },
82
- { "action": "tap", "selector": { "resourceId": "password" } },
83
- { "action": "typeText", "text": "password" },
84
158
  { "action": "tap", "selector": { "text": "Login" } },
85
159
  { "action": "waitVisible", "selector": { "text": "Welcome" }, "timeoutMs": 30000 }
86
160
  ]
87
161
  }
88
162
  ```
89
163
 
90
- Useful commands:
91
-
92
164
  ```bash
93
- zmr version --json
94
- zmr schemas --json
95
- zmr devices --json
96
- zmr init --app --json --dir . --app-id com.example.mobiletest
97
165
  zmr validate --json .zmr/login-smoke.json
98
166
  zmr run .zmr/login-smoke.json --json --trace-dir traces/login-smoke
99
- zmr explain --json traces/login-smoke
100
- zmr import flow-yaml .zmr/legacy-flow.yaml --out .zmr/legacy-flow.json
101
- zmr export traces/login-smoke --out traces/login-smoke-redacted.zmrtrace --redact
102
- ```
103
-
104
- See [docs/scenario-authoring.md](docs/scenario-authoring.md) for selector and
105
- wait guidance.
106
-
107
- ## Agent Workflow
108
-
109
- Agents can use the CLI, JSON-RPC, or MCP surface. Start JSON-RPC over stdio:
110
-
111
- ```bash
112
- zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent
167
+ zmr report traces/login-smoke --out traces/login-smoke/report.html --junit traces/login-smoke/junit.xml
168
+ zmr export traces/login-smoke --out login-smoke-redacted.zmrtrace --redact
113
169
  ```
114
170
 
115
- Agents that support the Model Context Protocol can use the native MCP surface:
116
-
117
- ```bash
118
- zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent
119
- ```
171
+ Traced `zmr run --json` responses include executable `nextCommands` so agents
172
+ can continue to reporting, explanation, discovery, or export without guessing.
173
+ Open any exported bundle in the static [trace viewer](viewer/index.html) — or
174
+ serve it and link straight to it with `viewer/index.html?bundle=<url>`.
120
175
 
121
- The MCP server exposes mobile-specific tools such as `semantic_snapshot`, `tap`,
122
- `type`, `wait_visible`, `trace_events`, and `trace_export`.
176
+ For repeat-run reliability gates, p95 duration thresholds, baseline
177
+ comparisons against your current E2E tool, and multi-device matrices, see
178
+ [docs/benchmarking.md](docs/benchmarking.md) and the public
179
+ [Benchmark Lab](docs/benchmarks/README.md) evidence.
123
180
 
124
- For agent-led discovery and test authoring, see
125
- [docs/agent-discovery.md](docs/agent-discovery.md). ZMR supports that loop
126
- through MCP and JSON-RPC today; a built-in autonomous crawler is not shipped in
127
- this preview.
128
-
129
- ## Optional Protocol Clients
130
-
131
- Clients are thin wrappers around `zmr serve --transport stdio`. They do not
132
- replace the runner; they make it easier for agents and test code to call the
133
- same JSON-RPC protocol.
134
-
135
- TypeScript and Python are the most common starting points for app teams and
136
- agent harnesses. Go, Rust, Swift, and Kotlin clients are reference integrations
137
- for teams that want to embed the protocol from those ecosystems.
138
-
139
- | Language | Entry point | Example |
140
- | --- | --- | --- |
141
- | TypeScript | `clients/typescript/index.mjs` + `index.d.ts` | `node clients/typescript/examples/fake-session.mjs` |
142
- | Python | `clients/python/zmr_client.py` + `pyproject.toml` | `python3 clients/python/examples/fake_session.py` |
143
- | Go | `clients/go/zmr/client.go` | `go run ./clients/go/examples/fake-session` |
144
- | Rust | `clients/rust/src/lib.rs` | `cargo run --manifest-path clients/rust/Cargo.toml --example fake_session` |
145
- | Swift | `clients/swift/Sources/ZMRClient` | `swift build --package-path clients/swift` |
146
- | Kotlin | `clients/kotlin/src/main/kotlin/dev/zmr` | `gradle -p clients/kotlin build` |
147
-
148
- See [clients/README.md](clients/README.md), [docs/clients.md](docs/clients.md),
149
- and [docs/client-installation.md](docs/client-installation.md).
150
-
151
- ## Platform Support
181
+ ## Platform support
152
182
 
153
183
  | Target | Status | Notes |
154
184
  | --- | --- | --- |
155
185
  | Android emulator | Supported | ADB/UI Automator, optional Android shim, emulator lifecycle helpers |
156
186
  | Android physical device | Supported | Requires ADB connection and app build/install surface |
157
- | iOS simulator | Supported | `simctl` plus app-local XCTest/XCUIAutomation shim for native selector actions, native waits, and bounded snapshots |
158
- | iOS physical device | Supported, validate locally | `devicectl` lifecycle plus app-local XCTest/XCUIAutomation shim; run pilots on your own app/device before relying on it in CI |
159
- | Cloud device farms | Not included | ZMR is focused on local and self-managed device targets in this preview |
187
+ | iOS simulator | Supported | `simctl` plus app-local XCTest/XCUIAutomation shim for native selector actions |
188
+ | iOS physical device | Supported, validate locally | `devicectl` lifecycle plus XCTest shim; pilot on your own app/device before relying on it in CI |
189
+ | Cloud device farms | Not included | ZMR focuses on local and self-managed device targets in this preview |
190
+
191
+ Slow CI hardware can extend the iOS shim cold-build timeout with
192
+ `ZMR_IOS_SHIM_TIMEOUT_MS`. Current release: `0.2.0` developer preview.
193
+ Protocol version: `2026-04-28`.
194
+
195
+ ## Optional protocol clients
160
196
 
161
- Current release: `0.1.3` developer preview. Protocol version:
162
- `2026-04-28`.
197
+ TypeScript and Python clients are the common starting points; Go, Rust, Swift,
198
+ and Kotlin reference clients embed the same JSON-RPC protocol from those
199
+ ecosystems. All are thin wrappers around `zmr serve --transport stdio`. See
200
+ [docs/clients.md](docs/clients.md) and
201
+ [docs/client-installation.md](docs/client-installation.md).
163
202
 
164
203
  ## Documentation
165
204
 
166
- - [FEATURES.md](FEATURES.md): complete feature list and limitations
205
+ **For agents**
206
+
207
+ - [docs/ai-agents.md](docs/ai-agents.md): JSON-RPC and MCP agent workflows
208
+ - [docs/agent-discovery.md](docs/agent-discovery.md): agent-led discovery, `zmr explore`/`discover`/`draft`, and the trace-to-test loop
209
+ - [skills/zmr-mobile-testing/SKILL.md](skills/zmr-mobile-testing/SKILL.md): reusable agent skill
210
+
211
+ **For test authors**
212
+
167
213
  - [docs/install.md](docs/install.md): source, npm, Homebrew, and app setup
168
214
  - [docs/frameworks.md](docs/frameworks.md): React Native, Expo, Flutter, and native app guidance
169
- - [docs/expo-smoke.md](docs/expo-smoke.md): reproducible Expo and iOS smoke test
170
- - [docs/app-integration.md](docs/app-integration.md): app-side Android/iOS shims
171
215
  - [docs/scenario-authoring.md](docs/scenario-authoring.md): selectors, waits, and scenario design
172
- - [docs/agent-discovery.md](docs/agent-discovery.md): agent-led discovery and scenario authoring loop
216
+ - [docs/app-integration.md](docs/app-integration.md): app-side Android/iOS shims
217
+ - [docs/expo-smoke.md](docs/expo-smoke.md): reproducible Expo and iOS smoke test
218
+ - [docs/benchmarking.md](docs/benchmarking.md): repeat-run gates, reports, device matrix, baselines
219
+
220
+ **Reference**
221
+
222
+ - [FEATURES.md](FEATURES.md): complete feature list and limitations
173
223
  - [docs/protocol.md](docs/protocol.md): JSON-RPC methods and schemas
174
- - [docs/ai-agents.md](docs/ai-agents.md): JSON-RPC and MCP agent workflows
175
- - [docs/clients.md](docs/clients.md): language client guide
176
- - [docs/client-installation.md](docs/client-installation.md): npm, Homebrew, TS, Python, Go, Rust, Swift, and Kotlin setup
177
224
  - [docs/trace-privacy.md](docs/trace-privacy.md): safe trace export
225
+ - [docs/production-readiness.md](docs/production-readiness.md): release, reliability, and agent-readiness gates
178
226
  - [docs/troubleshooting.md](docs/troubleshooting.md): common setup and runtime issues
179
- - [skills/zmr-mobile-testing/SKILL.md](skills/zmr-mobile-testing/SKILL.md): reusable agent skill
227
+ - [docs/benchmarks](docs/benchmarks/README.md): public-safe benchmark evidence
180
228
 
181
229
  ## License
182
230
 
package/build.zig.zon CHANGED
@@ -1,7 +1,7 @@
1
1
  .{
2
- .name = .zig_mobile_runner,
3
- .version = "0.0.1",
2
+ .name = .zeno_mobile_runner,
3
+ .version = "0.1.7",
4
4
  .minimum_zig_version = "0.15.2",
5
5
  .paths = .{""},
6
- .fingerprint = 0x5a9670ea13a33fab,
6
+ .fingerprint = 0xcc2c8187874868fc,
7
7
  }
package/clients/README.md CHANGED
@@ -19,9 +19,12 @@ actions instead of raw platform hierarchy classes.
19
19
  The TypeScript and Python clients expose the broadest app-facing control
20
20
  surface: session lifecycle, app launch/stop/link/state, snapshot and semantic
21
21
  snapshot, tap/type/erase/hide-keyboard/swipe/back/scroll, waits, assertions,
22
- trace event polling, and trace export. The Go, Rust, Swift, and Kotlin clients
23
- show the same protocol shape in other host languages and are useful starting
24
- points for custom integration work.
22
+ scenario validation, trace event polling, trace explanation, trace discovery,
23
+ trace exploration, and trace export.
24
+ The Go and Rust clients also include typed scenario validation and trace
25
+ explanation/exploration/discovery helpers. Swift and Kotlin include lightweight
26
+ trace explanation, validation, exploration, and discovery helpers for
27
+ host-side agents in those ecosystems.
25
28
  Use the `assertHealthy`/`assert_healthy` helper after launches, links, and major
26
29
  navigation steps to catch native crash overlays and development-client failures
27
30
  without hand-maintaining negative selectors in every client.
@@ -45,6 +48,7 @@ const zmr = createZmrClient({
45
48
  command: "zmr",
46
49
  args: ["serve", "--transport", "stdio", "--config", ".zmr/config.json"],
47
50
  });
51
+ const explored = await zmr.exploreTrace(".zmr/discovered/agent-goal.json", "find a stable login smoke", { includeActions: true, validate: true, force: true });
48
52
  ```
49
53
 
50
54
  ## Python
@@ -64,6 +68,8 @@ from zmr_client import ZmrClient
64
68
 
65
69
  with ZmrClient("zmr", ["serve", "--transport", "stdio", "--config", ".zmr/config.json"]) as zmr:
66
70
  snapshot = zmr.snapshot()
71
+ explanation = zmr.explain_trace()
72
+ explored = zmr.explore_trace(".zmr/discovered/agent-goal.json", "find a stable login smoke", include_actions=True, validate=True, force=True)
67
73
  ```
68
74
 
69
75
  ## Go
@@ -83,6 +89,10 @@ go run ./clients/go/examples/fake-session \
83
89
 
84
90
  ```go
85
91
  client, err := zmr.Start(ctx, "zmr", "serve", "--transport", "stdio", "--config", ".zmr/config.json")
92
+ discovered, err := client.DiscoverTrace(ctx, ".zmr/discovered/go-agent.json", zmr.TraceDiscoverOptions{IncludeActions: true, Validate: true, Force: true})
93
+ explored, err := client.ExploreTrace(ctx, ".zmr/discovered/go-goal.json", "find a stable login smoke", zmr.TraceDiscoverOptions{IncludeActions: true, Validate: true, Force: true})
94
+ validation, err := client.ValidateScenario(ctx, discovered.Out)
95
+ explanation, err := client.ExplainTrace(ctx)
86
96
  ```
87
97
 
88
98
  ## Rust
@@ -111,6 +121,20 @@ cargo run --manifest-path clients/rust/Cargo.toml --example fake_session -- \
111
121
  ```rust
112
122
  let mut client = zmr_client::Client::start("zmr", ["serve", "--transport", "stdio", "--config", ".zmr/config.json"])?;
113
123
  let snapshot = client.snapshot()?;
124
+ let discovered = client.discover_trace(".zmr/discovered/rust-agent.json", zmr_client::TraceDiscoverOptions {
125
+ include_actions: true,
126
+ validate: true,
127
+ force: true,
128
+ ..Default::default()
129
+ })?;
130
+ let explored = client.explore_trace(".zmr/discovered/rust-goal.json", "find a stable login smoke", zmr_client::TraceDiscoverOptions {
131
+ include_actions: true,
132
+ validate: true,
133
+ force: true,
134
+ ..Default::default()
135
+ })?;
136
+ let validation = client.validate_scenario(&discovered.out)?;
137
+ let explanation = client.explain_trace()?;
114
138
  ```
115
139
 
116
140
  ## Swift
@@ -128,6 +152,24 @@ git submodule add https://github.com/johnmikel/zeno-mobile-runner.git vendor/zen
128
152
  .package(path: "vendor/zeno-mobile-runner/clients/swift")
129
153
  ```
130
154
 
155
+ ```swift
156
+ let client = ZMRClient(arguments: ["serve", "--transport", "stdio", "--config", ".zmr/config.json"])
157
+ try client.start()
158
+ let out = ".zmr/discovered/swift-agent.json"
159
+ let discovered = try client.discoverTrace(
160
+ out: out,
161
+ options: TraceDiscoverOptions(includeActions: true, validate: true, force: true)
162
+ )
163
+ let explored = try client.exploreTrace(
164
+ out: ".zmr/discovered/swift-goal.json",
165
+ goal: "find a stable login smoke",
166
+ options: TraceDiscoverOptions(includeActions: true, validate: true, force: true)
167
+ )
168
+ let validation = try client.validateScenario(path: out)
169
+ let explanation = try client.explainTrace()
170
+ client.close()
171
+ ```
172
+
131
173
  Swift is useful for macOS host-side automation next to iOS app code. It is not
132
174
  an SDK embedded in the app under test.
133
175
 
@@ -140,6 +182,21 @@ git submodule add https://github.com/johnmikel/zeno-mobile-runner.git vendor/zen
140
182
  gradle -p vendor/zeno-mobile-runner/clients/kotlin build
141
183
  ```
142
184
 
185
+ ```kotlin
186
+ val out = ".zmr/discovered/kotlin-agent.json"
187
+ val discovered = client.discoverTrace(
188
+ out,
189
+ TraceDiscoverOptions(includeActions = true, validate = true, force = true)
190
+ )
191
+ val explored = client.exploreTrace(
192
+ ".zmr/discovered/kotlin-goal.json",
193
+ "find a stable login smoke",
194
+ TraceDiscoverOptions(includeActions = true, validate = true, force = true)
195
+ )
196
+ val validation = client.validateScenario(out)
197
+ val explanation = client.explainTrace()
198
+ ```
199
+
143
200
  Kotlin is useful for Android teams that want host-side orchestration in Kotlin.
144
201
  It still drives the external `zmr` binary.
145
202
 
@@ -12,6 +12,18 @@ defer client.Close()
12
12
 
13
13
  snapshot, err := client.Snapshot(ctx)
14
14
  healthy, err := client.AssertHealthy(ctx, 1000)
15
+ explanation, err := client.ExplainTrace(ctx)
16
+ discovered, err := client.DiscoverTrace(ctx, ".zmr/discovered/go-agent.json", zmr.TraceDiscoverOptions{
17
+ IncludeActions: true,
18
+ Validate: true,
19
+ Force: true,
20
+ })
21
+ explored, err := client.ExploreTrace(ctx, ".zmr/discovered/go-goal.json", "find a stable login smoke", zmr.TraceDiscoverOptions{
22
+ IncludeActions: true,
23
+ Validate: true,
24
+ Force: true,
25
+ })
26
+ validation, err := client.ValidateScenario(ctx, discovered.Out)
15
27
  ```
16
28
 
17
29
  Run the fake-session example from the repository root:
@@ -134,6 +134,39 @@ type TraceEvents struct {
134
134
  Events []map[string]interface{} `json:"events"`
135
135
  }
136
136
 
137
+ type TraceDiagnostic struct {
138
+ Kind string `json:"kind"`
139
+ Status string `json:"status,omitempty"`
140
+ SnapshotID string `json:"snapshotId,omitempty"`
141
+ ArtifactStatus string `json:"artifactStatus,omitempty"`
142
+ SemanticStatus string `json:"semanticStatus,omitempty"`
143
+ Error string `json:"error,omitempty"`
144
+ ScreenshotArtifact string `json:"screenshotArtifact,omitempty"`
145
+ Source string `json:"source,omitempty"`
146
+ ActivePackage string `json:"activePackage,omitempty"`
147
+ ActiveActivity string `json:"activeActivity,omitempty"`
148
+ VisibleTexts []string `json:"visibleTexts,omitempty"`
149
+ NearestTextMatches []string `json:"nearestTextMatches,omitempty"`
150
+ }
151
+
152
+ type TraceExplain struct {
153
+ OK bool `json:"ok"`
154
+ TraceDir string `json:"traceDir"`
155
+ Scenario string `json:"scenario"`
156
+ Status string `json:"status"`
157
+ AppID string `json:"appId,omitempty"`
158
+ DurationMS int64 `json:"durationMs,omitempty"`
159
+ EventCount int64 `json:"eventCount,omitempty"`
160
+ SnapshotCount int64 `json:"snapshotCount,omitempty"`
161
+ PartialFailureCount int64 `json:"partialFailureCount,omitempty"`
162
+ FailedStepIndex int64 `json:"failedStepIndex,omitempty"`
163
+ Error string `json:"error,omitempty"`
164
+ Diagnostic *TraceDiagnostic `json:"diagnostic,omitempty"`
165
+ PartialFailure *TraceDiagnostic `json:"partialFailure,omitempty"`
166
+ LastEvent string `json:"lastEvent,omitempty"`
167
+ NextCommands []string `json:"nextCommands"`
168
+ }
169
+
137
170
  type TraceExport struct {
138
171
  TraceDir string `json:"traceDir"`
139
172
  Out string `json:"out"`
@@ -141,6 +174,59 @@ type TraceExport struct {
141
174
  OmitScreenshots bool `json:"omitScreenshots"`
142
175
  }
143
176
 
177
+ type ValidationResult struct {
178
+ OK bool `json:"ok"`
179
+ Path string `json:"path"`
180
+ Name string `json:"name,omitempty"`
181
+ AppID string `json:"appId,omitempty"`
182
+ StepCount int `json:"stepCount,omitempty"`
183
+ ErrorCode string `json:"errorCode,omitempty"`
184
+ Message string `json:"message,omitempty"`
185
+ FieldPath string `json:"fieldPath,omitempty"`
186
+ Line int `json:"line,omitempty"`
187
+ Column int `json:"column,omitempty"`
188
+ NextCommands []string `json:"nextCommands,omitempty"`
189
+ }
190
+
191
+ type ReplaySummary struct {
192
+ Enabled bool `json:"enabled"`
193
+ EventCount int `json:"eventCount"`
194
+ StepCount int `json:"stepCount"`
195
+ SkippedEventCount int `json:"skippedEventCount"`
196
+ }
197
+
198
+ type TraceDiscoverOptions struct {
199
+ IncludeActions bool
200
+ Validate bool
201
+ Force bool
202
+ Name string
203
+ AppID string
204
+ }
205
+
206
+ type TraceDiscover struct {
207
+ OK bool `json:"ok"`
208
+ Mode string `json:"mode"`
209
+ SchemaVersion int `json:"schemaVersion"`
210
+ RunnerVersion string `json:"runnerVersion"`
211
+ ProtocolVersion string `json:"protocolVersion"`
212
+ Out string `json:"out"`
213
+ TraceDir string `json:"traceDir"`
214
+ SourceSnapshot string `json:"sourceSnapshot"`
215
+ Name string `json:"name"`
216
+ AppID string `json:"appId,omitempty"`
217
+ SelectorCount int `json:"selectorCount"`
218
+ StepCount int `json:"stepCount"`
219
+ Replay ReplaySummary `json:"replay"`
220
+ Warnings []string `json:"warnings"`
221
+ Validated bool `json:"validated"`
222
+ Validation *ValidationResult `json:"validation"`
223
+ NextCommands []string `json:"nextCommands"`
224
+ Goal string `json:"goal,omitempty"`
225
+ Autonomous bool `json:"autonomous,omitempty"`
226
+ ReviewRequired bool `json:"reviewRequired,omitempty"`
227
+ Guardrails []string `json:"guardrails,omitempty"`
228
+ }
229
+
144
230
  func Start(ctx context.Context, command string, args ...string) (*Client, error) {
145
231
  cmd := exec.CommandContext(ctx, command, args...)
146
232
  stdin, err := cmd.StdinPipe()
@@ -415,6 +501,12 @@ func (c *Client) AssertHealthy(ctx context.Context, timeoutMS int64) (bool, erro
415
501
  return out, err
416
502
  }
417
503
 
504
+ func (c *Client) ValidateScenario(ctx context.Context, path string) (ValidationResult, error) {
505
+ var out ValidationResult
506
+ err := c.Request(ctx, "scenario.validate", map[string]interface{}{"path": path}, &out)
507
+ return out, err
508
+ }
509
+
418
510
  func (c *Client) ExportTrace(ctx context.Context, outPath string, redact bool, omitScreenshots bool) (TraceExport, error) {
419
511
  var out TraceExport
420
512
  err := c.Request(ctx, "trace.export", map[string]interface{}{"out": outPath, "redact": redact, "omitScreenshots": omitScreenshots}, &out)
@@ -430,3 +522,53 @@ func (c *Client) TraceEvents(ctx context.Context, afterSeq int64, limit int64) (
430
522
  err := c.Request(ctx, "trace.events", params, &out)
431
523
  return out, err
432
524
  }
525
+
526
+ func (c *Client) ExplainTrace(ctx context.Context) (TraceExplain, error) {
527
+ var out TraceExplain
528
+ err := c.Request(ctx, "trace.explain", map[string]interface{}{}, &out)
529
+ return out, err
530
+ }
531
+
532
+ func (c *Client) DiscoverTrace(ctx context.Context, outPath string, options TraceDiscoverOptions) (TraceDiscover, error) {
533
+ var out TraceDiscover
534
+ params := map[string]interface{}{"out": outPath}
535
+ if options.IncludeActions {
536
+ params["includeActions"] = true
537
+ }
538
+ if options.Validate {
539
+ params["validate"] = true
540
+ }
541
+ if options.Force {
542
+ params["force"] = true
543
+ }
544
+ if options.Name != "" {
545
+ params["name"] = options.Name
546
+ }
547
+ if options.AppID != "" {
548
+ params["appId"] = options.AppID
549
+ }
550
+ err := c.Request(ctx, "trace.discover", params, &out)
551
+ return out, err
552
+ }
553
+
554
+ func (c *Client) ExploreTrace(ctx context.Context, outPath string, goal string, options TraceDiscoverOptions) (TraceDiscover, error) {
555
+ var out TraceDiscover
556
+ params := map[string]interface{}{"out": outPath, "goal": goal}
557
+ if options.IncludeActions {
558
+ params["includeActions"] = true
559
+ }
560
+ if options.Validate {
561
+ params["validate"] = true
562
+ }
563
+ if options.Force {
564
+ params["force"] = true
565
+ }
566
+ if options.Name != "" {
567
+ params["name"] = options.Name
568
+ }
569
+ if options.AppID != "" {
570
+ params["appId"] = options.AppID
571
+ }
572
+ err := c.Request(ctx, "trace.explore", params, &out)
573
+ return out, err
574
+ }