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
@@ -0,0 +1,95 @@
1
+ # Benchmark Lab v1
2
+
3
+ Benchmark Lab v1 is the public evidence plan for ZMR. It keeps framework
4
+ fixtures, runner adapters, timing modes, and claim boundaries explicit so speed
5
+ or reliability statements are reproducible instead of anecdotal.
6
+ It is the framework-level evidence map for React Native, Expo, Flutter, native
7
+ Android, and native iOS fixtures.
8
+
9
+ The machine-readable source is
10
+ [benchmark-lab-v1.json](benchmark-lab-v1.json). Render or validate it with:
11
+
12
+ ```bash
13
+ zmr-benchmark-lab --manifest docs/benchmarks/benchmark-lab-v1.json --format markdown
14
+ zmr-benchmark-lab --manifest docs/benchmarks/benchmark-lab-v1.json --format json
15
+ ```
16
+
17
+ ## Direction
18
+
19
+ ZMR should compete first where mobile teams already make framework choices:
20
+ React Native, Expo, Flutter, native Android, and native iOS. The lab is not a
21
+ generic benchmark scoreboard. Each fixture must represent an app workflow a
22
+ developer can inspect, build, run, and adapt.
23
+
24
+ The near-term wedge is agent-native mobile testing: structured observation,
25
+ selector-grade actions, trace-first debugging, and reviewable scenario
26
+ generation. Benchmarks should prove the local runner path is fast and reliable
27
+ without overstating what one fixture demonstrates.
28
+
29
+ ## Fixtures
30
+
31
+ | Fixture | Framework | Platforms | Status | Scenario |
32
+ | --- | --- | --- | --- | --- |
33
+ | Generated native iOS workflow | native-ios | iOS | evidence committed | `examples/ios-shim-workflow.json` |
34
+ | Generated native Android workflow | native-android | Android | evidence committed | `examples/android-workflow.json` |
35
+ | React Native and Expo workflow | react-native-expo | Android, iOS | fixture available | `examples/react-native-expo-workflow.json` |
36
+ | Flutter semantics workflow | flutter | Android, iOS | planned | pending |
37
+
38
+ The first richer iOS evidence pack is
39
+ [2026-06-09 iOS simulator workflow comparison](2026-06-09-ios-workflow-comparison.md).
40
+ It covers launch, profile entry, catalog scroll, item detail, save, review, and
41
+ final-state assertion on the generated native iOS demo app.
42
+
43
+ The first Android workflow evidence pack is
44
+ [2026-06-09 Android emulator workflow](2026-06-09-android-workflow.md). It
45
+ records 20 repeated ZMR runs through the platform UIAutomator path.
46
+
47
+ The React Native/Expo fixture is available through
48
+ `scripts/create-react-native-expo-demo-app.sh`. It generates an Expo app with
49
+ stable `testID` values, accessibility labels, a deep-link scheme, and matching
50
+ Android/iOS ZMR workflow scenarios. Public timing rows are still pending.
51
+
52
+ ## Runner Adapters
53
+
54
+ | Adapter | Status | Collector | Notes |
55
+ | --- | --- | --- | --- |
56
+ | ZMR | available | `scripts/benchmark.sh` | Candidate runner for all fixtures. |
57
+ | Maestro | evidence committed | `scripts/benchmark-command.sh` | Use YAML flows that match the same visible app state. |
58
+ | Appium | partial | `scripts/benchmark-command.sh` | The current public iOS workflow attempt failed while starting WebDriverAgent, so setup needs hardening before timing rows. |
59
+ | Detox | planned | `scripts/benchmark-command.sh` | Requires a React Native fixture with native build targets and a project-local test harness. |
60
+
61
+ Other local runner rows can be collected with the same generic command wrapper,
62
+ but public docs should only name tools when a fixture-specific evidence pack is
63
+ available or when a status row explains why evidence is missing.
64
+
65
+ ## Modes
66
+
67
+ | Mode | Meaning |
68
+ | --- | --- |
69
+ | Cold command | Measures the shell command a user runs, including runner startup. |
70
+ | Warm suite | Prepares the app, device, and runner bridge before timed rows, isolating repeated scenario execution. |
71
+ | Native floor | Measures a direct platform shim path as diagnostic lower-bound evidence, not a product comparison. |
72
+
73
+ Cold-command rows are the best default for user-facing claims. Warm-suite rows
74
+ are the best way to prove the core execution path can become faster without
75
+ hiding setup work. Native-floor rows show where remaining overhead lives.
76
+
77
+ ## Claim Rules
78
+
79
+ - Use at least 20 candidate rows and 20 baseline rows for public comparison
80
+ evidence.
81
+ - Require 100% candidate pass rate and zero candidate failures for any public
82
+ speed claim.
83
+ - Compare only rows with the same host class, OS/toolchain, device state, app
84
+ id, app build, scenario, and timing mode.
85
+ - Commit sanitized result rows and commands. Do not commit raw trace logs when
86
+ they contain local absolute paths or app data.
87
+ - Phrase every result as fixture-specific evidence. Do not describe one lab run
88
+ as a universal product claim.
89
+
90
+ ## Next Slices
91
+
92
+ 1. Add a Flutter semantics fixture that proves app-level Android/iOS support
93
+ without claiming widget-tree automation.
94
+ 2. Add warm-suite collection so bridge prewarm and repeated execution can be
95
+ measured separately from command startup.
package/docs/clients.md CHANGED
@@ -3,6 +3,22 @@
3
3
  ZMR clients are reference implementations for the JSON-RPC protocol used by
