zeno-mobile-runner 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/FEATURES.md +1 -1
  3. package/README.md +9 -1
  4. package/build.zig.zon +2 -2
  5. package/clients/kotlin/README.md +1 -1
  6. package/clients/kotlin/build.gradle.kts +1 -1
  7. package/clients/python/pyproject.toml +1 -1
  8. package/clients/rust/Cargo.lock +1 -1
  9. package/clients/rust/Cargo.toml +1 -1
  10. package/clients/typescript/package.json +1 -1
  11. package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
  12. package/docs/protocol.md +10 -10
  13. package/examples/ios-dev-client-open-link.json +24 -13
  14. package/examples/ios-dev-client-route-snapshot.json +33 -8
  15. package/npm/scenarios.mjs +15 -8
  16. package/npm/wizard.mjs +1 -1
  17. package/package.json +3 -1
  18. package/prebuilds/darwin-arm64/zmr +0 -0
  19. package/prebuilds/darwin-x64/zmr +0 -0
  20. package/prebuilds/linux-arm64/zmr +0 -0
  21. package/prebuilds/linux-x64/zmr +0 -0
  22. package/scripts/create-react-native-expo-demo-app.sh +11 -13
  23. package/shims/ios/ZMRShim.swift +40 -12
  24. package/shims/ios/ZMRShimUITestCase.swift +142 -16
  25. package/src/android.zig +10 -9
  26. package/src/android_emulator.zig +22 -11
  27. package/src/android_screen_recording.zig +11 -7
  28. package/src/bundle.zig +10 -9
  29. package/src/bundle_redaction.zig +29 -28
  30. package/src/bundle_tar.zig +15 -12
  31. package/src/cli_devices.zig +7 -3
  32. package/src/cli_discover.zig +7 -3
  33. package/src/cli_doctor.zig +7 -3
  34. package/src/cli_draft.zig +51 -47
  35. package/src/cli_explore.zig +7 -3
  36. package/src/cli_import.zig +8 -4
  37. package/src/cli_info.zig +13 -6
  38. package/src/cli_init.zig +9 -5
  39. package/src/cli_inspect.zig +8 -4
  40. package/src/cli_run.zig +22 -16
  41. package/src/cli_serve.zig +3 -3
  42. package/src/cli_trace.zig +25 -12
  43. package/src/cli_validate.zig +8 -4
  44. package/src/command.zig +81 -99
  45. package/src/config.zig +2 -1
  46. package/src/config_diagnostics.zig +2 -1
  47. package/src/config_paths.zig +2 -1
  48. package/src/doctor.zig +8 -7
  49. package/src/doctor_hints.zig +1 -1
  50. package/src/errors.zig +5 -5
  51. package/src/importer.zig +8 -7
  52. package/src/ios.zig +26 -29
  53. package/src/ios_devices.zig +6 -5
  54. package/src/ios_lifecycle.zig +4 -4
  55. package/src/json_rpc.zig +39 -40
  56. package/src/json_rpc_methods.zig +8 -8
  57. package/src/json_rpc_observation.zig +9 -8
  58. package/src/json_rpc_params.zig +1 -1
  59. package/src/json_rpc_trace.zig +22 -21
  60. package/src/main.zig +22 -10
  61. package/src/mcp.zig +28 -19
  62. package/src/mcp_trace.zig +30 -29
  63. package/src/report.zig +39 -36
  64. package/src/report_html.zig +5 -4
  65. package/src/runner.zig +2 -1
  66. package/src/runner_actions.zig +20 -17
  67. package/src/runner_diagnostics.zig +4 -4
  68. package/src/runner_events.zig +55 -51
  69. package/src/runner_native.zig +21 -19
  70. package/src/runner_waits.zig +46 -41
  71. package/src/scaffold.zig +25 -24
  72. package/src/scenario.zig +4 -3
  73. package/src/stdio.zig +129 -0
  74. package/src/trace.zig +34 -26
  75. package/src/trace_summary.zig +3 -2
  76. package/src/trace_summary_diagnostic.zig +15 -13
  77. package/src/validation.zig +5 -4
  78. package/src/version.zig +1 -1
