diff --git a/src/SemanticTree.zig b/src/SemanticTree.zig index 43b50db2..60df9fe7 100644 --- a/src/SemanticTree.zig +++ b/src/SemanticTree.zig @@ -150,9 +150,9 @@ fn dump(self: Self, node: *Node, jw: *std.json.Stringify, parent_xpath: []const try jw.beginObject(); try jw.objectField("nodeId"); - try jw.write(cdp_node.id); + try jw.write(try std.fmt.allocPrint(self.arena, "{d}", .{cdp_node.id})); - try jw.objectField("backendNodeId"); + try jw.objectField("backendDOMNodeId"); try jw.write(cdp_node.id); try jw.objectField("nodeName"); @@ -171,6 +171,26 @@ fn dump(self: Self, node: *Node, jw: *std.json.Stringify, parent_xpath: []const try jw.objectField("role"); try jw.write(role); + // Add accessible name (e.g. button label, aria-label, etc.) + if (try axn.getName(self.page, self.arena)) |name| { + if (name.len > 0) { + try jw.objectField("name"); + try jw.write(name); + } + } + + // Add value for input elements + if (el.is(Element.Html.Input)) |input| { + try jw.objectField("value"); + try jw.write(input.getValue()); + } else if (el.is(Element.Html.TextArea)) |textarea| { + try jw.objectField("value"); + try jw.write(textarea.getValue()); + } else if (el.is(Element.Html.Select)) |select| { + try jw.objectField("value"); + try jw.write(select.getValue(self.page)); + } + if (el._attributes) |attrs| { try jw.objectField("attributes"); try jw.beginObject(); diff --git a/src/cdp/AXNode.zig b/src/cdp/AXNode.zig index 27272de0..c74a02fc 100644 --- a/src/cdp/AXNode.zig +++ b/src/cdp/AXNode.zig @@ -756,6 +756,74 @@ const AXSource = enum(u8) { value, // input value }; +pub fn getName(self: AXNode, page: *Page, allocator: std.mem.Allocator) !?[]const u8 { + var aw: std.Io.Writer.Allocating = .init(allocator); + defer aw.deinit(); + + // We need to bypass the strict JSON writer used in writeName + // We'll create a dummy writer that just writes to our buffer + const DummyWriter = struct { + aw: *std.Io.Writer.Allocating, + writer: *std.Io.Writer, + + pub fn write(w: @This(), val: anytype) !void { + const T = @TypeOf(val); + if (T == []const u8 or T == [:0]const u8 or T == *const [val.len]u8) { + try w.aw.writer.writeAll(val); + } else if (comptime std.meta.hasMethod(T, "format")) { + try std.fmt.format(w.aw.writer, "{s}", .{val}); + } else { + // Ignore unexpected types to avoid garbage output + } + } + + pub fn beginWriteRaw(w: @This()) !void { + _ = w; + } + pub fn endWriteRaw(w: @This()) void { + _ = w; + } + pub fn writeByte(w: @This(), b: u8) !void { + try w.aw.writer.writeByte(b); + } + pub fn writeAll(w: @This(), s: []const u8) !void { + try w.aw.writer.writeAll(s); + } + + // Mock object methods + pub fn objectField(w: @This(), name: []const u8) !void { + _ = w; + _ = name; + } + pub fn beginObject(w: @This()) !void { + _ = w; + } + pub fn endObject(w: @This()) !void { + _ = w; + } + pub fn beginArray(w: @This()) !void { + _ = w; + } + pub fn endArray(w: @This()) !void { + _ = w; + } + }; + + const w = DummyWriter{ .aw = &aw, .writer = &aw.writer }; + + const source = try self.writeName(w, page); + if (source != null) { + var str = aw.written(); + // writeString manually injects literal quotes for JSON, we need to strip them + if (str.len >= 2 and str[0] == '"' and str[str.len - 1] == '"') { + str = str[1 .. str.len - 1]; + } + return try allocator.dupe(u8, str); + } + + return null; +} + fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource { const node = axnode.dom;