diff --git a/src/browser/tests/mcp_wait_for_selector.html b/src/browser/tests/mcp_wait_for_selector.html
new file mode 100644
index 00000000..111aadaf
--- /dev/null
+++ b/src/browser/tests/mcp_wait_for_selector.html
@@ -0,0 +1,14 @@
+
+
+
+ Already here
+
+
+
diff --git a/src/mcp/tools.zig b/src/mcp/tools.zig
index 48ba3de5..1e286209 100644
--- a/src/mcp/tools.zig
+++ b/src/mcp/tools.zig
@@ -723,3 +723,104 @@ test "MCP - Actions: click, fill, scroll" {
try testing.expect(result.isTrue());
}
+
+test "MCP - waitForSelector: existing element" {
+ defer testing.reset();
+ const allocator = testing.allocator;
+ const app = testing.test_app;
+
+ 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();
+
+ 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 } });
+ _ = server.session.wait(.{});
+
+ // waitForSelector on an element that already exists returns immediately
+ const msg =
+ \\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#existing","timeout":2000}}}
+ ;
+ try router.handleMessage(server, aa, msg);
+
+ try testing.expectJson(
+ \\{
+ \\ "id": 1,
+ \\ "result": {
+ \\ "content": [
+ \\ { "type": "text" }
+ \\ ]
+ \\ }
+ \\}
+ , out_alloc.writer.buffered());
+}
+
+test "MCP - waitForSelector: delayed element" {
+ defer testing.reset();
+ const allocator = testing.allocator;
+ const app = testing.test_app;
+
+ 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();
+
+ 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 } });
+ _ = server.session.wait(.{});
+
+ // waitForSelector on an element added after 200ms via setTimeout
+ const msg =
+ \\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#delayed","timeout":5000}}}
+ ;
+ try router.handleMessage(server, aa, msg);
+
+ try testing.expectJson(
+ \\{
+ \\ "id": 1,
+ \\ "result": {
+ \\ "content": [
+ \\ { "type": "text" }
+ \\ ]
+ \\ }
+ \\}
+ , out_alloc.writer.buffered());
+}
+
+test "MCP - waitForSelector: timeout" {
+ defer testing.reset();
+ const allocator = testing.allocator;
+ const app = testing.test_app;
+
+ 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();
+
+ 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 } });
+ _ = server.session.wait(.{});
+
+ // waitForSelector with a short timeout on a non-existent element should error
+ const msg =
+ \\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"waitForSelector","arguments":{"selector":"#nonexistent","timeout":100}}}
+ ;
+ try router.handleMessage(server, aa, msg);
+
+ try testing.expectJson(
+ \\{
+ \\ "id": 1,
+ \\ "error": {}
+ \\}
+ , out_alloc.writer.buffered());
+}