zeno-mobile-runner 0.2.1 → 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.
- package/CHANGELOG.md +29 -0
- package/FEATURES.md +1 -1
- package/README.md +1 -1
- 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 +19 -18
- package/src/ios_devices.zig +6 -5
- package/src/ios_lifecycle.zig +4 -4
- 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/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
|
|
17
|
-
|
|
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 =
|
|
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 =
|
|
115
|
-
defer payload.deinit(
|
|
116
|
-
try trace.writeSnapshotJson(payload.writer
|
|
117
|
-
try mcp_protocol.writeToolTextResult(writer, id, payload.
|
|
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 =
|
|
130
|
-
defer payload.deinit(
|
|
131
|
-
try semantic.writeSemanticSnapshotJson(payload.writer
|
|
132
|
-
try mcp_protocol.writeToolTextResult(writer, id, payload.
|
|
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 =
|
|
315
|
-
defer payload.deinit(
|
|
316
|
-
try cli_output.writeValidationJson(payload.writer
|
|
317
|
-
try mcp_protocol.writeToolTextResult(writer, id, std.mem.
|
|
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 =
|
|
386
|
-
defer payload.deinit(
|
|
387
|
-
const payload_writer = payload.writer
|
|
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,
|
|
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 =
|
|
20
|
-
defer no_trace_payload.deinit(
|
|
21
|
-
try no_trace_payload.writer
|
|
22
|
-
try mcp_protocol.writeToolTextResult(writer, id, no_trace_payload.
|
|
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 =
|
|
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 =
|
|
35
|
-
defer payload.deinit(
|
|
36
|
-
const payload_writer = payload.writer
|
|
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 =
|
|
41
|
-
defer events_json.deinit(
|
|
42
|
-
const events_writer = events_json.writer
|
|
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(
|
|
64
|
+
try payload_writer.writeAll(events_writer.buffered());
|
|
64
65
|
try payload_writer.writeAll("]}");
|
|
65
|
-
try mcp_protocol.writeToolTextResult(writer, id,
|
|
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 =
|
|
89
|
-
defer payload.deinit(
|
|
90
|
-
const payload_writer = payload.writer
|
|
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,
|
|
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 =
|
|
112
|
-
defer payload.deinit(
|
|
113
|
-
try report.writeTraceExplanationJson(allocator, tw.root_dir, payload.writer
|
|
114
|
-
try mcp_protocol.writeToolTextResult(writer, id, std.mem.
|
|
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 =
|
|
156
|
-
defer payload.deinit(
|
|
157
|
-
try cli_discover.writeJson(payload.writer
|
|
158
|
-
try mcp_protocol.writeToolTextResult(writer, id, std.mem.
|
|
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 =
|
|
202
|
-
defer payload.deinit(
|
|
203
|
-
try cli_explore.writeJson(payload.writer
|
|
204
|
-
try mcp_protocol.writeToolTextResult(writer, id, std.mem.
|
|
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.
|
|
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.
|
|
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
|
|
187
|
+
const content = try stdio.readFileAlloc(allocator, results_path, 64 * 1024 * 1024);
|
|
187
188
|
defer allocator.free(content);
|
|
188
189
|
|
|
189
|
-
var rows_html =
|
|
190
|
-
defer rows_html.deinit(
|
|
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
|
|
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 =
|
|
261
|
-
defer html.deinit(
|
|
262
|
-
const writer = html.writer
|
|
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.
|
|
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.
|
|
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
|
|
289
|
+
const content = try stdio.readFileAlloc(allocator, results_path, 64 * 1024 * 1024);
|
|
289
290
|
defer allocator.free(content);
|
|
290
291
|
|
|
291
|
-
var cases_xml =
|
|
292
|
-
defer cases_xml.deinit(
|
|
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
|
|
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 =
|
|
347
|
-
defer xml.deinit(
|
|
348
|
-
const writer = xml.writer
|
|
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.
|
|
361
|
+
try writer.writeAll(cases_xml.writer.buffered());
|
|
361
362
|
try writer.writeAll("</testsuite>\n");
|
|
362
|
-
try report_html.writeFile(out_path, xml.
|
|
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
|
|
373
|
+
const content = try stdio.readFileAlloc(allocator, events_path, 64 * 1024 * 1024);
|
|
373
374
|
defer allocator.free(content);
|
|
374
375
|
|
|
375
|
-
var events_html =
|
|
376
|
-
defer events_html.deinit(
|
|
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
|
|
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 =
|
|
421
|
-
defer html.deinit(
|
|
422
|
-
const writer = html.writer
|
|
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.
|
|
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.
|
|
444
|
+
try report_html.writeFile(out_path, html.writer.buffered());
|
|
444
445
|
|
|
445
|
-
const
|
|
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 =
|
|
460
|
-
defer xml.deinit(
|
|
461
|
-
const writer = xml.writer
|
|
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.
|
|
505
|
+
try report_html.writeFile(out_path, xml.writer.buffered());
|
|
503
506
|
}
|
|
504
507
|
|
|
505
508
|
fn isPassedStatus(status: []const u8) bool {
|
package/src/report_html.zig
CHANGED
|
@@ -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.
|
|
32
|
-
defer file.close();
|
|
33
|
-
try file.
|
|
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.
|
|
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
|
-
|
|
298
|
+
stdio.sleepNs(ms * std.time.ns_per_ms);
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
fn settleDevice(device: anytype, options: RunOptions) !void {
|
package/src/runner_actions.zig
CHANGED
|
@@ -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 =
|
|
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 =
|
|
29
|
-
defer payload.deinit(
|
|
30
|
-
|
|
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(
|
|
38
|
-
try
|
|
39
|
-
try tw.recordEvent("ui.tap",
|
|
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 (
|
|
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 =
|
|
66
|
-
defer payload.deinit(
|
|
67
|
-
|
|
68
|
-
try
|
|
69
|
-
try
|
|
70
|
-
try
|
|
71
|
-
try
|
|
72
|
-
try
|
|
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
|
-
|
|
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 =
|
|
31
|
-
defer payload.deinit(
|
|
32
|
-
try writeSelectorDiagnosticJsonWithOptions(payload.writer
|
|
33
|
-
try tw.recordEvent(kind, payload.
|
|
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(
|