4
4
  `zmr serve`. They are intentionally small and dependency-light.
5
5
 
6
+ TypeScript and Python are the most common starting points for app teams and
7
+ agent harnesses. Go, Rust, Swift, and Kotlin clients are reference integrations
8
+ for teams that want to embed the protocol from those ecosystems. Go and Rust
9
+ include typed trace discovery and scenario validation helpers for host-side
10
+ agent loops; Swift and Kotlin include lightweight discovery and validation
11
+ helpers for host-side automation.
12
+
13
+ | Language | Entry point | Example |
14
+ | --- | --- | --- |
15
+ | TypeScript | `clients/typescript/index.mjs` + `index.d.ts` | `node clients/typescript/examples/fake-session.mjs` |
16
+ | Python | `clients/python/zmr_client.py` + `pyproject.toml` | `python3 clients/python/examples/fake_session.py` |
17
+ | Go | `clients/go/zmr/client.go` | `go run ./clients/go/examples/fake-session` |
18
+ | Rust | `clients/rust/src/lib.rs` | `cargo run --manifest-path clients/rust/Cargo.toml --example fake_session` |
19
+ | Swift | `clients/swift/Sources/ZMRClient` | `swift build --package-path clients/swift` |
20
+ | Kotlin | `clients/kotlin/src/main/kotlin/dev/zmr` | `gradle -p clients/kotlin build` |
21
+
6
22
  ## What Clients Mean
7
23
 
8
24
  The runner is still the Zig binary. A client starts or connects to:
@@ -21,7 +37,11 @@ Then it sends JSON-RPC methods such as:
21
37
  - `wait.until`
22
38
  - `assert.visible`
23
39
  - `assert.healthy`
40
+ - `scenario.validate`
24
41
  - `trace.events`
42
+ - `trace.explain`
43
+ - `trace.explore`
44
+ - `trace.discover`
25
45
  - `trace.export`
26
46
 
27
47
  Use clients when an AI agent, service, or test harness wants to drive ZMR
@@ -37,12 +57,12 @@ even when normal page text is also present.
37
57
 
38
58
  | Language | Files | Why it looks this way |
39
59
  | --- | --- | --- |
40
- | TypeScript | `clients/typescript/index.mjs`, `index.d.ts` | ESM runtime plus type declarations, no build step required |
41
- | Python | `clients/python/zmr_client.py`, `pyproject.toml` | Standard-library importable module that can be vendored or pip-installed from source |
42
- | Go | `clients/go/zmr/client.go` | Normal Go package inside a module |
43
- | Rust | `clients/rust/src/lib.rs` | Cargo library crate convention |
44
- | Swift | `clients/swift/Sources/ZMRClient/ZMRClient.swift` | SwiftPM package for macOS host-side tools |
45
- | Kotlin | `clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt` | Gradle/Kotlin source package for JVM host-side tools |
60
+ | TypeScript | `clients/typescript/index.mjs`, `index.d.ts` | ESM runtime plus type declarations, including `explainTrace`, `exploreTrace`, `discoverTrace`, and `validateScenario` helpers |
61
+ | Python | `clients/python/zmr_client.py`, `pyproject.toml` | Standard-library importable module with `explain_trace`, `explore_trace`, `discover_trace`, and `validate_scenario` helpers |
62
+ | Go | `clients/go/zmr/client.go` | Normal Go package inside a module, including `ExplainTrace`, `ExploreTrace`, `DiscoverTrace`, and `ValidateScenario` helpers |
63
+ | Rust | `clients/rust/src/lib.rs` | Cargo library crate convention, including `explain_trace`, `explore_trace`, `discover_trace`, and `validate_scenario` helpers |
64
+ | Swift | `clients/swift/Sources/ZMRClient/ZMRClient.swift` | SwiftPM package for macOS host-side tools, including `explainTrace`, `exploreTrace`, `discoverTrace`, and `validateScenario` helpers |
65
+ | Kotlin | `clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt` | Gradle/Kotlin source package for JVM host-side tools, including `explainTrace`, `exploreTrace`, `discoverTrace`, and `validateScenario` helpers |
46
66
 
