mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-04-02 10:19:17 +00:00
Fix event order and add tests
- Fix setChecked event order: click fires before input/change to match browser behavior - Add tests for hover, press, selectOption, setChecked MCP tools - Merge all action tests into a single test case sharing one page load - Add test elements to mcp_actions.html (hover target, key input, second select, checkbox, radio)
This commit is contained in:
@@ -132,16 +132,7 @@ pub fn setChecked(node: *DOMNode, checked: bool, page: *Page) !void {
|
|||||||
return error.ActionFailed;
|
return error.ActionFailed;
|
||||||
};
|
};
|
||||||
|
|
||||||
const input_evt: *Event = try .initTrusted(comptime .wrap("input"), .{ .bubbles = true }, page);
|
// Match browser event order: click fires first, then input and change.
|
||||||
page._event_manager.dispatch(el.asEventTarget(), input_evt) catch |err| {
|
|
||||||
lp.log.err(.app, "dispatch input event failed", .{ .err = err });
|
|
||||||
};
|
|
||||||
|
|
||||||
const change_evt: *Event = try .initTrusted(comptime .wrap("change"), .{ .bubbles = true }, page);
|
|
||||||
page._event_manager.dispatch(el.asEventTarget(), change_evt) catch |err| {
|
|
||||||
lp.log.err(.app, "dispatch change event failed", .{ .err = err });
|
|
||||||
};
|
|
||||||
|
|
||||||
const click_event: *MouseEvent = try .initTrusted(comptime .wrap("click"), .{
|
const click_event: *MouseEvent = try .initTrusted(comptime .wrap("click"), .{
|
||||||
.bubbles = true,
|
.bubbles = true,
|
||||||
.cancelable = true,
|
.cancelable = true,
|
||||||
@@ -151,6 +142,16 @@ pub fn setChecked(node: *DOMNode, checked: bool, page: *Page) !void {
|
|||||||
page._event_manager.dispatch(el.asEventTarget(), click_event.asEvent()) catch |err| {
|
page._event_manager.dispatch(el.asEventTarget(), click_event.asEvent()) catch |err| {
|
||||||
lp.log.err(.app, "dispatch click event failed", .{ .err = err });
|
lp.log.err(.app, "dispatch click event failed", .{ .err = err });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const input_evt: *Event = try .initTrusted(comptime .wrap("input"), .{ .bubbles = true }, page);
|
||||||
|
page._event_manager.dispatch(el.asEventTarget(), input_evt) catch |err| {
|
||||||
|
lp.log.err(.app, "dispatch input event failed", .{ .err = err });
|
||||||
|
};
|
||||||
|
|
||||||
|
const change_evt: *Event = try .initTrusted(comptime .wrap("change"), .{ .bubbles = true }, page);
|
||||||
|
page._event_manager.dispatch(el.asEventTarget(), change_evt) catch |err| {
|
||||||
|
lp.log.err(.app, "dispatch change event failed", .{ .err = err });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill(node: *DOMNode, text: []const u8, page: *Page) !void {
|
pub fn fill(node: *DOMNode, text: []const u8, page: *Page) !void {
|
||||||
|
|||||||
@@ -10,5 +10,20 @@
|
|||||||
<div id="scrollbox" style="width: 100px; height: 100px; overflow: scroll;" onscroll="window.scrolled = true;">
|
<div id="scrollbox" style="width: 100px; height: 100px; overflow: scroll;" onscroll="window.scrolled = true;">
|
||||||
<div style="height: 500px;">Long content</div>
|
<div style="height: 500px;">Long content</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="hoverTarget" onmouseover="window.hovered = true;">Hover Me</div>
|
||||||
|
<input id="keyTarget" onkeydown="window.keyPressed = event.key;" onkeyup="window.keyReleased = event.key;">
|
||||||
|
<select id="sel2" onchange="window.sel2Changed = this.value">
|
||||||
|
<option value="a">Alpha</option>
|
||||||
|
<option value="b">Beta</option>
|
||||||
|
<option value="c">Gamma</option>
|
||||||
|
</select>
|
||||||
|
<input id="chk" type="checkbox">
|
||||||
|
<input id="rad" type="radio" name="group1">
|
||||||
|
<script>
|
||||||
|
document.getElementById('chk').addEventListener('click', function() { window.chkClicked = true; });
|
||||||
|
document.getElementById('chk').addEventListener('change', function() { window.chkChanged = true; });
|
||||||
|
document.getElementById('rad').addEventListener('click', function() { window.radClicked = true; });
|
||||||
|
document.getElementById('rad').addEventListener('change', function() { window.radChanged = true; });
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -928,7 +928,7 @@ test "MCP - evaluate error reporting" {
|
|||||||
} }, out.written());
|
} }, out.written());
|
||||||
}
|
}
|
||||||
|
|
||||||
test "MCP - Actions: click, fill, scroll" {
|
test "MCP - Actions: click, fill, scroll, hover, press, selectOption, setChecked" {
|
||||||
defer testing.reset();
|
defer testing.reset();
|
||||||
const aa = testing.arena_allocator;
|
const aa = testing.arena_allocator;
|
||||||
|
|
||||||
@@ -989,7 +989,67 @@ test "MCP - Actions: click, fill, scroll" {
|
|||||||
out.clearRetainingCapacity();
|
out.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate assertions
|
{
|
||||||
|
// Test Hover
|
||||||
|
const el = page.document.getElementById("hoverTarget", page).?.asNode();
|
||||||
|
const el_id = (try server.node_registry.register(el)).id;
|
||||||
|
var id_buf: [12]u8 = undefined;
|
||||||
|
const id_str = std.fmt.bufPrint(&id_buf, "{d}", .{el_id}) catch unreachable;
|
||||||
|
const msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{\"name\":\"hover\",\"arguments\":{\"backendNodeId\":", id_str, "}}}" });
|
||||||
|
try router.handleMessage(server, aa, msg);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, out.written(), "Hovered element") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test Press
|
||||||
|
const el = page.document.getElementById("keyTarget", page).?.asNode();
|
||||||
|
const el_id = (try server.node_registry.register(el)).id;
|
||||||
|
var id_buf: [12]u8 = undefined;
|
||||||
|
const id_str = std.fmt.bufPrint(&id_buf, "{d}", .{el_id}) catch unreachable;
|
||||||
|
const msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":6,\"method\":\"tools/call\",\"params\":{\"name\":\"press\",\"arguments\":{\"key\":\"Enter\",\"backendNodeId\":", id_str, "}}}" });
|
||||||
|
try router.handleMessage(server, aa, msg);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, out.written(), "Pressed key") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test SelectOption
|
||||||
|
const el = page.document.getElementById("sel2", page).?.asNode();
|
||||||
|
const el_id = (try server.node_registry.register(el)).id;
|
||||||
|
var id_buf: [12]u8 = undefined;
|
||||||
|
const id_str = std.fmt.bufPrint(&id_buf, "{d}", .{el_id}) catch unreachable;
|
||||||
|
const msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":7,\"method\":\"tools/call\",\"params\":{\"name\":\"selectOption\",\"arguments\":{\"backendNodeId\":", id_str, ",\"value\":\"b\"}}}" });
|
||||||
|
try router.handleMessage(server, aa, msg);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, out.written(), "Selected option") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test SetChecked (checkbox)
|
||||||
|
const el = page.document.getElementById("chk", page).?.asNode();
|
||||||
|
const el_id = (try server.node_registry.register(el)).id;
|
||||||
|
var id_buf: [12]u8 = undefined;
|
||||||
|
const id_str = std.fmt.bufPrint(&id_buf, "{d}", .{el_id}) catch unreachable;
|
||||||
|
const msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":8,\"method\":\"tools/call\",\"params\":{\"name\":\"setChecked\",\"arguments\":{\"backendNodeId\":", id_str, ",\"checked\":true}}}" });
|
||||||
|
try router.handleMessage(server, aa, msg);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, out.written(), "checked") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test SetChecked (radio)
|
||||||
|
const el = page.document.getElementById("rad", page).?.asNode();
|
||||||
|
const el_id = (try server.node_registry.register(el)).id;
|
||||||
|
var id_buf: [12]u8 = undefined;
|
||||||
|
const id_str = std.fmt.bufPrint(&id_buf, "{d}", .{el_id}) catch unreachable;
|
||||||
|
const msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"tools/call\",\"params\":{\"name\":\"setChecked\",\"arguments\":{\"backendNodeId\":", id_str, ",\"checked\":true}}}" });
|
||||||
|
try router.handleMessage(server, aa, msg);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, out.written(), "checked") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate JS assertions for all actions
|
||||||
var ls: js.Local.Scope = undefined;
|
var ls: js.Local.Scope = undefined;
|
||||||
page.js.localScope(&ls);
|
page.js.localScope(&ls);
|
||||||
defer ls.deinit();
|
defer ls.deinit();
|
||||||
@@ -1001,7 +1061,12 @@ test "MCP - Actions: click, fill, scroll" {
|
|||||||
const result = try ls.local.exec(
|
const result = try ls.local.exec(
|
||||||
\\ window.clicked === true && window.inputVal === 'hello' &&
|
\\ window.clicked === true && window.inputVal === 'hello' &&
|
||||||
\\ window.changed === true && window.selChanged === 'opt2' &&
|
\\ window.changed === true && window.selChanged === 'opt2' &&
|
||||||
\\ window.scrolled === true
|
\\ window.scrolled === true &&
|
||||||
|
\\ window.hovered === true &&
|
||||||
|
\\ window.keyPressed === 'Enter' && window.keyReleased === 'Enter' &&
|
||||||
|
\\ window.sel2Changed === 'b' &&
|
||||||
|
\\ window.chkClicked === true && window.chkChanged === true &&
|
||||||
|
\\ window.radClicked === true && window.radChanged === true
|
||||||
, null);
|
, null);
|
||||||
|
|
||||||
try testing.expect(result.isTrue());
|
try testing.expect(result.isTrue());
|
||||||
|
|||||||
Reference in New Issue
Block a user