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/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.3`.
5
+ Current runner version: `0.2.0`.
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.2.0","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.2.0","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.2.0","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.2.0","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 pilot"],"satisfied":["Android hardware pilot"],"failed":[],"planned":[],"missing":["iOS simulator hardware pilot","iOS physical hardware pilot"],"insufficient":[],"blocked":["iOS simulator hardware pilot","iOS physical hardware pilot"],"requirements":[{"name":"Android hardware pilot","status":"satisfied","evidenceName":"Android emulator pilot"},{"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":[],"recommendedWording":"Do not call this app/device matrix ready yet. Missing evidence: iOS simulator hardware pilot, iOS physical hardware pilot.","claimLimitations":["missing evidence"]}
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 an HTML report, explain the failure, or export a redacted
85
- trace bundle. Failed scenarios still exit non-zero after writing the JSON
86
- summary. The response is covered by `schemas/run-output.schema.json`:
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 an HTML
118
- report or exporting a redacted bundle. The response is covered by
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.3","protocolVersion":"2026-04-28","minimumCompatibleProtocolVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"}
217
+ {"name":"zmr","version":"0.2.0","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.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","observe.snapshot","observe.semanticSnapshot"]}
229
+ {"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"]}
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.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"]}}
435
+ {"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"]}}
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.2.0","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.2.0","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`, `tap`, `type`, `press_back`,
436
- `open_link`, `wait_visible`, `trace_events`, and `trace_export`. The MCP
437
- protocol handshake is intentionally standard, while the tool names and payloads
438
- are versioned with the ZMR runner and public schemas.
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
@@ -4,6 +4,19 @@ ZMR scenarios are JSON so agents can generate and mutate them without a second
4
4
  DSL. JSON is strict, schema-validatable, and easy for agents and code generators
5
5
  to emit. Keep scenarios explicit, short, and biased toward stable selectors.
6
6
 
7
+ Scenarios can be written by hand, or generated review-first from the trace of
8
+ a live session:
9
+
10
+ ```mermaid
11
+ flowchart LR
12
+ SESSION["Live agent session<br/>or zmr run"] --> TRACE["Trace directory"]
13
+ TRACE --> DISCOVER["zmr discover / draft / explore<br/>--from-trace"]
14
+ DISCOVER --> CANDIDATE["Scenario candidate<br/>.zmr/discovered/*.json"]
15
+ CANDIDATE --> REVIEW["Human / agent review"]
16
+ REVIEW --> VALIDATE["zmr validate --json"]
17
+ VALIDATE --> CI["zmr run in CI<br/>report.html · junit.xml"]
18
+ ```
19
+
7
20
  ## Selector Strategy
8
21
 
9
22
  Prefer selectors in this order:
@@ -71,6 +84,9 @@ The importer supports the common subset needed for smoke scenarios:
71
84
  generated JSON before committing it; native `.zmr/*.json` scenarios remain the
72
85
  runtime contract for agents and CI.
73
86
 
87
+ `assertVisible` and `assertNotVisible` accept the same `timeoutMs` field as
88
+ waits when a scenario needs assertion-specific timing.
89
+
74
90
  ## Example Templates
75
91
 
76
92
  The example directory includes templates for common app flows:
@@ -80,8 +96,10 @@ The example directory includes templates for common app flows:
80
96
  - `examples/android-app-onboarding.json`
81
97
  - `examples/android-app-referral-deep-link.json`
82
98
  - `examples/android-app-error-state.json`
99
+ - `examples/android-workflow.json`
83
100
  - `examples/ios-dev-client-open-link.json`
84
101
  - `examples/ios-dev-client-route-snapshot.json`
102
+ - `examples/ios-shim-workflow.json`
85
103
 
86
104
  Run `zmr validate --json <scenario.json>` before touching a device. Invalid
87
105
  scenarios report `fieldPath`, `line`, and `column` when ZMR can identify the
@@ -3,6 +3,15 @@
3
3
  ZMR traces are debugging artifacts. They can contain sensitive app state even
4
4
  when scenario files are generic.
5
5
 
6
+ ```mermaid
7
+ flowchart LR
8
+ RUN["zmr run / live session"] --> DIR["Trace directory<br/>events.jsonl · screenshots ·<br/>UI trees · logs · timings"]
9
+ DIR --> REPORT["zmr report<br/>report.html · junit.xml"]
10
+ DIR --> EXPLAIN["zmr explain<br/>failure diagnosis"]
11
+ DIR --> EXPORT["zmr export --redact<br/>.zmrtrace bundle"]
12
+ EXPORT --> VIEWER["Static trace viewer<br/>or shared evidence"]
13
+ ```
14
+
6
15
  Raw trace directories may include:
7
16
 
8
17
  - screenshots
@@ -188,6 +188,12 @@ app targets. Pass `--project` explicitly for still-ambiguous multi-project
188
188
  workspaces. Run with `--ios-shim ./.zmr/ios-shim` or set
189
189
  `tools.iosShimPath` in `.zmr/config.json`.
190
190
 
191
+ A clean prebuild can push the shim's first `build-for-testing` through a full
192
+ native dependency compile. ZMR waits up to 90 minutes by default; on slower CI
193
+ hardware, raise the ceiling with the `ZMR_IOS_SHIM_TIMEOUT_MS` environment
194
+ variable (milliseconds), for example `ZMR_IOS_SHIM_TIMEOUT_MS=10800000` for
195
+ three hours.
196
+
191
197
  If a real iOS run fails with CoreSimulator or Xcode cache errors such as
192
198
  `Operation not permitted`, `CoreSimulatorService connection became invalid`, or
193
199
  an unexpected workspace/build database error, rerun from a normal terminal or CI
@@ -228,7 +234,7 @@ When a run fails, do not rerun blindly. Inspect the recorded failure:
228
234
 