47
67
  Rust has `src/lib.rs` because Cargo expects a library crate there. The other
48
68
  clients do have equivalent entry points; they are just idiomatic for their
package/docs/demo.md CHANGED
@@ -17,10 +17,13 @@ The script builds `zig-out/bin/zmr`, then runs:
17
17
  - `zmr validate examples/android-app-referral-deep-link.json`
18
18
  - `zmr validate examples/android-app-error-state.json`
19
19
  - `zmr validate examples/android-shim-smoke.json`
20
+ - `zmr validate examples/android-workflow.json`
21
+ - `zmr validate examples/react-native-expo-workflow.json`
20
22
  - `zmr validate examples/ios-smoke.json`
21
23
  - `zmr validate examples/ios-dev-client-open-link.json`
22
24
  - `zmr validate examples/ios-dev-client-route-snapshot.json`
23
25
  - `zmr validate examples/ios-shim-smoke.json`
26
+ - `zmr validate examples/ios-shim-workflow.json`
24
27
  - expected-failing `zmr validate --json` output that shows `fieldPath`, `line`,
25
28
  and `column` for invalid scenarios covered by `schemas/validate-output.schema.json`
26
29
  - `zmr doctor --adb ./tests/fake-adb.sh --xcrun ./tests/fake-xcrun.sh --ios-shim ./tests/fake-ios-shim.sh`
@@ -81,6 +84,8 @@ The Swift and Kotlin reference client flows verify host-side native-language
81
84
  agent/test-harness integration for iOS and Android teams.
82
85
  The fake Android shim flow exercises shim-backed hierarchy, wait, tap, type,
83
86
  hide-keyboard, and snapshot handling.
87
+ The React Native/Expo workflow example validates the framework fixture scenario
88
+ that uses deep links, accessibility labels, and stable `testID` values.
84
89
  The fake iOS flow exercises simulator lifecycle, deep-link opening, screenshot
85
90
  artifact capture, log capture, and snapshot trace writing. The fake iOS shim
86
91
  flow exercises shim-backed hierarchy, wait, tap, type, hide-keyboard, and
@@ -88,7 +93,11 @@ snapshot handling.
88
93
 
89
94
  Load any generated `.zmrtrace` in `viewer/index.html` to inspect the replay
90
95
  timeline, payloads, screenshot, UI tree, selected node details, and raw
91
- artifacts side-by-side.
96
+ artifacts side-by-side. When the viewer and the bundle are served over HTTP,
97
+ link straight to a loaded trace with `viewer/index.html?bundle=<url>` — useful
98
+ for CI artifact links and shared triage.
99
+
100
+ ![ZMR trace viewer with a loaded Android demo trace showing the timeline, device screenshot, and UI tree](assets/viewer-android.png)
92
101
 
93
102
  ## Real Android Pilot Demo
94
103
 
@@ -124,7 +133,9 @@ The script builds `zmr` when needed, validates both sample scenarios, installs t
124
133
  For each single run it writes:
125
134
 
126
135
  - `auth/report.html`
136
+ - `auth/junit.xml`
127
137
  - `login-smoke/report.html`
138
+ - `login-smoke/junit.xml`
128
139
  - `auth.zmrtrace`
129
140
  - `auth-redacted.zmrtrace`
130
141
  - `login-smoke.zmrtrace`
@@ -183,6 +194,32 @@ zmr run /tmp/zmr-android-demo/.zmr/android-smoke.json \
183
194
  The scenario launches the app, waits for visible text, taps a button, types
184
195
  text into a field, and captures a trace-backed snapshot.
185
196
 
