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
package/src/mcp.zig CHANGED
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const stdio = @import("stdio.zig");
2
3
  const cli_output = @import("cli_output.zig");
3
4
  const errors = @import("errors.zig");
4
5
  const mcp_protocol = @import("mcp_protocol.zig");
@@ -13,12 +14,19 @@ const trace = @import("trace.zig");
13
14
  const validation = @import("validation.zig");
14
15
 
15
16
  pub fn serveStdioWithTrace(allocator: std.mem.Allocator, device: anytype, live_trace: ?*trace.TraceWriter) !void {
16
- var stdin = std.fs.File.stdin().deprecatedReader();
17
- const stdout = std.fs.File.stdout().deprecatedWriter();
17
+ var stdin_io: stdio.Input = .{};
18
+ stdin_io.init(.stdin());
19
+ const stdin = stdin_io.reader();
20
+
21
+ var stdout_io: stdio.Output = .{};
22
+ stdout_io.init(.stdout());
23
+ defer stdout_io.deinit();
24
+ const stdout = stdout_io.writer();
18
25
 
19
26
  while (true) {
20
- const line = stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', 16 * 1024 * 1024) catch |err| {
27
+ const line = stdio.readLineAlloc(stdin, allocator, 16 * 1024 * 1024) catch |err| {
21
28
  try mcp_protocol.writeError(stdout, null, -32700, @errorName(err));
29
+ try stdout_io.flush();
22
30
  continue;
23
31
  };
24
32
  const owned_line = line orelse break;
@@ -26,6 +34,7 @@ pub fn serveStdioWithTrace(allocator: std.mem.Allocator, device: anytype, live_t
26
34
  const trimmed = std.mem.trim(u8, owned_line, " \t\r\n");
27
35
  if (trimmed.len == 0) continue;
28
36
  try dispatchLine(allocator, device, trimmed, stdout, live_trace);
37
+ try stdout_io.flush();
29
38
  }
30
39
  }
31
40
 
@@ -111,10 +120,10 @@ fn callTool(
111
120
  if (std.mem.eql(u8, tool_name, "snapshot")) {
112
121
  var snap = try device.snapshot(live_trace);
113
122
  defer snap.deinit(device.allocator);
114
- var payload = std.ArrayList(u8).empty;
115
- defer payload.deinit(allocator);
116
- try trace.writeSnapshotJson(payload.writer(allocator), snap);
117
- try mcp_protocol.writeToolTextResult(writer, id, payload.items);
123
+ var payload: std.Io.Writer.Allocating = .init(allocator);
124
+ defer payload.deinit();
125
+ try trace.writeSnapshotJson(&payload.writer, snap);
126
+ try mcp_protocol.writeToolTextResult(writer, id, payload.writer.buffered());
118
127
  return;
119
128
  }
120
129
 
@@ -126,10 +135,10 @@ fn callTool(
126
135
  defer tw.allocator.free(path);
127
136
  try tw.recordEvent("observe.semanticSnapshot", "{\"status\":\"ok\"}");
128
137
  }
129
- var payload = std.ArrayList(u8).empty;
130
- defer payload.deinit(allocator);
131
- try semantic.writeSemanticSnapshotJson(payload.writer(allocator), snap);
132
- try mcp_protocol.writeToolTextResult(writer, id, payload.items);
138
+ var payload: std.Io.Writer.Allocating = .init(allocator);
139
+ defer payload.deinit();
140
+ try semantic.writeSemanticSnapshotJson(&payload.writer, snap);
141
+ try mcp_protocol.writeToolTextResult(writer, id, payload.writer.buffered());
133
142
  return;
134
143
  }
135
144
 
@@ -311,10 +320,10 @@ fn callTool(
311
320
  const path = try requiredParamString(arguments, "path");
312
321
  var result = try validation.validateFile(allocator, path);
313
322
  defer result.deinit(allocator);
314
- var payload = std.ArrayList(u8).empty;
315
- defer payload.deinit(allocator);
316
- try cli_output.writeValidationJson(payload.writer(allocator), path, result);
317
- try mcp_protocol.writeToolTextResult(writer, id, std.mem.trimRight(u8, payload.items, " \t\r\n"));
323
+ var payload: std.Io.Writer.Allocating = .init(allocator);
324
+ defer payload.deinit();
325
+ try cli_output.writeValidationJson(&payload.writer, path, result);
326
+ try mcp_protocol.writeToolTextResult(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
318
327
  return;
319
328
  }
320
329
 
@@ -382,15 +391,15 @@ fn writeMatchedIndexToolResult(
382
391
  id: ?std.json.Value,
383
392
  matched: ?usize,
384
393
  ) !void {
385
- var payload = std.ArrayList(u8).empty;
386
- defer payload.deinit(allocator);
387
- const payload_writer = payload.writer(allocator);
394
+ var payload: std.Io.Writer.Allocating = .init(allocator);
395
+ defer payload.deinit();
396
+ const payload_writer = &payload.writer;
388
397
  if (matched) |index| {
389
398
  try payload_writer.print("{{\"matchedIndex\":{d}}}", .{index});
390
399
  } else {
391
400
  try payload_writer.writeAll("{\"matchedIndex\":null}");
392
401
  }
393
- try mcp_protocol.writeToolTextResult(writer, id, payload.items);
402
+ try mcp_protocol.writeToolTextResult(writer, id, payload_writer.buffered());
394
403
  }
395
404
 
396
405
  fn parseArgumentsSelector(allocator: std.mem.Allocator, arguments: ?std.json.Value) !selector.Selector {
package/src/mcp_trace.zig CHANGED
@@ -5,6 +5,7 @@ const cli_explore = @import("cli_explore.zig");
5
5
  const mcp_protocol = @import("mcp_protocol.zig");
6
6
  const report = @import("report.zig");
7
7
  const runner_events = @import("runner_events.zig");
8
+ const stdio = @import("stdio.zig");
8
9
  const trace = @import("trace.zig");
9
10
 
10
11
  pub fn writeEventsToolResult(
@@ -16,30 +17,30 @@ pub fn writeEventsToolResult(
16
17
  limit: u64,
17
18
  ) !void {
18
19
  const tw = live_trace orelse {
19
- var no_trace_payload = std.ArrayList(u8).empty;
20
- defer no_trace_payload.deinit(allocator);
21
- try no_trace_payload.writer(allocator).print("{{\"traceDir\":null,\"afterSeq\":{d},\"nextSeq\":{d},\"latestSeq\":0,\"events\":[]}}", .{ after_seq, after_seq });
22
- try mcp_protocol.writeToolTextResult(writer, id, no_trace_payload.items);
20
+ var no_trace_payload: std.Io.Writer.Allocating = .init(allocator);
21
+ defer no_trace_payload.deinit();
22
+ try no_trace_payload.writer.print("{{\"traceDir\":null,\"afterSeq\":{d},\"nextSeq\":{d},\"latestSeq\":0,\"events\":[]}}", .{ after_seq, after_seq });
23
+ try mcp_protocol.writeToolTextResult(writer, id, no_trace_payload.writer.buffered());
23
24
  return;
24
25
  };
25
26
 
26
27
  const events_path = try std.fs.path.join(allocator, &.{ tw.root_dir, "events.jsonl" });
27
28
  defer allocator.free(events_path);
28
- const content = std.fs.cwd().readFileAlloc(allocator, events_path, 64 * 1024 * 1024) catch |err| switch (err) {
29
+ const content = stdio.readFileAlloc(allocator, events_path, 64 * 1024 * 1024) catch |err| switch (err) {
29
30
  error.FileNotFound => try allocator.dupe(u8, ""),
30
31
  else => return err,
31
32
  };
32
33
  defer allocator.free(content);
33
34
 
34
- var payload = std.ArrayList(u8).empty;
35
- defer payload.deinit(allocator);
36
- const payload_writer = payload.writer(allocator);
35
+ var payload: std.Io.Writer.Allocating = .init(allocator);
36
+ defer payload.deinit();
37
+ const payload_writer = &payload.writer;
37
38
  try payload_writer.writeAll("{\"traceDir\":");
38
39
  try trace.writeJsonString(payload_writer, tw.root_dir);
39
40
  try payload_writer.print(",\"afterSeq\":{d},\"nextSeq\":", .{after_seq});
40
- var events_json = std.ArrayList(u8).empty;
41
- defer events_json.deinit(allocator);
42
- const events_writer = events_json.writer(allocator);
41
+ var events_json: std.Io.Writer.Allocating = .init(allocator);
42
+ defer events_json.deinit();
43
+ const events_writer = &events_json.writer;
43
44
  var next_seq = after_seq;
44
45
  var emitted: u64 = 0;
45
46
  var lines = std.mem.splitScalar(u8, content, '\n');
@@ -60,9 +61,9 @@ pub fn writeEventsToolResult(
60
61
  emitted += 1;
61
62
  }
62
63
  try payload_writer.print("{d},\"latestSeq\":{d},\"events\":[", .{ next_seq, tw.event_count });
63
- try payload_writer.writeAll(events_json.items);
64
+ try payload_writer.writeAll(events_writer.buffered());
64
65
  try payload_writer.writeAll("]}");
65
- try mcp_protocol.writeToolTextResult(writer, id, payload.items);
66
+ try mcp_protocol.writeToolTextResult(writer, id, payload_writer.buffered());
66
67
  }
67
68
 
68
69
  pub fn writeExportToolResult(
@@ -85,15 +86,15 @@ pub fn writeExportToolResult(
85
86
  .omit_screenshots = omit_screenshots,
86
87
  });
87
88
 
88
- var payload = std.ArrayList(u8).empty;
89
- defer payload.deinit(allocator);
90
- const payload_writer = payload.writer(allocator);
89
+ var payload: std.Io.Writer.Allocating = .init(allocator);
90
+ defer payload.deinit();
91
+ const payload_writer = &payload.writer;
91
92
  try payload_writer.writeAll("{\"traceDir\":");
92
93
  try trace.writeJsonString(payload_writer, tw.root_dir);
93
94
  try payload_writer.writeAll(",\"out\":");
94
95
  try trace.writeJsonString(payload_writer, out_path);
95
96
  try payload_writer.print(",\"redacted\":{},\"omitScreenshots\":{}}}", .{ redact, omit_screenshots });
96
- try mcp_protocol.writeToolTextResult(writer, id, payload.items);
97
+ try mcp_protocol.writeToolTextResult(writer, id, payload_writer.buffered());
97
98
  }
98
99
 
99
100
  pub fn writeExplainToolResult(
@@ -108,10 +109,10 @@ pub fn writeExplainToolResult(
108
109
  };
109
110
 
110
111
  try tw.flushManifest();
111
- var payload = std.ArrayList(u8).empty;
112
- defer payload.deinit(allocator);
113
- try report.writeTraceExplanationJson(allocator, tw.root_dir, payload.writer(allocator));
114
- try mcp_protocol.writeToolTextResult(writer, id, std.mem.trimRight(u8, payload.items, " \t\r\n"));
112
+ var payload: std.Io.Writer.Allocating = .init(allocator);
113
+ defer payload.deinit();
114
+ try report.writeTraceExplanationJson(allocator, tw.root_dir, &payload.writer);
115
+ try mcp_protocol.writeToolTextResult(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
115
116
  try tw.recordEvent("trace.explain", "{\"status\":\"ok\"}");
116
117
  }
117
118
 
@@ -152,10 +153,10 @@ pub fn writeDiscoverToolResult(
152
153
  discovered.summary.validated,
153
154
  );
154
155
 
155
- var payload = std.ArrayList(u8).empty;
156
- defer payload.deinit(allocator);
157
- try cli_discover.writeJson(payload.writer(allocator), discovered.summary, discovered.validation);
158
- try mcp_protocol.writeToolTextResult(writer, id, std.mem.trimRight(u8, payload.items, " \t\r\n"));
156
+ var payload: std.Io.Writer.Allocating = .init(allocator);
157
+ defer payload.deinit();
158
+ try cli_discover.writeJson(&payload.writer, discovered.summary, discovered.validation);
159
+ try mcp_protocol.writeToolTextResult(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
159
160
  }
160
161
 
161
162
  pub fn writeExploreToolResult(
@@ -198,8 +199,8 @@ pub fn writeExploreToolResult(
198
199
  explored.discovered.summary.validated,
199
200
  );
200
201
 
201
- var payload = std.ArrayList(u8).empty;
202
- defer payload.deinit(allocator);
203
- try cli_explore.writeJson(payload.writer(allocator), explored.summary, explored.discovered.summary, explored.discovered.validation);
204
- try mcp_protocol.writeToolTextResult(writer, id, std.mem.trimRight(u8, payload.items, " \t\r\n"));
202
+ var payload: std.Io.Writer.Allocating = .init(allocator);
203
+ defer payload.deinit();
204
+ try cli_explore.writeJson(&payload.writer, explored.summary, explored.discovered.summary, explored.discovered.validation);
205
+ try mcp_protocol.writeToolTextResult(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
205
206
  }
package/src/report.zig CHANGED
@@ -2,6 +2,7 @@ const std = @import("std");
2
2
  const cli_output = @import("cli_output.zig");
3
3
  const report_html = @import("report_html.zig");
4
4
  const report_values = @import("report_values.zig");
5
+ const stdio = @import("stdio.zig");
5
6
  const trace = @import("trace.zig");
6
7
  const trace_summary = @import("trace_summary.zig");
7
8
 
@@ -13,8 +14,8 @@ pub fn writeHtmlReport(
13
14
  const results_path = try std.fs.path.join(allocator, &.{ input_path, "results.jsonl" });
14
15
  defer allocator.free(results_path);
15
16
 
16
- if (std.fs.cwd().openFile(results_path, .{})) |file| {
17
- file.close();
17
+ if (std.Io.Dir.cwd().openFile(stdio.io(), results_path, .{})) |file| {
18
+ file.close(stdio.io());
18
19
  return try writeBenchmarkReport(allocator, input_path, results_path, out_path);
19
20
  } else |err| switch (err) {
20
21
  error.FileNotFound => return try writeTraceReport(allocator, input_path, out_path),
@@ -30,8 +31,8 @@ pub fn writeJUnitReport(
30
31
  const results_path = try std.fs.path.join(allocator, &.{ input_path, "results.jsonl" });
31
32
  defer allocator.free(results_path);
32
33
 
33
- if (std.fs.cwd().openFile(results_path, .{})) |file| {
34
- file.close();
34
+ if (std.Io.Dir.cwd().openFile(stdio.io(), results_path, .{})) |file| {
35
+ file.close(stdio.io());
35
36
  return try writeBenchmarkJUnitReport(allocator, input_path, results_path, out_path);
36
37
  } else |err| switch (err) {
37
38
  error.FileNotFound => return try writeTraceJUnitReport(allocator, input_path, out_path),
@@ -183,11 +184,11 @@ fn writeBenchmarkReport(
183
184
  results_path: []const u8,
184
185
  out_path: []const u8,
185
186
  ) !void {
186
- const content = try std.fs.cwd().readFileAlloc(allocator, results_path, 64 * 1024 * 1024);
187
+ const content = try stdio.readFileAlloc(allocator, results_path, 64 * 1024 * 1024);
187
188
  defer allocator.free(content);
188
189
 
189
- var rows_html = std.ArrayList(u8).empty;
190
- defer rows_html.deinit(allocator);
190
+ var rows_html: std.Io.Writer.Allocating = .init(allocator);
191
+ defer rows_html.deinit();
191
192
  var durations = std.ArrayList(i64).empty;
192
193
  defer durations.deinit(allocator);
193
194
 
@@ -223,7 +224,7 @@ fn writeBenchmarkReport(
223
224
  failed += 1;
224
225
  }
225
226
 
226
- const writer = rows_html.writer(allocator);
227
+ const writer = &rows_html.writer;
227
228
  try writer.writeAll("<tr><td>");
228
229
  try writer.print("{d}", .{run});
229
230
  try writer.writeAll("</td><td>");
@@ -257,9 +258,9 @@ fn writeBenchmarkReport(
257
258
  const mean = report_values.meanDuration(durations.items);
258
259
  const p95 = report_values.percentile95(durations.items);
259
260
 
260
- var html = std.ArrayList(u8).empty;
261
- defer html.deinit(allocator);
262
- const writer = html.writer(allocator);
261
+ var html: std.Io.Writer.Allocating = .init(allocator);
262
+ defer html.deinit();
263
+ const writer = &html.writer;
263
264
  try report_html.writeStart(writer, "ZMR Report");
264
265
  try writer.writeAll("<h1>ZMR Report</h1>\n");
265
266
  try writer.writeAll("<p class=\"muted\">Source: ");
@@ -272,11 +273,11 @@ fn writeBenchmarkReport(
272
273
  try writer.print("<dt>P95</dt><dd>{d}ms</dd>", .{p95});
273
274
  try writer.writeAll("</dl></section>\n");
274
275
  try writer.writeAll("<section><h2>Runs</h2><table><thead><tr><th>Run</th><th>Tool</th><th>Status</th><th>Duration</th><th>Trace Status</th><th>Failure</th><th>Artifacts</th></tr></thead><tbody>\n");
275
- try writer.writeAll(rows_html.items);
276
+ try writer.writeAll(rows_html.writer.buffered());
276
277
  try writer.writeAll("</tbody></table></section>\n");
277
278
  try writer.writeAll("<p class=\"warning\">Screenshots and raw UI XML may contain app data. Sanitize trace bundles before public sharing.</p>\n");
278
279
  try report_html.writeEnd(writer);
279
- try report_html.writeFile(out_path, html.items);
280
+ try report_html.writeFile(out_path, html.writer.buffered());
280
281
  }
281
282
 
282
283
  fn writeBenchmarkJUnitReport(
@@ -285,11 +286,11 @@ fn writeBenchmarkJUnitReport(
285
286
  results_path: []const u8,
286
287
  out_path: []const u8,
287
288
  ) !void {
288
- const content = try std.fs.cwd().readFileAlloc(allocator, results_path, 64 * 1024 * 1024);
289
+ const content = try stdio.readFileAlloc(allocator, results_path, 64 * 1024 * 1024);
289
290
  defer allocator.free(content);
290
291
 
291
- var cases_xml = std.ArrayList(u8).empty;
292
- defer cases_xml.deinit(allocator);
292
+ var cases_xml: std.Io.Writer.Allocating = .init(allocator);
293
+ defer cases_xml.deinit();
293
294
 
294
295
  var total: usize = 0;
295
296
  var failed: usize = 0;
@@ -319,7 +320,7 @@ fn writeBenchmarkJUnitReport(
319
320
  if (!row_passed) failed += 1;
320
321
  if (duration_ms > 0) total_duration_ms += duration_ms;
321
322
 
322
- const writer = cases_xml.writer(allocator);
323
+ const writer = &cases_xml.writer;
323
324
  try writer.writeAll(" <testcase classname=\"");
324
325
  try report_html.escape(writer, tool);
325
326
  try writer.writeAll("\" name=\"run ");
@@ -343,9 +344,9 @@ fn writeBenchmarkJUnitReport(
343
344
  try writer.writeAll("</testcase>\n");
344
345
  }
345
346
 
346
- var xml = std.ArrayList(u8).empty;
347
- defer xml.deinit(allocator);
348
- const writer = xml.writer(allocator);
347
+ var xml: std.Io.Writer.Allocating = .init(allocator);
348
+ defer xml.deinit();
349
+ const writer = &xml.writer;
349
350
  try writeJUnitHeader(writer);
350
351
  try writer.writeAll("<testsuite name=\"ZMR Benchmark\" tests=\"");
351
352
  try writer.print("{d}", .{total});
@@ -357,9 +358,9 @@ fn writeBenchmarkJUnitReport(
357
358
  try writer.writeAll(" <properties>\n");
358
359
  try writeJUnitProperty(writer, "source", input_path);
359
360
  try writer.writeAll(" </properties>\n");
360
- try writer.writeAll(cases_xml.items);
361
+ try writer.writeAll(cases_xml.writer.buffered());
361
362
  try writer.writeAll("</testsuite>\n");
362
- try report_html.writeFile(out_path, xml.items);
363
+ try report_html.writeFile(out_path, xml.writer.buffered());
363
364
  }
364
365
 
365
366
  fn writeTraceReport(
@@ -369,11 +370,11 @@ fn writeTraceReport(
369
370
  ) !void {
370
371
  const events_path = try std.fs.path.join(allocator, &.{ input_path, "events.jsonl" });
371
372
  defer allocator.free(events_path);
372
- const content = try std.fs.cwd().readFileAlloc(allocator, events_path, 64 * 1024 * 1024);
373
+ const content = try stdio.readFileAlloc(allocator, events_path, 64 * 1024 * 1024);
373
374
  defer allocator.free(content);
374
375
 
375
- var events_html = std.ArrayList(u8).empty;
376
- defer events_html.deinit(allocator);
376
+ var events_html: std.Io.Writer.Allocating = .init(allocator);
377
+ defer events_html.deinit();
377
378
  var total: usize = 0;
378
379
  var terminal_status: ?[]u8 = null;
379
380
  defer if (terminal_status) |value| allocator.free(value);
@@ -407,7 +408,7 @@ fn writeTraceReport(
407
408
  }
408
409
 
409
410
  total += 1;
410
- const writer = events_html.writer(allocator);
411
+ const writer = &events_html.writer;
411
412
  try writer.writeAll("<tr><td>");
412
413
  try writer.print("{d}", .{seq});
413
414
  try writer.writeAll("</td><td>");
@@ -417,9 +418,9 @@ fn writeTraceReport(
417
418
  try writer.writeAll("</code></td></tr>\n");
418
419
  }
419
420
 
420
- var html = std.ArrayList(u8).empty;
421
- defer html.deinit(allocator);
422
- const writer = html.writer(allocator);
421
+ var html: std.Io.Writer.Allocating = .init(allocator);
422
+ defer html.deinit();
423
+ const writer = &html.writer;
423
424
  try report_html.writeStart(writer, "ZMR Trace Report");
424
425
  try writer.writeAll("<h1>ZMR Trace Report</h1>\n");
425
426
  try writer.writeAll("<p class=\"muted\">Source: ");
@@ -433,16 +434,18 @@ fn writeTraceReport(
433
434
  try report_html.escape(writer, terminal_error orelse "");
434
435
  try writer.writeAll("</dd></dl></section>\n");
435
436
  try writer.writeAll("<section><h2>Timeline</h2><table><thead><tr><th>Seq</th><th>Kind</th><th>Event</th></tr></thead><tbody>\n");
436
- try writer.writeAll(events_html.items);
437
+ try writer.writeAll(events_html.writer.buffered());
437
438
  try writer.writeAll("</tbody></table></section>\n");
438
439
  try writer.writeAll("<p>");
439
440
  try report_html.writeArtifactLink(allocator, writer, events_path, "events.jsonl");
440
441
  try writer.writeAll("</p>\n");
441
442
  try writer.writeAll("<p class=\"warning\">Screenshots and raw UI XML may contain app data. Sanitize trace bundles before public sharing.</p>\n");
442
443
  try report_html.writeEnd(writer);
443
- try report_html.writeFile(out_path, html.items);
444
+ try report_html.writeFile(out_path, html.writer.buffered());
444
445
 
445
- const relative_report_path = std.fs.path.relative(allocator, input_path, out_path) catch try allocator.dupe(u8, out_path);
446
+ const cwd = std.process.currentPathAlloc(stdio.io(), allocator) catch try allocator.dupeZ(u8, ".");
447
+ defer allocator.free(cwd);
448
+ const relative_report_path = std.fs.path.relative(allocator, cwd, null, input_path, out_path) catch try allocator.dupe(u8, out_path);
446
449
  defer allocator.free(relative_report_path);
447
450
  try trace.attachReportPath(allocator, input_path, relative_report_path);
448
451
  }
@@ -456,9 +459,9 @@ fn writeTraceJUnitReport(
456
459
  defer summary.deinit(allocator);
457
460
 
458
461
  const failed = !isPassedStatus(summary.status);
459
- var xml = std.ArrayList(u8).empty;
460
- defer xml.deinit(allocator);
461
- const writer = xml.writer(allocator);
462
+ var xml: std.Io.Writer.Allocating = .init(allocator);
463
+ defer xml.deinit();
464
+ const writer = &xml.writer;
462
465
 
463
466
  try writeJUnitHeader(writer);
464
467
  try writer.writeAll("<testsuite name=\"ZMR\" tests=\"1\" failures=\"");
@@ -499,7 +502,7 @@ fn writeTraceJUnitReport(
499
502
  }
500
503
  try writer.writeAll("</testcase>\n");
501
504
  try writer.writeAll("</testsuite>\n");
502
- try report_html.writeFile(out_path, xml.items);
505
+ try report_html.writeFile(out_path, xml.writer.buffered());
503
506
  }
504
507
 
505
508
  fn isPassedStatus(status: []const u8) bool {
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const stdio = @import("stdio.zig");
2
3
 
3
4
  pub fn writeStart(writer: anytype, title: []const u8) !void {
4
5
  try writer.writeAll("<!doctype html><html><head><meta charset=\"utf-8\"><title>");
@@ -28,9 +29,9 @@ pub fn writeEnd(writer: anytype) !void {
28
29
  }
29
30
 
30
31
  pub fn writeFile(path: []const u8, bytes: []const u8) !void {
31
- var file = try std.fs.cwd().createFile(path, .{ .truncate = true });
32
- defer file.close();
33
- try file.writeAll(bytes);
32
+ var file = try std.Io.Dir.cwd().createFile(stdio.io(), path, .{ .truncate = true });
33
+ defer file.close(stdio.io());
34
+ try std.Io.File.writeStreamingAll(file, stdio.io(), bytes);
34
35
  }
35
36
 
36
37
  pub fn writeArtifactLink(
@@ -39,7 +40,7 @@ pub fn writeArtifactLink(
39
40
  path: []const u8,
40
41
  label: []const u8,
41
42
  ) !void {
42
- const href = std.fs.cwd().realpathAlloc(allocator, path) catch try allocator.dupe(u8, path);
43
+ const href = std.Io.Dir.cwd().realPathFileAlloc(stdio.io(), path, allocator) catch try allocator.dupeZ(u8, path);
43
44
  defer allocator.free(href);
44
45
 
45
46
  try writer.writeAll("<a href=\"file://");
package/src/runner.zig CHANGED
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const stdio = @import("stdio.zig");
2
3
  const runner_actions = @import("runner_actions.zig");
3
4
  const runner_config = @import("runner_config.zig");
4
5
  const runner_events = @import("runner_events.zig");
@@ -294,7 +295,7 @@ fn isVisibleNow(
294
295
  }
295
296
 
296
297
  fn sleepMs(ms: u64) !void {
297
- std.Thread.sleep(ms * std.time.ns_per_ms);
298
+ stdio.sleepNs(ms * std.time.ns_per_ms);
298
299
  }
299
300
 
300
301
  fn settleDevice(device: anytype, options: RunOptions) !void {
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const stdio = @import("stdio.zig");
2
3
  const runner_config = @import("runner_config.zig");
3
4
  const runner_events = @import("runner_events.zig");
4
5
  const runner_native = @import("runner_native.zig");
@@ -16,7 +17,7 @@ pub fn tapSelector(
16
17
  ) !void {
17
18
  if (try runner_native.tryTapSelector(device, wanted, writer, options.settle_ms)) return;
18
19
 
19
- const deadline = std.time.milliTimestamp() + @as(i64, @intCast(options.action_timeout_ms));
20
+ const deadline = stdio.nowMs() + @as(i64, @intCast(options.action_timeout_ms));
20
21
  var attempts: u32 = 0;
21
22
  while (true) {
22
23
  attempts += 1;
@@ -25,23 +26,24 @@ pub fn tapSelector(
25
26
  if (findActionable(snap, wanted)) |node| {
26
27
  try device.tap(node.bounds.centerX(), node.bounds.centerY());
27
28
  if (writer) |tw| {
28
- var payload = std.ArrayList(u8).empty;
29
- defer payload.deinit(tw.allocator);
30
- try payload.writer(tw.allocator).print("{{\"snapshotId\":\"{s}\",\"target\":\"{s}\",\"x\":{d},\"y\":{d},\"attempts\":{d},\"selector\":", .{
29
+ var payload: std.Io.Writer.Allocating = .init(tw.allocator);
30
+ defer payload.deinit();
31
+ const out = &payload.writer;
32
+ try out.print("{{\"snapshotId\":\"{s}\",\"target\":\"{s}\",\"x\":{d},\"y\":{d},\"attempts\":{d},\"selector\":", .{
31
33
  snap.id,
32
34
  node.stable_id,
33
35
  node.bounds.centerX(),
34
36
  node.bounds.centerY(),
35
37
  attempts,
36
38
  });
37
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
38
- try payload.writer(tw.allocator).writeAll("}");
39
- try tw.recordEvent("ui.tap", payload.items);
39
+ try trace.writeSelectorJson(out, wanted);
40
+ try out.writeAll("}");
41
+ try tw.recordEvent("ui.tap", out.buffered());
40
42
  }
41
43
  try settleDevice(device, options);
42
44
  return;
43
45
  }
44
- if (std.time.milliTimestamp() >= deadline) {
46
+ if (stdio.nowMs() >= deadline) {
45
47
  if (writer) |tw| {
46
48
  try runner_events.recordSelectorMiss(tw, "ui.tap.notFound", wanted, snap);
47
49
  }
@@ -62,14 +64,15 @@ pub fn typeTextSelector(
62
64
  try tapSelector(device, wanted, writer, options);
63
65
  try device.typeText(text);
64
66
  if (writer) |tw| {
65
- var payload = std.ArrayList(u8).empty;
66
- defer payload.deinit(tw.allocator);
67
- try payload.writer(tw.allocator).writeAll("{\"status\":\"ok\",\"selector\":");
68
- try trace.writeSelectorJson(payload.writer(tw.allocator), wanted);
69
- try payload.writer(tw.allocator).writeAll(",\"text\":");
70
- try trace.writeJsonString(payload.writer(tw.allocator), text);
71
- try payload.writer(tw.allocator).writeAll("}");
72
- try tw.recordEvent("ui.type", payload.items);
67
+ var payload: std.Io.Writer.Allocating = .init(tw.allocator);
68
+ defer payload.deinit();
69
+ const out = &payload.writer;
70
+ try out.writeAll("{\"status\":\"ok\",\"selector\":");
71
+ try trace.writeSelectorJson(out, wanted);
72
+ try out.writeAll(",\"text\":");
73
+ try trace.writeJsonString(out, text);
74
+ try out.writeAll("}");
75
+ try tw.recordEvent("ui.type", out.buffered());
73
76
  }
74
77
  try settleDevice(device, options);
75
78
  }
@@ -115,5 +118,5 @@ fn settleDevice(device: anytype, options: RunOptions) !void {
115
118
  }
116
119
 
117
120
  fn sleepMs(ms: u64) !void {
118
- std.Thread.sleep(ms * std.time.ns_per_ms);
121
+ stdio.sleepNs(ms * std.time.ns_per_ms);
119
122
  }
@@ -27,10 +27,10 @@ pub fn recordWithOptions(
27
27
  snap: types.ObservationSnapshot,
28
28
  options: DiagnosticOptions,
29
29
  ) !void {
30
- var payload = std.ArrayList(u8).empty;
31
- defer payload.deinit(tw.allocator);
32
- try writeSelectorDiagnosticJsonWithOptions(payload.writer(tw.allocator), status, strategy, selectors, snap, options);
33
- try tw.recordEvent(kind, payload.items);
30
+ var payload: std.Io.Writer.Allocating = .init(tw.allocator);
31
+ defer payload.deinit();
32
+ try writeSelectorDiagnosticJsonWithOptions(&payload.writer, status, strategy, selectors, snap, options);
33
+ try tw.recordEvent(kind, payload.writer.buffered());
34
34
  }
35
35
 
36
36
  pub fn writeSelectorDiagnosticJson(