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
package/clients/go/zmr/client.go
CHANGED
|
@@ -134,6 +134,39 @@ type TraceEvents struct {
|
|
|
134
134
|
Events []map[string]interface{} `json:"events"`
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
type TraceDiagnostic struct {
|
|
138
|
+
Kind string `json:"kind"`
|
|
139
|
+
Status string `json:"status,omitempty"`
|
|
140
|
+
SnapshotID string `json:"snapshotId,omitempty"`
|
|
141
|
+
ArtifactStatus string `json:"artifactStatus,omitempty"`
|
|
142
|
+
SemanticStatus string `json:"semanticStatus,omitempty"`
|
|
143
|
+
Error string `json:"error,omitempty"`
|
|
144
|
+
ScreenshotArtifact string `json:"screenshotArtifact,omitempty"`
|
|
145
|
+
Source string `json:"source,omitempty"`
|
|
146
|
+
ActivePackage string `json:"activePackage,omitempty"`
|
|
147
|
+
ActiveActivity string `json:"activeActivity,omitempty"`
|
|
148
|
+
VisibleTexts []string `json:"visibleTexts,omitempty"`
|
|
149
|
+
NearestTextMatches []string `json:"nearestTextMatches,omitempty"`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
type TraceExplain struct {
|
|
153
|
+
OK bool `json:"ok"`
|
|
154
|
+
TraceDir string `json:"traceDir"`
|
|
155
|
+
Scenario string `json:"scenario"`
|
|
156
|
+
Status string `json:"status"`
|
|
157
|
+
AppID string `json:"appId,omitempty"`
|
|
158
|
+
DurationMS int64 `json:"durationMs,omitempty"`
|
|
159
|
+
EventCount int64 `json:"eventCount,omitempty"`
|
|
160
|
+
SnapshotCount int64 `json:"snapshotCount,omitempty"`
|
|
161
|
+
PartialFailureCount int64 `json:"partialFailureCount,omitempty"`
|
|
162
|
+
FailedStepIndex int64 `json:"failedStepIndex,omitempty"`
|
|
163
|
+
Error string `json:"error,omitempty"`
|
|
164
|
+
Diagnostic *TraceDiagnostic `json:"diagnostic,omitempty"`
|
|
165
|
+
PartialFailure *TraceDiagnostic `json:"partialFailure,omitempty"`
|
|
166
|
+
LastEvent string `json:"lastEvent,omitempty"`
|
|
167
|
+
NextCommands []string `json:"nextCommands"`
|
|
168
|
+
}
|
|
169
|
+
|
|
137
170
|
type TraceExport struct {
|
|
138
171
|
TraceDir string `json:"traceDir"`
|
|
139
172
|
Out string `json:"out"`
|
|
@@ -141,6 +174,59 @@ type TraceExport struct {
|
|
|
141
174
|
OmitScreenshots bool `json:"omitScreenshots"`
|
|
142
175
|
}
|
|
143
176
|
|
|
177
|
+
type ValidationResult struct {
|
|
178
|
+
OK bool `json:"ok"`
|
|
179
|
+
Path string `json:"path"`
|
|
180
|
+
Name string `json:"name,omitempty"`
|
|
181
|
+
AppID string `json:"appId,omitempty"`
|
|
182
|
+
StepCount int `json:"stepCount,omitempty"`
|
|
183
|
+
ErrorCode string `json:"errorCode,omitempty"`
|
|
184
|
+
Message string `json:"message,omitempty"`
|
|
185
|
+
FieldPath string `json:"fieldPath,omitempty"`
|
|
186
|
+
Line int `json:"line,omitempty"`
|
|
187
|
+
Column int `json:"column,omitempty"`
|
|
188
|
+
NextCommands []string `json:"nextCommands,omitempty"`
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
type ReplaySummary struct {
|
|
192
|
+
Enabled bool `json:"enabled"`
|
|
193
|
+
EventCount int `json:"eventCount"`
|
|
194
|
+
StepCount int `json:"stepCount"`
|
|
195
|
+
SkippedEventCount int `json:"skippedEventCount"`
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
type TraceDiscoverOptions struct {
|
|
199
|
+
IncludeActions bool
|
|
200
|
+
Validate bool
|
|
201
|
+
Force bool
|
|
202
|
+
Name string
|
|
203
|
+
AppID string
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
type TraceDiscover struct {
|
|
207
|
+
OK bool `json:"ok"`
|
|
208
|
+
Mode string `json:"mode"`
|
|
209
|
+
SchemaVersion int `json:"schemaVersion"`
|
|
210
|
+
RunnerVersion string `json:"runnerVersion"`
|
|
211
|
+
ProtocolVersion string `json:"protocolVersion"`
|
|
212
|
+
Out string `json:"out"`
|
|
213
|
+
TraceDir string `json:"traceDir"`
|
|
214
|
+
SourceSnapshot string `json:"sourceSnapshot"`
|
|
215
|
+
Name string `json:"name"`
|
|
216
|
+
AppID string `json:"appId,omitempty"`
|
|
217
|
+
SelectorCount int `json:"selectorCount"`
|
|
218
|
+
StepCount int `json:"stepCount"`
|
|
219
|
+
Replay ReplaySummary `json:"replay"`
|
|
220
|
+
Warnings []string `json:"warnings"`
|
|
221
|
+
Validated bool `json:"validated"`
|
|
222
|
+
Validation *ValidationResult `json:"validation"`
|
|
223
|
+
NextCommands []string `json:"nextCommands"`
|
|
224
|
+
Goal string `json:"goal,omitempty"`
|
|
225
|
+
Autonomous bool `json:"autonomous,omitempty"`
|
|
226
|
+
ReviewRequired bool `json:"reviewRequired,omitempty"`
|
|
227
|
+
Guardrails []string `json:"guardrails,omitempty"`
|
|
228
|
+
}
|
|
229
|
+
|
|
144
230
|
func Start(ctx context.Context, command string, args ...string) (*Client, error) {
|
|
145
231
|
cmd := exec.CommandContext(ctx, command, args...)
|
|
146
232
|
stdin, err := cmd.StdinPipe()
|
|
@@ -415,6 +501,12 @@ func (c *Client) AssertHealthy(ctx context.Context, timeoutMS int64) (bool, erro
|
|
|
415
501
|
return out, err
|
|
416
502
|
}
|
|
417
503
|
|
|
504
|
+
func (c *Client) ValidateScenario(ctx context.Context, path string) (ValidationResult, error) {
|
|
505
|
+
var out ValidationResult
|
|
506
|
+
err := c.Request(ctx, "scenario.validate", map[string]interface{}{"path": path}, &out)
|
|
507
|
+
return out, err
|
|
508
|
+
}
|
|
509
|
+
|
|
418
510
|
func (c *Client) ExportTrace(ctx context.Context, outPath string, redact bool, omitScreenshots bool) (TraceExport, error) {
|
|
419
511
|
var out TraceExport
|
|
420
512
|
err := c.Request(ctx, "trace.export", map[string]interface{}{"out": outPath, "redact": redact, "omitScreenshots": omitScreenshots}, &out)
|
|
@@ -430,3 +522,53 @@ func (c *Client) TraceEvents(ctx context.Context, afterSeq int64, limit int64) (
|
|
|
430
522
|
err := c.Request(ctx, "trace.events", params, &out)
|
|
431
523
|
return out, err
|
|
432
524
|
}
|
|
525
|
+
|
|
526
|
+
func (c *Client) ExplainTrace(ctx context.Context) (TraceExplain, error) {
|
|
527
|
+
var out TraceExplain
|
|
528
|
+
err := c.Request(ctx, "trace.explain", map[string]interface{}{}, &out)
|
|
529
|
+
return out, err
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
func (c *Client) DiscoverTrace(ctx context.Context, outPath string, options TraceDiscoverOptions) (TraceDiscover, error) {
|
|
533
|
+
var out TraceDiscover
|
|
534
|
+
params := map[string]interface{}{"out": outPath}
|
|
535
|
+
if options.IncludeActions {
|
|
536
|
+
params["includeActions"] = true
|
|
537
|
+
}
|
|
538
|
+
if options.Validate {
|
|
539
|
+
params["validate"] = true
|
|
540
|
+
}
|
|
541
|
+
if options.Force {
|
|
542
|
+
params["force"] = true
|
|
543
|
+
}
|
|
544
|
+
if options.Name != "" {
|
|
545
|
+
params["name"] = options.Name
|
|
546
|
+
}
|
|
547
|
+
if options.AppID != "" {
|
|
548
|
+
params["appId"] = options.AppID
|
|
549
|
+
}
|
|
550
|
+
err := c.Request(ctx, "trace.discover", params, &out)
|
|
551
|
+
return out, err
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
func (c *Client) ExploreTrace(ctx context.Context, outPath string, goal string, options TraceDiscoverOptions) (TraceDiscover, error) {
|
|
555
|
+
var out TraceDiscover
|
|
556
|
+
params := map[string]interface{}{"out": outPath, "goal": goal}
|
|
557
|
+
if options.IncludeActions {
|
|
558
|
+
params["includeActions"] = true
|
|
559
|
+
}
|
|
560
|
+
if options.Validate {
|
|
561
|
+
params["validate"] = true
|
|
562
|
+
}
|
|
563
|
+
if options.Force {
|
|
564
|
+
params["force"] = true
|
|
565
|
+
}
|
|
566
|
+
if options.Name != "" {
|
|
567
|
+
params["name"] = options.Name
|
|
568
|
+
}
|
|
569
|
+
if options.AppID != "" {
|
|
570
|
+
params["appId"] = options.AppID
|
|
571
|
+
}
|
|
572
|
+
err := c.Request(ctx, "trace.explore", params, &out)
|
|
573
|
+
return out, err
|
|
574
|
+
}
|
package/clients/kotlin/README.md
CHANGED
|
@@ -27,7 +27,24 @@ gradle -p clients/kotlin runFakeSession \
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
```kotlin
|
|
30
|
-
implementation(files("path/to/zeno-mobile-runner/clients/kotlin/build/libs/zmr-client-0.1.
|
|
30
|
+
implementation(files("path/to/zeno-mobile-runner/clients/kotlin/build/libs/zmr-client-0.1.8.jar"))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```kotlin
|
|
34
|
+
val client = ZmrClient(listOf("zmr", "serve", "--transport", "stdio", "--config", ".zmr/config.json"))
|
|
35
|
+
val out = ".zmr/discovered/kotlin-agent.json"
|
|
36
|
+
val discovered = client.discoverTrace(
|
|
37
|
+
out,
|
|
38
|
+
TraceDiscoverOptions(includeActions = true, validate = true, force = true)
|
|
39
|
+
)
|
|
40
|
+
val explored = client.exploreTrace(
|
|
41
|
+
".zmr/discovered/kotlin-goal.json",
|
|
42
|
+
"find a stable login smoke",
|
|
43
|
+
TraceDiscoverOptions(includeActions = true, validate = true, force = true)
|
|
44
|
+
)
|
|
45
|
+
val validation = client.validateScenario(out)
|
|
46
|
+
val explanation = client.explainTrace()
|
|
47
|
+
client.close()
|
|
31
48
|
```
|
|
32
49
|
|
|
33
50
|
The Kotlin client is host-side. It is useful for Android teams that want test
|
|
@@ -12,6 +12,14 @@ class ZmrRpcException(
|
|
|
12
12
|
val publicCode: String? = null
|
|
13
13
|
) : RuntimeException(message)
|
|
14
14
|
|
|
15
|
+
data class TraceDiscoverOptions(
|
|
16
|
+
val includeActions: Boolean = false,
|
|
17
|
+
val validate: Boolean = false,
|
|
18
|
+
val force: Boolean = false,
|
|
19
|
+
val name: String? = null,
|
|
20
|
+
val appId: String? = null
|
|
21
|
+
)
|
|
22
|
+
|
|
15
23
|
class ZmrClient(
|
|
16
24
|
private val command: List<String> = listOf("zmr", "serve", "--transport", "stdio")
|
|
17
25
|
) : Closeable {
|
|
@@ -31,6 +39,34 @@ class ZmrClient(
|
|
|
31
39
|
return call("assert.healthy", params)
|
|
32
40
|
}
|
|
33
41
|
|
|
42
|
+
fun validateScenario(path: String): String =
|
|
43
|
+
call("scenario.validate", """{"path":"${escapeJson(path)}"}""")
|
|
44
|
+
|
|
45
|
+
fun explainTrace(): String = call("trace.explain")
|
|
46
|
+
|
|
47
|
+
fun discoverTrace(out: String, options: TraceDiscoverOptions = TraceDiscoverOptions()): String {
|
|
48
|
+
val fields = mutableListOf(""""out":"${escapeJson(out)}"""")
|
|
49
|
+
if (options.includeActions) fields.add(""""includeActions":true""")
|
|
50
|
+
if (options.validate) fields.add(""""validate":true""")
|
|
51
|
+
if (options.force) fields.add(""""force":true""")
|
|
52
|
+
options.name?.let { fields.add(""""name":"${escapeJson(it)}"""") }
|
|
53
|
+
options.appId?.let { fields.add(""""appId":"${escapeJson(it)}"""") }
|
|
54
|
+
return call("trace.discover", "{${fields.joinToString(",")}}")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fun exploreTrace(out: String, goal: String, options: TraceDiscoverOptions = TraceDiscoverOptions()): String {
|
|
58
|
+
val fields = mutableListOf(
|
|
59
|
+
""""out":"${escapeJson(out)}"""",
|
|
60
|
+
""""goal":"${escapeJson(goal)}""""
|
|
61
|
+
)
|
|
62
|
+
if (options.includeActions) fields.add(""""includeActions":true""")
|
|
63
|
+
if (options.validate) fields.add(""""validate":true""")
|
|
64
|
+
if (options.force) fields.add(""""force":true""")
|
|
65
|
+
options.name?.let { fields.add(""""name":"${escapeJson(it)}"""") }
|
|
66
|
+
options.appId?.let { fields.add(""""appId":"${escapeJson(it)}"""") }
|
|
67
|
+
return call("trace.explore", "{${fields.joinToString(",")}}")
|
|
68
|
+
}
|
|
69
|
+
|
|
34
70
|
@Synchronized
|
|
35
71
|
fun call(method: String, paramsJson: String? = null): String {
|
|
36
72
|
val id = nextId++
|
|
@@ -39,7 +75,7 @@ class ZmrClient(
|
|
|
39
75
|
input.newLine()
|
|
40
76
|
input.flush()
|
|
41
77
|
val response = output.readLine() ?: error("zmr closed stdout")
|
|
42
|
-
if (response
|
|
78
|
+
if (hasTopLevelKey(response, "error")) {
|
|
43
79
|
throw ZmrRpcException(
|
|
44
80
|
code = extractNumber(response, "code") ?: -32000,
|
|
45
81
|
message = extractString(response, "message").ifEmpty { "ZMR JSON-RPC error" },
|
|
@@ -65,3 +101,42 @@ private fun extractNumber(json: String, key: String): Int? {
|
|
|
65
101
|
val pattern = """"$key"\s*:\s*(-?[0-9]+)""".toRegex()
|
|
66
102
|
return pattern.find(json)?.groupValues?.get(1)?.toIntOrNull()
|
|
67
103
|
}
|
|
104
|
+
|
|
105
|
+
private fun hasTopLevelKey(json: String, key: String): Boolean {
|
|
106
|
+
var depth = 0
|
|
107
|
+
var inString = false
|
|
108
|
+
var escaped = false
|
|
109
|
+
var stringStart = 0
|
|
110
|
+
var i = 0
|
|
111
|
+
while (i < json.length) {
|
|
112
|
+
val ch = json[i]
|
|
113
|
+
if (inString) {
|
|
114
|
+
when {
|
|
115
|
+
escaped -> escaped = false
|
|
116
|
+
ch == '\\' -> escaped = true
|
|
117
|
+
ch == '"' -> {
|
|
118
|
+
inString = false
|
|
119
|
+
if (depth == 1 && json.substring(stringStart, i) == key) {
|
|
120
|
+
var j = i + 1
|
|
121
|
+
while (j < json.length && json[j].isWhitespace()) j += 1
|
|
122
|
+
if (j < json.length && json[j] == ':') return true
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
when (ch) {
|
|
128
|
+
'"' -> {
|
|
129
|
+
inString = true
|
|
130
|
+
stringStart = i + 1
|
|
131
|
+
}
|
|
132
|
+
'{', '[' -> depth += 1
|
|
133
|
+
'}', ']' -> depth -= 1
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
i += 1
|
|
137
|
+
}
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private fun escapeJson(value: String): String =
|
|
142
|
+
value.replace("\\", "\\\\").replace("\"", "\\\"")
|
package/clients/python/README.md
CHANGED
|
@@ -20,8 +20,27 @@ with ZmrClient(
|
|
|
20
20
|
zmr.wait_until({"text": "E2E auth probe"}, timeout_ms=30000)
|
|
21
21
|
snapshot = zmr.snapshot()
|
|
22
22
|
events = zmr.trace_events(0, limit=100)
|
|
23
|
+
explanation = zmr.explain_trace()
|
|
24
|
+
discovered = zmr.discover_trace(
|
|
25
|
+
".zmr/discovered/agent-session.json",
|
|
26
|
+
include_actions=True,
|
|
27
|
+
validate=True,
|
|
28
|
+
force=True,
|
|
29
|
+
)
|
|
30
|
+
explored = zmr.explore_trace(
|
|
31
|
+
".zmr/discovered/agent-goal.json",
|
|
32
|
+
"find a stable login smoke",
|
|
33
|
+
include_actions=True,
|
|
34
|
+
validate=True,
|
|
35
|
+
force=True,
|
|
36
|
+
)
|
|
37
|
+
validation = zmr.validate_scenario(discovered["out"])
|
|
23
38
|
print(snapshot["nodes"])
|
|
24
39
|
print(len(events["events"]))
|
|
40
|
+
print(explanation["status"])
|
|
41
|
+
print(discovered["out"])
|
|
42
|
+
print(explored["reviewRequired"])
|
|
43
|
+
print(validation["ok"])
|
|
25
44
|
zmr.export_trace("traces/agent-session-redacted.zmrtrace", redact=True, omit_screenshots=True)
|
|
26
45
|
```
|
|
27
46
|
|
|
@@ -160,6 +160,9 @@ class ZmrClient:
|
|
|
160
160
|
params["timeoutMs"] = timeout_ms
|
|
161
161
|
return self.request("assert.healthy", params)
|
|
162
162
|
|
|
163
|
+
def validate_scenario(self, path):
|
|
164
|
+
return self.request("scenario.validate", {"path": path})
|
|
165
|
+
|
|
163
166
|
def export_trace(self, out, redact=False, omit_screenshots=False):
|
|
164
167
|
return self.request(
|
|
165
168
|
"trace.export",
|
|
@@ -176,6 +179,36 @@ class ZmrClient:
|
|
|
176
179
|
params["limit"] = limit
|
|
177
180
|
return self.request("trace.events", params)
|
|
178
181
|
|
|
182
|
+
def explain_trace(self):
|
|
183
|
+
return self.request("trace.explain")
|
|
184
|
+
|
|
185
|
+
def discover_trace(self, out, include_actions=False, validate=False, force=False, name=None, app_id=None):
|
|
186
|
+
params = {
|
|
187
|
+
"out": out,
|
|
188
|
+
"includeActions": include_actions,
|
|
189
|
+
"validate": validate,
|
|
190
|
+
"force": force,
|
|
191
|
+
}
|
|
192
|
+
if name is not None:
|
|
193
|
+
params["name"] = name
|
|
194
|
+
if app_id is not None:
|
|
195
|
+
params["appId"] = app_id
|
|
196
|
+
return self.request("trace.discover", params)
|
|
197
|
+
|
|
198
|
+
def explore_trace(self, out, goal, include_actions=False, validate=False, force=False, name=None, app_id=None):
|
|
199
|
+
params = {
|
|
200
|
+
"out": out,
|
|
201
|
+
"goal": goal,
|
|
202
|
+
"includeActions": include_actions,
|
|
203
|
+
"validate": validate,
|
|
204
|
+
"force": force,
|
|
205
|
+
}
|
|
206
|
+
if name is not None:
|
|
207
|
+
params["name"] = name
|
|
208
|
+
if app_id is not None:
|
|
209
|
+
params["appId"] = app_id
|
|
210
|
+
return self.request("trace.explore", params)
|
|
211
|
+
|
|
179
212
|
def close(self):
|
|
180
213
|
if self._closed:
|
|
181
214
|
return
|
package/clients/rust/Cargo.lock
CHANGED
package/clients/rust/Cargo.toml
CHANGED
package/clients/rust/README.md
CHANGED
|
@@ -4,9 +4,33 @@ Small synchronous JSON-RPC client for driving `zmr serve --transport stdio`
|
|
|
4
4
|
from Rust agents and test harnesses.
|
|
5
5
|
|
|
6
6
|
```rust
|
|
7
|
-
let mut client = zmr_client::Client::start(
|
|
7
|
+
let mut client = zmr_client::Client::start(
|
|
8
|
+
"zmr",
|
|
9
|
+
["serve", "--transport", "stdio", "--config", ".zmr/config.json"],
|
|
10
|
+
)?;
|
|
8
11
|
let snapshot = client.snapshot()?;
|
|
9
12
|
let healthy = client.assert_healthy(Some(1000))?;
|
|
13
|
+
let explanation = client.explain_trace()?;
|
|
14
|
+
let discovered = client.discover_trace(
|
|
15
|
+
".zmr/discovered/rust-agent.json",
|
|
16
|
+
zmr_client::TraceDiscoverOptions {
|
|
17
|
+
include_actions: true,
|
|
18
|
+
validate: true,
|
|
19
|
+
force: true,
|
|
20
|
+
..Default::default()
|
|
21
|
+
},
|
|
22
|
+
)?;
|
|
23
|
+
let explored = client.explore_trace(
|
|
24
|
+
".zmr/discovered/rust-goal.json",
|
|
25
|
+
"find a stable login smoke",
|
|
26
|
+
zmr_client::TraceDiscoverOptions {
|
|
27
|
+
include_actions: true,
|
|
28
|
+
validate: true,
|
|
29
|
+
force: true,
|
|
30
|
+
..Default::default()
|
|
31
|
+
},
|
|
32
|
+
)?;
|
|
33
|
+
let validation = client.validate_scenario(&discovered.out)?;
|
|
10
34
|
```
|
|
11
35
|
|
|
12
36
|
Run the fake-session example from the repository root:
|
package/clients/rust/src/lib.rs
CHANGED
|
@@ -204,6 +204,150 @@ pub struct TraceEvents {
|
|
|
204
204
|
pub events: Vec<Value>,
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
#[derive(Debug, Deserialize)]
|
|
208
|
+
pub struct TraceDiagnostic {
|
|
209
|
+
pub kind: String,
|
|
210
|
+
#[serde(default)]
|
|
211
|
+
pub status: Option<String>,
|
|
212
|
+
#[serde(default, rename = "snapshotId")]
|
|
213
|
+
pub snapshot_id: Option<String>,
|
|
214
|
+
#[serde(default, rename = "artifactStatus")]
|
|
215
|
+
pub artifact_status: Option<String>,
|
|
216
|
+
#[serde(default, rename = "semanticStatus")]
|
|
217
|
+
pub semantic_status: Option<String>,
|
|
218
|
+
#[serde(default)]
|
|
219
|
+
pub error: Option<String>,
|
|
220
|
+
#[serde(default, rename = "screenshotArtifact")]
|
|
221
|
+
pub screenshot_artifact: Option<String>,
|
|
222
|
+
#[serde(default)]
|
|
223
|
+
pub source: Option<String>,
|
|
224
|
+
#[serde(default, rename = "activePackage")]
|
|
225
|
+
pub active_package: Option<String>,
|
|
226
|
+
#[serde(default, rename = "activeActivity")]
|
|
227
|
+
pub active_activity: Option<String>,
|
|
228
|
+
#[serde(default, rename = "visibleTexts")]
|
|
229
|
+
pub visible_texts: Vec<String>,
|
|
230
|
+
#[serde(default, rename = "nearestTextMatches")]
|
|
231
|
+
pub nearest_text_matches: Vec<String>,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[derive(Debug, Deserialize)]
|
|
235
|
+
pub struct TraceExplain {
|
|
236
|
+
pub ok: bool,
|
|
237
|
+
#[serde(rename = "traceDir")]
|
|
238
|
+
pub trace_dir: String,
|
|
239
|
+
pub scenario: String,
|
|
240
|
+
pub status: String,
|
|
241
|
+
#[serde(default, rename = "appId")]
|
|
242
|
+
pub app_id: Option<String>,
|
|
243
|
+
#[serde(default, rename = "durationMs")]
|
|
244
|
+
pub duration_ms: Option<i64>,
|
|
245
|
+
#[serde(default, rename = "eventCount")]
|
|
246
|
+
pub event_count: Option<i64>,
|
|
247
|
+
#[serde(default, rename = "snapshotCount")]
|
|
248
|
+
pub snapshot_count: Option<i64>,
|
|
249
|
+
#[serde(default, rename = "partialFailureCount")]
|
|
250
|
+
pub partial_failure_count: Option<i64>,
|
|
251
|
+
#[serde(default, rename = "failedStepIndex")]
|
|
252
|
+
pub failed_step_index: Option<i64>,
|
|
253
|
+
#[serde(default)]
|
|
254
|
+
pub error: Option<String>,
|
|
255
|
+
#[serde(default)]
|
|
256
|
+
pub diagnostic: Option<TraceDiagnostic>,
|
|
257
|
+
#[serde(default, rename = "partialFailure")]
|
|
258
|
+
pub partial_failure: Option<TraceDiagnostic>,
|
|
259
|
+
#[serde(default, rename = "lastEvent")]
|
|
260
|
+
pub last_event: Option<String>,
|
|
261
|
+
#[serde(default, rename = "nextCommands")]
|
|
262
|
+
pub next_commands: Vec<String>,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#[derive(Debug, Deserialize)]
|
|
266
|
+
pub struct ValidationResult {
|
|
267
|
+
pub ok: bool,
|
|
268
|
+
pub path: String,
|
|
269
|
+
#[serde(default)]
|
|
270
|
+
pub name: Option<String>,
|
|
271
|
+
#[serde(default, rename = "appId")]
|
|
272
|
+
pub app_id: Option<String>,
|
|
273
|
+
#[serde(default, rename = "stepCount")]
|
|
274
|
+
pub step_count: i64,
|
|
275
|
+
#[serde(default, rename = "errorCode")]
|
|
276
|
+
pub error_code: Option<String>,
|
|
277
|
+
#[serde(default)]
|
|
278
|
+
pub message: Option<String>,
|
|
279
|
+
#[serde(default, rename = "fieldPath")]
|
|
280
|
+
pub field_path: Option<String>,
|
|
281
|
+
#[serde(default)]
|
|
282
|
+
pub line: Option<i64>,
|
|
283
|
+
#[serde(default)]
|
|
284
|
+
pub column: Option<i64>,
|
|
285
|
+
#[serde(default, rename = "nextCommands")]
|
|
286
|
+
pub next_commands: Vec<String>,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#[derive(Debug, Deserialize)]
|
|
290
|
+
pub struct ReplaySummary {
|
|
291
|
+
#[serde(default)]
|
|
292
|
+
pub enabled: bool,
|
|
293
|
+
#[serde(default, rename = "eventCount")]
|
|
294
|
+
pub event_count: i64,
|
|
295
|
+
#[serde(default, rename = "stepCount")]
|
|
296
|
+
pub step_count: i64,
|
|
297
|
+
#[serde(default, rename = "skippedEventCount")]
|
|
298
|
+
pub skipped_event_count: i64,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#[derive(Debug, Default)]
|
|
302
|
+
pub struct TraceDiscoverOptions {
|
|
303
|
+
pub include_actions: bool,
|
|
304
|
+
pub validate: bool,
|
|
305
|
+
pub force: bool,
|
|
306
|
+
pub name: Option<String>,
|
|
307
|
+
pub app_id: Option<String>,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#[derive(Debug, Deserialize)]
|
|
311
|
+
pub struct TraceDiscover {
|
|
312
|
+
pub ok: bool,
|
|
313
|
+
pub mode: String,
|
|
314
|
+
#[serde(rename = "schemaVersion")]
|
|
315
|
+
pub schema_version: i64,
|
|
316
|
+
#[serde(rename = "runnerVersion")]
|
|
317
|
+
pub runner_version: String,
|
|
318
|
+
#[serde(rename = "protocolVersion")]
|
|
319
|
+
pub protocol_version: String,
|
|
320
|
+
pub out: String,
|
|
321
|
+
#[serde(rename = "traceDir")]
|
|
322
|
+
pub trace_dir: String,
|
|
323
|
+
#[serde(rename = "sourceSnapshot")]
|
|
324
|
+
pub source_snapshot: String,
|
|
325
|
+
pub name: String,
|
|
326
|
+
#[serde(default, rename = "appId")]
|
|
327
|
+
pub app_id: Option<String>,
|
|
328
|
+
#[serde(rename = "selectorCount")]
|
|
329
|
+
pub selector_count: i64,
|
|
330
|
+
#[serde(rename = "stepCount")]
|
|
331
|
+
pub step_count: i64,
|
|
332
|
+
pub replay: ReplaySummary,
|
|
333
|
+
#[serde(default)]
|
|
334
|
+
pub warnings: Vec<String>,
|
|
335
|
+
#[serde(default)]
|
|
336
|
+
pub validated: bool,
|
|
337
|
+
#[serde(default)]
|
|
338
|
+
pub validation: Option<ValidationResult>,
|
|
339
|
+
#[serde(default, rename = "nextCommands")]
|
|
340
|
+
pub next_commands: Vec<String>,
|
|
341
|
+
#[serde(default)]
|
|
342
|
+
pub goal: Option<String>,
|
|
343
|
+
#[serde(default)]
|
|
344
|
+
pub autonomous: bool,
|
|
345
|
+
#[serde(default, rename = "reviewRequired")]
|
|
346
|
+
pub review_required: bool,
|
|
347
|
+
#[serde(default)]
|
|
348
|
+
pub guardrails: Vec<String>,
|
|
349
|
+
}
|
|
350
|
+
|
|
207
351
|
impl Client {
|
|
208
352
|
pub fn start<I, S>(command: &str, args: I) -> Result<Self, Error>
|
|
209
353
|
where
|
|
@@ -420,6 +564,10 @@ impl Client {
|
|
|
420
564
|
self.request("assert.healthy", params)
|
|
421
565
|
}
|
|
422
566
|
|
|
567
|
+
pub fn validate_scenario(&mut self, path: &str) -> Result<ValidationResult, Error> {
|
|
568
|
+
self.request("scenario.validate", json!({ "path": path }))
|
|
569
|
+
}
|
|
570
|
+
|
|
423
571
|
pub fn export_trace(
|
|
424
572
|
&mut self,
|
|
425
573
|
out: &str,
|
|
@@ -443,6 +591,59 @@ impl Client {
|
|
|
443
591
|
}
|
|
444
592
|
self.request("trace.events", params)
|
|
445
593
|
}
|
|
594
|
+
|
|
595
|
+
pub fn explain_trace(&mut self) -> Result<TraceExplain, Error> {
|
|
596
|
+
self.request("trace.explain", json!({}))
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
pub fn discover_trace(
|
|
600
|
+
&mut self,
|
|
601
|
+
out: &str,
|
|
602
|
+
options: TraceDiscoverOptions,
|
|
603
|
+
) -> Result<TraceDiscover, Error> {
|
|
604
|
+
let mut params = json!({ "out": out });
|
|
605
|
+
if options.include_actions {
|
|
606
|
+
params["includeActions"] = json!(true);
|
|
607
|
+
}
|
|
608
|
+
if options.validate {
|
|
609
|
+
params["validate"] = json!(true);
|
|
610
|
+
}
|
|
611
|
+
if options.force {
|
|
612
|
+
params["force"] = json!(true);
|
|
613
|
+
}
|
|
614
|
+
if let Some(name) = options.name {
|
|
615
|
+
params["name"] = json!(name);
|
|
616
|
+
}
|
|
617
|
+
if let Some(app_id) = options.app_id {
|
|
618
|
+
params["appId"] = json!(app_id);
|
|
619
|
+
}
|
|
620
|
+
self.request("trace.discover", params)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
pub fn explore_trace(
|
|
624
|
+
&mut self,
|
|
625
|
+
out: &str,
|
|
626
|
+
goal: &str,
|
|
627
|
+
options: TraceDiscoverOptions,
|
|
628
|
+
) -> Result<TraceDiscover, Error> {
|
|
629
|
+
let mut params = json!({ "out": out, "goal": goal });
|
|
630
|
+
if options.include_actions {
|
|
631
|
+
params["includeActions"] = json!(true);
|
|
632
|
+
}
|
|
633
|
+
if options.validate {
|
|
634
|
+
params["validate"] = json!(true);
|
|
635
|
+
}
|
|
636
|
+
if options.force {
|
|
637
|
+
params["force"] = json!(true);
|
|
638
|
+
}
|
|
639
|
+
if let Some(name) = options.name {
|
|
640
|
+
params["name"] = json!(name);
|
|
641
|
+
}
|
|
642
|
+
if let Some(app_id) = options.app_id {
|
|
643
|
+
params["appId"] = json!(app_id);
|
|
644
|
+
}
|
|
645
|
+
self.request("trace.explore", params)
|
|
646
|
+
}
|
|
446
647
|
}
|
|
447
648
|
|
|
448
649
|
impl Drop for Client {
|
package/clients/swift/README.md
CHANGED
|
@@ -16,6 +16,24 @@ git submodule add https://github.com/johnmikel/zeno-mobile-runner.git vendor/zen
|
|
|
16
16
|
|
|
17
17
|
Then depend on the `ZMRClient` product from `clients/swift`.
|
|
18
18
|
|
|
19
|
+
```swift
|
|
20
|
+
let client = ZMRClient(arguments: ["serve", "--transport", "stdio", "--config", ".zmr/config.json"])
|
|
21
|
+
try client.start()
|
|
22
|
+
let out = ".zmr/discovered/swift-agent.json"
|
|
23
|
+
let discovered = try client.discoverTrace(
|
|
24
|
+
out: out,
|
|
25
|
+
options: TraceDiscoverOptions(includeActions: true, validate: true, force: true)
|
|
26
|
+
)
|
|
27
|
+
let explored = try client.exploreTrace(
|
|
28
|
+
out: ".zmr/discovered/swift-goal.json",
|
|
29
|
+
goal: "find a stable login smoke",
|
|
30
|
+
options: TraceDiscoverOptions(includeActions: true, validate: true, force: true)
|
|
31
|
+
)
|
|
32
|
+
let validation = try client.validateScenario(path: out)
|
|
33
|
+
let explanation = try client.explainTrace()
|
|
34
|
+
client.close()
|
|
35
|
+
```
|
|
36
|
+
|
|
19
37
|
Run the package test from this directory:
|
|
20
38
|
|
|
21
39
|
```bash
|