cdp: handle STARTUP session into Page.getFrameTree gracefully

This commit is contained in:
Pierre Tachoire
2026-03-21 16:21:59 +01:00
parent 384b2f7614
commit fbc71d6ff7
2 changed files with 53 additions and 68 deletions

View File

@@ -189,27 +189,10 @@ pub fn CDPT(comptime TypeProvider: type) type {
// (I can imagine this logic will become driver-specific) // (I can imagine this logic will become driver-specific)
fn dispatchStartupCommand(command: anytype, method: []const u8) !void { fn dispatchStartupCommand(command: anytype, method: []const u8) !void {
// Stagehand parses the response and error if we don't return a // Stagehand parses the response and error if we don't return a
// correct one for this call. // correct one for Page.getFrameTree on startup call.
if (std.mem.eql(u8, method, "Page.getFrameTree")) { if (std.mem.eql(u8, method, "Page.getFrameTree")) {
// If we have a brower context and a target id, we can call the // The Page.getFrameTree handles startup response gracefully.
// real dispatch command, even during STARTUP. return dispatchCommand(command, method);
if (command.cdp.browser_context) |*bc| {
if (bc.target_id != null) {
return dispatchCommand(command, method);
}
}
return command.sendResult(.{
.frameTree = .{
.frame = .{
.id = "TID-STARTUP",
.loaderId = "LID-STARTUP",
.securityOrigin = URL_BASE,
.url = "about:blank",
.secureContextType = "Secure",
},
},
}, .{});
} }
return command.sendResult(null, .{}); return command.sendResult(null, .{});
@@ -995,47 +978,3 @@ test "cdp: STARTUP sessionId" {
try ctx.expectSentResult(null, .{ .id = 4, .index = 0, .session_id = "STARTUP" }); try ctx.expectSentResult(null, .{ .id = 4, .index = 0, .session_id = "STARTUP" });
} }
} }
test "cdp: STARTUP getFrameTree returns real frame ID when page exists" {
var ctx = testing.context();
defer ctx.deinit();
{
// no browser context - should return TID-STARTUP
try ctx.processMessage(.{ .id = 1, .method = "Page.getFrameTree", .sessionId = "STARTUP" });
try ctx.expectSentResult(.{
.frameTree = .{
.frame = .{
.id = "TID-STARTUP",
.loaderId = "LID-STARTUP",
.url = "about:blank",
.secureContextType = "Secure",
},
},
}, .{ .id = 1, .session_id = "STARTUP" });
}
{
// browser context with target_id - should return real frame ID
const bc = try ctx.loadBrowserContext(.{ .target_id = "TID-000000000X".* });
try ctx.processMessage(.{ .id = 2, .method = "Page.getFrameTree", .sessionId = "STARTUP" });
try ctx.expectSentResult(.{
.frameTree = .{
.frame = .{
.id = "TID-000000000X",
.loaderId = "LID-0000000001",
.url = "about:blank",
.domainAndRegistry = "",
.securityOrigin = bc.security_origin,
.mimeType = "text/html",
.adFrameStatus = .{
.adFrameType = "none",
},
.secureContextType = bc.secure_context_type,
.crossOriginIsolatedContextType = "NotIsolated",
.gatedAPIFeatures = [_][]const u8{},
},
},
}, .{ .id = 2, .session_id = "STARTUP" });
}
}

View File

@@ -75,8 +75,21 @@ const Frame = struct {
}; };
fn getFrameTree(cmd: anytype) !void { fn getFrameTree(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; // Stagehand parses the response and error if we don't return a
const target_id = bc.target_id orelse return error.TargetNotLoaded; // correct one for this call when browser context or target id are missing.
const startup = .{
.frameTree = .{
.frame = .{
.id = "TID-STARTUP",
.loaderId = "LID-STARTUP",
.securityOrigin = @import("../cdp.zig").URL_BASE,
.url = "about:blank",
.secureContextType = "Secure",
},
},
};
const bc = cmd.browser_context orelse return cmd.sendResult(startup, .{});
const target_id = bc.target_id orelse return cmd.sendResult(startup, .{});
return cmd.sendResult(.{ return cmd.sendResult(.{
.frameTree = .{ .frameTree = .{
@@ -633,8 +646,18 @@ test "cdp.page: getFrameTree" {
defer ctx.deinit(); defer ctx.deinit();
{ {
try ctx.processMessage(.{ .id = 10, .method = "Page.getFrameTree", .params = .{ .targetId = "X" } }); // no browser context - should return TID-STARTUP
try ctx.expectSentError(-31998, "BrowserContextNotLoaded", .{ .id = 10 }); try ctx.processMessage(.{ .id = 1, .method = "Page.getFrameTree", .sessionId = "STARTUP" });
try ctx.expectSentResult(.{
.frameTree = .{
.frame = .{
.id = "TID-STARTUP",
.loaderId = "LID-STARTUP",
.url = "about:blank",
.secureContextType = "Secure",
},
},
}, .{ .id = 1, .session_id = "STARTUP" });
} }
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* }); const bc = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
@@ -659,6 +682,29 @@ test "cdp.page: getFrameTree" {
}, },
}, .{ .id = 11 }); }, .{ .id = 11 });
} }
{
// STARTUP sesion is handled when a broweser context and a target id exists.
try ctx.processMessage(.{ .id = 12, .method = "Page.getFrameTree", .session_id = "STARTUP" });
try ctx.expectSentResult(.{
.frameTree = .{
.frame = .{
.id = "FID-000000000X",
.loaderId = "LID-0000000001",
.url = "http://127.0.0.1:9582/src/browser/tests/hi.html",
.domainAndRegistry = "",
.securityOrigin = bc.security_origin,
.mimeType = "text/html",
.adFrameStatus = .{
.adFrameType = "none",
},
.secureContextType = bc.secure_context_type,
.crossOriginIsolatedContextType = "NotIsolated",
.gatedAPIFeatures = [_][]const u8{},
},
},
}, .{ .id = 12 });
}
} }
test "cdp.page: captureScreenshot" { test "cdp.page: captureScreenshot" {