zeno-mobile-runner 0.2.3 → 0.2.5
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 +19 -0
- package/FEATURES.md +1 -1
- package/README.md +1 -1
- package/build.zig +10 -0
- package/build.zig.zon +1 -1
- package/clients/kotlin/README.md +1 -1
- package/clients/kotlin/build.gradle.kts +1 -1
- package/clients/python/pyproject.toml +1 -1
- package/clients/rust/Cargo.lock +1 -1
- package/clients/rust/Cargo.toml +1 -1
- package/clients/typescript/package.json +1 -1
- package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
- package/docs/protocol.md +10 -10
- package/package.json +1 -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/src/ios.zig +3 -3
- package/src/runner.zig +110 -2
- package/src/runner_events.zig +12 -0
- package/src/version.zig +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to Zeno Mobile Runner are tracked here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.2.5 (2026-06-15)
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `whenVisible` now treats visibility-probe command failures as a skipped
|
|
12
|
+
conditional block, recording the skip error in the trace instead of failing
|
|
13
|
+
the scenario. This matches the action's optional-control-flow contract and
|
|
14
|
+
fixes Expo dev-client deep-link flows where the chooser probe can race with
|
|
15
|
+
the app already reaching the target screen.
|
|
16
|
+
|
|
17
|
+
## 0.2.4 (2026-06-15)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Extended the iOS simulator `openLink` interruption sweep to cover Expo
|
|
22
|
+
dev-client deep-link chooser sheets that appear more than six seconds after
|
|
23
|
+
`simctl openurl` returns. The sweep remains bounded, but now covers the
|
|
24
|
+
delayed chooser timing observed in the Rently auth smoke.
|
|
25
|
+
|
|
7
26
|
## 0.2.3 (2026-06-15)
|
|
8
27
|
|
|
9
28
|
### Fixed
|
package/FEATURES.md
CHANGED
|
@@ -142,7 +142,7 @@ state, and writes deterministic traces. It does not embed an LLM.
|
|
|
142
142
|
|
|
143
143
|
## Current Limitations
|
|
144
144
|
|
|
145
|
-
- Current release status is `0.2.
|
|
145
|
+
- Current release status is `0.2.5`, a public developer preview rather than
|
|
146
146
|
a production-stable `1.0.0`.
|
|
147
147
|
- Physical iOS log capture is still simulator-first. Physical iOS screenshots
|
|
148
148
|
are available when the XCTest/XCUIAutomation shim is configured.
|
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ comparisons against your current E2E tool, and multi-device matrices, see
|
|
|
197
197
|
| Cloud device farms | Not included | ZMR focuses on local and self-managed device targets in this preview |
|
|
198
198
|
|
|
199
199
|
Slow CI hardware can extend the iOS shim cold-build timeout with
|
|
200
|
-
`ZMR_IOS_SHIM_TIMEOUT_MS`. Current release: `0.2.
|
|
200
|
+
`ZMR_IOS_SHIM_TIMEOUT_MS`. Current release: `0.2.5` developer preview.
|
|
201
201
|
Protocol version: `2026-04-28`.
|
|
202
202
|
|
|
203
203
|
## Optional protocol clients
|
package/build.zig
CHANGED
|
@@ -42,7 +42,17 @@ pub fn build(b: *std.Build) void {
|
|
|
42
42
|
});
|
|
43
43
|
const run_ios_tests = b.addRunArtifact(ios_tests);
|
|
44
44
|
|
|
45
|
+
const runner_tests = b.addTest(.{
|
|
46
|
+
.root_module = b.createModule(.{
|
|
47
|
+
.root_source_file = b.path("src/runner.zig"),
|
|
48
|
+
.target = target,
|
|
49
|
+
.optimize = optimize,
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
const run_runner_tests = b.addRunArtifact(runner_tests);
|
|
53
|
+
|
|
45
54
|
const test_step = b.step("test", "Run unit tests");
|
|
46
55
|
test_step.dependOn(&run_unit_tests.step);
|
|
47
56
|
test_step.dependOn(&run_ios_tests.step);
|
|
57
|
+
test_step.dependOn(&run_runner_tests.step);
|
|
48
58
|
}
|
package/build.zig.zon
CHANGED
package/clients/kotlin/README.md
CHANGED
|
@@ -27,7 +27,7 @@ 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.2.
|
|
30
|
+
implementation(files("path/to/zeno-mobile-runner/clients/kotlin/build/libs/zmr-client-0.2.5.jar"))
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
```kotlin
|
package/clients/rust/Cargo.lock
CHANGED
package/clients/rust/Cargo.toml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.2.
|
|
1
|
+
{"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.2.5","protocolVersion":"2026-04-28","protocol":{"version":"2026-04-28","minimumCompatibleVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"},"platforms":["android","ios"],"platformSupport":{"android":{"status":"supported","deviceTypes":["emulator","physical"],"automation":["adb","uiautomator","android-shim"]},"ios":{"status":"supported","deviceTypes":["simulator","physical"],"automation":["simctl","devicectl","xctest-shim"],"physicalDevices":true}},"iosPreview":false,"transports":["stdio","tcp"],"methods":["runner.capabilities","device.list","session.create","session.close","app.install","app.launch","app.stop","app.openLink","app.clearState","observe.snapshot","observe.semanticSnapshot","ui.tap","ui.type","ui.eraseText","ui.hideKeyboard","ui.swipe","ui.pressBack","ui.scrollUntilVisible","wait.until","wait.any","wait.gone","assert.visible","assert.notVisible","assert.healthy","scenario.validate","trace.events","trace.explore","trace.discover","trace.explain","trace.export"]}}
|
|
2
2
|
{"jsonrpc":"2.0","id":2,"result":[{"serial":"fake-device-1","state":"device","ready":true}]}
|
|
3
3
|
{"jsonrpc":"2.0","id":3,"result":{"sessionId":"default"}}
|
|
4
4
|
{"jsonrpc":"2.0","id":4,"result":true}
|
package/docs/protocol.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
ZMR exposes newline-delimited JSON-RPC 2.0 over stdio or localhost TCP in v1. Each request is one JSON object followed by `\n`. Each response is one JSON object followed by `\n`.
|
|
4
4
|
|
|
5
|
-
Current runner version: `0.2.
|
|
5
|
+
Current runner version: `0.2.5`.
|
|
6
6
|
|
|
7
7
|
Current protocol version: `2026-04-28`.
|
|
8
8
|
|
|
@@ -47,7 +47,7 @@ and protocol versions. The response is covered by
|
|
|
47
47
|
`schemas/inspect-output.schema.json`:
|
|
48
48
|
|
|
49
49
|
```json
|
|
50
|
-
{"ok":true,"status":"ready","schemaVersion":1,"runnerVersion":"0.2.
|
|
50
|
+
{"ok":true,"status":"ready","schemaVersion":1,"runnerVersion":"0.2.5","protocolVersion":"2026-04-28","dir":".","configPath":".zmr/config.json","configExists":true,"agentInstructionsPath":".zmr/AGENTS.md","agentInstructionsExists":true,"platforms":[{"name":"android","enabled":true,"defaultDevice":"emulator-5554","smokeScenario":".zmr/android-smoke.json","smokeScenarioExists":true,"traceDir":"traces/zmr-android"},{"name":"ios","enabled":true,"defaultDevice":"booted","smokeScenario":".zmr/ios-smoke.json","smokeScenarioExists":true,"traceDir":"traces/zmr-ios"}],"recommendedCommands":["zmr doctor --strict --json --config .zmr/config.json","zmr schemas --json","zmr validate --json .zmr/android-smoke.json","zmr validate --json .zmr/ios-smoke.json","zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent","zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent"],"claimsPolicy":["verify runs with trace evidence before making readiness claims","do not claim Flutter widget-tree inspection"],"limitations":["inspect is read-only and does not launch devices","autonomous crawling is not shipped; generate or edit scenarios for human review"]}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
`zmr discover --from-trace <trace-dir> --out <scenario.json> --validate --json`
|
|
@@ -60,7 +60,7 @@ invent credentials, or commit files. The response is covered by
|
|
|
60
60
|
`schemas/discover-output.schema.json`:
|
|
61
61
|
|
|
62
62
|
```json
|
|
63
|
-
{"ok":true,"mode":"discover","schemaVersion":1,"runnerVersion":"0.2.
|
|
63
|
+
{"ok":true,"mode":"discover","schemaVersion":1,"runnerVersion":"0.2.5","protocolVersion":"2026-04-28","out":".zmr/discovered/replay-smoke.json","traceDir":"traces/zmr-agent","sourceSnapshot":"traces/zmr-agent/artifacts/snapshot-2.json","name":"draft from login smoke","appId":"com.example.mobiletest","selectorCount":2,"stepCount":6,"replay":{"enabled":true,"eventCount":4,"stepCount":3,"skippedEventCount":1},"warnings":["draft requires human review before commit"],"validated":true,"validation":{"ok":true,"path":".zmr/discovered/replay-smoke.json","name":"draft from login smoke","appId":"com.example.mobiletest","stepCount":6},"nextCommands":["zmr validate --json .zmr/discovered/replay-smoke.json","zmr run .zmr/discovered/replay-smoke.json --json --trace-dir traces/zmr-agent"]}
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
`zmr explore --from-trace <trace-dir> --out <scenario.json> --goal <goal>
|
|
@@ -71,7 +71,7 @@ launch devices, crawl the app, invent missing actions, discover credentials, or
|
|
|
71
71
|
commit files. The response is covered by `schemas/explore-output.schema.json`:
|
|
72
72
|
|
|
73
73
|
```json
|
|
74
|
-
{"ok":true,"mode":"explore","schemaVersion":1,"runnerVersion":"0.2.
|
|
74
|
+
{"ok":true,"mode":"explore","schemaVersion":1,"runnerVersion":"0.2.5","protocolVersion":"2026-04-28","goal":"find a stable login smoke","autonomous":false,"reviewRequired":true,"guardrails":["writes from existing trace evidence only","does not crawl the app","does not discover credentials or secrets","requires human review before commit"],"out":".zmr/discovered/login-smoke.json","traceDir":"traces/zmr-agent","sourceSnapshot":"traces/zmr-agent/artifacts/snapshot-2.json","name":"draft from login smoke","appId":"com.example.mobiletest","selectorCount":2,"stepCount":6,"replay":{"enabled":true,"eventCount":4,"stepCount":3,"skippedEventCount":1},"warnings":["draft requires human review before commit"],"validated":true,"validation":{"ok":true,"path":".zmr/discovered/login-smoke.json","name":"draft from login smoke","appId":"com.example.mobiletest","stepCount":6},"nextCommands":["zmr validate --json .zmr/discovered/login-smoke.json","zmr run .zmr/discovered/login-smoke.json --json --trace-dir traces/zmr-agent"]}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
`zmr draft --from-trace <trace-dir> --out <scenario.json> --json` is the lower
|
|
@@ -84,7 +84,7 @@ into fields, or commit files. The response is covered by
|
|
|
84
84
|
`schemas/draft-output.schema.json`:
|
|
85
85
|
|
|
86
86
|
```json
|
|
87
|
-
{"ok":true,"mode":"draft","schemaVersion":1,"runnerVersion":"0.2.
|
|
87
|
+
{"ok":true,"mode":"draft","schemaVersion":1,"runnerVersion":"0.2.5","protocolVersion":"2026-04-28","out":".zmr/discovered/surface-smoke.json","traceDir":"traces/zmr-agent","sourceSnapshot":"traces/zmr-agent/artifacts/snapshot-2.json","name":"draft from login smoke","appId":"com.example.mobiletest","selectorCount":2,"stepCount":4,"replay":{"enabled":false,"eventCount":0,"stepCount":0,"skippedEventCount":0},"warnings":["draft requires human review before commit"],"nextCommands":["zmr validate --json .zmr/discovered/surface-smoke.json","zmr run .zmr/discovered/surface-smoke.json --json --trace-dir traces/zmr-agent"]}
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
`zmr draft --include-actions` additionally parses `events.jsonl` and prepends
|
|
@@ -214,7 +214,7 @@ installers, setup scripts, and generated clients. The response is covered by
|
|
|
214
214
|
`schemas/version-output.schema.json`:
|
|
215
215
|
|
|
216
216
|
```json
|
|
217
|
-
{"name":"zmr","version":"0.2.
|
|
217
|
+
{"name":"zmr","version":"0.2.5","protocolVersion":"2026-04-28","minimumCompatibleProtocolVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"}
|
|
218
218
|
```
|
|
219
219
|
|
|
220
220
|
## Capabilities Output Contract
|
|
@@ -226,7 +226,7 @@ and method inventory for JSON-RPC clients. The result object is covered by
|
|
|
226
226
|
iOS simulator, or physical iOS workflows are available.
|
|
227
227
|
|
|
228
228
|
```json
|
|
229
|
-
{"name":"zmr","version":"0.2.
|
|
229
|
+
{"name":"zmr","version":"0.2.5","protocolVersion":"2026-04-28","protocol":{"version":"2026-04-28","minimumCompatibleVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"},"platforms":["android","ios"],"platformSupport":{"android":{"status":"supported","deviceTypes":["emulator","physical"],"automation":["adb","uiautomator","android-shim"]},"ios":{"status":"supported","deviceTypes":["simulator","physical"],"automation":["simctl","devicectl","xctest-shim"],"physicalDevices":true}},"iosPreview":false,"transports":["stdio","tcp"],"methods":["runner.capabilities","device.list","session.create","session.close","app.install","app.launch","app.stop","app.openLink","app.clearState","observe.snapshot","observe.semanticSnapshot","ui.tap","ui.type","ui.eraseText","ui.hideKeyboard","ui.swipe","ui.pressBack","ui.scrollUntilVisible","wait.until","wait.any","wait.gone","assert.visible","assert.notVisible","assert.healthy","scenario.validate","trace.events","trace.explore","trace.discover","trace.explain","trace.export"]}
|
|
230
230
|
```
|
|
231
231
|
|
|
232
232
|
## Doctor Output Contract
|
|
@@ -432,7 +432,7 @@ Request:
|
|
|
432
432
|
Response:
|
|
433
433
|
|
|
434
434
|
```json
|
|
435
|
-
{"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.2.
|
|
435
|
+
{"jsonrpc":"2.0","id":1,"result":{"name":"zmr","version":"0.2.5","protocolVersion":"2026-04-28","protocol":{"version":"2026-04-28","minimumCompatibleVersion":"2026-04-28","stability":"dev-preview","breakingChangePolicy":"version-and-changelog"},"platforms":["android","ios"],"platformSupport":{"android":{"status":"supported","deviceTypes":["emulator","physical"],"automation":["adb","uiautomator","android-shim"]},"ios":{"status":"supported","deviceTypes":["simulator","physical"],"automation":["simctl","devicectl","xctest-shim"],"physicalDevices":true}},"iosPreview":false,"transports":["stdio","tcp"],"methods":["runner.capabilities","device.list","session.create","session.close","app.install","app.launch","app.stop","app.openLink","app.clearState","observe.snapshot","observe.semanticSnapshot","ui.tap","ui.type","ui.eraseText","ui.hideKeyboard","ui.swipe","ui.pressBack","ui.scrollUntilVisible","wait.until","wait.any","wait.gone","assert.visible","assert.notVisible","assert.healthy","scenario.validate","trace.events","trace.explore","trace.discover","trace.explain","trace.export"]}}
|
|
436
436
|
```
|
|
437
437
|
|
|
438
438
|
### `trace.events`
|
|
@@ -514,7 +514,7 @@ Request:
|
|
|
514
514
|
Response:
|
|
515
515
|
|
|
516
516
|
```json
|
|
517
|
-
{"jsonrpc":"2.0","id":25,"result":{"ok":true,"mode":"discover","schemaVersion":1,"runnerVersion":"0.2.
|
|
517
|
+
{"jsonrpc":"2.0","id":25,"result":{"ok":true,"mode":"discover","schemaVersion":1,"runnerVersion":"0.2.5","protocolVersion":"2026-04-28","out":".zmr/discovered/agent-smoke.json","traceDir":"traces/agent-session","sourceSnapshot":"traces/agent-session/artifacts/snapshot-1.json","name":"agent smoke","appId":"com.example.mobiletest","selectorCount":1,"stepCount":4,"replay":{"enabled":true,"eventCount":2,"stepCount":1,"skippedEventCount":1},"warnings":["draft requires human review before commit"],"validated":true,"validation":{"ok":true,"path":".zmr/discovered/agent-smoke.json","name":"agent smoke","appId":"com.example.mobiletest","stepCount":4},"nextCommands":["zmr validate --json .zmr/discovered/agent-smoke.json","zmr run .zmr/discovered/agent-smoke.json --json --trace-dir traces/agent-session"]}}
|
|
518
518
|
```
|
|
519
519
|
|
|
520
520
|
Without `--trace-dir`, it returns `ok: false` with `traceDir: null`. Generated
|
|
@@ -537,7 +537,7 @@ Request:
|
|
|
537
537
|
Response:
|
|
538
538
|
|
|
539
539
|
```json
|
|
540
|
-
{"jsonrpc":"2.0","id":27,"result":{"ok":true,"mode":"explore","schemaVersion":1,"runnerVersion":"0.2.
|
|
540
|
+
{"jsonrpc":"2.0","id":27,"result":{"ok":true,"mode":"explore","schemaVersion":1,"runnerVersion":"0.2.5","protocolVersion":"2026-04-28","out":".zmr/discovered/agent-goal.json","traceDir":"traces/agent-session","sourceSnapshot":"traces/agent-session/artifacts/snapshot-1.json","name":"agent goal smoke","appId":"com.example.mobiletest","selectorCount":1,"stepCount":4,"replay":{"enabled":true,"eventCount":2,"stepCount":1,"skippedEventCount":1},"warnings":["draft requires human review before commit"],"validated":true,"validation":{"ok":true,"path":".zmr/discovered/agent-goal.json","name":"agent goal smoke","appId":"com.example.mobiletest","stepCount":4},"nextCommands":["zmr validate --json .zmr/discovered/agent-goal.json","zmr run .zmr/discovered/agent-goal.json --json --trace-dir traces/agent-session"],"goal":"find a stable login smoke","autonomous":false,"reviewRequired":true,"guardrails":["writes from existing trace evidence only","does not crawl the app","does not discover credentials or secrets","requires human review before commit"]}}
|
|
541
541
|
```
|
|
542
542
|
|
|
543
543
|
Without `--trace-dir`, it returns `ok: false` with `traceDir: null`.
|
package/package.json
CHANGED
|
Binary file
|
package/prebuilds/darwin-x64/zmr
CHANGED
|
Binary file
|
|
Binary file
|
package/prebuilds/linux-x64/zmr
CHANGED
|
Binary file
|
package/src/ios.zig
CHANGED
|
@@ -16,7 +16,7 @@ const default_max_output = 32 * 1024 * 1024;
|
|
|
16
16
|
const default_shim_timeout_ms = 5_400_000;
|
|
17
17
|
const shim_timeout_env = "ZMR_IOS_SHIM_TIMEOUT_MS";
|
|
18
18
|
const shim_best_effort_timeout_ms = 10_000;
|
|
19
|
-
const open_link_interruption_attempts =
|
|
19
|
+
const open_link_interruption_attempts = 8;
|
|
20
20
|
const open_link_interruption_retry_delay_ms = 1_000;
|
|
21
21
|
const shim_command_attempts = 2;
|
|
22
22
|
const shim_bootstrap_retry_delay_ms = 500;
|
|
@@ -457,7 +457,7 @@ test "ios simulator openLink keeps sweeping delayed XCTest interruptions until a
|
|
|
457
457
|
\\fi
|
|
458
458
|
\\count=$((count + 1))
|
|
459
459
|
\\printf '%s' "$count" > "$count_file"
|
|
460
|
-
\\if [[ "$count" -lt
|
|
460
|
+
\\if [[ "$count" -lt 6 ]]; then
|
|
461
461
|
\\ printf '{{"status":"ok","accepted":false,"count":0}}\n'
|
|
462
462
|
\\else
|
|
463
463
|
\\ printf '{{"status":"ok","accepted":true,"label":"Brick Rewards Test","count":1}}\n'
|
|
@@ -486,7 +486,7 @@ test "ios simulator openLink keeps sweeping delayed XCTest interruptions until a
|
|
|
486
486
|
const count_raw = try stdio.readFileAlloc(allocator, count_path, 1024);
|
|
487
487
|
defer allocator.free(count_raw);
|
|
488
488
|
const count = try std.fmt.parseInt(u8, count_raw, 10);
|
|
489
|
-
try std.testing.expectEqual(@as(u8,
|
|
489
|
+
try std.testing.expectEqual(@as(u8, 6), count);
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
pub fn listDevices(allocator: std.mem.Allocator, xcrun_path: []const u8) ![]types.DeviceInfo {
|
package/src/runner.zig
CHANGED
|
@@ -7,6 +7,7 @@ const runner_waits = @import("runner_waits.zig");
|
|
|
7
7
|
const scenario = @import("scenario.zig");
|
|
8
8
|
const selector = @import("selector.zig");
|
|
9
9
|
const trace = @import("trace.zig");
|
|
10
|
+
const types = @import("types.zig");
|
|
10
11
|
|
|
11
12
|
pub const RunOptions = runner_config.RunOptions;
|
|
12
13
|
|
|
@@ -148,9 +149,15 @@ pub fn executeStep(
|
|
|
148
149
|
},
|
|
149
150
|
.when_visible => |block| {
|
|
150
151
|
const visible = if (block.timeout_ms == 0)
|
|
151
|
-
|
|
152
|
+
isVisibleNow(device, block.selector, writer) catch |err| {
|
|
153
|
+
if (writer) |tw| try runner_events.recordSelectorEventWithError(tw, "step.whenVisible.skipped", block.selector, err);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
152
156
|
else
|
|
153
|
-
|
|
157
|
+
waitUntilVisible(device, block.selector, block.timeout_ms, writer, options) catch |err| {
|
|
158
|
+
if (writer) |tw| try runner_events.recordSelectorEventWithError(tw, "step.whenVisible.skipped", block.selector, err);
|
|
159
|
+
return;
|
|
160
|
+
};
|
|
154
161
|
if (visible) {
|
|
155
162
|
for (block.steps) |inner| try executeStep(allocator, device, inner, writer, options);
|
|
156
163
|
} else if (writer) |tw| {
|
|
@@ -301,3 +308,104 @@ fn sleepMs(ms: u64) !void {
|
|
|
301
308
|
fn settleDevice(device: anytype, options: RunOptions) !void {
|
|
302
309
|
try device.settle(options.settle_ms);
|
|
303
310
|
}
|
|
311
|
+
|
|
312
|
+
test "whenVisible skips the conditional block when the visibility probe command fails" {
|
|
313
|
+
const allocator = std.testing.allocator;
|
|
314
|
+
const dir = "zig-cache-test-runner-when-visible-command-failed";
|
|
315
|
+
std.Io.Dir.cwd().deleteTree(stdio.io(), dir) catch {};
|
|
316
|
+
defer std.Io.Dir.cwd().deleteTree(stdio.io(), dir) catch {};
|
|
317
|
+
|
|
318
|
+
const ProbeFailureDevice = struct {
|
|
319
|
+
allocator: std.mem.Allocator,
|
|
320
|
+
typed: usize = 0,
|
|
321
|
+
|
|
322
|
+
pub fn launch(self: *@This()) !void {
|
|
323
|
+
_ = self;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
pub fn stop(self: *@This()) !void {
|
|
327
|
+
_ = self;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
pub fn clearState(self: *@This()) !void {
|
|
331
|
+
_ = self;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
pub fn openLink(self: *@This(), url: []const u8) !void {
|
|
335
|
+
_ = self;
|
|
336
|
+
_ = url;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
pub fn tap(self: *@This(), x: i32, y: i32) !void {
|
|
340
|
+
_ = self;
|
|
341
|
+
_ = x;
|
|
342
|
+
_ = y;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
pub fn snapshot(self: *@This(), writer: anytype) !types.ObservationSnapshot {
|
|
346
|
+
_ = self;
|
|
347
|
+
_ = writer;
|
|
348
|
+
return error.CommandFailed;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
pub fn typeText(self: *@This(), text: []const u8) !void {
|
|
352
|
+
_ = text;
|
|
353
|
+
self.typed += 1;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
pub fn eraseText(self: *@This(), max_chars: u32) !void {
|
|
357
|
+
_ = self;
|
|
358
|
+
_ = max_chars;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
pub fn pressBack(self: *@This()) !void {
|
|
362
|
+
_ = self;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
pub fn hideKeyboard(self: *@This()) !void {
|
|
366
|
+
_ = self;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
pub fn swipe(self: *@This(), x1: i32, y1: i32, x2: i32, y2: i32, duration_ms: u32) !void {
|
|
370
|
+
_ = self;
|
|
371
|
+
_ = x1;
|
|
372
|
+
_ = y1;
|
|
373
|
+
_ = x2;
|
|
374
|
+
_ = y2;
|
|
375
|
+
_ = duration_ms;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
pub fn settle(self: *@This(), ms: u64) !void {
|
|
379
|
+
_ = self;
|
|
380
|
+
_ = ms;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const script_json =
|
|
385
|
+
\\{
|
|
386
|
+
\\ "name": "conditional probe failure",
|
|
387
|
+
\\ "steps": [
|
|
388
|
+
\\ {"action": "whenVisible", "selector": {"text": "Deep link received:"}, "steps": [
|
|
389
|
+
\\ {"action": "typeText", "text": "not-run"}
|
|
390
|
+
\\ ]}
|
|
391
|
+
\\ ]
|
|
392
|
+
\\}
|
|
393
|
+
;
|
|
394
|
+
const script = try scenario.parseSlice(allocator, script_json);
|
|
395
|
+
defer script.deinit(allocator);
|
|
396
|
+
|
|
397
|
+
var device = ProbeFailureDevice{ .allocator = allocator };
|
|
398
|
+
var tw = try trace.TraceWriter.init(allocator, dir);
|
|
399
|
+
defer tw.deinit();
|
|
400
|
+
|
|
401
|
+
try runScenario(allocator, &device, script, &tw, .{ .settle_ms = 0 });
|
|
402
|
+
|
|
403
|
+
try std.testing.expectEqual(@as(usize, 0), device.typed);
|
|
404
|
+
|
|
405
|
+
const events_path = try std.fs.path.join(allocator, &.{ dir, "events.jsonl" });
|
|
406
|
+
defer allocator.free(events_path);
|
|
407
|
+
const events = try stdio.readFileAlloc(allocator, events_path, 1024 * 1024);
|
|
408
|
+
defer allocator.free(events);
|
|
409
|
+
try std.testing.expect(std.mem.indexOf(u8, events, "\"kind\":\"step.whenVisible.skipped\"") != null);
|
|
410
|
+
try std.testing.expect(std.mem.indexOf(u8, events, "\"error\":\"CommandFailed\"") != null);
|
|
411
|
+
}
|
package/src/runner_events.zig
CHANGED
|
@@ -73,6 +73,18 @@ pub fn recordSelectorEvent(tw: *trace.TraceWriter, kind: []const u8, wanted: sel
|
|
|
73
73
|
try tw.recordEvent(kind, writer.buffered());
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
pub fn recordSelectorEventWithError(tw: *trace.TraceWriter, kind: []const u8, wanted: selector.Selector, err: anyerror) !void {
|
|
77
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
78
|
+
defer payload.deinit();
|
|
79
|
+
const writer = &payload.writer;
|
|
80
|
+
try writer.writeAll("{\"selector\":");
|
|
81
|
+
try trace.writeSelectorJson(writer, wanted);
|
|
82
|
+
try writer.writeAll(",\"error\":");
|
|
83
|
+
try trace.writeJsonString(writer, @errorName(err));
|
|
84
|
+
try writer.writeAll("}");
|
|
85
|
+
try tw.recordEvent(kind, writer.buffered());
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
pub fn recordActionStatus(tw: *trace.TraceWriter, kind: []const u8, status: []const u8, err: ?anyerror, url: ?[]const u8) !void {
|
|
77
89
|
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
78
90
|
defer payload.deinit();
|
package/src/version.zig
CHANGED