zeno-mobile-runner 0.2.1 → 0.2.3
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 +39 -0
- package/FEATURES.md +1 -1
- package/README.md +1 -1
- package/build.zig +10 -0
- package/build.zig.zon +2 -2
- 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 +3 -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/scripts/create-react-native-expo-demo-app.sh +11 -13
- package/shims/ios/ZMRShim.swift +40 -12
- package/shims/ios/ZMRShimUITestCase.swift +135 -15
- package/src/android.zig +10 -9
- package/src/android_emulator.zig +22 -11
- package/src/android_screen_recording.zig +11 -7
- package/src/bundle.zig +10 -9
- package/src/bundle_redaction.zig +29 -28
- package/src/bundle_tar.zig +15 -12
- package/src/cli_devices.zig +7 -3
- package/src/cli_discover.zig +7 -3
- package/src/cli_doctor.zig +7 -3
- package/src/cli_draft.zig +51 -47
- package/src/cli_explore.zig +7 -3
- package/src/cli_import.zig +8 -4
- package/src/cli_info.zig +13 -6
- package/src/cli_init.zig +9 -5
- package/src/cli_inspect.zig +8 -4
- package/src/cli_run.zig +22 -16
- package/src/cli_serve.zig +3 -3
- package/src/cli_trace.zig +25 -12
- package/src/cli_validate.zig +8 -4
- package/src/command.zig +81 -99
- package/src/config.zig +2 -1
- package/src/config_diagnostics.zig +2 -1
- package/src/config_paths.zig +2 -1
- package/src/doctor.zig +8 -7
- package/src/doctor_hints.zig +1 -1
- package/src/errors.zig +5 -5
- package/src/importer.zig +8 -7
- package/src/ios.zig +98 -19
- package/src/ios_devices.zig +6 -5
- package/src/ios_lifecycle.zig +4 -4
- package/src/ios_shim.zig +12 -0
- package/src/json_rpc.zig +39 -40
- package/src/json_rpc_methods.zig +8 -8
- package/src/json_rpc_observation.zig +9 -8
- package/src/json_rpc_params.zig +1 -1
- package/src/json_rpc_trace.zig +22 -21
- package/src/main.zig +19 -10
- package/src/mcp.zig +28 -19
- package/src/mcp_trace.zig +30 -29
- package/src/report.zig +39 -36
- package/src/report_html.zig +5 -4
- package/src/runner.zig +2 -1
- package/src/runner_actions.zig +20 -17
- package/src/runner_diagnostics.zig +4 -4
- package/src/runner_events.zig +55 -51
- package/src/runner_native.zig +21 -19
- package/src/runner_waits.zig +46 -41
- package/src/scaffold.zig +25 -24
- package/src/scenario.zig +4 -3
- package/src/stdio.zig +129 -0
- package/src/trace.zig +34 -26
- package/src/trace_summary.zig +3 -2
- package/src/trace_summary_diagnostic.zig +15 -13
- package/src/validation.zig +5 -4
- package/src/version.zig +1 -1
package/src/runner_events.zig
CHANGED
|
@@ -5,35 +5,38 @@ const trace = @import("trace.zig");
|
|
|
5
5
|
const types = @import("types.zig");
|
|
6
6
|
|
|
7
7
|
pub fn eventString(allocator: std.mem.Allocator, value: []const u8) ![]const u8 {
|
|
8
|
-
var buffer =
|
|
9
|
-
errdefer buffer.deinit(
|
|
10
|
-
|
|
11
|
-
try
|
|
12
|
-
try
|
|
13
|
-
|
|
8
|
+
var buffer: std.Io.Writer.Allocating = .init(allocator);
|
|
9
|
+
errdefer buffer.deinit();
|
|
10
|
+
const writer = &buffer.writer;
|
|
11
|
+
try writer.writeAll("{\"value\":");
|
|
12
|
+
try trace.writeJsonString(writer, value);
|
|
13
|
+
try writer.writeAll("}");
|
|
14
|
+
return try buffer.toOwnedSlice();
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
pub fn recordNativeWait(tw: *trace.TraceWriter, kind: []const u8, wanted: selector.Selector, matched_index: ?usize, timeout_ms: u64) !void {
|
|
17
|
-
var payload =
|
|
18
|
-
defer payload.deinit(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
try
|
|
22
|
-
try
|
|
23
|
-
try
|
|
24
|
-
try
|
|
18
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
19
|
+
defer payload.deinit();
|
|
20
|
+
const writer = &payload.writer;
|
|
21
|
+
try writer.writeAll("{\"status\":\"ok\",\"strategy\":\"nativeSelector\"");
|
|
22
|
+
if (matched_index) |index| try writer.print(",\"matchedIndex\":{d}", .{index});
|
|
23
|
+
try writer.writeAll(",\"selector\":");
|
|
24
|
+
try trace.writeSelectorJson(writer, wanted);
|
|
25
|
+
try writer.print(",\"timeoutMs\":{d}}}", .{timeout_ms});
|
|
26
|
+
try tw.recordEvent(kind, writer.buffered());
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
pub fn recordNativeWaitTimeout(tw: *trace.TraceWriter, kind: []const u8, selectors: []const selector.Selector, timeout_ms: u64) !void {
|
|
28
|
-
var payload =
|
|
29
|
-
defer payload.deinit(
|
|
30
|
-
|
|
30
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
31
|
+
defer payload.deinit();
|
|
32
|
+
const writer = &payload.writer;
|
|
33
|
+
try writer.print("{{\"status\":\"timeout\",\"strategy\":\"nativeSelector\",\"timeoutMs\":{d},\"selectors\":[", .{timeout_ms});
|
|
31
34
|
for (selectors, 0..) |wanted, index| {
|
|
32
|
-
if (index > 0) try
|
|
33
|
-
try trace.writeSelectorJson(
|
|
35
|
+
if (index > 0) try writer.writeAll(",");
|
|
36
|
+
try trace.writeSelectorJson(writer, wanted);
|
|
34
37
|
}
|
|
35
|
-
try
|
|
36
|
-
try tw.recordEvent(kind,
|
|
38
|
+
try writer.writeAll("]}");
|
|
39
|
+
try tw.recordEvent(kind, writer.buffered());
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
pub fn recordNativeWaitTimeoutWithDiagnostics(device: anytype, tw: *trace.TraceWriter, kind: []const u8, selectors: []const selector.Selector, timeout_ms: u64) !void {
|
|
@@ -46,9 +49,9 @@ pub fn recordNativeWaitTimeoutWithDiagnostics(device: anytype, tw: *trace.TraceW
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
pub fn recordSelectorArrayStatus(tw: *trace.TraceWriter, kind: []const u8, status: []const u8, selectors: []const selector.Selector, timeout_ms: u64) !void {
|
|
49
|
-
var payload =
|
|
50
|
-
defer payload.deinit(
|
|
51
|
-
const out = payload.writer
|
|
52
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
53
|
+
defer payload.deinit();
|
|
54
|
+
const out = &payload.writer;
|
|
52
55
|
try out.writeAll("{\"status\":");
|
|
53
56
|
try trace.writeJsonString(out, status);
|
|
54
57
|
try out.writeAll(",\"selectors\":[");
|
|
@@ -57,22 +60,23 @@ pub fn recordSelectorArrayStatus(tw: *trace.TraceWriter, kind: []const u8, statu
|
|
|
57
60
|
try trace.writeSelectorJson(out, wanted);
|
|
58
61
|
}
|
|
59
62
|
try out.print("],\"timeoutMs\":{d}}}", .{timeout_ms});
|
|
60
|
-
try tw.recordEvent(kind,
|
|
63
|
+
try tw.recordEvent(kind, out.buffered());
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
pub fn recordSelectorEvent(tw: *trace.TraceWriter, kind: []const u8, wanted: selector.Selector) !void {
|
|
64
|
-
var payload =
|
|
65
|
-
defer payload.deinit(
|
|
66
|
-
|
|
67
|
-
try
|
|
68
|
-
try
|
|
69
|
-
try
|
|
67
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
68
|
+
defer payload.deinit();
|
|
69
|
+
const writer = &payload.writer;
|
|
70
|
+
try writer.writeAll("{\"selector\":");
|
|
71
|
+
try trace.writeSelectorJson(writer, wanted);
|
|
72
|
+
try writer.writeAll("}");
|
|
73
|
+
try tw.recordEvent(kind, writer.buffered());
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
pub fn recordActionStatus(tw: *trace.TraceWriter, kind: []const u8, status: []const u8, err: ?anyerror, url: ?[]const u8) !void {
|
|
73
|
-
var payload =
|
|
74
|
-
defer payload.deinit(
|
|
75
|
-
const out = payload.writer
|
|
77
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
78
|
+
defer payload.deinit();
|
|
79
|
+
const out = &payload.writer;
|
|
76
80
|
try out.writeAll("{\"status\":");
|
|
77
81
|
try trace.writeJsonString(out, status);
|
|
78
82
|
if (err) |actual| {
|
|
@@ -84,7 +88,7 @@ pub fn recordActionStatus(tw: *trace.TraceWriter, kind: []const u8, status: []co
|
|
|
84
88
|
try trace.writeJsonString(out, value);
|
|
85
89
|
}
|
|
86
90
|
try out.writeAll("}");
|
|
87
|
-
try tw.recordEvent(kind,
|
|
91
|
+
try tw.recordEvent(kind, out.buffered());
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
pub fn recordSwipe(tw: *trace.TraceWriter, x1: i32, y1: i32, x2: i32, y2: i32, duration_ms: u32) !void {
|
|
@@ -98,21 +102,21 @@ pub fn recordSwipe(tw: *trace.TraceWriter, x1: i32, y1: i32, x2: i32, y2: i32, d
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
pub fn recordTraceDiscover(tw: *trace.TraceWriter, status: []const u8, out_path: []const u8, include_actions: bool, validated: bool) !void {
|
|
101
|
-
var payload =
|
|
102
|
-
defer payload.deinit(
|
|
103
|
-
const out = payload.writer
|
|
105
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
106
|
+
defer payload.deinit();
|
|
107
|
+
const out = &payload.writer;
|
|
104
108
|
try out.writeAll("{\"status\":");
|
|
105
109
|
try trace.writeJsonString(out, status);
|
|
106
110
|
try out.writeAll(",\"out\":");
|
|
107
111
|
try trace.writeJsonString(out, out_path);
|
|
108
112
|
try out.print(",\"includeActions\":{},\"validated\":{}}}", .{ include_actions, validated });
|
|
109
|
-
try tw.recordEvent("trace.discover",
|
|
113
|
+
try tw.recordEvent("trace.discover", out.buffered());
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
pub fn recordTraceExplore(tw: *trace.TraceWriter, status: []const u8, out_path: []const u8, goal: []const u8, include_actions: bool, validated: bool) !void {
|
|
113
|
-
var payload =
|
|
114
|
-
defer payload.deinit(
|
|
115
|
-
const out = payload.writer
|
|
117
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
118
|
+
defer payload.deinit();
|
|
119
|
+
const out = &payload.writer;
|
|
116
120
|
try out.writeAll("{\"status\":");
|
|
117
121
|
try trace.writeJsonString(out, status);
|
|
118
122
|
try out.writeAll(",\"out\":");
|
|
@@ -120,7 +124,7 @@ pub fn recordTraceExplore(tw: *trace.TraceWriter, status: []const u8, out_path:
|
|
|
120
124
|
try out.writeAll(",\"goal\":");
|
|
121
125
|
try trace.writeJsonString(out, goal);
|
|
122
126
|
try out.print(",\"includeActions\":{},\"validated\":{}}}", .{ include_actions, validated });
|
|
123
|
-
try tw.recordEvent("trace.explore",
|
|
127
|
+
try tw.recordEvent("trace.explore", out.buffered());
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
pub fn recordStepError(tw: *trace.TraceWriter, index: usize, err: anyerror) !void {
|
|
@@ -140,9 +144,9 @@ pub fn recordScenarioEnd(
|
|
|
140
144
|
failed_index: ?usize,
|
|
141
145
|
err: ?anyerror,
|
|
142
146
|
) !void {
|
|
143
|
-
var payload =
|
|
144
|
-
defer payload.deinit(
|
|
145
|
-
const writer = payload.writer
|
|
147
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
148
|
+
defer payload.deinit();
|
|
149
|
+
const writer = &payload.writer;
|
|
146
150
|
try writer.writeAll("{\"value\":");
|
|
147
151
|
try trace.writeJsonString(writer, name);
|
|
148
152
|
try writer.writeAll(",\"status\":");
|
|
@@ -155,7 +159,7 @@ pub fn recordScenarioEnd(
|
|
|
155
159
|
try trace.writeJsonString(writer, @errorName(actual));
|
|
156
160
|
}
|
|
157
161
|
try writer.writeAll("}");
|
|
158
|
-
try tw.recordEvent("scenario.end",
|
|
162
|
+
try tw.recordEvent("scenario.end", writer.buffered());
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
pub fn recordSelectorMiss(
|
|
@@ -178,15 +182,15 @@ pub fn recordWaitTimeout(
|
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
pub fn recordObservationRetry(tw: *trace.TraceWriter, kind: []const u8, err: anyerror) !void {
|
|
181
|
-
var payload =
|
|
182
|
-
defer payload.deinit(
|
|
183
|
-
const writer = payload.writer
|
|
185
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
186
|
+
defer payload.deinit();
|
|
187
|
+
const writer = &payload.writer;
|
|
184
188
|
try writer.writeAll("{\"status\":\"retry\",\"kind\":");
|
|
185
189
|
try trace.writeJsonString(writer, kind);
|
|
186
190
|
try writer.writeAll(",\"error\":");
|
|
187
191
|
try trace.writeJsonString(writer, @errorName(err));
|
|
188
192
|
try writer.writeAll("}");
|
|
189
|
-
try tw.recordEvent("observe.retry",
|
|
193
|
+
try tw.recordEvent("observe.retry", writer.buffered());
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
pub fn recordDiagnostic(
|
package/src/runner_native.zig
CHANGED
|
@@ -61,13 +61,14 @@ fn recordSelectorAction(
|
|
|
61
61
|
wanted: selector.Selector,
|
|
62
62
|
max_chars: ?u32,
|
|
63
63
|
) !void {
|
|
64
|
-
var payload =
|
|
65
|
-
defer payload.deinit(
|
|
66
|
-
|
|
67
|
-
try
|
|
68
|
-
|
|
69
|
-
try
|
|
70
|
-
try
|
|
64
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
65
|
+
defer payload.deinit();
|
|
66
|
+
const writer = &payload.writer;
|
|
67
|
+
try writer.writeAll("{\"status\":\"ok\",\"strategy\":\"nativeSelector\",\"selector\":");
|
|
68
|
+
try trace.writeSelectorJson(writer, wanted);
|
|
69
|
+
if (max_chars) |value| try writer.print(",\"maxChars\":{d}", .{value});
|
|
70
|
+
try writer.writeAll("}");
|
|
71
|
+
try tw.recordEvent(kind, writer.buffered());
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
fn recordSelectorTextAction(
|
|
@@ -76,14 +77,15 @@ fn recordSelectorTextAction(
|
|
|
76
77
|
wanted: selector.Selector,
|
|
77
78
|
text: []const u8,
|
|
78
79
|
) !void {
|
|
79
|
-
var payload =
|
|
80
|
-
defer payload.deinit(
|
|
81
|
-
|
|
82
|
-
try
|
|
83
|
-
try
|
|
84
|
-
try
|
|
85
|
-
try
|
|
86
|
-
try
|
|
80
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
81
|
+
defer payload.deinit();
|
|
82
|
+
const writer = &payload.writer;
|
|
83
|
+
try writer.writeAll("{\"status\":\"ok\",\"strategy\":\"nativeSelector\",\"selector\":");
|
|
84
|
+
try trace.writeSelectorJson(writer, wanted);
|
|
85
|
+
try writer.writeAll(",\"text\":");
|
|
86
|
+
try trace.writeJsonString(writer, text);
|
|
87
|
+
try writer.writeAll("}");
|
|
88
|
+
try tw.recordEvent(kind, writer.buffered());
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
fn recordSelectorActionFailure(
|
|
@@ -92,13 +94,13 @@ fn recordSelectorActionFailure(
|
|
|
92
94
|
wanted: selector.Selector,
|
|
93
95
|
err: anyerror,
|
|
94
96
|
) !void {
|
|
95
|
-
var payload =
|
|
96
|
-
defer payload.deinit(
|
|
97
|
-
const out = payload.writer
|
|
97
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
98
|
+
defer payload.deinit();
|
|
99
|
+
const out = &payload.writer;
|
|
98
100
|
try out.writeAll("{\"status\":\"failed\",\"strategy\":\"nativeSelector\",\"error\":");
|
|
99
101
|
try trace.writeJsonString(out, @errorName(err));
|
|
100
102
|
try out.writeAll(",\"selector\":");
|
|
101
103
|
try trace.writeSelectorJson(out, wanted);
|
|
102
104
|
try out.writeAll("}");
|
|
103
|
-
try tw.recordEvent(kind,
|
|
105
|
+
try tw.recordEvent(kind, out.buffered());
|
|
104
106
|
}
|
package/src/runner_waits.zig
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const stdio = @import("stdio.zig");
|
|
2
3
|
const health = @import("health.zig");
|
|
3
4
|
const runner_config = @import("runner_config.zig");
|
|
4
5
|
const runner_events = @import("runner_events.zig");
|
|
@@ -37,14 +38,14 @@ fn untilVisibleKind(
|
|
|
37
38
|
options: RunOptions,
|
|
38
39
|
kind: []const u8,
|
|
39
40
|
) !bool {
|
|
40
|
-
const deadline =
|
|
41
|
+
const deadline = stdio.nowMs() + @as(i64, @intCast(timeout_ms));
|
|
41
42
|
while (true) {
|
|
42
43
|
if (try nativeVisibleBySelector(device, wanted)) |visible| {
|
|
43
44
|
if (visible) {
|
|
44
45
|
if (writer) |tw| try runner_events.recordNativeWait(tw, kind, wanted, null, timeout_ms);
|
|
45
46
|
return true;
|
|
46
47
|
}
|
|
47
|
-
if (
|
|
48
|
+
if (stdio.nowMs() >= deadline) {
|
|
48
49
|
if (writer) |tw| try runner_events.recordNativeWaitTimeoutWithDiagnostics(device, tw, kind, &[_]selector.Selector{wanted}, timeout_ms);
|
|
49
50
|
return false;
|
|
50
51
|
}
|
|
@@ -58,16 +59,17 @@ fn untilVisibleKind(
|
|
|
58
59
|
defer snap.deinit(device.allocator);
|
|
59
60
|
if (selector.find(snap.nodes, wanted)) |node| {
|
|
60
61
|
if (writer) |tw| {
|
|
61
|
-
var payload =
|
|
62
|
-
defer payload.deinit(
|
|
63
|
-
|
|
64
|
-
try
|
|
65
|
-
try
|
|
66
|
-
try
|
|
62
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
63
|
+
defer payload.deinit();
|
|
64
|
+
const out = &payload.writer;
|
|
65
|
+
try out.print("{{\"status\":\"ok\",\"target\":\"{s}\",\"selector\":", .{node.stable_id});
|
|
66
|
+
try trace.writeSelectorJson(out, wanted);
|
|
67
|
+
try out.print(",\"timeoutMs\":{d}}}", .{timeout_ms});
|
|
68
|
+
try tw.recordEvent(kind, out.buffered());
|
|
67
69
|
}
|
|
68
70
|
return true;
|
|
69
71
|
}
|
|
70
|
-
if (
|
|
72
|
+
if (stdio.nowMs() >= deadline) {
|
|
71
73
|
if (writer) |tw| {
|
|
72
74
|
const selectors = [_]selector.Selector{wanted};
|
|
73
75
|
try runner_events.recordDiagnosticWithStrategyAndTimeout(tw, kind, "timeout", null, selectors[0..], snap, timeout_ms);
|
|
@@ -106,14 +108,14 @@ fn untilNotVisibleKind(
|
|
|
106
108
|
options: RunOptions,
|
|
107
109
|
kind: []const u8,
|
|
108
110
|
) !bool {
|
|
109
|
-
const deadline =
|
|
111
|
+
const deadline = stdio.nowMs() + @as(i64, @intCast(timeout_ms));
|
|
110
112
|
while (true) {
|
|
111
113
|
if (try nativeVisibleBySelector(device, wanted)) |visible| {
|
|
112
114
|
if (!visible) {
|
|
113
115
|
if (writer) |tw| try runner_events.recordNativeWait(tw, kind, wanted, null, timeout_ms);
|
|
114
116
|
return true;
|
|
115
117
|
}
|
|
116
|
-
if (
|
|
118
|
+
if (stdio.nowMs() >= deadline) {
|
|
117
119
|
if (writer) |tw| try runner_events.recordNativeWaitTimeoutWithDiagnostics(device, tw, kind, &[_]selector.Selector{wanted}, timeout_ms);
|
|
118
120
|
return false;
|
|
119
121
|
}
|
|
@@ -127,16 +129,17 @@ fn untilNotVisibleKind(
|
|
|
127
129
|
defer snap.deinit(device.allocator);
|
|
128
130
|
if (selector.find(snap.nodes, wanted) == null) {
|
|
129
131
|
if (writer) |tw| {
|
|
130
|
-
var payload =
|
|
131
|
-
defer payload.deinit(
|
|
132
|
-
|
|
133
|
-
try
|
|
134
|
-
try
|
|
135
|
-
try
|
|
132
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
133
|
+
defer payload.deinit();
|
|
134
|
+
const out = &payload.writer;
|
|
135
|
+
try out.writeAll("{\"status\":\"ok\",\"selector\":");
|
|
136
|
+
try trace.writeSelectorJson(out, wanted);
|
|
137
|
+
try out.print(",\"timeoutMs\":{d}}}", .{timeout_ms});
|
|
138
|
+
try tw.recordEvent(kind, out.buffered());
|
|
136
139
|
}
|
|
137
140
|
return true;
|
|
138
141
|
}
|
|
139
|
-
if (
|
|
142
|
+
if (stdio.nowMs() >= deadline) {
|
|
140
143
|
if (writer) |tw| {
|
|
141
144
|
const selectors = [_]selector.Selector{wanted};
|
|
142
145
|
try runner_events.recordDiagnosticWithStrategyAndTimeout(tw, kind, "timeout", null, selectors[0..], snap, timeout_ms);
|
|
@@ -154,7 +157,7 @@ pub fn waitUntilAnyVisible(
|
|
|
154
157
|
writer: ?*trace.TraceWriter,
|
|
155
158
|
options: RunOptions,
|
|
156
159
|
) !?usize {
|
|
157
|
-
const deadline =
|
|
160
|
+
const deadline = stdio.nowMs() + @as(i64, @intCast(timeout_ms));
|
|
158
161
|
while (true) {
|
|
159
162
|
var all_native = true;
|
|
160
163
|
for (selectors, 0..) |wanted, index| {
|
|
@@ -169,7 +172,7 @@ pub fn waitUntilAnyVisible(
|
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
174
|
if (all_native) {
|
|
172
|
-
if (
|
|
175
|
+
if (stdio.nowMs() >= deadline) {
|
|
173
176
|
if (writer) |tw| try runner_events.recordNativeWaitTimeoutWithDiagnostics(device, tw, "wait.any", selectors, timeout_ms);
|
|
174
177
|
return null;
|
|
175
178
|
}
|
|
@@ -184,17 +187,18 @@ pub fn waitUntilAnyVisible(
|
|
|
184
187
|
for (selectors, 0..) |wanted, index| {
|
|
185
188
|
if (selector.find(snap.nodes, wanted)) |node| {
|
|
186
189
|
if (writer) |tw| {
|
|
187
|
-
var payload =
|
|
188
|
-
defer payload.deinit(
|
|
189
|
-
|
|
190
|
-
try
|
|
191
|
-
try
|
|
192
|
-
try
|
|
190
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
191
|
+
defer payload.deinit();
|
|
192
|
+
const out = &payload.writer;
|
|
193
|
+
try out.print("{{\"status\":\"ok\",\"matchedIndex\":{d},\"target\":\"{s}\",\"selector\":", .{ index, node.stable_id });
|
|
194
|
+
try trace.writeSelectorJson(out, wanted);
|
|
195
|
+
try out.print(",\"timeoutMs\":{d}}}", .{timeout_ms});
|
|
196
|
+
try tw.recordEvent("wait.any", out.buffered());
|
|
193
197
|
}
|
|
194
198
|
return index;
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
|
-
if (
|
|
201
|
+
if (stdio.nowMs() >= deadline) {
|
|
198
202
|
if (writer) |tw| try runner_events.recordWaitTimeout(tw, "wait.any", selectors, snap);
|
|
199
203
|
return null;
|
|
200
204
|
}
|
|
@@ -209,7 +213,7 @@ pub fn assertNoneVisible(
|
|
|
209
213
|
writer: ?*trace.TraceWriter,
|
|
210
214
|
options: RunOptions,
|
|
211
215
|
) !bool {
|
|
212
|
-
const deadline =
|
|
216
|
+
const deadline = stdio.nowMs() + @as(i64, @intCast(timeout_ms));
|
|
213
217
|
while (true) {
|
|
214
218
|
var snap = device.snapshot(writer) catch |err| {
|
|
215
219
|
if (try retryTransientObservation(err, "assert.noneVisible", writer, deadline, options)) continue;
|
|
@@ -230,7 +234,7 @@ pub fn assertNoneVisible(
|
|
|
230
234
|
return true;
|
|
231
235
|
}
|
|
232
236
|
|
|
233
|
-
if (
|
|
237
|
+
if (stdio.nowMs() >= deadline) {
|
|
234
238
|
if (writer) |tw| try runner_events.recordDiagnosticWithStrategyAndTimeout(tw, "assert.noneVisible", "visible", null, selectors, snap, timeout_ms);
|
|
235
239
|
return false;
|
|
236
240
|
}
|
|
@@ -246,7 +250,7 @@ pub fn assertHealthy(
|
|
|
246
250
|
options: RunOptions,
|
|
247
251
|
) !bool {
|
|
248
252
|
const health_selectors = health.defaultSelectors();
|
|
249
|
-
const deadline =
|
|
253
|
+
const deadline = stdio.nowMs() + @as(i64, @intCast(timeout_ms));
|
|
250
254
|
while (true) {
|
|
251
255
|
var snap = device.snapshot(writer) catch |err| {
|
|
252
256
|
if (try retryTransientObservation(err, "assert.healthy", writer, deadline, options)) continue;
|
|
@@ -263,7 +267,7 @@ pub fn assertHealthy(
|
|
|
263
267
|
return true;
|
|
264
268
|
}
|
|
265
269
|
|
|
266
|
-
if (
|
|
270
|
+
if (stdio.nowMs() >= deadline) {
|
|
267
271
|
if (writer) |tw| try runner_events.recordDiagnosticWithStrategyAndTimeout(tw, "assert.healthy", "unhealthy", null, health_selectors, snap, timeout_ms);
|
|
268
272
|
return false;
|
|
269
273
|
}
|
|
@@ -280,7 +284,7 @@ pub fn scrollUntilVisible(
|
|
|
280
284
|
writer: ?*trace.TraceWriter,
|
|
281
285
|
options: RunOptions,
|
|
282
286
|
) !bool {
|
|
283
|
-
const deadline =
|
|
287
|
+
const deadline = stdio.nowMs() + @as(i64, @intCast(timeout_ms));
|
|
284
288
|
while (true) {
|
|
285
289
|
var snap = device.snapshot(writer) catch |err| {
|
|
286
290
|
if (try retryTransientObservation(err, "ui.scrollUntilVisible", writer, deadline, options)) continue;
|
|
@@ -289,19 +293,20 @@ pub fn scrollUntilVisible(
|
|
|
289
293
|
defer snap.deinit(device.allocator);
|
|
290
294
|
if (selector.find(snap.nodes, wanted)) |node| {
|
|
291
295
|
if (writer) |tw| {
|
|
292
|
-
var payload =
|
|
293
|
-
defer payload.deinit(
|
|
294
|
-
|
|
295
|
-
try
|
|
296
|
-
try
|
|
296
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
297
|
+
defer payload.deinit();
|
|
298
|
+
const out = &payload.writer;
|
|
299
|
+
try out.print("{{\"status\":\"ok\",\"target\":\"{s}\",\"selector\":", .{node.stable_id});
|
|
300
|
+
try trace.writeSelectorJson(out, wanted);
|
|
301
|
+
try out.print(",\"direction\":\"{s}\",\"timeoutMs\":{d}}}", .{
|
|
297
302
|
if (direction == .down) "down" else "up",
|
|
298
303
|
timeout_ms,
|
|
299
304
|
});
|
|
300
|
-
try tw.recordEvent("ui.scrollUntilVisible",
|
|
305
|
+
try tw.recordEvent("ui.scrollUntilVisible", out.buffered());
|
|
301
306
|
}
|
|
302
307
|
return true;
|
|
303
308
|
}
|
|
304
|
-
if (
|
|
309
|
+
if (stdio.nowMs() >= deadline) {
|
|
305
310
|
if (writer) |tw| {
|
|
306
311
|
const selectors = [_]selector.Selector{wanted};
|
|
307
312
|
try runner_events.recordWaitTimeout(tw, "ui.scrollUntilVisible", selectors[0..], snap);
|
|
@@ -348,7 +353,7 @@ fn retryTransientObservation(
|
|
|
348
353
|
options: RunOptions,
|
|
349
354
|
) !bool {
|
|
350
355
|
if (err != error.CommandTimedOut) return false;
|
|
351
|
-
if (
|
|
356
|
+
if (stdio.nowMs() >= deadline) return false;
|
|
352
357
|
if (writer) |tw| try runner_events.recordObservationRetry(tw, kind, err);
|
|
353
358
|
try sleepMs(options.poll_ms);
|
|
354
359
|
return true;
|
|
@@ -359,5 +364,5 @@ fn settleDevice(device: anytype, options: RunOptions) !void {
|
|
|
359
364
|
}
|
|
360
365
|
|
|
361
366
|
fn sleepMs(ms: u64) !void {
|
|
362
|
-
|
|
367
|
+
stdio.sleepNs(ms * std.time.ns_per_ms);
|
|
363
368
|
}
|
package/src/scaffold.zig
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const stdio = @import("stdio.zig");
|
|
2
3
|
|
|
3
4
|
pub const app_config_file = ".zmr/config.json";
|
|
4
5
|
pub const app_android_smoke_file = ".zmr/android-smoke.json";
|
|
@@ -40,24 +41,24 @@ pub fn writeStarterScenario(
|
|
|
40
41
|
force: bool,
|
|
41
42
|
) !void {
|
|
42
43
|
if (!force) {
|
|
43
|
-
|
|
44
|
+
stdio.access(path) catch |err| switch (err) {
|
|
44
45
|
error.FileNotFound => {},
|
|
45
46
|
else => return err,
|
|
46
47
|
};
|
|
47
|
-
if (
|
|
48
|
+
if (stdio.access(path)) |_| return error.PathAlreadyExists else |err| switch (err) {
|
|
48
49
|
error.FileNotFound => {},
|
|
49
50
|
else => return err,
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
if (std.fs.path.dirname(path)) |parent| {
|
|
54
|
-
if (parent.len > 0) try std.
|
|
55
|
+
if (parent.len > 0) try std.Io.Dir.cwd().createDirPath(stdio.io(), parent);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
var file = try std.
|
|
58
|
-
defer file.close();
|
|
58
|
+
var file = try std.Io.Dir.cwd().createFile(stdio.io(), path, .{ .truncate = true });
|
|
59
|
+
defer file.close(stdio.io());
|
|
59
60
|
var buffer: [4096]u8 = undefined;
|
|
60
|
-
var file_writer = file.
|
|
61
|
+
var file_writer = file.writerStreaming(stdio.io(), &buffer);
|
|
61
62
|
const writer = &file_writer.interface;
|
|
62
63
|
try writer.writeAll(
|
|
63
64
|
\\{
|
|
@@ -87,7 +88,7 @@ pub fn writeAppScaffold(
|
|
|
87
88
|
) !void {
|
|
88
89
|
const zmr_dir = try std.fs.path.join(allocator, &.{ dir, ".zmr" });
|
|
89
90
|
defer allocator.free(zmr_dir);
|
|
90
|
-
try std.
|
|
91
|
+
try std.Io.Dir.cwd().createDirPath(stdio.io(), zmr_dir);
|
|
91
92
|
|
|
92
93
|
const config_path = try std.fs.path.join(allocator, &.{ zmr_dir, appFileBasename(app_config_file) });
|
|
93
94
|
defer allocator.free(config_path);
|
|
@@ -114,9 +115,9 @@ fn appFileBasename(path: []const u8) []const u8 {
|
|
|
114
115
|
|
|
115
116
|
fn writeAppConfig(path: []const u8, app_id: []const u8, force: bool) !void {
|
|
116
117
|
var file = try createOutputFile(path, force);
|
|
117
|
-
defer file.close();
|
|
118
|
+
defer file.close(stdio.io());
|
|
118
119
|
var buffer: [8192]u8 = undefined;
|
|
119
|
-
var file_writer = file.
|
|
120
|
+
var file_writer = file.writerStreaming(stdio.io(), &buffer);
|
|
120
121
|
const writer = &file_writer.interface;
|
|
121
122
|
try writer.writeAll(
|
|
122
123
|
\\{
|
|
@@ -190,9 +191,9 @@ fn writeAppConfig(path: []const u8, app_id: []const u8, force: bool) !void {
|
|
|
190
191
|
|
|
191
192
|
fn writeDeviceMatrix(path: []const u8, app_id: []const u8, force: bool) !void {
|
|
192
193
|
var file = try createOutputFile(path, force);
|
|
193
|
-
defer file.close();
|
|
194
|
+
defer file.close(stdio.io());
|
|
194
195
|
var buffer: [8192]u8 = undefined;
|
|
195
|
-
var file_writer = file.
|
|
196
|
+
var file_writer = file.writerStreaming(stdio.io(), &buffer);
|
|
196
197
|
const writer = &file_writer.interface;
|
|
197
198
|
try writer.writeAll(
|
|
198
199
|
\\{
|
|
@@ -228,9 +229,9 @@ fn writeDeviceMatrix(path: []const u8, app_id: []const u8, force: bool) !void {
|
|
|
228
229
|
fn writePlatformSmoke(path: []const u8, name: []const u8, app_id: []const u8, force: bool) !void {
|
|
229
230
|
if (!force and try pathExists(path)) return;
|
|
230
231
|
var file = try createOutputFile(path, force);
|
|
231
|
-
defer file.close();
|
|
232
|
+
defer file.close(stdio.io());
|
|
232
233
|
var buffer: [4096]u8 = undefined;
|
|
233
|
-
var file_writer = file.
|
|
234
|
+
var file_writer = file.writerStreaming(stdio.io(), &buffer);
|
|
234
235
|
const writer = &file_writer.interface;
|
|
235
236
|
try writer.writeAll(
|
|
236
237
|
\\{
|
|
@@ -256,7 +257,7 @@ fn writePlatformSmoke(path: []const u8, name: []const u8, app_id: []const u8, fo
|
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
fn pathExists(path: []const u8) !bool {
|
|
259
|
-
|
|
260
|
+
stdio.access(path) catch |err| switch (err) {
|
|
260
261
|
error.FileNotFound => return false,
|
|
261
262
|
else => return err,
|
|
262
263
|
};
|
|
@@ -300,9 +301,9 @@ fn isShellSafe(value: []const u8) bool {
|
|
|
300
301
|
|
|
301
302
|
fn writeAgentInstructions(path: []const u8, app_id: []const u8, force: bool) !void {
|
|
302
303
|
var file = try createOutputFile(path, force);
|
|
303
|
-
defer file.close();
|
|
304
|
+
defer file.close(stdio.io());
|
|
304
305
|
var buffer: [4096]u8 = undefined;
|
|
305
|
-
var file_writer = file.
|
|
306
|
+
var file_writer = file.writerStreaming(stdio.io(), &buffer);
|
|
306
307
|
const writer = &file_writer.interface;
|
|
307
308
|
try writer.writeAll(
|
|
308
309
|
\\# ZMR Agent Instructions
|
|
@@ -434,19 +435,19 @@ fn writeAgentInstructions(path: []const u8, app_id: []const u8, force: bool) !vo
|
|
|
434
435
|
try writer.flush();
|
|
435
436
|
}
|
|
436
437
|
|
|
437
|
-
fn createOutputFile(path: []const u8, force: bool) !std.
|
|
438
|
+
fn createOutputFile(path: []const u8, force: bool) !std.Io.File {
|
|
438
439
|
if (std.fs.path.dirname(path)) |parent| {
|
|
439
|
-
if (parent.len > 0) try std.
|
|
440
|
+
if (parent.len > 0) try std.Io.Dir.cwd().createDirPath(stdio.io(), parent);
|
|
440
441
|
}
|
|
441
|
-
if (!force) return try std.
|
|
442
|
-
return try std.
|
|
442
|
+
if (!force) return try std.Io.Dir.cwd().createFile(stdio.io(), path, .{ .exclusive = true });
|
|
443
|
+
return try std.Io.Dir.cwd().createFile(stdio.io(), path, .{ .truncate = true });
|
|
443
444
|
}
|
|
444
445
|
|
|
445
446
|
fn ensureTraceGitignore(allocator: std.mem.Allocator, dir: []const u8) !void {
|
|
446
447
|
const path = try std.fs.path.join(allocator, &.{ dir, ".gitignore" });
|
|
447
448
|
defer allocator.free(path);
|
|
448
449
|
|
|
449
|
-
const existing =
|
|
450
|
+
const existing = stdio.readFileAlloc(allocator, path, 1024 * 1024) catch |err| switch (err) {
|
|
450
451
|
error.FileNotFound => "",
|
|
451
452
|
else => return err,
|
|
452
453
|
};
|
|
@@ -455,10 +456,10 @@ fn ensureTraceGitignore(allocator: std.mem.Allocator, dir: []const u8) !void {
|
|
|
455
456
|
|
|
456
457
|
if (std.mem.indexOf(u8, existing, "traces/") != null) return;
|
|
457
458
|
|
|
458
|
-
var file = try std.
|
|
459
|
-
defer file.close();
|
|
459
|
+
var file = try std.Io.Dir.cwd().createFile(stdio.io(), path, .{ .truncate = true });
|
|
460
|
+
defer file.close(stdio.io());
|
|
460
461
|
var buffer: [4096]u8 = undefined;
|
|
461
|
-
var file_writer = file.
|
|
462
|
+
var file_writer = file.writerStreaming(stdio.io(), &buffer);
|
|
462
463
|
const writer = &file_writer.interface;
|
|
463
464
|
if (existing.len > 0) {
|
|
464
465
|
try writer.writeAll(existing);
|
package/src/scenario.zig
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const stdio = @import("stdio.zig");
|
|
2
3
|
const fields = @import("scenario_fields.zig");
|
|
3
4
|
const selector = @import("selector.zig");
|
|
4
5
|
|
|
@@ -162,7 +163,7 @@ pub const Scenario = struct {
|
|
|
162
163
|
};
|
|
163
164
|
|
|
164
165
|
pub fn parseFile(allocator: std.mem.Allocator, path: []const u8) !Scenario {
|
|
165
|
-
const content = try
|
|
166
|
+
const content = try stdio.readFileAlloc(allocator, path, 16 * 1024 * 1024);
|
|
166
167
|
defer allocator.free(content);
|
|
167
168
|
return try parseSlice(allocator, content);
|
|
168
169
|
}
|
|
@@ -338,7 +339,7 @@ fn parseRawStep(allocator: std.mem.Allocator, object: std.json.ObjectMap) anyerr
|
|
|
338
339
|
} };
|
|
339
340
|
}
|
|
340
341
|
|
|
341
|
-
return error.
|
|
342
|
+
return error.unknownScenarioAction;
|
|
342
343
|
}
|
|
343
344
|
|
|
344
345
|
fn appendParsedSteps(allocator: std.mem.Allocator, steps: *std.ArrayList(Step), value: std.json.Value) anyerror!void {
|
|
@@ -365,7 +366,7 @@ fn optionalDirection(object: std.json.ObjectMap, key: []const u8, default_value:
|
|
|
365
366
|
if (value != .string) return error.OptionalFieldMustBeString;
|
|
366
367
|
if (std.mem.eql(u8, value.string, "down")) return .down;
|
|
367
368
|
if (std.mem.eql(u8, value.string, "up")) return .up;
|
|
368
|
-
return error.
|
|
369
|
+
return error.unknownScrollDirection;
|
|
369
370
|
}
|
|
370
371
|
|
|
371
372
|
fn optionalTimeoutMs(object: std.json.ObjectMap) !?u64 {
|