zeno-mobile-runner 0.1.2

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 (213) hide show
  1. package/CHANGELOG.md +497 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/FEATURES.md +111 -0
  4. package/LICENSE +21 -0
  5. package/README.md +176 -0
  6. package/SECURITY.md +34 -0
  7. package/build.zig +38 -0
  8. package/build.zig.zon +7 -0
  9. package/clients/README.md +149 -0
  10. package/clients/go/README.md +24 -0
  11. package/clients/go/examples/fake-session/main.go +93 -0
  12. package/clients/go/go.mod +3 -0
  13. package/clients/go/zmr/client.go +432 -0
  14. package/clients/kotlin/README.md +35 -0
  15. package/clients/kotlin/build.gradle.kts +35 -0
  16. package/clients/kotlin/settings.gradle.kts +15 -0
  17. package/clients/kotlin/src/main/kotlin/dev/zmr/FakeSession.kt +86 -0
  18. package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +67 -0
  19. package/clients/python/README.md +29 -0
  20. package/clients/python/examples/fake_session.py +48 -0
  21. package/clients/python/pyproject.toml +13 -0
  22. package/clients/python/zmr_client.py +202 -0
  23. package/clients/rust/Cargo.lock +107 -0
  24. package/clients/rust/Cargo.toml +10 -0
  25. package/clients/rust/README.md +19 -0
  26. package/clients/rust/examples/fake_session.rs +70 -0
  27. package/clients/rust/src/lib.rs +461 -0
  28. package/clients/swift/Package.swift +16 -0
  29. package/clients/swift/README.md +36 -0
  30. package/clients/swift/Sources/ZMRClient/ZMRClient.swift +114 -0
  31. package/clients/swift/Sources/ZMRFakeSession/main.swift +86 -0
  32. package/clients/typescript/README.md +34 -0
  33. package/clients/typescript/examples/fake-session.mjs +36 -0
  34. package/clients/typescript/index.d.ts +144 -0
  35. package/clients/typescript/index.mjs +192 -0
  36. package/clients/typescript/package.json +8 -0
  37. package/docs/adr/0001-agent-native-runner-boundary.md +31 -0
  38. package/docs/adr/0002-app-local-zmr-contract.md +39 -0
  39. package/docs/adr/0003-ios-simulator-xctest-shim.md +41 -0
  40. package/docs/adr/0004-benchmark-claims-and-baseline-collection.md +37 -0
  41. package/docs/adr/README.md +12 -0
  42. package/docs/ai-agents.md +154 -0
  43. package/docs/app-integration.md +330 -0
  44. package/docs/benchmarking.md +273 -0
  45. package/docs/client-installation.md +133 -0
  46. package/docs/clients.md +98 -0
  47. package/docs/config.md +175 -0
  48. package/docs/demo.md +259 -0
  49. package/docs/frameworks.md +72 -0
  50. package/docs/install.md +95 -0
  51. package/docs/npm.md +356 -0
  52. package/docs/protocol-fixtures/README.md +8 -0
  53. package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
  54. package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
  55. package/docs/protocol-versioning.md +65 -0
  56. package/docs/protocol.md +560 -0
  57. package/docs/scenario-authoring.md +88 -0
  58. package/docs/trace-privacy.md +88 -0
  59. package/docs/troubleshooting.md +256 -0
  60. package/examples/android-app-auth-probe.json +89 -0
  61. package/examples/android-app-error-state.json +13 -0
  62. package/examples/android-app-login-smoke.json +192 -0
  63. package/examples/android-app-onboarding.json +12 -0
  64. package/examples/android-app-referral-deep-link.json +12 -0
  65. package/examples/android-shim-smoke.json +19 -0
  66. package/examples/demo-failure.json +12 -0
  67. package/examples/demo-fake.json +14 -0
  68. package/examples/ios-dev-client-open-link.json +26 -0
  69. package/examples/ios-dev-client-route-snapshot.json +24 -0
  70. package/examples/ios-shim-smoke.json +23 -0
  71. package/examples/ios-smoke.json +9 -0
  72. package/go.work +3 -0
  73. package/npm/agents.mjs +183 -0
  74. package/npm/app-config.mjs +95 -0
  75. package/npm/build-zmr.mjs +21 -0
  76. package/npm/commands.mjs +104 -0
  77. package/npm/generated-files.mjs +50 -0
  78. package/npm/index.mjs +75 -0
  79. package/npm/init-app.mjs +80 -0
  80. package/npm/package-scripts.mjs +72 -0
  81. package/npm/postinstall.mjs +21 -0
  82. package/npm/scaffold.mjs +179 -0
  83. package/npm/scenarios.mjs +93 -0
  84. package/npm/setup.mjs +69 -0
  85. package/npm/wizard.mjs +117 -0
  86. package/npm/zmr.mjs +23 -0
  87. package/package.json +118 -0
  88. package/schemas/README.md +26 -0
  89. package/schemas/action-result.schema.json +27 -0
  90. package/schemas/capabilities-output.schema.json +98 -0
  91. package/schemas/devices-output.schema.json +25 -0
  92. package/schemas/doctor-output.schema.json +51 -0
  93. package/schemas/explain-output.schema.json +51 -0
  94. package/schemas/import-output.schema.json +23 -0
  95. package/schemas/init-output.schema.json +71 -0
  96. package/schemas/json-rpc.schema.json +55 -0
  97. package/schemas/release-manifest.schema.json +43 -0
  98. package/schemas/release-readiness-output.schema.json +127 -0
  99. package/schemas/run-output.schema.json +43 -0
  100. package/schemas/scenario.schema.json +128 -0
  101. package/schemas/schemas-output.schema.json +26 -0
  102. package/schemas/semantic-snapshot.schema.json +116 -0
  103. package/schemas/snapshot.schema.json +60 -0
  104. package/schemas/trace-event.schema.json +14 -0
  105. package/schemas/trace-manifest.schema.json +59 -0
  106. package/schemas/validate-output.schema.json +42 -0
  107. package/schemas/version-output.schema.json +23 -0
  108. package/schemas/zmr-config.schema.json +75 -0
  109. package/scripts/android-emulator.sh +126 -0
  110. package/scripts/assert-ios-physical-ready.sh +213 -0
  111. package/scripts/benchmark-command.sh +307 -0
  112. package/scripts/benchmark.sh +359 -0
  113. package/scripts/benchmark_gate.py +117 -0
  114. package/scripts/benchmark_result_row.py +88 -0
  115. package/scripts/compare-benchmarks.py +288 -0
  116. package/scripts/create-android-demo-app.sh +342 -0
  117. package/scripts/create-ios-demo-app.sh +261 -0
  118. package/scripts/demo-android-real.sh +232 -0
  119. package/scripts/demo-ios-real.sh +270 -0
  120. package/scripts/demo.sh +464 -0
  121. package/scripts/device-matrix.sh +338 -0
  122. package/scripts/ensure-ios-shim-target.rb +237 -0
  123. package/scripts/install-android-shim.sh +281 -0
  124. package/scripts/install-ios-shim.sh +589 -0
  125. package/scripts/pilot-gate.sh +560 -0
  126. package/scripts/release-readiness.py +838 -0
  127. package/scripts/release-readiness.sh +91 -0
  128. package/scripts/run-android-pilot.sh +561 -0
  129. package/scripts/run-ios-pilot.sh +509 -0
  130. package/shims/android/README.md +21 -0
  131. package/shims/android/ZMRShimInstrumentedTest.java +152 -0
  132. package/shims/android/protocol.md +18 -0
  133. package/shims/ios/README.md +50 -0
  134. package/shims/ios/ZMRShim.swift +110 -0
  135. package/shims/ios/ZMRShimUITestCase.swift +518 -0
  136. package/shims/ios/protocol.md +74 -0
  137. package/skills/zmr-mobile-testing/SKILL.md +127 -0
  138. package/src/android.zig +344 -0
  139. package/src/android_device_info.zig +99 -0
  140. package/src/android_emulator.zig +154 -0
  141. package/src/android_screen_recording.zig +112 -0
  142. package/src/android_shell.zig +112 -0
  143. package/src/bundle.zig +124 -0
  144. package/src/bundle_redaction.zig +272 -0
  145. package/src/bundle_tar.zig +123 -0
  146. package/src/cli_devices.zig +97 -0
  147. package/src/cli_doctor.zig +114 -0
  148. package/src/cli_import.zig +70 -0
  149. package/src/cli_info.zig +39 -0
  150. package/src/cli_init.zig +72 -0
  151. package/src/cli_output.zig +467 -0
  152. package/src/cli_run.zig +259 -0
  153. package/src/cli_serve.zig +287 -0
  154. package/src/cli_trace.zig +111 -0
  155. package/src/cli_validate.zig +41 -0
  156. package/src/command.zig +211 -0
  157. package/src/config.zig +305 -0
  158. package/src/config_diagnostics.zig +212 -0
  159. package/src/config_paths.zig +49 -0
  160. package/src/device_registry.zig +37 -0
  161. package/src/doctor.zig +412 -0
  162. package/src/doctor_hints.zig +52 -0
  163. package/src/errors.zig +55 -0
  164. package/src/fake_device.zig +163 -0
  165. package/src/health.zig +28 -0
  166. package/src/importer.zig +343 -0
  167. package/src/importer_json.zig +100 -0
  168. package/src/importer_model.zig +103 -0
  169. package/src/ios.zig +399 -0
  170. package/src/ios_devices.zig +219 -0
  171. package/src/ios_lifecycle.zig +72 -0
  172. package/src/ios_shim.zig +242 -0
  173. package/src/ios_snapshot.zig +20 -0
  174. package/src/json_fields.zig +80 -0
  175. package/src/json_rpc.zig +150 -0
  176. package/src/json_rpc_methods.zig +318 -0
  177. package/src/json_rpc_observation.zig +31 -0
  178. package/src/json_rpc_params.zig +52 -0
  179. package/src/json_rpc_protocol.zig +110 -0
  180. package/src/json_rpc_trace.zig +73 -0
  181. package/src/main.zig +131 -0
  182. package/src/mcp.zig +234 -0
  183. package/src/mcp_protocol.zig +64 -0
  184. package/src/mcp_trace.zig +83 -0
  185. package/src/report.zig +346 -0
  186. package/src/report_html.zig +63 -0
  187. package/src/report_values.zig +27 -0
  188. package/src/run_options.zig +152 -0
  189. package/src/runner.zig +280 -0
  190. package/src/runner_actions.zig +109 -0
  191. package/src/runner_config.zig +6 -0
  192. package/src/runner_diagnostics.zig +268 -0
  193. package/src/runner_events.zig +170 -0
  194. package/src/runner_native.zig +88 -0
  195. package/src/runner_waits.zig +300 -0
  196. package/src/scaffold.zig +472 -0
  197. package/src/scenario.zig +346 -0
  198. package/src/scenario_fields.zig +50 -0
  199. package/src/schema_registry.zig +53 -0
  200. package/src/selector.zig +84 -0
  201. package/src/semantic.zig +171 -0
  202. package/src/trace.zig +315 -0
  203. package/src/trace_json.zig +340 -0
  204. package/src/trace_summary.zig +218 -0
  205. package/src/trace_summary_diagnostic.zig +202 -0
  206. package/src/types.zig +120 -0
  207. package/src/uiautomator.zig +164 -0
  208. package/src/validation.zig +187 -0
  209. package/src/version.zig +22 -0
  210. package/viewer/app.js +373 -0
  211. package/viewer/index.html +126 -0
  212. package/viewer/parser.js +233 -0
  213. package/viewer/styles.css +585 -0