229
235
  ```bash
230
236
  zmr explain traces/zmr-android
231
- zmr report traces/zmr-android --out traces/zmr-android/report.html
237
+ zmr report traces/zmr-android --out traces/zmr-android/report.html --junit traces/zmr-android/junit.xml
232
238
  ```
233
239
 
234
240
  Timeout diagnostics include the active package/activity, visible text, hidden or
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "ZMR Android workflow demo",
3
+ "appId": "com.example.mobiletest",
4
+ "steps": [
5
+ { "action": "stop" },
6
+ { "action": "launch" },
7
+ {
8
+ "action": "waitVisible",
9
+ "selector": { "text": "ZMR Android Demo" },
10
+ "timeoutMs": 30000
11
+ },
12
+ {
13
+ "action": "tap",
14
+ "selector": { "resourceId": "com.example.mobiletest:id/continue_button" }
15
+ },
16
+ {
17
+ "action": "waitVisible",
18
+ "selector": { "text": "Profile" },
19
+ "timeoutMs": 10000
20
+ },
21
+ {
22
+ "action": "typeText",
23
+ "selector": { "resourceId": "com.example.mobiletest:id/profile_name_input" },
24
+ "text": "Riley"
25
+ },
26
+ {
27
+ "action": "typeText",
28
+ "selector": { "resourceId": "com.example.mobiletest:id/profile_email_input" },
29
+ "text": "riley@example.test"
30
+ },
31
+ { "action": "hideKeyboard" },
32
+ {
33
+ "action": "tap",
34
+ "selector": { "resourceId": "com.example.mobiletest:id/save_profile_button" }
35
+ },
36
+ {
37
+ "action": "waitVisible",
38
+ "selector": { "text": "Catalog" },
39
+ "timeoutMs": 10000
40
+ },
41
+ {
42
+ "action": "scrollUntilVisible",
43
+ "selector": { "resourceId": "com.example.mobiletest:id/catalog_item_north_ridge_pack" },
44
+ "direction": "down",
45
+ "timeoutMs": 10000
46
+ },
47
+ {
48
+ "action": "tap",
49
+ "selector": { "resourceId": "com.example.mobiletest:id/catalog_item_north_ridge_pack" }
50
+ },
51
+ {
52
+ "action": "waitVisible",
53
+ "selector": { "text": "North Ridge Pack" },
54
+ "timeoutMs": 10000
55
+ },
56
+ {
57
+ "action": "tap",
58
+ "selector": { "resourceId": "com.example.mobiletest:id/detail_save_button" }
59
+ },
60
+ {
61
+ "action": "waitVisible",
62
+ "selector": { "text": "Saved North Ridge Pack" },
63
+ "timeoutMs": 10000
64
+ },
65
+ {
66
+ "action": "tap",
67
+ "selector": { "resourceId": "com.example.mobiletest:id/review_button" }
68
+ },
69
+ {
70
+ "action": "assertVisible",
71
+ "selector": {
72
+ "resourceId": "com.example.mobiletest:id/workflow_status",
73
+ "text": "Workflow complete"
74
+ },
75
+ "timeoutMs": 10000
76
+ },
77
+ { "action": "snapshot" }
78
+ ]
79
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "ZMR iOS shim workflow demo",
3
+ "appId": "com.example.mobiletest",
4
+ "steps": [
5
+ { "action": "stop" },
6
+ { "action": "launch" },
7
+ {
8
+ "action": "waitVisible",
9
+ "selector": { "text": "ZMR iOS Demo" },
10
+ "timeoutMs": 30000
11
+ },
12
+ {
13
+ "action": "tap",
14
+ "selector": { "resourceId": "continue_button" }
15
+ },
16
+ {
17
+ "action": "waitVisible",
18
+ "selector": { "text": "Profile" },
19
+ "timeoutMs": 10000
20
+ },
21
+ {
22
+ "action": "typeText",
23
+ "selector": { "resourceId": "profile_name_input" },
24
+ "text": "Riley"
25
+ },
26
+ {
27
+ "action": "typeText",
28
+ "selector": { "resourceId": "profile_email_input" },
29
+ "text": "riley@example.test"
30
+ },
31
+ { "action": "hideKeyboard" },
32
+ {
33
+ "action": "tap",
34
+ "selector": { "resourceId": "save_profile_button" }
35
+ },
36
+ {
37
+ "action": "waitVisible",
38
+ "selector": { "text": "Catalog" },
39
+ "timeoutMs": 10000
40
+ },
41
+ {
42
+ "action": "scrollUntilVisible",
43
+ "selector": { "resourceId": "catalog_item_north_ridge_pack" },
44
+ "direction": "down",
45
+ "timeoutMs": 10000
46
+ },
47
+ {
48
+ "action": "tap",
49
+ "selector": { "resourceId": "catalog_item_north_ridge_pack" }
50
+ },
51
+ {
52
+ "action": "waitVisible",
53
+ "selector": { "text": "North Ridge Pack" },
54
+ "timeoutMs": 10000
55
+ },
56
+ {
57
+ "action": "tap",
58
+ "selector": { "resourceId": "detail_save_button" }
59
+ },
60
+ {
61
+ "action": "waitVisible",
62
+ "selector": { "text": "Saved North Ridge Pack" },
63
+ "timeoutMs": 10000
64
+ },
65
+ {
66
+ "action": "tap",
67
+ "selector": { "resourceId": "review_button" }
68
+ },
69
+ {
70
+ "action": "assertVisible",
71
+ "selector": {
72
+ "resourceId": "workflow_status",
73
+ "text": "Workflow complete"
74
+ },
75
+ "timeoutMs": 10000
76
+ },
77
+ { "action": "snapshot" }
78
+ ]
79
+ }