mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 15:40:04 +00:00
Merge pull request #1966 from lightpanda-io/mcp_tools_test
Improve MCP tools test
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
el.id = "delayed";
|
el.id = "delayed";
|
||||||
el.textContent = "Appeared after delay";
|
el.textContent = "Appeared after delay";
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
}, 200);
|
}, 20);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -629,22 +629,14 @@ fn performGoto(server: *Server, url: [:0]const u8, id: std.json.Value) !void {
|
|||||||
try runner.wait(.{ .ms = 2000 });
|
try runner.wait(.{ .ms = 2000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const testing = @import("../testing.zig");
|
|
||||||
const router = @import("router.zig");
|
const router = @import("router.zig");
|
||||||
|
const testing = @import("../testing.zig");
|
||||||
|
|
||||||
test "MCP - evaluate error reporting" {
|
test "MCP - evaluate error reporting" {
|
||||||
defer testing.reset();
|
defer testing.reset();
|
||||||
const allocator = testing.allocator;
|
var out: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
||||||
const app = testing.test_app;
|
const server = try testLoadPage("about:blank", &out.writer);
|
||||||
|
|
||||||
var out_alloc: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
|
||||||
defer out_alloc.deinit();
|
|
||||||
|
|
||||||
var server = try Server.init(allocator, app, &out_alloc.writer);
|
|
||||||
defer server.deinit();
|
defer server.deinit();
|
||||||
_ = try server.session.createPage();
|
|
||||||
|
|
||||||
const aa = testing.arena_allocator;
|
|
||||||
|
|
||||||
// Call evaluate with a script that throws an error
|
// Call evaluate with a script that throws an error
|
||||||
const msg =
|
const msg =
|
||||||
@@ -661,81 +653,74 @@ test "MCP - evaluate error reporting" {
|
|||||||
\\}
|
\\}
|
||||||
;
|
;
|
||||||
|
|
||||||
try router.handleMessage(server, aa, msg);
|
try router.handleMessage(server, testing.arena_allocator, msg);
|
||||||
|
|
||||||
try testing.expectJson(
|
try testing.expectJson(.{ .id = 1, .result = .{
|
||||||
\\{
|
.isError = true,
|
||||||
\\ "id": 1,
|
.content = &.{.{ .type = "text" }},
|
||||||
\\ "result": {
|
} }, out.written());
|
||||||
\\ "isError": true,
|
|
||||||
\\ "content": [
|
|
||||||
\\ { "type": "text" }
|
|
||||||
\\ ]
|
|
||||||
\\ }
|
|
||||||
\\}
|
|
||||||
, out_alloc.writer.buffered());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "MCP - Actions: click, fill, scroll" {
|
test "MCP - Actions: click, fill, scroll" {
|
||||||
defer testing.reset();
|
defer testing.reset();
|
||||||
const allocator = testing.allocator;
|
const aa = testing.arena_allocator;
|
||||||
const app = testing.test_app;
|
|
||||||
|
|
||||||
var out_alloc: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
var out: std.io.Writer.Allocating = .init(aa);
|
||||||
defer out_alloc.deinit();
|
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_actions.html", &out.writer);
|
||||||
|
|
||||||
var server = try Server.init(allocator, app, &out_alloc.writer);
|
|
||||||
defer server.deinit();
|
defer server.deinit();
|
||||||
|
|
||||||
const aa = testing.arena_allocator;
|
const page = &server.session.page.?;
|
||||||
const page = try server.session.createPage();
|
|
||||||
const url = "http://localhost:9582/src/browser/tests/mcp_actions.html";
|
|
||||||
try page.navigate(url, .{ .reason = .address_bar, .kind = .{ .push = null } });
|
|
||||||
var runner = try server.session.runner(.{});
|
|
||||||
try runner.wait(.{ .ms = 2000 });
|
|
||||||
|
|
||||||
// Test Click
|
{
|
||||||
const btn = page.document.getElementById("btn", page).?.asNode();
|
// Test Click
|
||||||
const btn_id = (try server.node_registry.register(btn)).id;
|
const btn = page.document.getElementById("btn", page).?.asNode();
|
||||||
var btn_id_buf: [12]u8 = undefined;
|
const btn_id = (try server.node_registry.register(btn)).id;
|
||||||
const btn_id_str = std.fmt.bufPrint(&btn_id_buf, "{d}", .{btn_id}) catch unreachable;
|
var btn_id_buf: [12]u8 = undefined;
|
||||||
const click_msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"click\",\"arguments\":{\"backendNodeId\":", btn_id_str, "}}}" });
|
const btn_id_str = std.fmt.bufPrint(&btn_id_buf, "{d}", .{btn_id}) catch unreachable;
|
||||||
try router.handleMessage(server, aa, click_msg);
|
const click_msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"click\",\"arguments\":{\"backendNodeId\":", btn_id_str, "}}}" });
|
||||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "Clicked element") != null);
|
try router.handleMessage(server, aa, click_msg);
|
||||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "Page url: http://localhost:9582/src/browser/tests/mcp_actions.html") != null);
|
try testing.expect(std.mem.indexOf(u8, out.written(), "Clicked element") != null);
|
||||||
out_alloc.clearRetainingCapacity();
|
try testing.expect(std.mem.indexOf(u8, out.written(), "Page url: http://localhost:9582/src/browser/tests/mcp_actions.html") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
// Test Fill Input
|
{
|
||||||
const inp = page.document.getElementById("inp", page).?.asNode();
|
// Test Fill Input
|
||||||
const inp_id = (try server.node_registry.register(inp)).id;
|
const inp = page.document.getElementById("inp", page).?.asNode();
|
||||||
var inp_id_buf: [12]u8 = undefined;
|
const inp_id = (try server.node_registry.register(inp)).id;
|
||||||
const inp_id_str = std.fmt.bufPrint(&inp_id_buf, "{d}", .{inp_id}) catch unreachable;
|
var inp_id_buf: [12]u8 = undefined;
|
||||||
const fill_msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"fill\",\"arguments\":{\"backendNodeId\":", inp_id_str, ",\"text\":\"hello\"}}}" });
|
const inp_id_str = std.fmt.bufPrint(&inp_id_buf, "{d}", .{inp_id}) catch unreachable;
|
||||||
try router.handleMessage(server, aa, fill_msg);
|
const fill_msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"fill\",\"arguments\":{\"backendNodeId\":", inp_id_str, ",\"text\":\"hello\"}}}" });
|
||||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "Filled element") != null);
|
try router.handleMessage(server, aa, fill_msg);
|
||||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "with \\\"hello\\\"") != null);
|
try testing.expect(std.mem.indexOf(u8, out.written(), "Filled element") != null);
|
||||||
out_alloc.clearRetainingCapacity();
|
try testing.expect(std.mem.indexOf(u8, out.written(), "with \\\"hello\\\"") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
// Test Fill Select
|
{
|
||||||
const sel = page.document.getElementById("sel", page).?.asNode();
|
// Test Fill Select
|
||||||
const sel_id = (try server.node_registry.register(sel)).id;
|
const sel = page.document.getElementById("sel", page).?.asNode();
|
||||||
var sel_id_buf: [12]u8 = undefined;
|
const sel_id = (try server.node_registry.register(sel)).id;
|
||||||
const sel_id_str = std.fmt.bufPrint(&sel_id_buf, "{d}", .{sel_id}) catch unreachable;
|
var sel_id_buf: [12]u8 = undefined;
|
||||||
const fill_sel_msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"fill\",\"arguments\":{\"backendNodeId\":", sel_id_str, ",\"text\":\"opt2\"}}}" });
|
const sel_id_str = std.fmt.bufPrint(&sel_id_buf, "{d}", .{sel_id}) catch unreachable;
|
||||||
try router.handleMessage(server, aa, fill_sel_msg);
|
const fill_sel_msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"fill\",\"arguments\":{\"backendNodeId\":", sel_id_str, ",\"text\":\"opt2\"}}}" });
|
||||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "Filled element") != null);
|
try router.handleMessage(server, aa, fill_sel_msg);
|
||||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "with \\\"opt2\\\"") != null);
|
try testing.expect(std.mem.indexOf(u8, out.written(), "Filled element") != null);
|
||||||
out_alloc.clearRetainingCapacity();
|
try testing.expect(std.mem.indexOf(u8, out.written(), "with \\\"opt2\\\"") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
// Test Scroll
|
{
|
||||||
const scrollbox = page.document.getElementById("scrollbox", page).?.asNode();
|
// Test Scroll
|
||||||
const scrollbox_id = (try server.node_registry.register(scrollbox)).id;
|
const scrollbox = page.document.getElementById("scrollbox", page).?.asNode();
|
||||||
var scroll_id_buf: [12]u8 = undefined;
|
const scrollbox_id = (try server.node_registry.register(scrollbox)).id;
|
||||||
const scroll_id_str = std.fmt.bufPrint(&scroll_id_buf, "{d}", .{scrollbox_id}) catch unreachable;
|
var scroll_id_buf: [12]u8 = undefined;
|
||||||
const scroll_msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"tools/call\",\"params\":{\"name\":\"scroll\",\"arguments\":{\"backendNodeId\":", scroll_id_str, ",\"y\":50}}}" });
|
const scroll_id_str = std.fmt.bufPrint(&scroll_id_buf, "{d}", .{scrollbox_id}) catch unreachable;
|
||||||
try router.handleMessage(server, aa, scroll_msg);
|
const scroll_msg = try std.mem.concat(aa, u8, &.{ "{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"tools/call\",\"params\":{\"name\":\"scroll\",\"arguments\":{\"backendNodeId\":", scroll_id_str, ",\"y\":50}}}" });
|
||||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "Scrolled to x: 0, y: 50") != null);
|
try router.handleMessage(server, aa, scroll_msg);
|
||||||
out_alloc.clearRetainingCapacity();
|
try testing.expect(std.mem.indexOf(u8, out.written(), "Scrolled to x: 0, y: 50") != null);
|
||||||
|
out.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
// Evaluate assertions
|
// Evaluate assertions
|
||||||
var ls: js.Local.Scope = undefined;
|
var ls: js.Local.Scope = undefined;
|
||||||
@@ -746,111 +731,79 @@ test "MCP - Actions: click, fill, scroll" {
|
|||||||
try_catch.init(&ls.local);
|
try_catch.init(&ls.local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
const result = try ls.local.compileAndRun("window.clicked === true && window.inputVal === 'hello' && window.changed === true && window.selChanged === 'opt2' && window.scrolled === true", null);
|
const result = try ls.local.exec(
|
||||||
|
\\ window.clicked === true && window.inputVal === 'hello' &&
|
||||||
|
\\ window.changed === true && window.selChanged === 'opt2' &&
|
||||||
|
\\ window.scrolled === true
|
||||||
|
, null);
|
||||||
|
|
||||||
try testing.expect(result.isTrue());
|
try testing.expect(result.isTrue());
|
||||||
}
|
}
|
||||||
|
|
||||||
test "MCP - waitForSelector: existing element" {
|
test "MCP - waitForSelector: existing element" {
|
||||||
defer testing.reset();
|
defer testing.reset();
|
||||||
const allocator = testing.allocator;
|
var out: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
||||||
const app = testing.test_app;
|
const server = try testLoadPage(
|
||||||
|
"http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html",
|
||||||
var out_alloc: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
&out.writer,
|
||||||
defer out_alloc.deinit();
|
);
|
||||||
|
|
||||||
var server = try Server.init(allocator, app, &out_alloc.writer);
|
|
||||||
defer server.deinit();
|
defer server.deinit();
|
||||||
|
|
||||||
const aa = testing.arena_allocator;
|
|
||||||
const page = try server.session.createPage();
|
|
||||||
const url = "http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html";
|
|
||||||
try page.navigate(url, .{ .reason = .address_bar, .kind = .{ .push = null } });
|
|
||||||
var runner = try server.session.runner(.{});
|
|
||||||
try runner.wait(.{ .ms = 2000 });
|
|
||||||
|
|
||||||
// waitForSelector on an element that already exists returns immediately
|
// waitForSelector on an element that already exists returns immediately
|
||||||
const msg =
|
const msg =
|
||||||
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#existing","timeout":2000}}}
|
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#existing","timeout":2000}}}
|
||||||
;
|
;
|
||||||
try router.handleMessage(server, aa, msg);
|
try router.handleMessage(server, testing.arena_allocator, msg);
|
||||||
|
|
||||||
try testing.expectJson(
|
try testing.expectJson(.{ .id = 1, .result = .{ .content = &.{.{ .type = "text" }} } }, out.written());
|
||||||
\\{
|
|
||||||
\\ "id": 1,
|
|
||||||
\\ "result": {
|
|
||||||
\\ "content": [
|
|
||||||
\\ { "type": "text" }
|
|
||||||
\\ ]
|
|
||||||
\\ }
|
|
||||||
\\}
|
|
||||||
, out_alloc.writer.buffered());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "MCP - waitForSelector: delayed element" {
|
test "MCP - waitForSelector: delayed element" {
|
||||||
defer testing.reset();
|
defer testing.reset();
|
||||||
const allocator = testing.allocator;
|
var out: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
||||||
const app = testing.test_app;
|
const server = try testLoadPage(
|
||||||
|
"http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html",
|
||||||
var out_alloc: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
&out.writer,
|
||||||
defer out_alloc.deinit();
|
);
|
||||||
|
|
||||||
var server = try Server.init(allocator, app, &out_alloc.writer);
|
|
||||||
defer server.deinit();
|
defer server.deinit();
|
||||||
|
|
||||||
const aa = testing.arena_allocator;
|
|
||||||
const page = try server.session.createPage();
|
|
||||||
const url = "http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html";
|
|
||||||
try page.navigate(url, .{ .reason = .address_bar, .kind = .{ .push = null } });
|
|
||||||
var runner = try server.session.runner(.{});
|
|
||||||
try runner.wait(.{ .ms = 2000 });
|
|
||||||
|
|
||||||
// waitForSelector on an element added after 200ms via setTimeout
|
// waitForSelector on an element added after 200ms via setTimeout
|
||||||
const msg =
|
const msg =
|
||||||
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#delayed","timeout":5000}}}
|
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#delayed","timeout":5000}}}
|
||||||
;
|
;
|
||||||
try router.handleMessage(server, aa, msg);
|
try router.handleMessage(server, testing.arena_allocator, msg);
|
||||||
|
|
||||||
try testing.expectJson(
|
try testing.expectJson(.{ .id = 1, .result = .{ .content = &.{.{ .type = "text" }} } }, out.written());
|
||||||
\\{
|
|
||||||
\\ "id": 1,
|
|
||||||
\\ "result": {
|
|
||||||
\\ "content": [
|
|
||||||
\\ { "type": "text" }
|
|
||||||
\\ ]
|
|
||||||
\\ }
|
|
||||||
\\}
|
|
||||||
, out_alloc.writer.buffered());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "MCP - waitForSelector: timeout" {
|
test "MCP - waitForSelector: timeout" {
|
||||||
defer testing.reset();
|
defer testing.reset();
|
||||||
const allocator = testing.allocator;
|
var out: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
||||||
const app = testing.test_app;
|
const server = try testLoadPage(
|
||||||
|
"http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html",
|
||||||
var out_alloc: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
&out.writer,
|
||||||
defer out_alloc.deinit();
|
);
|
||||||
|
|
||||||
var server = try Server.init(allocator, app, &out_alloc.writer);
|
|
||||||
defer server.deinit();
|
defer server.deinit();
|
||||||
|
|
||||||
const aa = testing.arena_allocator;
|
|
||||||
const page = try server.session.createPage();
|
|
||||||
const url = "http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html";
|
|
||||||
try page.navigate(url, .{ .reason = .address_bar, .kind = .{ .push = null } });
|
|
||||||
var runner = try server.session.runner(.{});
|
|
||||||
try runner.wait(.{ .ms = 2000 });
|
|
||||||
|
|
||||||
// waitForSelector with a short timeout on a non-existent element should error
|
// waitForSelector with a short timeout on a non-existent element should error
|
||||||
const msg =
|
const msg =
|
||||||
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#nonexistent","timeout":100}}}
|
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#nonexistent","timeout":100}}}
|
||||||
;
|
;
|
||||||
try router.handleMessage(server, aa, msg);
|
try router.handleMessage(server, testing.arena_allocator, msg);
|
||||||
|
try testing.expectJson(.{
|
||||||
try testing.expectJson(
|
.id = 1,
|
||||||
\\{
|
.@"error" = struct {}{},
|
||||||
\\ "id": 1,
|
}, out.written());
|
||||||
\\ "error": {}
|
}
|
||||||
\\}
|
|
||||||
, out_alloc.writer.buffered());
|
fn testLoadPage(url: [:0]const u8, writer: *std.Io.Writer) !*Server {
|
||||||
|
var server = try Server.init(testing.allocator, testing.test_app, writer);
|
||||||
|
errdefer server.deinit();
|
||||||
|
|
||||||
|
const page = try server.session.createPage();
|
||||||
|
try page.navigate(url, .{});
|
||||||
|
|
||||||
|
var runner = try server.session.runner(.{});
|
||||||
|
try runner.wait(.{ .ms = 2000 });
|
||||||
|
return server;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user