@@ -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 = std.ArrayList(u8).empty;
9
- errdefer buffer.deinit(allocator);
10
- try buffer.writer(allocator).writeAll("{\"value\":");
11
- try trace.writeJsonString(buffer.writer(allocator), value);
12
- try buffer.writer(allocator).writeAll("}");
13
- return try buffer.toOwnedSlice(allocator);
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 = std.ArrayList(u8).empty;
18
- defer payload.deinit(tw.allocator);
19
- try payload.writer(tw.allocator).writeAll("{\"status\":\"ok\",\"strategy\":\"nativeSelector\"");
20
- if (matched_index) |index| try payload.writer(tw.allocator).print(",\"matchedIndex\":{d}", .{index});
21
- try payload.writer(tw.allocator).writeAll(",\"selector\":");
22
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
23
- try payload.writer(tw.allocator).print(",\"timeoutMs\":{d}}}", .{timeout_ms});
24
- try tw.recordEvent(kind, payload.items);
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 = std.ArrayList(u8).empty;
29
- defer payload.deinit(tw.allocator);
30
- try payload.writer(tw.allocator).print("{{\"status\":\"timeout\",\"strategy\":\"nativeSelector\",\"timeoutMs\":{d},\"selectors\":[", .{timeout_ms});
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 payload.writer(tw.allocator).writeAll(",");
33
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
35
+ if (index > 0) try writer.writeAll(",");
36
+ try trace.writeSelectorJson(writer, wanted);
34
37
  }
35
- try payload.writer(tw.allocator).writeAll("]}");
36
- try tw.recordEvent(kind, payload.items);
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 = std.ArrayList(u8).empty;
50
- defer payload.deinit(tw.allocator);
51
- const out = payload.writer(tw.allocator);
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, payload.items);
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 = std.ArrayList(u8).empty;
65
- defer payload.deinit(tw.allocator);
66
- try payload.writer(tw.allocator).writeAll("{\"selector\":");
67
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
68
- try payload.writer(tw.allocator).writeAll("}");
69
- try tw.recordEvent(kind, payload.items);
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 = std.ArrayList(u8).empty;
74
- defer payload.deinit(tw.allocator);
75
- const out = payload.writer(tw.allocator);
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, payload.items);
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 = std.ArrayList(u8).empty;
102
- defer payload.deinit(tw.allocator);
103
- const out = payload.writer(tw.allocator);
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", payload.items);
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 = std.ArrayList(u8).empty;
114
- defer payload.deinit(tw.allocator);
115
- const out = payload.writer(tw.allocator);
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", payload.items);
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 = std.ArrayList(u8).empty;
144
- defer payload.deinit(tw.allocator);
145
- const writer = payload.writer(tw.allocator);
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", payload.items);
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 = std.ArrayList(u8).empty;
182
- defer payload.deinit(tw.allocator);
183
- const writer = payload.writer(tw.allocator);
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", payload.items);
193
+ try tw.recordEvent("observe.retry", writer.buffered());
190
194
  }
191
195
 
192
196
  pub fn recordDiagnostic(
@@ -61,13 +61,14 @@ fn recordSelectorAction(
61
61
  wanted: selector.Selector,
62
62
  max_chars: ?u32,
63
63
  ) !void {
64
- var payload = std.ArrayList(u8).empty;
65
- defer payload.deinit(tw.allocator);
66
- try payload.writer(tw.allocator).writeAll("{\"status\":\"ok\",\"strategy\":\"nativeSelector\",\"selector\":");
67
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
68
- if (max_chars) |value| try payload.writer(tw.allocator).print(",\"maxChars\":{d}", .{value});
69
- try payload.writer(tw.allocator).writeAll("}");
70
- try tw.recordEvent(kind, payload.items);
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 = std.ArrayList(u8).empty;
80
- defer payload.deinit(tw.allocator);
81
- try payload.writer(tw.allocator).writeAll("{\"status\":\"ok\",\"strategy\":\"nativeSelector\",\"selector\":");
82
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
83
- try payload.writer(tw.allocator).writeAll(",\"text\":");
84
- try trace.writeJsonString(payload.writer(tw.allocator), text);
85
- try payload.writer(tw.allocator).writeAll("}");
86
- try tw.recordEvent(kind, payload.items);
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 = std.ArrayList(u8).empty;
96
- defer payload.deinit(tw.allocator);
97
- const out = payload.writer(tw.allocator);
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, payload.items);
105
+ try tw.recordEvent(kind, out.buffered());
104
106
  }