197
+ ## React Native And Expo Fixture
198
+
199
+ To generate a public React Native and Expo app with stable test IDs,
200
+ accessibility labels, deep-link routing, and matching Android/iOS ZMR workflow
201
+ scenarios:
202
+
203
+ ```bash
204
+ npx zmr-create-react-native-expo-demo-app --out /tmp/zmr-rn-expo-demo
205
+ cd /tmp/zmr-rn-expo-demo
206
+ bun install
207
+ bunx expo start
208
+ ```
209
+
210
+ After installing a development build on the target device, run the generated
211
+ scenario for the platform you are measuring:
212
+
213
+ ```bash
214
+ zmr run .zmr/react-native-expo-android-workflow.json \
215
+ --device emulator-5554 \
216
+ --app-id com.example.mobiletest \
217
+ --trace-dir traces/zmr-rn-expo-android
218
+ ```
219
+
220
+ The fixture includes `expo-dev-client` and is available for benchmark
221
+ collection, but it does not have public timing rows yet.
222
+
186
223
  ## Real iOS Simulator Demo
187
224
 
188
225
  To generate a small public demo app with the ZMR XCTest shim already installed:
@@ -233,9 +270,11 @@ Build the app for an iOS simulator, boot a simulator, then run:
233
270
  For each run it writes:
234
271
 
235
272
  - `ios-smoke/report.html`
273
+ - `ios-smoke/junit.xml`
236
274
  - `ios-smoke.zmrtrace`
237
275
  - `ios-smoke-redacted.zmrtrace`
238
276
  - `ios-shim-smoke/report.html` when `--ios-shim` is set
277
+ - `ios-shim-smoke/junit.xml` when `--ios-shim` is set
239
278
  - `ios-shim-smoke.zmrtrace` when `--ios-shim` is set
240
279
  - `ios-shim-smoke-redacted.zmrtrace` when `--ios-shim` is set
241
280
 
@@ -2,11 +2,11 @@
2
2
 
3
3
  This is the quickest public smoke path for an Expo app. It proves that the npm
4
4
  package installs, the wizard scaffolds a scenario, ZMR can launch an iOS app,
5
- and the runner can produce screenshots, traces, HTML reports, and redacted trace
6
- bundles.
5
+ and the runner can produce screenshots, traces, HTML reports, JUnit XML, and
6
+ redacted trace bundles.
7
7
 
8
- The flow below was verified locally with `zeno-mobile-runner@0.1.3` on an iOS
9
- simulator.
8
+ Run the flow below on a local iOS simulator before treating a specific app build
9
+ as validated.
10
10
 
11
11
  ```bash
12
12
  npx create-expo-app@latest /tmp/zmr-expo-smoke --template blank --yes
@@ -35,7 +35,7 @@ npx zmr run .zmr/ios-smoke.json \
35
35
  --trace-dir traces/zmr-ios \
36
36
  --json
37
37
 
38
- npx zmr report traces/zmr-ios --out traces/zmr-ios/report.html
38
+ npx zmr report traces/zmr-ios --out traces/zmr-ios/report.html --junit traces/zmr-ios/junit.xml
39
39
  npx zmr export traces/zmr-ios --out traces/zmr-ios-redacted.zmrtrace --redact
40
40
  ```
41
41
 
@@ -55,9 +55,9 @@ Expected result shape:
55
55
  ```
56
56
 
57
57
  This smoke validates the platform-level loop: app launch, health check,
58
- screenshot capture, trace collection, report generation, and redacted export.
59
- For selector-grade React Native or Expo assertions on iOS, add the XCTest shim
60
- described in [app integration](app-integration.md).
58
+ screenshot capture, trace collection, HTML/JUnit report generation, and
59
+ redacted export. For selector-grade React Native or Expo assertions on iOS, add
60
+ the XCTest shim described in [app integration](app-integration.md).
61
61
 
62
62
  Android follows the same pattern with a connected emulator or device:
63
63
 
@@ -20,6 +20,16 @@ Keep generated ZMR files under `.zmr/` and run the wizard from the app repo:
20
20
  npx zmr-wizard --app-id com.example.mobiletest --package-json
21
21
  ```
22
22
 
23
+ To inspect a generated public fixture with a longer workflow:
24
+
25
+ ```bash
26
+ npx zmr-create-react-native-expo-demo-app --out /tmp/zmr-rn-expo-demo
27
+ ```
28
+
29
+ That fixture includes `expo-dev-client`, deep-link setup, stable `testID`
30
+ values, accessibility labels, and Android/iOS ZMR workflow scenarios under
31
+ `.zmr/`.
32
+
23
33
  ## Expo
24
34
 
25
35
  Expo development builds work like React Native apps once they are installed on a
