zeno-mobile-runner 0.1.2 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +162 -3
- package/FEATURES.md +50 -7
- package/README.md +133 -7
- package/build.zig.zon +3 -3
- package/clients/README.md +60 -3
- package/clients/go/README.md +12 -0
- package/clients/go/zmr/client.go +142 -0
- package/clients/kotlin/README.md +18 -1
- package/clients/kotlin/build.gradle.kts +1 -1
- package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +76 -1
- package/clients/python/README.md +19 -0
- package/clients/python/pyproject.toml +1 -1
- package/clients/python/zmr_client.py +33 -0
- package/clients/rust/Cargo.lock +1 -1
- package/clients/rust/Cargo.toml +1 -1
- package/clients/rust/README.md +25 -1
- package/clients/rust/src/lib.rs +201 -0
- package/clients/swift/README.md +18 -0
- package/clients/swift/Sources/ZMRClient/ZMRClient.swift +82 -0
- package/clients/typescript/README.md +16 -0
- package/clients/typescript/index.d.ts +12 -0
- package/clients/typescript/index.mjs +16 -0
- package/clients/typescript/package.json +1 -1
- package/docs/agent-discovery.md +202 -0
- package/docs/ai-agents.md +87 -6
- package/docs/benchmarking.md +10 -3
- package/docs/clients.md +10 -6
- package/docs/demo.md +4 -0
- package/docs/expo-smoke.md +79 -0
- package/docs/install.md +3 -2
- package/docs/npm.md +58 -4
- package/docs/production-readiness.md +123 -0
- package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
- package/docs/protocol.md +215 -16
- package/docs/scenario-authoring.md +3 -0
- package/docs/troubleshooting.md +1 -1
- package/npm/agents.mjs +16 -0
- package/npm/build-zmr.mjs +1 -1
- package/npm/commands.mjs +9 -5
- package/npm/postinstall.mjs +28 -2
- package/npm/verify-publish.mjs +36 -0
- package/package.json +2 -1
- package/prebuilds/darwin-arm64/zmr +0 -0
- package/prebuilds/darwin-x64/zmr +0 -0
- package/prebuilds/linux-arm64/zmr +0 -0
- package/prebuilds/linux-x64/zmr +0 -0
- package/schemas/README.md +4 -0
- package/schemas/discover-output.schema.json +83 -0
- package/schemas/draft-output.schema.json +58 -0
- package/schemas/explore-output.schema.json +94 -0
- package/schemas/inspect-output.schema.json +88 -0
- package/schemas/run-output.schema.json +2 -0
- package/scripts/install-ios-shim.sh +79 -14
- package/scripts/release-readiness.py +43 -0
- package/scripts/run-android-pilot.sh +35 -9
- package/scripts/run-ios-pilot.sh +11 -4
- package/shims/ios/ZMRShim.swift +3 -0
- package/shims/ios/ZMRShimUITestCase.swift +41 -11
- package/skills/zmr-mobile-testing/SKILL.md +28 -3
- package/src/cli_discover.zig +239 -0
- package/src/cli_draft.zig +924 -0
- package/src/cli_explore.zig +136 -0
- package/src/cli_inspect.zig +310 -0
- package/src/cli_output.zig +26 -2
- package/src/cli_run.zig +28 -0
- package/src/cli_trace.zig +8 -0
- package/src/errors.zig +9 -0
- package/src/ios.zig +11 -4
- package/src/ios_lifecycle.zig +36 -0
- package/src/ios_shim.zig +42 -0
- package/src/json_rpc_methods.zig +85 -11
- package/src/json_rpc_params.zig +8 -0
- package/src/json_rpc_protocol.zig +1 -1
- package/src/json_rpc_trace.zig +112 -0
- package/src/main.zig +24 -2
- package/src/mcp.zig +209 -6
- package/src/mcp_protocol.zig +29 -1
- package/src/mcp_trace.zig +126 -4
- package/src/report.zig +186 -0
- package/src/runner.zig +26 -4
- package/src/runner_actions.zig +10 -0
- package/src/runner_diagnostics.zig +31 -1
- package/src/runner_events.zig +70 -7
- package/src/runner_native.zig +17 -1
- package/src/runner_waits.zig +82 -19
- package/src/scaffold.zig +28 -12
- package/src/scenario.zig +32 -4
- package/src/schema_registry.zig +4 -0
- package/src/version.zig +1 -1
|
@@ -6,6 +6,28 @@ public enum ZMRError: Error {
|
|
|
6
6
|
case rpcError([String: Any])
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
public struct TraceDiscoverOptions {
|
|
10
|
+
public var includeActions: Bool
|
|
11
|
+
public var validate: Bool
|
|
12
|
+
public var force: Bool
|
|
13
|
+
public var name: String?
|
|
14
|
+
public var appId: String?
|
|
15
|
+
|
|
16
|
+
public init(
|
|
17
|
+
includeActions: Bool = false,
|
|
18
|
+
validate: Bool = false,
|
|
19
|
+
force: Bool = false,
|
|
20
|
+
name: String? = nil,
|
|
21
|
+
appId: String? = nil
|
|
22
|
+
) {
|
|
23
|
+
self.includeActions = includeActions
|
|
24
|
+
self.validate = validate
|
|
25
|
+
self.force = force
|
|
26
|
+
self.name = name
|
|
27
|
+
self.appId = appId
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
9
31
|
public final class ZMRClient {
|
|
10
32
|
private let process: Process
|
|
11
33
|
private let input: FileHandle
|
|
@@ -98,6 +120,66 @@ public final class ZMRClient {
|
|
|
98
120
|
return result
|
|
99
121
|
}
|
|
100
122
|
|
|
123
|
+
public func validateScenario(path: String) throws -> [String: Any] {
|
|
124
|
+
guard let result = try call("scenario.validate", params: ["path": path]) as? [String: Any] else {
|
|
125
|
+
throw ZMRError.invalidResponse
|
|
126
|
+
}
|
|
127
|
+
return result
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public func discoverTrace(out: String, options: TraceDiscoverOptions = TraceDiscoverOptions()) throws -> [String: Any] {
|
|
131
|
+
var params: [String: Any] = ["out": out]
|
|
132
|
+
if options.includeActions {
|
|
133
|
+
params["includeActions"] = true
|
|
134
|
+
}
|
|
135
|
+
if options.validate {
|
|
136
|
+
params["validate"] = true
|
|
137
|
+
}
|
|
138
|
+
if options.force {
|
|
139
|
+
params["force"] = true
|
|
140
|
+
}
|
|
141
|
+
if let name = options.name {
|
|
142
|
+
params["name"] = name
|
|
143
|
+
}
|
|
144
|
+
if let appId = options.appId {
|
|
145
|
+
params["appId"] = appId
|
|
146
|
+
}
|
|
147
|
+
guard let result = try call("trace.discover", params: params) as? [String: Any] else {
|
|
148
|
+
throw ZMRError.invalidResponse
|
|
149
|
+
}
|
|
150
|
+
return result
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public func exploreTrace(out: String, goal: String, options: TraceDiscoverOptions = TraceDiscoverOptions()) throws -> [String: Any] {
|
|
154
|
+
var params: [String: Any] = ["out": out, "goal": goal]
|
|
155
|
+
if options.includeActions {
|
|
156
|
+
params["includeActions"] = true
|
|
157
|
+
}
|
|
158
|
+
if options.validate {
|
|
159
|
+
params["validate"] = true
|
|
160
|
+
}
|
|
161
|
+
if options.force {
|
|
162
|
+
params["force"] = true
|
|
163
|
+
}
|
|
164
|
+
if let name = options.name {
|
|
165
|
+
params["name"] = name
|
|
166
|
+
}
|
|
167
|
+
if let appId = options.appId {
|
|
168
|
+
params["appId"] = appId
|
|
169
|
+
}
|
|
170
|
+
guard let result = try call("trace.explore", params: params) as? [String: Any] else {
|
|
171
|
+
throw ZMRError.invalidResponse
|
|
172
|
+
}
|
|
173
|
+
return result
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public func explainTrace() throws -> [String: Any] {
|
|
177
|
+
guard let result = try call("trace.explain", params: [:]) as? [String: Any] else {
|
|
178
|
+
throw ZMRError.invalidResponse
|
|
179
|
+
}
|
|
180
|
+
return result
|
|
181
|
+
}
|
|
182
|
+
|
|
101
183
|
private func readLineData() throws -> Data {
|
|
102
184
|
var data = Data()
|
|
103
185
|
while true {
|
|
@@ -22,8 +22,24 @@ try {
|
|
|
22
22
|
await zmr.waitUntil({ text: "E2E auth probe" }, { timeoutMs: 30000 });
|
|
23
23
|
const snapshot = await zmr.snapshot();
|
|
24
24
|
const events = await zmr.traceEvents(0, { limit: 100 });
|
|
25
|
+
const explanation = await zmr.explainTrace();
|
|
26
|
+
const discovered = await zmr.discoverTrace(".zmr/discovered/agent-session.json", {
|
|
27
|
+
includeActions: true,
|
|
28
|
+
validate: true,
|
|
29
|
+
force: true,
|
|
30
|
+
});
|
|
31
|
+
const explored = await zmr.exploreTrace(".zmr/discovered/agent-goal.json", "find a stable login smoke", {
|
|
32
|
+
includeActions: true,
|
|
33
|
+
validate: true,
|
|
34
|
+
force: true,
|
|
35
|
+
});
|
|
36
|
+
const validation = await zmr.validateScenario(discovered.out);
|
|
25
37
|
console.log(snapshot.nodes);
|
|
26
38
|
console.log(events.events.length);
|
|
39
|
+
console.log(explanation.status);
|
|
40
|
+
console.log(discovered.out);
|
|
41
|
+
console.log(explored.reviewRequired);
|
|
42
|
+
console.log(validation.ok);
|
|
27
43
|
await zmr.exportTrace("traces/agent-session-redacted.zmrtrace", { redact: true, omitScreenshots: true });
|
|
28
44
|
} finally {
|
|
29
45
|
await zmr.close();
|
|
@@ -99,6 +99,14 @@ export interface Capabilities {
|
|
|
99
99
|
methods: string[];
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
export interface TraceDiscoverOptions {
|
|
103
|
+
includeActions?: boolean;
|
|
104
|
+
validate?: boolean;
|
|
105
|
+
force?: boolean;
|
|
106
|
+
name?: string;
|
|
107
|
+
appId?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
102
110
|
export interface DeviceInfo {
|
|
103
111
|
serial: string;
|
|
104
112
|
state: string;
|
|
@@ -130,8 +138,12 @@ export interface ZmrClient {
|
|
|
130
138
|
assertVisible(selector: Selector, options?: { timeoutMs?: number }): Promise<boolean>;
|
|
131
139
|
assertNotVisible(selector: Selector, options?: { timeoutMs?: number }): Promise<boolean>;
|
|
132
140
|
assertHealthy(options?: { timeoutMs?: number }): Promise<boolean>;
|
|
141
|
+
validateScenario(path: string): Promise<Record<string, unknown>>;
|
|
133
142
|
exportTrace(out: string, options?: { redact?: boolean; omitScreenshots?: boolean }): Promise<Record<string, unknown>>;
|
|
134
143
|
traceEvents(afterSeq?: number, options?: { limit?: number }): Promise<Record<string, unknown>>;
|
|
144
|
+
explainTrace(): Promise<Record<string, unknown>>;
|
|
145
|
+
discoverTrace(out: string, options?: TraceDiscoverOptions): Promise<Record<string, unknown>>;
|
|
146
|
+
exploreTrace(out: string, goal: string, options?: TraceDiscoverOptions): Promise<Record<string, unknown>>;
|
|
135
147
|
close(): Promise<void>;
|
|
136
148
|
}
|
|
137
149
|
|
|
@@ -146,6 +146,10 @@ export class ZmrClient {
|
|
|
146
146
|
return this.request("assert.healthy", options);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
validateScenario(path) {
|
|
150
|
+
return this.request("scenario.validate", { path });
|
|
151
|
+
}
|
|
152
|
+
|
|
149
153
|
exportTrace(out, options = {}) {
|
|
150
154
|
return this.request("trace.export", { out, ...options });
|
|
151
155
|
}
|
|
@@ -154,6 +158,18 @@ export class ZmrClient {
|
|
|
154
158
|
return this.request("trace.events", { afterSeq, ...options });
|
|
155
159
|
}
|
|
156
160
|
|
|
161
|
+
explainTrace() {
|
|
162
|
+
return this.request("trace.explain", {});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
discoverTrace(out, options = {}) {
|
|
166
|
+
return this.request("trace.discover", { out, ...options });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
exploreTrace(out, goal, options = {}) {
|
|
170
|
+
return this.request("trace.explore", { out, goal, ...options });
|
|
171
|
+
}
|
|
172
|
+
|
|
157
173
|
async close() {
|
|
158
174
|
if (this.#closed) return;
|
|
159
175
|
this.#closed = true;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Agent Discovery
|
|
2
|
+
|
|
3
|
+
ZMR supports agent-led discovery today through its JSON-RPC and MCP interfaces,
|
|
4
|
+
trace events, semantic snapshot artifacts, guarded trace exploration, in-band
|
|
5
|
+
trace discovery, and offline scenario drafting. An external agent can observe
|
|
6
|
+
the app, choose typed actions, inspect trace events, ask ZMR to write a small
|
|
7
|
+
repeatable scenario from the trace, and then edit it as it learns a flow.
|
|
8
|
+
|
|
9
|
+
`zmr explore` is the built-in review-first exploration command. It is
|
|
10
|
+
trace-backed, not an unbounded crawler: it does not launch devices, invent
|
|
11
|
+
missing actions, discover credentials, or commit files. Keep autonomous
|
|
12
|
+
planning in the agent, and keep ZMR as the deterministic mobile control plane.
|
|
13
|
+
|
|
14
|
+
## Recommended Loop
|
|
15
|
+
|
|
16
|
+
1. Validate local setup:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
zmr inspect --json --dir .
|
|
20
|
+
zmr doctor --json --config .zmr/config.json
|
|
21
|
+
zmr validate --json .zmr/ios-smoke.json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. Start a live session:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Agents that speak MCP can use:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
3. Call `runner.capabilities`, then `session.create`.
|
|
37
|
+
4. Call `observe.semanticSnapshot` before choosing an action.
|
|
38
|
+
5. Choose one typed action, such as `ui.tap`, `ui.type`, `app.openLink`, or
|
|
39
|
+
`wait.until`.
|
|
40
|
+
6. Observe again and inspect `trace.events`.
|
|
41
|
+
7. If you used `zmr run --json --trace-dir`, read `nextCommands`; traced run
|
|
42
|
+
summaries include HTML/JUnit report output and the matching
|
|
43
|
+
`zmr discover --from-trace` command.
|
|
44
|
+
8. If you want the CLI run itself to write the candidate, use:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
zmr run .zmr/login-smoke.json \
|
|
48
|
+
--trace-dir traces/zmr-agent \
|
|
49
|
+
--discover-out .zmr/discovered/replay-smoke.json \
|
|
50
|
+
--json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The run response embeds `discovery`, the same JSON payload returned by
|
|
54
|
+
`zmr discover --json`, including `replay` coverage metadata for converted
|
|
55
|
+
and skipped trace actions.
|
|
56
|
+
9. Generate a reviewable scenario candidate from the trace. For CLI-driven
|
|
57
|
+
agent loops, prefer `zmr explore` so the goal and guardrails travel with the
|
|
58
|
+
machine-readable result:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
zmr explore --from-trace traces/zmr-agent \
|
|
62
|
+
--out .zmr/discovered/login-smoke.json \
|
|
63
|
+
--goal "find a stable login smoke" \
|
|
64
|
+
--include-actions \
|
|
65
|
+
--validate \
|
|
66
|
+
--json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The output is covered by `schemas/explore-output.schema.json` and includes
|
|
70
|
+
`autonomous:false`, `reviewRequired:true`, `guardrails`, replay coverage,
|
|
71
|
+
validation, and deterministic next commands.
|
|
72
|
+
|
|
73
|
+
10. Use live trace exploration when the agent should keep the goal attached to
|
|
74
|
+
the generated draft. JSON-RPC agents can call `trace.explore`:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{"jsonrpc":"2.0","id":7,"method":"trace.explore","params":{"out":".zmr/discovered/login-smoke.json","goal":"find a stable login smoke","includeActions":true,"validate":true,"force":true}}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
MCP agents can call `trace_explore` with `out`, `goal`,
|
|
81
|
+
`includeActions`, `validate`, and `force`. The response includes
|
|
82
|
+
`autonomous:false`, `reviewRequired:true`, and `guardrails`.
|
|
83
|
+
|
|
84
|
+
11. Use the lower-level trace discovery primitive when the agent already owns
|
|
85
|
+
goal tracking. JSON-RPC agents can
|
|
86
|
+
call `trace.discover`:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{"jsonrpc":"2.0","id":7,"method":"trace.discover","params":{"out":".zmr/discovered/replay-smoke.json","includeActions":true,"validate":true,"force":true}}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
MCP agents can call `trace_discover` with the same `out`,
|
|
93
|
+
`includeActions`, `validate`, and `force` arguments. The offline CLI
|
|
94
|
+
equivalent is:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
zmr discover --from-trace traces/zmr-agent \
|
|
98
|
+
--out .zmr/discovered/replay-smoke.json \
|
|
99
|
+
--include-actions \
|
|
100
|
+
--validate \
|
|
101
|
+
--json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`zmr discover` writes a scenario from trace evidence and, with
|
|
105
|
+
`--validate`, immediately proves that the generated file is syntactically
|
|
106
|
+
runnable by ZMR. It is still review-first: it does not crawl, invent missing
|
|
107
|
+
actions, discover credentials, or commit the scenario.
|
|
108
|
+
Read the `replay` object before trusting coverage: `eventCount` is the
|
|
109
|
+
trace action event count considered for replay, `stepCount` is the number of
|
|
110
|
+
generated replay steps, and `skippedEventCount` is the number of events left
|
|
111
|
+
out.
|
|
112
|
+
|
|
113
|
+
11. After editing a generated scenario, validate it in-band with JSON-RPC:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{"jsonrpc":"2.0","id":8,"method":"scenario.validate","params":{"path":".zmr/discovered/replay-smoke.json"}}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
MCP agents can call `scenario_validate` with the same `path` argument. The
|
|
120
|
+
result matches `zmr validate --json`, including field paths and source
|
|
121
|
+
locations for invalid files.
|
|
122
|
+
|
|
123
|
+
12. Use the lower-level draft primitive when you want separate surface and
|
|
124
|
+
replay files. For a conservative surface-smoke scenario:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
zmr draft --from-trace traces/zmr-agent \
|
|
128
|
+
--out .zmr/discovered/surface-smoke.json \
|
|
129
|
+
--json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The draft contains `launch`, `snapshot`, and `assertVisible` steps from
|
|
133
|
+
stable visible selectors. It does not tap, type, crawl, or commit anything.
|
|
134
|
+
If the trace contains successful typed actions and you want a replayable
|
|
135
|
+
starting point, include those supported events explicitly:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
zmr draft --from-trace traces/zmr-agent \
|
|
139
|
+
--out .zmr/discovered/replay-smoke.json \
|
|
140
|
+
--include-actions \
|
|
141
|
+
--json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Replay drafts include only supported events with stable replay data, such as
|
|
145
|
+
launch, deep links, selector taps, selector text entry, back, keyboard hiding,
|
|
146
|
+
coordinate-complete swipes, selector/timeout-preserving waits, and
|
|
147
|
+
direction/timeout-preserving selector scrolls, selector/timeout-preserving
|
|
148
|
+
`assertVisible` and `assertNotVisible`, `assertNoneVisible` selector arrays,
|
|
149
|
+
and timed `assertHealthy` checks. Native selector wait traces also retain
|
|
150
|
+
timeout context for successful waits and timeout diagnostics.
|
|
151
|
+
Unsupported events stay out of the scenario and are reported as warnings.
|
|
152
|
+
|
|
153
|
+
13. Edit the draft, discovery, or exploration output into a candidate flow, for example
|
|
154
|
+
`.zmr/discovered/login-smoke.json`, by copying only steps that were observed
|
|
155
|
+
and understood.
|
|
156
|
+
14. Validate the candidate scenario:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
zmr validate --json .zmr/discovered/login-smoke.json
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
15. Re-run it deterministically:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
zmr run .zmr/discovered/login-smoke.json \
|
|
166
|
+
--platform ios \
|
|
167
|
+
--device booted \
|
|
168
|
+
--trace-dir traces/zmr-login-smoke \
|
|
169
|
+
--json
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
16. Export a redacted bundle before sharing artifacts:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
zmr export traces/zmr-login-smoke \
|
|
176
|
+
--out traces/zmr-login-smoke-redacted.zmrtrace \
|
|
177
|
+
--redact
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Guardrails
|
|
181
|
+
|
|
182
|
+
- Set a step budget and a time budget before discovery starts.
|
|
183
|
+
- Restrict discovery to known app ids, deep-link schemes, and test accounts.
|
|
184
|
+
- Do not ask an agent to discover credentials or secrets.
|
|
185
|
+
- Prefer accessibility identifiers, resource ids, stable labels, and exact text
|
|
186
|
+
over coordinates.
|
|
187
|
+
- Require human review before committing generated tests.
|
|
188
|
+
- Treat `zmr explore` output as a starting point, not as a production-ready
|
|
189
|
+
flow.
|
|
190
|
+
- Treat `zmr discover` output as a starting point, not as a production-ready
|
|
191
|
+
flow.
|
|
192
|
+
- Treat `zmr draft` output as a starting point, not as a production-ready flow.
|
|
193
|
+
- Use `--include-actions` only after reviewing the trace events that produced
|
|
194
|
+
the replay draft.
|
|
195
|
+
- Redact traces before sharing them outside the local team.
|
|
196
|
+
|
|
197
|
+
## Current Shape
|
|
198
|
+
|
|
199
|
+
`zmr explore` is the first shipped goal-carrying command in this loop. It still
|
|
200
|
+
requires an existing trace because the current product direction is to keep
|
|
201
|
+
scenario generation explicit, reviewable, and trace-backed before any future
|
|
202
|
+
goal-driven crawler can safely act inside an app.
|
package/docs/ai-agents.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# AI Agent Guide
|
|
2
2
|
|
|
3
3
|
ZMR is built for external agents. The runner provides device state, typed
|
|
4
|
-
actions, waits, assertions, and trace export; the agent
|
|
4
|
+
actions, waits, assertions, trace explanation, and trace export; the agent
|
|
5
|
+
decides the next step.
|
|
5
6
|
|
|
6
7
|
## Agent Setup Loop
|
|
7
8
|
|
|
8
9
|
Start inside the app checkout:
|
|
9
10
|
|
|
10
11
|
```bash
|
|
12
|
+
zmr inspect --json --dir .
|
|
11
13
|
zmr doctor --json --config .zmr/config.json
|
|
12
14
|
zmr validate --json .zmr/android-smoke.json
|
|
13
15
|
zmr validate --json .zmr/ios-smoke.json
|
|
@@ -18,6 +20,10 @@ Use `zmr doctor --strict --json` in CI or setup flows that should fail on any
|
|
|
18
20
|
warning. Prefer JSON output for automation because it includes stable error
|
|
19
21
|
codes, field paths, and remediation hints.
|
|
20
22
|
|
|
23
|
+
Use `zmr inspect --json --dir .` first when an agent enters a repo. It is a
|
|
24
|
+
read-only handoff with config status, generated agent instruction status,
|
|
25
|
+
platform smoke scenario paths, safe next commands, and explicit claim limits.
|
|
26
|
+
|
|
21
27
|
## Live JSON-RPC Session
|
|
22
28
|
|
|
23
29
|
Agents should prefer `zmr serve` for interactive work:
|
|
@@ -35,8 +41,15 @@ Recommended flow:
|
|
|
35
41
|
4. Choose one typed action or assertion.
|
|
36
42
|
5. Let ZMR settle, then observe again.
|
|
37
43
|
6. Poll `trace.events` during long runs.
|
|
38
|
-
7. Call `trace.
|
|
39
|
-
|
|
44
|
+
7. Call `trace.explain` when you need the active trace status, failure
|
|
45
|
+
diagnostic, or next commands.
|
|
46
|
+
8. Call `trace.explore` when you want a review-required scenario candidate for
|
|
47
|
+
a stated goal from the active trace.
|
|
48
|
+
9. Call `trace.discover` when you want a lower-level reviewable scenario
|
|
49
|
+
candidate from the active trace and the agent already owns goal tracking.
|
|
50
|
+
10. Call `scenario.validate` after editing generated scenario files.
|
|
51
|
+
11. Call `trace.export` with `redact: true` before sharing artifacts.
|
|
52
|
+
12. Call `session.close`.
|
|
40
53
|
|
|
41
54
|
Do not parse screenshots or terminal text when the same fact is available from
|
|
42
55
|
snapshot nodes, action results, CLI JSON, or trace events.
|
|
@@ -47,6 +60,14 @@ For iOS visual captures, `artifactStatus: "captured"` with
|
|
|
47
60
|
XCTest hierarchy extraction failed. Use `zmr explain --json <trace-dir>` for
|
|
48
61
|
the same diagnostic shape after the run.
|
|
49
62
|
|
|
63
|
+
For traced CLI runs, `zmr run --json` also returns `nextCommands` with the
|
|
64
|
+
HTML/JUnit report, explain, `zmr discover --from-trace`, and redacted export
|
|
65
|
+
handoffs.
|
|
66
|
+
Agents should prefer those commands over reconstructing trace paths from text.
|
|
67
|
+
When an agent should create the reviewable scenario in the same process, pass
|
|
68
|
+
`--discover-out .zmr/discovered/<name>.json`; the run JSON will include a
|
|
69
|
+
`discovery` object with validation results and `replay` coverage metadata.
|
|
70
|
+
|
|
50
71
|
## MCP Session
|
|
51
72
|
|
|
52
73
|
Agents that support the Model Context Protocol can use ZMR directly as a local
|
|
@@ -61,13 +82,73 @@ The MCP server exposes mobile-specific tools:
|
|
|
61
82
|
- `snapshot`: raw ZMR observation JSON
|
|
62
83
|
- `semantic_snapshot`: normalized roles, names, selectors, bounds, and
|
|
63
84
|
recommended actions
|
|
64
|
-
- `
|
|
65
|
-
- `
|
|
66
|
-
|
|
85
|
+
- `install_app`, `launch_app`, `stop_app`, and `clear_state`
|
|
86
|
+
- `tap`, `type`, `erase_text`, `hide_keyboard`, `swipe`, `press_back`,
|
|
87
|
+
`open_link`, and `scroll_until_visible`
|
|
88
|
+
- `wait_visible`, `wait_not_visible`, and `wait_any`
|
|
89
|
+
- `assert_visible`, `assert_not_visible`, and `assert_healthy`
|
|
90
|
+
- `scenario_validate`
|
|
91
|
+
- `trace_events`, `trace_explain`, `trace_explore`, `trace_discover`, and
|
|
92
|
+
`trace_export`
|
|
67
93
|
|
|
68
94
|
Prefer `semantic_snapshot` for action planning. It avoids forcing an agent to
|
|
69
95
|
infer intent from platform-specific Android/UI Automator or XCTest class names.
|
|
70
96
|
|
|
97
|
+
## Agent-Led Discovery
|
|
98
|
+
|
|
99
|
+
Agents can use ZMR to discover flows and draft scenarios by looping over
|
|
100
|
+
`observe.semanticSnapshot`, one typed action, trace events, and scenario
|
|
101
|
+
validation. After a session has produced trace artifacts, call JSON-RPC
|
|
102
|
+
`trace.explain` or MCP `trace_explain` for in-band triage, then call JSON-RPC
|
|
103
|
+
`trace.explore` or MCP `trace_explore` when the generated draft should carry a
|
|
104
|
+
stated goal and guardrails. Use JSON-RPC `trace.discover` or MCP
|
|
105
|
+
`trace_discover` for the lower-level trace-backed draft when the agent already
|
|
106
|
+
owns goal tracking. Use JSON-RPC `scenario.validate` or MCP
|
|
107
|
+
`scenario_validate` after edits. The CLI command is the offline equivalent:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
zmr discover --from-trace traces/zmr-agent \
|
|
111
|
+
--out .zmr/discovered/replay-smoke.json \
|
|
112
|
+
--include-actions \
|
|
113
|
+
--validate \
|
|
114
|
+
--json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`zmr discover` is review-first. It writes from trace evidence, validates the
|
|
118
|
+
generated scenario when asked, and returns next commands for deterministic
|
|
119
|
+
reruns. It does not crawl, discover credentials, or commit tests. The JSON
|
|
120
|
+
`replay` object lets agents compare trace action events considered for replay,
|
|
121
|
+
generated replay steps, and skipped events before making coverage claims.
|
|
122
|
+
|
|
123
|
+
Use `zmr draft` when you want the lower-level split workflow. It writes
|
|
124
|
+
`launch`, `snapshot`, and conservative `assertVisible` checks by default. For
|
|
125
|
+
traces produced by an agent session with successful typed actions, add
|
|
126
|
+
`--include-actions` to generate a replay draft from supported events before the
|
|
127
|
+
final snapshot assertions:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
zmr draft --from-trace traces/zmr-agent \
|
|
131
|
+
--out .zmr/discovered/replay-smoke.json \
|
|
132
|
+
--include-actions \
|
|
133
|
+
--json
|
|
134
|
+
zmr validate --json .zmr/discovered/replay-smoke.json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Unsupported or underspecified events are skipped with warnings instead of being
|
|
138
|
+
guessed. Supported replay steps preserve selector and timeout data for waits,
|
|
139
|
+
selector and timeout data for `assertVisible` and `assertNotVisible`, selector
|
|
140
|
+
arrays for `assertNoneVisible`, and timeouts for `assertHealthy` when the trace
|
|
141
|
+
records them. See [Agent Discovery](agent-discovery.md) for the
|
|
142
|
+
recommended reviewable loop.
|
|
143
|
+
|
|
144
|
+
CLI agents can use `zmr explore --from-trace <trace-dir> --out <scenario.json>
|
|
145
|
+
--goal <goal> --include-actions --validate --json` when the goal should travel
|
|
146
|
+
with the generated scenario candidate. The result includes `autonomous:false`,
|
|
147
|
+
`reviewRequired:true`, `guardrails`, replay coverage, validation, and next
|
|
148
|
+
commands. ZMR still does not ship an unbounded autonomous crawler or test
|
|
149
|
+
writer in this developer preview. Keep autonomous planning outside the runner,
|
|
150
|
+
then commit only reviewed scenario JSON.
|
|
151
|
+
|
|
71
152
|
## Scenario File Workflow
|
|
72
153
|
|
|
73
154
|
For repeatable tests, generate or edit `.zmr/*.json` scenarios:
|
package/docs/benchmarking.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Benchmarking
|
|
2
2
|
|
|
3
|
-
ZMR benchmark output is intentionally simple: each run appends one JSON object
|
|
3
|
+
ZMR benchmark output is intentionally simple: each run appends one JSON object
|
|
4
|
+
to `results.jsonl`, and `zmr report` turns that directory into local HTML and
|
|
5
|
+
optional JUnit XML artifacts.
|
|
4
6
|
|
|
5
7
|
## Single Tool Benchmark
|
|
6
8
|
|
|
@@ -29,7 +31,9 @@ or p95 duration misses the configured threshold.
|
|
|
29
31
|
Generate a report:
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
|
-
zmr report traces/bench-<timestamp>
|
|
34
|
+
zmr report traces/bench-<timestamp> \
|
|
35
|
+
--out traces/bench-<timestamp>/report.html \
|
|
36
|
+
--junit traces/bench-<timestamp>/junit.xml
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
## Pilot Wrapper
|
|
@@ -62,7 +66,9 @@ Use `--screen-record` when investigating visual flakes:
|
|
|
62
66
|
--max-failures 0
|
|
63
67
|
```
|
|
64
68
|
|
|
65
|
-
For `--runs 1`, the script exports normal and redacted `.zmrtrace` bundles.
|
|
69
|
+
For `--runs 1`, the script exports normal and redacted `.zmrtrace` bundles.
|
|
70
|
+
For `--runs > 1`, the pilot wrappers and generated app reliability scripts
|
|
71
|
+
write benchmark directories with HTML and JUnit reports.
|
|
66
72
|
|
|
67
73
|
The iOS pilot wrapper supports the same repeated-run gates:
|
|
68
74
|
|
|
@@ -128,6 +134,7 @@ Benchmark reports include:
|
|
|
128
134
|
- terminal trace status
|
|
129
135
|
- failed step index and error when available
|
|
130
136
|
- links to each run's `events.jsonl`
|
|
137
|
+
- optional JUnit XML with one testcase per benchmark row for CI test reports
|
|
131
138
|
|
|
132
139
|
Before making public performance claims, run the same scenario repeatedly on a clean emulator image and include the raw `results.jsonl` plus the redacted trace bundle for any failure.
|
|
133
140
|
|
package/docs/clients.md
CHANGED
|
@@ -21,7 +21,11 @@ Then it sends JSON-RPC methods such as:
|
|
|
21
21
|
- `wait.until`
|
|
22
22
|
- `assert.visible`
|
|
23
23
|
- `assert.healthy`
|
|
24
|
+
- `scenario.validate`
|
|
24
25
|
- `trace.events`
|
|
26
|
+
- `trace.explain`
|
|
27
|
+
- `trace.explore`
|
|
28
|
+
- `trace.discover`
|
|
25
29
|
- `trace.export`
|
|
26
30
|
|
|
27
31
|
Use clients when an AI agent, service, or test harness wants to drive ZMR
|
|
@@ -37,12 +41,12 @@ even when normal page text is also present.
|
|
|
37
41
|
|
|
38
42
|
| Language | Files | Why it looks this way |
|
|
39
43
|
| --- | --- | --- |
|
|
40
|
-
| TypeScript | `clients/typescript/index.mjs`, `index.d.ts` | ESM runtime plus type declarations,
|
|
41
|
-
| Python | `clients/python/zmr_client.py`, `pyproject.toml` | Standard-library importable module
|
|
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 |
|
|
44
|
+
| TypeScript | `clients/typescript/index.mjs`, `index.d.ts` | ESM runtime plus type declarations, including `explainTrace`, `exploreTrace`, `discoverTrace`, and `validateScenario` helpers |
|
|
45
|
+
| Python | `clients/python/zmr_client.py`, `pyproject.toml` | Standard-library importable module with `explain_trace`, `explore_trace`, `discover_trace`, and `validate_scenario` helpers |
|
|
46
|
+
| Go | `clients/go/zmr/client.go` | Normal Go package inside a module, including `ExplainTrace`, `ExploreTrace`, `DiscoverTrace`, and `ValidateScenario` helpers |
|
|
47
|
+
| Rust | `clients/rust/src/lib.rs` | Cargo library crate convention, including `explain_trace`, `explore_trace`, `discover_trace`, and `validate_scenario` helpers |
|
|
48
|
+
| Swift | `clients/swift/Sources/ZMRClient/ZMRClient.swift` | SwiftPM package for macOS host-side tools, including `explainTrace`, `exploreTrace`, `discoverTrace`, and `validateScenario` helpers |
|
|
49
|
+
| 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
50
|
|
|
47
51
|
Rust has `src/lib.rs` because Cargo expects a library crate there. The other
|
|
48
52
|
clients do have equivalent entry points; they are just idiomatic for their
|
package/docs/demo.md
CHANGED
|
@@ -124,7 +124,9 @@ The script builds `zmr` when needed, validates both sample scenarios, installs t
|
|
|
124
124
|
For each single run it writes:
|
|
125
125
|
|
|
126
126
|
- `auth/report.html`
|
|
127
|
+
- `auth/junit.xml`
|
|
127
128
|
- `login-smoke/report.html`
|
|
129
|
+
- `login-smoke/junit.xml`
|
|
128
130
|
- `auth.zmrtrace`
|
|
129
131
|
- `auth-redacted.zmrtrace`
|
|
130
132
|
- `login-smoke.zmrtrace`
|
|
@@ -233,9 +235,11 @@ Build the app for an iOS simulator, boot a simulator, then run:
|
|
|
233
235
|
For each run it writes:
|
|
234
236
|
|
|
235
237
|
- `ios-smoke/report.html`
|
|
238
|
+
- `ios-smoke/junit.xml`
|
|
236
239
|
- `ios-smoke.zmrtrace`
|
|
237
240
|
- `ios-smoke-redacted.zmrtrace`
|
|
238
241
|
- `ios-shim-smoke/report.html` when `--ios-shim` is set
|
|
242
|
+
- `ios-shim-smoke/junit.xml` when `--ios-shim` is set
|
|
239
243
|
- `ios-shim-smoke.zmrtrace` when `--ios-shim` is set
|
|
240
244
|
- `ios-shim-smoke-redacted.zmrtrace` when `--ios-shim` is set
|
|
241
245
|
|