@@ -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 = std.time.milliTimestamp() + @as(i64, @intCast(timeout_ms));
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 (std.time.milliTimestamp() >= deadline) {
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 = std.ArrayList(u8).empty;
62
- defer payload.deinit(tw.allocator);
63
- try payload.writer(tw.allocator).print("{{\"status\":\"ok\",\"target\":\"{s}\",\"selector\":", .{node.stable_id});
64
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
65
- try payload.writer(tw.allocator).print(",\"timeoutMs\":{d}}}", .{timeout_ms});
66
- try tw.recordEvent(kind, payload.items);
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 (std.time.milliTimestamp() >= deadline) {
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 = std.time.milliTimestamp() + @as(i64, @intCast(timeout_ms));
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 (std.time.milliTimestamp() >= deadline) {
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 = std.ArrayList(u8).empty;
131
- defer payload.deinit(tw.allocator);
132
- try payload.writer(tw.allocator).writeAll("{\"status\":\"ok\",\"selector\":");
133
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
134
- try payload.writer(tw.allocator).print(",\"timeoutMs\":{d}}}", .{timeout_ms});
135
- try tw.recordEvent(kind, payload.items);
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 (std.time.milliTimestamp() >= deadline) {
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 = std.time.milliTimestamp() + @as(i64, @intCast(timeout_ms));
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 (std.time.milliTimestamp() >= deadline) {
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 = std.ArrayList(u8).empty;
188
- defer payload.deinit(tw.allocator);
189
- try payload.writer(tw.allocator).print("{{\"status\":\"ok\",\"matchedIndex\":{d},\"target\":\"{s}\",\"selector\":", .{ index, node.stable_id });
190
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
191
- try payload.writer(tw.allocator).print(",\"timeoutMs\":{d}}}", .{timeout_ms});
192
- try tw.recordEvent("wait.any", payload.items);
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 (std.time.milliTimestamp() >= deadline) {
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 = std.time.milliTimestamp() + @as(i64, @intCast(timeout_ms));
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 (std.time.milliTimestamp() >= deadline) {
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 = std.time.milliTimestamp() + @as(i64, @intCast(timeout_ms));
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 (std.time.milliTimestamp() >= deadline) {
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 = std.time.milliTimestamp() + @as(i64, @intCast(timeout_ms));
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 = std.ArrayList(u8).empty;
293
- defer payload.deinit(tw.allocator);
294
- try payload.writer(tw.allocator).print("{{\"status\":\"ok\",\"target\":\"{s}\",\"selector\":", .{node.stable_id});
295
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
296
- try payload.writer(tw.allocator).print(",\"direction\":\"{s}\",\"timeoutMs\":{d}}}", .{
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", payload.items);
305
+ try tw.recordEvent("ui.scrollUntilVisible", out.buffered());
301
306
  }
302
307
  return true;
303
308
  }
304
- if (std.time.milliTimestamp() >= deadline) {
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 (std.time.milliTimestamp() >= deadline) return false;
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
- std.Thread.sleep(ms * std.time.ns_per_ms);
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
- std.fs.cwd().access(path, .{}) catch |err| switch (err) {
44
+ stdio.access(path) catch |err| switch (err) {
44
45
  error.FileNotFound => {},
45
46
  else => return err,
46
47
  };
47
- if (std.fs.cwd().access(path, .{})) |_| return error.PathAlreadyExists else |err| switch (err) {
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.fs.cwd().makePath(parent);
55
+ if (parent.len > 0) try std.Io.Dir.cwd().createDirPath(stdio.io(), parent);
55
56
  }
56
57
 
57
- var file = try std.fs.cwd().createFile(path, .{ .truncate = true });
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.writer(&buffer);
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.fs.cwd().makePath(zmr_dir);
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.writer(&buffer);
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.writer(&buffer);
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.writer(&buffer);
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
- std.fs.cwd().access(path, .{}) catch |err| switch (err) {
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.writer(&buffer);
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.fs.File {
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.fs.cwd().makePath(parent);
440
+ if (parent.len > 0) try std.Io.Dir.cwd().createDirPath(stdio.io(), parent);
440
441
  }
441
- if (!force) return try std.fs.cwd().createFile(path, .{ .exclusive = true });
442
- return try std.fs.cwd().createFile(path, .{ .truncate = true });
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 = std.fs.cwd().readFileAlloc(allocator, path, 1024 * 1024) catch |err| switch (err) {
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.fs.cwd().createFile(path, .{ .truncate = true });
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.writer(&buffer);
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 std.fs.cwd().readFileAlloc(allocator, path, 16 * 1024 * 1024);
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.UnknownScenarioAction;
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.UnknownScrollDirection;
369
+ return error.unknownScrollDirection;
369
370
  }
370
371
 
371
372
  fn optionalTimeoutMs(object: std.json.ObjectMap) !?u64 {