package/docs/install.md CHANGED
@@ -54,8 +54,9 @@ zig build-exe src/main.zig -target aarch64-macos.15.0 -O ReleaseSafe -femit-bin=
54
54
  ./zig-out/bin/zmr version
55
55
  ```
56
56
 
57
- On macOS hosts where Zig can infer the target, `zig build test` and `zig build`
58
- are also valid.
57
+ On hosts where Zig can infer the target and locate the system SDK, `zig build`
58
+ can also build the binary. The explicit target form above is the supported
59
+ verification path used by CI and release gates.
59
60
 
60
61
  ## First Run Without A Device
61
62
 
package/docs/npm.md CHANGED
@@ -33,6 +33,9 @@ The package exposes:
33
33
  matching `.zmr/` smoke scenario for public demos and emulator pilots.
34
34
  - `zmr-create-ios-demo-app`: creates a generic SwiftUI simulator app with
35
35
  `.zmr/` scenarios and the iOS shim already installed for public demos.
36
+ - `zmr-create-react-native-expo-demo-app`: creates a generic React Native and
37
+ Expo app with stable `testID` values, accessibility labels, deep-link config,
38
+ and Android/iOS `.zmr/` workflow scenarios.
36
39
  - `zmr-demo-android`: creates, installs, and runs the generated Android demo
37
40
  through a real emulator/device.
38
41
  - `zmr-demo-ios`: creates, builds, and runs the generated iOS simulator demo
@@ -68,6 +71,8 @@ This creates:
68
71
  `.zmr/AGENTS.md` gives AI agents an app-local operating note with strict
69
72
  doctor/validate commands, schema discovery, direct `zmr run` smoke commands,
70
73
  JSON-RPC and MCP startup commands, selector guidance, the exact
74
+ `zmr discover --from-trace traces/zmr-agent --out .zmr/discovered/replay-smoke.json --include-actions --validate --json`
75
+ trace-to-test command,
71
76
  `zmr explain traces/zmr-agent --json` failure-triage command, the exact
72
77
  `zmr export traces/zmr-agent --out traces/zmr-agent-redacted.zmrtrace --redact`
73
78
  redacted trace export command.
@@ -91,12 +96,12 @@ Add app-local scripts:
91
96
  "zmr:schemas": "zmr schemas --json",
92
97
  "zmr:validate": "zmr validate --json .zmr/android-smoke.json && zmr validate --json .zmr/ios-smoke.json",
93
98
  "zmr:android": "zmr run .zmr/android-smoke.json --device emulator-5554 --trace-dir traces/zmr-android",
94
- "zmr:android:report": "zmr report traces/zmr-android --out traces/zmr-android/report.html",
95
- "zmr:android:reliability": "export ZMR_BIN=\"${ZMR_BIN:-zmr}\"; zmr-benchmark --zmr .zmr/android-smoke.json --device emulator-5554 --app-id com.example.mobiletest --runs 20 --trace-root traces/zmr-android-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 30000 && \"$ZMR_BIN\" report traces/zmr-android-reliability --out traces/zmr-android-reliability/report.html",
99
+ "zmr:android:report": "zmr report traces/zmr-android --out traces/zmr-android/report.html --junit traces/zmr-android/junit.xml",
100
+ "zmr:android:reliability": "export ZMR_BIN=\"${ZMR_BIN:-zmr}\"; zmr-benchmark --zmr .zmr/android-smoke.json --device emulator-5554 --app-id com.example.mobiletest --runs 20 --trace-root traces/zmr-android-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 30000 && \"$ZMR_BIN\" report traces/zmr-android-reliability --out traces/zmr-android-reliability/report.html --junit traces/zmr-android-reliability/junit.xml",
96
101
  "zmr:matrix": "ZMR_BIN=${ZMR_BIN:-zmr} zmr-device-matrix --matrix .zmr/device-matrix.json --trace-root traces/zmr-matrix --min-pass-rate 100 --max-failures 0",
97
102
  "zmr:ios": "zmr run .zmr/ios-smoke.json --platform ios --device booted --trace-dir traces/zmr-ios",
98
- "zmr:ios:report": "zmr report traces/zmr-ios --out traces/zmr-ios/report.html",
99
- "zmr:ios:reliability": "export ZMR_BIN=\"${ZMR_BIN:-zmr}\"; zmr-benchmark --zmr .zmr/ios-smoke.json --platform ios --device booted --app-id com.example.mobiletest --xcrun xcrun --runs 20 --trace-root traces/zmr-ios-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 45000 && \"$ZMR_BIN\" report traces/zmr-ios-reliability --out traces/zmr-ios-reliability/report.html",
103
+ "zmr:ios:report": "zmr report traces/zmr-ios --out traces/zmr-ios/report.html --junit traces/zmr-ios/junit.xml",
104
+ "zmr:ios:reliability": "export ZMR_BIN=\"${ZMR_BIN:-zmr}\"; zmr-benchmark --zmr .zmr/ios-smoke.json --platform ios --device booted --app-id com.example.mobiletest --xcrun xcrun --runs 20 --trace-root traces/zmr-ios-reliability --min-pass-rate 100 --max-failures 0 --max-p95-ms 45000 && \"$ZMR_BIN\" report traces/zmr-ios-reliability --out traces/zmr-ios-reliability/report.html --junit traces/zmr-ios-reliability/junit.xml",
100
105
  "zmr:pilot": "zmr-pilot-gate --android --ios --android-app-root . --android-app-id com.example.mobiletest --android-device emulator-5554 --ios-app-root . --ios-app-path ./build/Debug-iphonesimulator/Sample.app --ios-app-id com.example.mobiletest --ios-device booted --runs 20 --min-pass-rate 100 --max-failures 0 --evidence-out traces/zmr-pilots/evidence.jsonl",
101
106
  "zmr:readiness": "zmr-release-readiness --evidence traces/zmr-pilots/evidence.jsonl --target production --json",
102
107
  "zmr:serve": "zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent",
@@ -214,6 +219,24 @@ zmr run /tmp/zmr-android-demo/.zmr/android-smoke.json \
214
219
  --trace-dir /tmp/zmr-android-demo/traces/android-demo
215
220
  ```
