Verifier: deduplicate logic and relocate tests

Refactor `Verifier.zig` to use shared helpers for DOM property checks.
Move `substituteEnvVars` tests to `browser/tools.zig` and remove
redundant debug prints in `Agent.zig`.
This commit is contained in:
Adrià Arrufat
2026-04-13 19:26:21 +02:00
parent 6b7cd72e69
commit 68a6ef16db
4 changed files with 51 additions and 73 deletions

View File

@@ -588,9 +588,7 @@ fn runHealTurn(self: *Self, prompt: []const u8, arena: std.mem.Allocator) ![]Com
}
if (result.text) |text| {
std.debug.print("\n", .{});
self.terminal.printAssistant(text);
std.debug.print("\n", .{});
}
return cmds.toOwnedSlice(arena) catch &.{};
@@ -686,9 +684,7 @@ fn processUserMessage(self: *Self, user_input: []const u8, record_comment: []con
}
if (result.text) |text| {
std.debug.print("\n", .{});
self.terminal.printAssistant(text);
std.debug.print("\n", .{});
} else {
self.terminal.printInfo("(no response from model)");
}

View File

@@ -106,36 +106,3 @@ fn buildJson(arena: std.mem.Allocator, value: anytype) []const u8 {
std.json.Stringify.value(value, .{}, &aw.writer) catch return "{}";
return aw.written();
}
test "substituteEnvVars no vars" {
const result = substituteEnvVars(std.testing.allocator, "hello world");
try std.testing.expectEqualStrings("hello world", result);
}
test "substituteEnvVars with HOME" {
// Use arena since substituteEnvVars makes intermediate allocations (dupeZ)
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const a = arena.allocator();
const result = substituteEnvVars(a, "dir=$HOME/test");
// Result should not contain $HOME literally (it got substituted)
try std.testing.expect(std.mem.indexOf(u8, result, "$HOME") == null);
try std.testing.expect(std.mem.indexOf(u8, result, "/test") != null);
}
test "substituteEnvVars missing var kept literal" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const result = substituteEnvVars(arena.allocator(), "$UNLIKELY_VAR_12345");
try std.testing.expectEqualStrings("$UNLIKELY_VAR_12345", result);
}
test "substituteEnvVars bare dollar" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const result = substituteEnvVars(arena.allocator(), "price is $ 5");
try std.testing.expectEqualStrings("price is $ 5", result);
}

View File