@@ -0,0 +1,88 @@
1
+ # Trace Privacy
2
+
3
+ ZMR traces are debugging artifacts. They can contain sensitive app state even
4
+ when scenario files are generic.
5
+
6
+ Raw trace directories may include:
7
+
8
+ - screenshots
9
+ - screen recordings
10
+ - UI hierarchy XML or JSON
11
+ - visible text and accessibility labels
12
+ - log windows
13
+ - app ids, package/activity names, and timing data
14
+ - action inputs
15
+
16
+ ## Sharing Rules
17
+
18
+ - Disable unnecessary raw artifacts in `.zmr/config.json`:
19
+
20
+ ```json
21
+ {
22
+ "schemaVersion": 1,
23
+ "artifacts": {
24
+ "screenshots": false,
25
+ "hierarchy": false,
26
+ "logs": false
27
+ },
28
+ "redaction": {
29
+ "denylistText": ["customer dob", "internal token"],
30
+ "denylistResourceIds": ["password-field", "ssn"],
31
+ "allowlistResourceIds": ["public-token-label"]
32
+ }
33
+ }
34
+ ```
35
+
36
+ - Add app-specific text and resource-id denylist rules for customer data,
37
+ regulated identifiers, and internal identifiers that ZMR cannot infer.
38
+ - Share redacted `.zmrtrace` bundles, not raw trace directories.
39
+ - Use `zmr export <trace-dir> --out <bundle.zmrtrace> --redact`.
40
+ - Review redacted bundles before attaching them to public issues.
41
+ - Do not publish raw private app screenshots.
42
+ - Do not publish private app screen recordings.
43
+ - Do not publish logs containing credentials, tokens, emails, or customer data.
44
+
45
+ ## Current Redaction Behavior
46
+
47
+ Persisted trace JSON scrubs obvious emails, bearer/JWT-like tokens, sensitive
48
+ JSON keys, app-configured denylisted text, and app-configured sensitive
49
+ resource ids. Resource-id denylist matches redact the id and force that node's
50
+ `text` and `contentDesc` to secret placeholders. App-specific allowlists only
51
+ skip app-specific denylist matches; built-in email/token scrubbing still
52
+ applies.
53
+
54
+ Redacted exports scrub common text secrets, replace PNG screenshots with safe
55
+ placeholder frames, and omit screen recordings. Add `--omit-screenshots` to
56
+ remove screenshot artifacts from the exported bundle entirely. Local trace
57
+ directories are not mutated by export.
58
+
59
+ Pixel-level screenshot masking is not implemented. Raw hierarchy XML can still
60
+ contain app text; disable hierarchy capture or review redacted bundles before
61
+ sharing them outside a trusted machine.
62
+
63
+ ## Screenshot And XML Strategy
64
+
65
+ ZMR uses conservative artifact handling instead of partial visual masking:
66
+
67
+ - Redacted `.zmrtrace` exports replace PNG screenshot files with generated
68
+ placeholder PNGs at the same artifact paths. This keeps trace replay frames
69
+ stable without shipping rendered app pixels.
70
+ - `zmr export --redact --omit-screenshots` omits screenshot files entirely for
71
+ teams that cannot share visual artifacts outside trusted machines.
72
+ - Redacted `.zmrtrace` exports omit screen recording files entirely. This avoids
73
+ shipping unreliable video masking that could miss rendered secrets.
74
+ - Redacted exports scrub text-based artifacts, including JSON events, snapshot
75
+ JSON, logs, reports, and XML-like hierarchy files, for common emails, bearer
76
+ tokens, sensitive key names, and sensitive node attributes.
77
+ - App-specific `.zmr/config.json` redaction rules apply before trace files are
78
+ written. Use them for private resource ids and business-specific text that
79
+ generic scrubbing cannot identify.
80
+ - Set `artifacts.hierarchy: false` for apps whose raw accessibility trees are
81
+ sensitive by default. Selector matching still uses the live tree; this only
82
+ disables raw hierarchy persistence.
83
+ - Keep unredacted local trace directories on trusted developer machines only.
84
+
85
+ This strategy favors predictable placeholders, explicit visual omission, and
86
+ app-owned denylist rules over a best-effort screenshot masker. Pixel-level
87
+ masking can be added later as an opt-in exporter mode once it has visual
88
+ verification tests.
@@ -0,0 +1,256 @@
1
+ # Troubleshooting
2
+
3
+ Start with structured diagnostics instead of reading terminal output by hand:
4
+
5
+ ```bash
6
+ zmr doctor --json
7
+ zmr doctor --strict --json
8
+ zmr validate --json .zmr/android-smoke.json
9
+ zmr explain traces/zmr-android
10
+ ./scripts/release-gate.sh --dry-run
11
+ ```
12
+
13
+ `zmr doctor --json` is the first command to run when setup is unclear. It
14
+ reports Zig, ADB, Android device count, `xcrun`, iOS simulator state, physical
15
+ iOS device state, and configured Android/iOS shim command paths in a
16
+ machine-readable shape that scripts and agents can inspect. Device readiness
17
+ checks report `warning` when ADB sees zero devices, `xcrun` sees zero booted
18
+ iOS simulators, `devicectl` sees zero paired physical iOS devices, or all
19
+ listed physical devices are disconnected/unavailable, with stable
20
+ `setup.android.no_devices`, `setup.ios.no_booted_simulators`,
21
+ `setup.ios.no_physical_devices`, and
22
+ `setup.ios.no_ready_physical_devices` error codes.
23
+ Missing tool and shim checks also include stable setup codes such as
24
+ `setup.adb.not_found` and `setup.android_shim.not_found`.
25
+ By default `doctor` exits zero after printing diagnostics so interactive setup
26
+ can keep going; add `--strict` when CI or install scripts should exit non-zero
27
+ for any warning or missing check.
28
+ When run with `--config .zmr/config.json`, it
29
+ first reports whether the config file itself loaded, then validates configured
30
+ smoke scenario files from `android.smokeScenario` and
31
+ `ios.smokeScenario` so app-local setup mistakes fail before device orchestration
32
+ starts. Config files with wrong types, such as string values for boolean
33
+ artifact controls, unknown fields, such as misspelled `smokeScenario`, or empty strings
34
+ where paths/app ids/script commands are required are reported as `config` warnings.
35
+ Those warnings include `fieldPath` in JSON mode when ZMR can identify the
36
+ invalid `.zmr/config.json` key, plus stable `errorCode` values for setup
37
+ automation.
38
+ Missing scenario files are reported as `missing`; malformed scenario files are
39
+ reported as `warning` with a hint to run `zmr validate` on the configured file.
40
+ Non-`ok` checks include a `"hint"` field with the next concrete remediation
41
+ step. The JSON contract is published at
42
+ `schemas/doctor-output.schema.json`.
43
+
44
+ Top-level CLI failures use stable public error codes instead of Zig stack
45
+ traces. For example, `error[device.command_failed]: device command failed`
46
+ means a platform command such as ADB or `xcrun` failed before ZMR could collect
47
+ a device response; run `zmr doctor --json` next for setup details. An
48
+ `error[cli.unknown_command]: unknown command` response means the command name
49
+ was not recognized; run `zmr help` to inspect the supported CLI surface.
50
+
51
+ The real Android and iOS pilot wrappers run setup preflights before expensive
52
+ install or benchmark work. `scripts/run-android-pilot.sh --skip-emulator`
53
+ reports `no Android device found: <serial>` plus `setup.android.no_devices`
54
+ when the requested serial is not attached. `scripts/run-ios-pilot.sh` reports
55
+ `no booted iOS simulator found` plus `setup.ios.no_booted_simulators` when
56
+ `--device booted` has no target. Physical iOS pilot runs report
57
+ `setup.ios.physical_device_required`, `setup.ios.physical_device_not_found`, or
58
+ `setup.ios.physical_device_not_ready` for invalid or disconnected physical
59
+ device identifiers. Use the `serial` value from `zmr devices --json --platform
60
+ ios --ios-device-type physical`. The not-ready preflight also prints the
61
+ matched device state, such as
62
+ `state: disconnected`, while `zmr doctor --json` reports the broader
63
+ `ios-physical-devices` check with
64
+ `setup.ios.no_physical_devices` when no physical devices are listed, or
65
+ `setup.ios.no_ready_physical_devices` when only disconnected/unavailable
66
+ devices are listed. The no-ready detail includes a state breakdown such as
67
+ `disconnected=1, unavailable=1`. When at least one physical device is ready but
68
+ other devices are listed in unusable states, `doctor` keeps the check `ok` and
69
+ still includes the broader breakdown, for example
70
+ `1 ready physical iOS device(s); 3 listed (disconnected=1, unavailable=1)`.
71
+ In both cases the wrapper also prints the matching `zmr doctor --json` output
72
+ so CI logs contain the next remediation.
73
+ The JSON output also includes numeric `count` and `readyCount` fields on
74
+ device checks, so agents and scripts can branch without parsing the human
75
+ `detail` string.
76
+
77
+ ## Install Or Binary Issues
78
+
79
+ If `zmr` is not found from an app repo, check the npm wrapper resolution order:
80
+
81
+ ```bash
82
+ node -e 'import("zeno-mobile-runner").then(m => console.log(m.resolveBinary()))'
83
+ npx zmr version
84
+ ```
85
+
86
+ The npm wrapper resolves `ZMR_BIN`, then bundled prebuilds, then
87
+ `zig-out/bin/zmr`. If none exist, install Zig and run:
88
+
89
+ ```bash
90
+ npm run build:zmr
91
+ ```
92
+
93
+ For source checkouts on this macOS host, use the explicit macOS 15 target shown
94
+ in the README:
95
+
96
+ ```bash
97
+ zig test src/test_harness.zig -target aarch64-macos.15.0
98
+ zig build-exe src/main.zig -target aarch64-macos.15.0 -O Debug -femit-bin=zig-out/bin/zmr
99
+ ```
100
+
101
+ ## Scenario Issues
102
+
103
+ Validate scenarios before touching a device:
104
+
105
+ ```bash
106
+ zmr validate --json .zmr/android-smoke.json
107
+ ```
108
+
109
+ The JSON output includes `errorCode`, `fieldPath`, `line`, and `column` when ZMR
110
+ can locate the source. Fix schema and selector mistakes there first; device
111
+ state debugging is slower and less reliable when the scenario itself is invalid.
112
+
113
+ ## Android Device Issues
114
+
115
+ For a real emulator run, confirm that ADB sees exactly the device you intend to
116
+ use:
117
+
118
+ ```bash
119
+ adb devices
120
+ zmr doctor --adb adb
121
+ ```
122
+
123
+ If repeated local runs need a known state, prefer ZMR's emulator lifecycle flags
124
+ or the Android pilot wrapper:
125
+
126
+ ```bash
127
+ zmr run .zmr/android-smoke.json \
128
+ --android-avd Small_Phone \
129
+ --create-avd-if-missing \
130
+ --avd-system-image 'system-images;android-35;google_apis;arm64-v8a' \
131
+ --avd-device pixel_6 \
132
+ --restore-snapshot zmr-clean \
133
+ --wait-emulator \
134
+ --device emulator-5554
135
+ ```
136
+
137
+ If selector actions are slow or flaky through shell/UI Automator, install the
138
+ Android shim in the app repo:
139
+
140
+ ```bash
141
+ npx zmr-install-android-shim \
142
+ --app-root . \
143
+ --test-package com.example.mobiletest.test \
144
+ --android-module android/app \
145
+ --gradle-file android/app/build.gradle
146
+ ```
147
+
148
+ Then run with `--android-shim ./.zmr/android-shim` or set
149
+ `tools.androidShimPath` in `.zmr/config.json`.
150
+
151
+ ## iOS Simulator Issues
152
+
153
+ Confirm that `xcrun` sees a booted simulator:
154
+
155
+ ```bash
156
+ xcrun simctl list devices booted
157
+ zmr doctor --xcrun xcrun
158
+ ```
159
+
160
+ The simulator `.app` must be built and installed before launch/open-link flows.
161
+ A device `.ipa` is not simulator-compatible; `scripts/run-ios-pilot.sh` rejects
162
+ that mismatch with `setup.ios.simulator_app_required`. Build an
163
+ `iphonesimulator` `.app` for simulator pilots, or pass `--ios-device-type
164
+ physical` with a signed device artifact and a real physical device identifier.
165
+
166
+ On iOS, `clearState` is best-effort uninstall by bundle id; if the app is
167
+ already missing, ZMR treats the simulator as clean and continues. Simulator
168
+ `launch` is also idempotent when an XCTest shim is configured: if `simctl
169
+ launch` reports an error but `{"cmd":"appState"}` shows the app is already
170
+ running, ZMR treats the app as usable instead of failing the scenario.
171
+
172
+ Selector-grade iOS actions require an XCTest/XCUIAutomation shim command:
173
+
174
+ ```bash
175
+ npx zmr-install-ios-shim \
176
+ --app-root . \
177
+ --scheme SampleUITests \
178
+ --workspace ios/Sample.xcworkspace \
179
+ --app-target SampleApp \
180
+ --bundle-id com.example.mobiletest \
181
+ --patch-xcodeproj
182
+ ```
183
+
184
+ The generated `.zmr/ensure-ios-shim-target.sh` helper resolves the referenced
185
+ `.xcodeproj` from the workspace when there is one project, or when exactly one
186
+ project contains `--app-target`, or when `--bundle-id` disambiguates matching
187
+ app targets. Pass `--project` explicitly for still-ambiguous multi-project
188
+ workspaces. Run with `--ios-shim ./.zmr/ios-shim` or set
189
+ `tools.iosShimPath` in `.zmr/config.json`.
190
+
191
+ If a real iOS run fails with CoreSimulator or Xcode cache errors such as
192
+ `Operation not permitted`, `CoreSimulatorService connection became invalid`, or
193
+ an unexpected workspace/build database error, rerun from a normal terminal or CI
194
+ worker that has access to the user's Xcode, simulator, and DerivedData paths.
195
+ These errors usually mean the host process is sandboxed away from Apple's local
196
+ developer services, not that the scenario JSON is malformed.
197
+ For the generated public iOS demo, `scripts/demo-ios-real.sh --device booted`
198
+ tries available iOS simulators in order when no simulator is already booted.
199
+ This avoids failing the whole demo when one local simulator cannot start
200
+ `launchd_sim`, while still surfacing a setup error if every available simulator
201
+ fails to boot.
202
+
203
+ If a previous Xcode build was interrupted, remove only the app-local ZMR derived
204
+ data path configured for the shim, then rerun the shim once to prewarm it:
205
+
206
+ ```bash
207
+ rm -rf ios/build/ZMRDerivedData
208
+ printf '{"cmd":"appState"}\n' | ./.zmr/ios-shim
209
+ ```
210
+
211
+ Cold shim builds can take several minutes on large apps. Warm runs should use
212
+ the cached `build-for-testing` output and respond much faster.
213
+ `scripts/run-ios-pilot.sh` performs this prewarm automatically when
214
+ `--ios-shim` is set; pass `--skip-shim-prewarm` only when debugging cold-start
215
+ timing.
216
+
217
+ If a freshly booted simulator reports `iOS shim server exited before it became
218
+ ready`, `Early unexpected exit`, or `operation never finished bootstrapping`,
219
+ ZMR retries the shim command once because XCTest can miss its first server
220
+ bootstrap immediately after CoreSimulator startup. Persistent failures after
221
+ that retry should be treated as setup failures: run `xcrun simctl bootstatus
222
+ booted -b`, inspect `.zmr/ios-shim-state/xcodebuild.log`, and prewarm the shim
223
+ with the `appState` command above.
224
+
225
+ ## Trace And Failure Issues
226
+
227
+ When a run fails, do not rerun blindly. Inspect the recorded failure:
228
+
229
+ ```bash
230
+ zmr explain traces/zmr-android
231
+ zmr report traces/zmr-android --out traces/zmr-android/report.html
232
+ ```
233
+
234
+ Timeout diagnostics include the active package/activity, visible text, hidden or
235
+ disabled exact selector candidates, offscreen candidates, and nearest text
236
+ matches from the last snapshot. Redacted bundles replace PNG screenshots with
237
+ placeholder frames and omit screen recordings, so use them for sharing:
238
+
239
+ ```bash
240
+ zmr export traces/zmr-android --out traces/zmr-android-redacted.zmrtrace --redact
241
+ ```
242
+
243
+ ## Release Gate Issues
244
+
245
+ To see exactly what the local release gate will run:
246
+
247
+ ```bash
248
+ ./scripts/release-gate.sh --dry-run
249
+ ```
250
+
251
+ The gate intentionally prints real Android and iOS pilot commands at the end
252
+ instead of running them by default. Those pilots need app builds and devices, so
253
+ run them explicitly before publishing reliability or performance claims. Use
254
+ `zmr-pilot-gate --dry-run` with the same app path flags from an app repo or
255
+ release machine to inspect the combined Android+iOS external gate command
256
+ before starting the real runs.
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "Sample App auth probe",
3
+ "appId": "com.example.mobiletest",
4
+ "steps": [
5
+ {
6
+ "action": "stop"
7
+ },
8
+ {
9
+ "action": "openLink",
10
+ "url": "exampleapp:///e2e-auth?probe=1"
11
+ },
12
+ {
13
+ "action": "waitAny",
14
+ "selectors": [
15
+ {
16
+ "text": "E2E auth probe"
17
+ },
18
+ {
19
+ "text": "Deep link received:"
20
+ },
21
+ {
22
+ "text": "Development servers"
23
+ }
24
+ ],
25
+ "timeoutMs": 60000
26
+ },
27
+ {
28
+ "action": "whenVisible",
29
+ "selector": {
30
+ "text": "Deep link received:"
31
+ },
32
+ "steps": [
33
+ {
34
+ "action": "tap",
35
+ "selector": {
36
+ "contentDescContains": "Sample App Test on"
37
+ },
38
+ "optional": true
39
+ },
40
+ {
41
+ "action": "tap",
42
+ "selector": {
43
+ "contentDesc": "Sample App Test"
44
+ },
45
+ "optional": true
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "action": "whenVisible",
51
+ "selector": {
52
+ "text": "Development servers"
53
+ },
54
+ "steps": [
55
+ {
56
+ "action": "tap",
57
+ "selector": {
58
+ "contentDescContains": "Sample App Test on"
59
+ },
60
+ "optional": true
61
+ },
62
+ {
63
+ "action": "tap",
64
+ "selector": {
65
+ "contentDesc": "Sample App Test, http://10.0.2.2:8081"
66
+ },
67
+ "optional": true
68
+ },
69
+ {
70
+ "action": "tap",
71
+ "selector": {
72
+ "text": "http://10.0.2.2:8081"
73
+ },
74
+ "optional": true
75
+ }
76
+ ]
77
+ },
78
+ {
79
+ "action": "waitVisible",
80
+ "selector": {
81
+ "text": "E2E auth probe"
82
+ },
83
+ "timeoutMs": 120000
84
+ },
85
+ {
86
+ "action": "snapshot"
87
+ }
88
+ ]
89
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "Android app error-state smoke",
3
+ "appId": "com.example.mobiletest",
4
+ "steps": [
5
+ { "action": "launch" },
6
+ { "action": "openLink", "url": "exampleapp://error-demo?mode=offline" },
7
+ { "action": "waitAny", "selectors": [{ "textContains": "Try again" }, { "textContains": "offline" }, { "textContains": "Something went wrong" }], "timeoutMs": 15000 },
8
+ { "action": "snapshot" },
9
+ { "action": "tap", "selector": { "textContains": "Try again" }, "optional": true },
10
+ { "action": "waitNotVisible", "selector": { "textContains": "Loading" }, "timeoutMs": 10000 },
11
+ { "action": "assertHealthy" }
12
+ ]
13
+ }
@@ -0,0 +1,192 @@
1
+ {
2
+ "name": "Sample App email login and referral smoke",
3
+ "appId": "com.example.mobiletest",
4
+ "steps": [
5
+ {
6
+ "action": "stop"
7
+ },
8
+ {
9
+ "action": "clearState"
10
+ },
11
+ {
12
+ "action": "launch"
13
+ },
14
+ {
15
+ "action": "waitAny",
16
+ "selectors": [
17
+ { "textContains": "Development servers" },
18
+ { "textContains": "Downloading 100%" },
19
+ { "text": "Or sign up via email here" },
20
+ { "text": "Sign in with email" },
21
+ { "textContains": "Dashboard" },
22
+ { "textContains": "Request a feature" },
23
+ { "textContains": "Earn points" },
24
+ { "textContains": "Spend points" },
25
+ { "textContains": "points" }
26
+ ],
27
+ "timeoutMs": 60000
28
+ },
29
+ {
30
+ "action": "whenVisible",
31
+ "selector": { "textContains": "Development servers" },
32
+ "steps": [
33
+ {
34
+ "action": "tap",
35
+ "selector": { "contentDescContains": "Sample App Test on" },
36
+ "optional": true
37
+ },
38
+ {
39
+ "action": "tap",
40
+ "selector": { "contentDesc": "Sample App Test, http://10.0.2.2:8081" },
41
+ "optional": true
42
+ },
43
+ {
44
+ "action": "tap",
45
+ "selector": { "text": "http://10.0.2.2:8081" },
46
+ "optional": true
47
+ },
48
+ {
49
+ "action": "waitNotVisible",
50
+ "selector": { "textContains": "Development servers" },
51
+ "timeoutMs": 60000,
52
+ "optional": true
53
+ }
54
+ ]
55
+ },
56
+ {
57
+ "action": "whenVisible",
58
+ "selector": { "text": "Continue" },
59
+ "timeoutMs": 120000,
60
+ "steps": [
61
+ {
62
+ "action": "tap",
63
+ "selector": { "text": "Continue" }
64
+ },
65
+ {
66
+ "action": "whenVisible",
67
+ "selector": { "textContains": "Connected to:" },
68
+ "steps": [
69
+ {
70
+ "action": "pressBack"
71
+ }
72
+ ]
73
+ }
74
+ ]
75
+ },
76
+ {
77
+ "action": "whenVisible",
78
+ "selector": { "textContains": "Connected to:" },
79
+ "timeoutMs": 10000,
80
+ "steps": [
81
+ {
82
+ "action": "pressBack"
83
+ }
84
+ ]
85
+ },
86
+ {
87
+ "action": "waitAny",
88
+ "selectors": [
89
+ { "id": "email-login-email-input" },
90
+ { "contentDesc": "Open email sign up" },
91
+ { "text": "Or sign up via email here" },
92
+ { "textContains": "Welcome" },
93
+ { "textContains": "Sample landing" },
94
+ { "text": "Sign in with email" },
95
+ { "textContains": "Dashboard" },
96
+ { "textContains": "Request a feature" },
97
+ { "textContains": "Earn points" },
98
+ { "textContains": "Spend points" },
99
+ { "textContains": "points" }
100
+ ],
101
+ "timeoutMs": 60000
102
+ },
103
+ {
104
+ "action": "whenVisible",
105
+ "selector": { "textContains": "Downloading 100%" },
106
+ "steps": [
107
+ {
108
+ "action": "waitNotVisible",
109
+ "selector": { "textContains": "Downloading 100%" },
110
+ "timeoutMs": 180000
111
+ }
112
+ ]
113
+ },
114
+ {
115
+ "action": "whenVisible",
116
+ "selector": { "contentDesc": "Use E2E sign in" },
117
+ "steps": [
118
+ {
119
+ "action": "tap",
120
+ "selector": { "contentDesc": "Use E2E sign in" }
121
+ },
122
+ {
123
+ "action": "waitAny",
124
+ "selectors": [
125
+ { "textContains": "Signing in to your account" },
126
+ { "textContains": "Requesting test token" },
127
+ { "textContains": "Bootstrapping session" },
128
+ { "textContains": "Routing to app" },
129
+ { "textContains": "Dashboard" },
130
+ { "textContains": "Request a feature" },
131
+ { "textContains": "Invite teammates" },
132
+ { "textContains": "Earn points" },
133
+ { "textContains": "Spend points" },
134
+ { "textContains": "points" }
135
+ ],
136
+ "timeoutMs": 45000
137
+ }
138
+ ]
139
+ },
140
+ {
141
+ "action": "waitAny",
142
+ "selectors": [
143
+ { "textContains": "Dashboard" },
144
+ { "textContains": "Request a feature" },
145
+ { "textContains": "Invite teammates" },
146
+ { "textContains": "Earn points" },
147
+ { "textContains": "Spend points" },
148
+ { "textContains": "points" }
149
+ ],
150
+ "timeoutMs": 90000
151
+ },
152
+ {
153
+ "action": "openLink",
154
+ "url": "exampleapp:///referrals/invite"
155
+ },
156
+ {
157
+ "action": "waitAny",
158
+ "selectors": [
159
+ { "textContains": "Invite a teammate and both earn" },
160
+ { "textContains": "Share my link" },
161
+ { "textContains": "Your Referral Code" },
162
+ { "textContains": "No referrals yet" },
163
+ { "text": "Retry" },
164
+ { "textContains": "Request timed out" }
165
+ ],
166
+ "timeoutMs": 20000
167
+ },
168
+ {
169
+ "action": "whenVisible",
170
+ "selector": { "text": "Retry" },
171
+ "steps": [
172
+ {
173
+ "action": "tap",
174
+ "selector": { "text": "Retry" }
175
+ },
176
+ {
177
+ "action": "waitAny",
178
+ "selectors": [
179
+ { "textContains": "Invite a teammate and both earn" },
180
+ { "textContains": "Share my link" },
181
+ { "textContains": "Your Referral Code" },
182
+ { "textContains": "No referrals yet" }
183
+ ],
184
+ "timeoutMs": 60000
185
+ }
186
+ ]
187
+ },
188
+ {
189
+ "action": "snapshot"
190
+ }
191
+ ]
192
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "Android app onboarding smoke",
3
+ "appId": "com.example.mobiletest",
4
+ "steps": [
5
+ { "action": "clearState" },
6
+ { "action": "launch" },
7
+ { "action": "waitVisible", "selector": { "textContains": "Welcome" }, "timeoutMs": 15000 },
8
+ { "action": "tap", "selector": { "textContains": "Get started" } },
9
+ { "action": "waitAny", "selectors": [{ "textContains": "Home" }, { "textContains": "Sign in" }], "timeoutMs": 15000 },
10
+ { "action": "snapshot" }
11
+ ]
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "Android referral deep link smoke",
3
+ "appId": "com.example.mobiletest",
4
+ "steps": [
5
+ { "action": "clearState" },
6
+ { "action": "openLink", "url": "exampleapp://invite?code=DEMO-CODE" },
7
+ { "action": "waitVisible", "selector": { "textContains": "Invite" }, "timeoutMs": 15000 },
8
+ { "action": "scrollUntilVisible", "selector": { "id": "invite-card" }, "direction": "down", "timeoutMs": 10000 },
9
+ { "action": "assertVisible", "selector": { "id": "invite-card" } },
10
+ { "action": "snapshot" }
11
+ ]
12
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "ZMR Android shim selector demo",
3
+ "appId": "com.example.mobiletest",
4
+ "steps": [
5
+ { "action": "launch" },
6
+ {
7
+ "action": "waitVisible",
8
+ "selector": { "text": "Continue" },
9
+ "timeoutMs": 1000
10
+ },
11
+ {
12
+ "action": "tap",
13
+ "selector": { "resourceId": "continue_button" }
14
+ },
15
+ { "action": "typeText", "text": "hello" },
16
+ { "action": "hideKeyboard" },
17
+ { "action": "snapshot" }
18
+ ]
19
+ }