216
221
 
222
+ ## React Native And Expo Demo Fixture
223
+
224
+ Generate a public React Native and Expo app when you need a framework-level
225
+ benchmark fixture before collecting timing rows:
226
+
227
+ ```bash
228
+ npx zmr-create-react-native-expo-demo-app --out /tmp/zmr-rn-expo-demo
229
+ cd /tmp/zmr-rn-expo-demo
230
+ bun install
231
+ bunx expo start
232
+ ```
233
+
234
+ The generated app includes `expo-dev-client`, stable `testID` values,
235
+ accessibility labels, an Expo deep-link scheme, and platform-specific ZMR
236
+ workflow scenarios under `.zmr/`. After installing a development build on the
237
+ target device, run the generated Android or iOS workflow scenario from the app
238
+ directory.
239
+
217
240
  ## iOS Demo App
218
241
 
219
242
  For a clean public iOS demo that does not depend on a private app:
@@ -341,6 +364,79 @@ npm run pack:npm
341
364
 
342
365
  That command builds release binaries, copies them into `prebuilds/`, and runs `npm pack`.
343
366
 
367
+ Tagged GitHub releases publish through npm trusted publishing, not a long-lived
368
+ `NPM_TOKEN` secret. Configure the npm package trusted publisher before relying
369
+ on the tag workflow:
370
+
371
+ - Package: `zeno-mobile-runner`
372
+ - Provider: GitHub Actions
373
+ - Organization or user: `johnmikel`
374
+ - Repository: `zeno-mobile-runner`
375
+ - Workflow filename: `release.yml`
376
+ - Environment name: leave blank unless the release job also declares a GitHub
377
+ deployment environment.
378
+ - Allowed actions: `npm publish`
379
+
380
+ The release workflow already requests `id-token: write`, builds the npm tarball
381
+ from the tag, attests the generated release artifacts, uploads the GitHub
382
+ release assets, verifies exactly one local npm tarball exists under `./dist/`,
383
+ and then publishes that tarball with public access.
384
+
385
+ Trusted publishing requires a current npm runtime. The tag workflow uses Node
386
+ 24 so the npm CLI can exchange the GitHub Actions OIDC identity for publish
387
+ authorization.
388
+
389
+ With `npm@11.10.0` or newer, maintainers can also configure the same trust
390
+ relationship from an authenticated local shell:
391
+
392
+ ```bash
393
+ npm trust list zeno-mobile-runner
394
+ npm trust github zeno-mobile-runner \
395
+ --repo johnmikel/zeno-mobile-runner \
396
+ --file release.yml \
397
+ --allow-publish
398
+ ```
399
+
400
+ If `npm trust` is not available, update npm or use the package settings page on
401
+ npmjs.com. A failed publish with `E404` for an existing package usually means
402
+ the trusted-publisher configuration is missing, points at a different GitHub
403
+ owner/repository/workflow filename, names an environment that the workflow does
404
+ not use, or does not allow `npm publish`.
405
+
406
+ ### Manual publish with passkey or 2FA
407
+
408
+ Use trusted publishing for normal tagged releases. If you need to publish a
409
+ verified local tarball manually, authenticate first:
410
+
411
+ ```bash
412
+ npm login --auth-type=web
413
+ npm whoami
414
+ ```
415
+
416
+ The browser/passkey step must finish before publishing. If `npm whoami` returns
417
+ `E401 Unauthorized`, the local machine is not authenticated and `npm publish`
418
+ will fail.
419
+
420
+ Build and verify the package before publishing:
421
+
422
+ ```bash
423
+ ./scripts/ci-gate.sh
424
+ npm pack --dry-run --json
425
+ npm run pack:npm
426
+ ```
427
+
428
+ Publish the generated tarball from `dist/`:
429
+
430
+ ```bash
431
+ npm publish ./dist/zeno-mobile-runner-<version>.tgz --access public
432
+ ```
433
+
434
+ If npm returns `E403` with a two-factor authentication message, the account or
435
+ organization requires either a current interactive 2FA challenge or a granular
436
+ automation token configured to bypass 2FA. For local passkey accounts, rerun
437
+ `npm login --auth-type=web`, complete the passkey challenge in the browser, and
438
+ confirm `npm whoami` before retrying the same `npm publish` command.
439
+
344
440
  ## Node API