@@ -43,15 +43,10 @@ pub fn verify(self: *Self, arena: std.mem.Allocator, cmd: Command.Command, pre:
}
fn verifyFill(self: *Self, arena: std.mem.Allocator, selector: []const u8, expected_value: []const u8) VerifyResult {
const script = std.fmt.allocPrint(
arena,
"(function(){{ var el = document.querySelector({s}); return el ? el.value : null; }})()",
.{jsonQuote(arena, selector)},
) catch return .{ .result = .inconclusive };
const actual = self.tool_executor.callEval(arena, script) orelse return .{ .result = .inconclusive };
// Secret env-var references can't be compared literally — just
// verify the field isn't empty after substitution.
if (std.mem.indexOf(u8, expected_value, "$LP_") != null) {
const actual = self.queryElementProperty(arena, selector, "value") orelse return .{ .result = .inconclusive };
if (actual.len == 0 or std.mem.eql(u8, actual, "null"))
return .{
.result = .failed,
@@ -59,46 +54,36 @@ fn verifyFill(self: *Self, arena: std.mem.Allocator, selector: []const u8, expec
};
return .{ .result = .passed };
}
if (!std.mem.eql(u8, actual, expected_value))
return .{
.result = .failed,
.reason = std.fmt.allocPrint(arena, "element value is \"{s}\" after fill (expected \"{s}\")", .{ actual, expected_value }) catch null,
};
return .{ .result = .passed };
return self.verifyElementValue(arena, selector, "value", expected_value, "value");
}
fn verifyCheck(self: *Self, arena: std.mem.Allocator, selector: []const u8, expected: bool) VerifyResult {
const script = std.fmt.allocPrint(
arena,
"(function(){{ var el = document.querySelector({s}); return el ? String(el.checked) : null; }})()",
.{jsonQuote(arena, selector)},
) catch return .{ .result = .inconclusive };
const actual = self.tool_executor.callEval(arena, script) orelse return .{ .result = .inconclusive };
const expected_str: []const u8 = if (expected) "true" else "false";
if (!std.mem.eql(u8, actual, expected_str))
return self.verifyElementValue(arena, selector, "String(el.checked)", expected_str, "checked state");
}
fn verifySelect(self: *Self, arena: std.mem.Allocator, selector: []const u8, expected_value: []const u8) VerifyResult {
return self.verifyElementValue(arena, selector, "value", expected_value, "selected value");
}
/// Shared verification: query a DOM property and compare against an expected value.
fn verifyElementValue(self: *Self, arena: std.mem.Allocator, selector: []const u8, js_property: []const u8, expected: []const u8, label: []const u8) VerifyResult {
const actual = self.queryElementProperty(arena, selector, js_property) orelse return .{ .result = .inconclusive };
if (!std.mem.eql(u8, actual, expected))
return .{
.result = .failed,
.reason = std.fmt.allocPrint(arena, "element checked state is {s} (expected {s})", .{ actual, expected_str }) catch null,
.reason = std.fmt.allocPrint(arena, "element {s} is \"{s}\" (expected \"{s}\")", .{ label, actual, expected }) catch null,
};
return .{ .result = .passed };
}
fn verifySelect(self: *Self, arena: std.mem.Allocator, selector: []const u8, expected_value: []const u8) VerifyResult {
fn queryElementProperty(self: *Self, arena: std.mem.Allocator, selector: []const u8, js_property: []const u8) ?[]const u8 {
const script = std.fmt.allocPrint(
arena,
"(function(){{ var el = document.querySelector({s}); return el ? el.value : null; }})()",
.{jsonQuote(arena, selector)},
) catch return .{ .result = .inconclusive };
const actual = self.tool_executor.callEval(arena, script) orelse return .{ .result = .inconclusive };
if (!std.mem.eql(u8, actual, expected_value))
return .{
.result = .failed,
.reason = std.fmt.allocPrint(arena, "element selected value is \"{s}\" (expected \"{s}\")", .{ actual, expected_value }) catch null,
};
return .{ .result = .passed };
"(function(){{ var el = document.querySelector({s}); return el ? {s} : null; }})()",
.{ jsonQuote(arena, selector), js_property },
) catch return null;
return self.tool_executor.callEval(arena, script);
}
fn verifyClick(self: *Self, arena: std.mem.Allocator, pre: PreState) VerifyResult {

View File

@@ -959,3 +959,33 @@ pub fn substituteEnvVars(arena: std.mem.Allocator, input: []const u8) []const u8
}
return result.toOwnedSlice(arena) catch input;
}
test "substituteEnvVars no vars" {
const r = substituteEnvVars(std.testing.allocator, "hello world");
try std.testing.expectEqualStrings("hello world", r);
}
test "substituteEnvVars with HOME" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const r = substituteEnvVars(arena.allocator(), "dir=$HOME/test");
try std.testing.expect(std.mem.indexOf(u8, r, "$HOME") == null);
try std.testing.expect(std.mem.indexOf(u8, r, "/test") != null);
}
test "substituteEnvVars missing var kept literal" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const r = substituteEnvVars(arena.allocator(), "$UNLIKELY_VAR_12345");
try std.testing.expectEqualStrings("$UNLIKELY_VAR_12345", r);
}
test "substituteEnvVars bare dollar" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const r = substituteEnvVars(arena.allocator(), "price is $ 5");
try std.testing.expectEqualStrings("price is $ 5", r);
}