345
441
 
346
442
  ```js
@@ -0,0 +1,123 @@
1
+ # Production Readiness
2
+
3
+ ZMR is a public developer preview. The npm package is live, release artifacts
4
+ are signed by GitHub release attestations, and local app teams can collect
5
+ repeatable Android, iOS simulator, and physical iOS evidence. ZMR should not be
6
+ called production-stable until the gates below are met and kept passing.
7
+
8
+ ## Current Release Standard
9
+
10
+ Every public release should satisfy:
11
+
12
+ - `bash tests/docs-readiness-test.sh`
13
+ - `bash tests/public-safety-test.sh`
14
+ - `npm test`
15
+ - `zig test src/test_harness.zig -target aarch64-macos.15.0`
16
+ - `./scripts/release-gate.sh`
17
+ - `npm run pack:npm`
18
+ - `./scripts/verify-release-artifacts.sh --dist dist`
19
+ - at least one trace or benchmark report rendered with `zmr report --junit`,
20
+ or a pilot wrapper run that produced both `report.html` and `junit.xml`
21
+ - a fresh npm install smoke:
22
+
23
+ ```bash
24
+ npm install --save-dev zeno-mobile-runner
25
+ npx zmr version --json
26
+ ```
27
+
28
+ Tagged releases are expected to build release archives, generate
29
+ `RELEASE_MANIFEST.json`, publish GitHub artifact attestations, upload release
30
+ assets, and publish the npm tarball through trusted publishing after the npm
31
+ package is configured with the `release.yml` trusted publisher.
32
+
33
+ CI runs retain `traces/`, `zig-cache/coverage/`, and `zig-out/bin/zmr` for 14
34
+ days when those files are produced. Tagged releases retain `dist/` as a
35
+ workflow artifact for 30 days in addition to GitHub release assets.
36
+
37
+ ## Product Gates Before 1.0
38
+
39
+ | Area | Required evidence | Current status |
40
+ | --- | --- | --- |
41
+ | Android emulator | 20-run pilot gate with zero failures and trace/report artifacts | Supported by `zmr-pilot-gate` and demo app |
42
+ | Android physical device | 20-run pilot gate on a real connected device | Supported by ADB flow; app teams must collect evidence |
43
+ | iOS simulator | 20-run pilot gate with XCTest shim selectors, screenshots, and reports | Supported by iOS demo and app-local shim |
44
+ | iOS physical device | 20-run pilot gate on a real trusted device | Supported for lifecycle and shim screenshots; needs repeated public evidence |
45
+ | React Native | Public setup guidance plus selector-grade app evidence using stable labels or ids | Guidance exists; repeated public demo evidence is still needed |
46
+ | Expo | Public smoke, dev-client scaffold, and iOS/Android run evidence | Basic iOS smoke is documented; repeated matrix evidence is still needed |
47
+ | Flutter | Platform-level Android/iOS smoke using semantics, deep links, and screenshots | Supported at platform level; widget-tree claims are intentionally out of scope |
48
+ | Agent workflows | MCP and JSON-RPC loop with semantic snapshots, typed actions, traces, redacted export, guarded trace exploration, and scenario validation | Supported and enforced by the `agent workflow smoke` readiness gate; `zmr explore` is review-first and trace-backed, and an unbounded autonomous crawler is not shipped |
49
+ | CI reporting | HTML reports plus JUnit XML artifacts from trace, benchmark, and pilot directories | Supported by `zmr report --junit` and pilot wrappers |
50
+ | Trace privacy | Redacted export path, denylist/allowlist controls, and public-safety tests | Supported and gated |
51
+ | Release supply chain | Trusted npm publish, GitHub artifact attestations, checksums, SBOM, and release manifest | Workflow is ready; npm trusted publisher must be configured in package settings |
52
+
53
+ ## Reliability Evidence
54
+
55
+ Use repeated app-local pilots before making app or device claims:
56
+
57
+ ```bash
58
+ zmr-pilot-gate \
59
+ --android \
60
+ --ios \
61
+ --android-app-root . \
62
+ --android-app-id com.example.mobiletest \
63
+ --android-device emulator-5554 \
64
+ --ios-app-root . \
65
+ --ios-app-path ./build/Debug-iphonesimulator/Sample.app \
66
+ --ios-app-id com.example.mobiletest \
67
+ --ios-device booted \
68
+ --ios-shim ./.zmr/ios-shim \
69
+ --runs 20 \
70
+ --min-pass-rate 100 \
71
+ --max-failures 0 \
72
+ --evidence-out traces/zmr-pilots/evidence.jsonl
73
+ ```
74
+
75
+ Then summarize readiness:
76
+
77
+ ```bash
78
+ zmr-release-readiness \
79
+ --evidence traces/zmr-pilots/evidence.jsonl \
80
+ --target production \
81
+ --json
82
+ ```
83
+
84
+ Keep the generated evidence in the app repository unless it is fully redacted
85
+ and safe to publish.
86
+
87
+ ## Agentic Standard
88
+
89
+ ZMR is agentic when an external agent can work from structured state instead of
90
+ screenscraping or guessing:
91
+
92
+ - `zmr doctor --json` explains setup state and remediation.
93
+ - `zmr schemas --json` exposes machine-readable contracts.
94
+ - `zmr validate --json` catches scenario mistakes before device runs.
95
+ - `zmr serve` exposes JSON-RPC for long-running sessions.
96
+ - `zmr mcp` exposes MCP tools for semantic snapshots, typed actions, and
97
+ assertion-grade checks.
98
+ - `zmr explain --json` summarizes failed traces.
99
+ - `zmr report --junit` emits CI-compatible test results from trace and
100
+ benchmark evidence.
101
+ - `zmr export --redact` produces shareable trace bundles.
102
+ - `zmr-release-readiness --target production` requires the `agent workflow
103
+ smoke` gate, satisfied by a passed `./scripts/release-gate.sh` row or
104
+ structured evidence for MCP, JSON-RPC, semantic snapshots, typed actions,
105
+ trace events, trace explanation, trace discovery/exploration, scenario
106
+ validation, and redacted export.
107
+
108
+ The safe discovery pattern is still external-agent-first: observe with
109
+ `semantic_snapshot`, choose one typed action, record successful steps into a
110
+ candidate scenario, validate it, rerun it deterministically, and require human
111
+ review before committing generated tests.
112
+
113
+ ## Claims Policy
114
+
115
+ - Claim Android and iOS app-level support only for flows that pass local pilot
116
+ evidence on the target device class.
117
+ - Claim React Native and Expo support through app-level lifecycle, deep links,
118
+ accessibility labels, selectors, screenshots, traces, and reports.
119
+ - Claim Flutter support at the Android/iOS app level when the app exposes stable
120
+ semantics, labels, ids, or deep links.
121
+ - Do not claim Flutter widget-tree inspection, Dart state inspection, managed
122
+ device-farm coverage, or a built-in autonomous test writer until those
123
+ features exist and have public evidence.
@@ -1,4 +1,4 @@
1
- {"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.1.3","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"]}}
1
+ {"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.2.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","scenario.validate","trace.events","trace.explore","trace.discover","trace.explain","trace.export"]}}
2
2
  {"jsonrpc":"2.0","id":2,"result":[{"serial":"fake-device-1","state":"device","ready":true}]}
3
3
  {"jsonrpc":"2.0","id":3,"result":{"sessionId":"default"}}
4
4
  {"jsonrpc":"2.0","id":4,"result":true}