mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 15:40:04 +00:00
Removes CDPT (generic CDP)
CDPT used to be a generic so that we could inject Browser, Session, Page and Client. At some point, it [thankfully] became a generic only to inject Client. This commit removes the generic and bakes the *Server.Client instance in CDP. It uses a socketpair for testing. BrowserContext is still generic, but that's generic for a very different reason and, while I'd like to remove that generic too, it belongs in a different PR.
This commit is contained in:
@@ -27,7 +27,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
|
|||||||
const log = @import("log.zig");
|
const log = @import("log.zig");
|
||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const Config = @import("Config.zig");
|
const Config = @import("Config.zig");
|
||||||
const CDP = @import("cdp/cdp.zig").CDP;
|
const CDP = @import("cdp/CDP.zig");
|
||||||
const Net = @import("network/websocket.zig");
|
const Net = @import("network/websocket.zig");
|
||||||
const HttpClient = @import("browser/HttpClient.zig");
|
const HttpClient = @import("browser/HttpClient.zig");
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ pub const Client = struct {
|
|||||||
http: *HttpClient,
|
http: *HttpClient,
|
||||||
ws: Net.WsConnection,
|
ws: Net.WsConnection,
|
||||||
|
|
||||||
fn init(
|
pub fn init(
|
||||||
socket: posix.socket_t,
|
socket: posix.socket_t,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
app: *App,
|
app: *App,
|
||||||
@@ -250,7 +250,7 @@ pub const Client = struct {
|
|||||||
self.ws.shutdown();
|
self.ws.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Client) void {
|
pub fn deinit(self: *Client) void {
|
||||||
switch (self.mode) {
|
switch (self.mode) {
|
||||||
.cdp => |*cdp| cdp.deinit(),
|
.cdp => |*cdp| cdp.deinit(),
|
||||||
.http => {},
|
.http => {},
|
||||||
@@ -461,7 +461,7 @@ pub const Client = struct {
|
|||||||
|
|
||||||
fn upgradeConnection(self: *Client, request: []u8) !void {
|
fn upgradeConnection(self: *Client, request: []u8) !void {
|
||||||
try self.ws.upgrade(request);
|
try self.ws.upgrade(request);
|
||||||
self.mode = .{ .cdp = try CDP.init(self.app, self.http, self) };
|
self.mode = .{ .cdp = try CDP.init(self) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeHTTPErrorResponse(self: *Client, comptime status: u16, comptime body: []const u8) void {
|
fn writeHTTPErrorResponse(self: *Client, comptime status: u16, comptime body: []const u8) void {
|
||||||
|
|||||||
@@ -22,36 +22,36 @@ const lp = @import("lightpanda");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
|
|
||||||
const log = @import("../log.zig");
|
const Incrementing = @import("id.zig").Incrementing;
|
||||||
const js = @import("../browser/js/js.zig");
|
|
||||||
|
|
||||||
|
const log = @import("../log.zig");
|
||||||
const App = @import("../App.zig");
|
const App = @import("../App.zig");
|
||||||
|
const Notification = @import("../Notification.zig");
|
||||||
|
|
||||||
|
const Client = @import("../Server.zig").Client;
|
||||||
|
|
||||||
|
const js = @import("../browser/js/js.zig");
|
||||||
const Browser = @import("../browser/Browser.zig");
|
const Browser = @import("../browser/Browser.zig");
|
||||||
const Session = @import("../browser/Session.zig");
|
const Session = @import("../browser/Session.zig");
|
||||||
const HttpClient = @import("../browser/HttpClient.zig");
|
|
||||||
const Page = @import("../browser/Page.zig");
|
const Page = @import("../browser/Page.zig");
|
||||||
const Incrementing = @import("id.zig").Incrementing;
|
|
||||||
const Notification = @import("../Notification.zig");
|
|
||||||
const InterceptState = @import("domains/fetch.zig").InterceptState;
|
|
||||||
const Mime = @import("../browser/Mime.zig");
|
const Mime = @import("../browser/Mime.zig");
|
||||||
|
const HttpClient = @import("../browser/HttpClient.zig");
|
||||||
|
|
||||||
|
const InterceptState = @import("domains/fetch.zig").InterceptState;
|
||||||
|
|
||||||
pub const URL_BASE = "chrome://newtab/";
|
pub const URL_BASE = "chrome://newtab/";
|
||||||
|
|
||||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
pub const CDP = CDPT(struct {
|
|
||||||
const Client = *@import("../Server.zig").Client;
|
|
||||||
});
|
|
||||||
|
|
||||||
const SessionIdGen = Incrementing(u32, "SID");
|
|
||||||
const TargetIdGen = Incrementing(u32, "TID");
|
const TargetIdGen = Incrementing(u32, "TID");
|
||||||
|
const SessionIdGen = Incrementing(u32, "SID");
|
||||||
const BrowserContextIdGen = Incrementing(u32, "BID");
|
const BrowserContextIdGen = Incrementing(u32, "BID");
|
||||||
|
|
||||||
// Generic so that we can inject mocks into it.
|
// Generic so that we can inject mocks into it.
|
||||||
pub fn CDPT(comptime TypeProvider: type) type {
|
const CDP = @This();
|
||||||
return struct {
|
|
||||||
// Used for sending message to the client and closing on error
|
// Used for sending message to the client and closing on error
|
||||||
client: TypeProvider.Client,
|
client: *Client,
|
||||||
|
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
session_id_gen: SessionIdGen = .{},
|
session_id_gen: SessionIdGen = .{},
|
||||||
browser_context_id_gen: BrowserContextIdGen = .{},
|
browser_context_id_gen: BrowserContextIdGen = .{},
|
||||||
|
|
||||||
browser_context: ?BrowserContext(Self),
|
browser_context: ?BrowserContext(CDP),
|
||||||
|
|
||||||
// Re-used arena for processing a message. We're assuming that we're getting
|
// Re-used arena for processing a message. We're assuming that we're getting
|
||||||
// 1 message at a time.
|
// 1 message at a time.
|
||||||
@@ -81,13 +81,12 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
// (or altogether elimiate) our use of this.
|
// (or altogether elimiate) our use of this.
|
||||||
browser_context_arena: std.heap.ArenaAllocator,
|
browser_context_arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
const Self = @This();
|
pub fn init(client: *Client) !CDP {
|
||||||
|
const app = client.app;
|
||||||
pub fn init(app: *App, http_client: *HttpClient, client: TypeProvider.Client) !Self {
|
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
const browser = try Browser.init(app, .{
|
const browser = try Browser.init(app, .{
|
||||||
.env = .{ .with_inspector = true },
|
.env = .{ .with_inspector = true },
|
||||||
.http_client = http_client,
|
.http_client = client.http,
|
||||||
});
|
});
|
||||||
errdefer browser.deinit();
|
errdefer browser.deinit();
|
||||||
|
|
||||||
@@ -103,7 +102,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *CDP) void {
|
||||||
if (self.browser_context) |*bc| {
|
if (self.browser_context) |*bc| {
|
||||||
bc.deinit();
|
bc.deinit();
|
||||||
}
|
}
|
||||||
@@ -114,13 +113,13 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
self.browser_context_arena.deinit();
|
self.browser_context_arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handleMessage(self: *Self, msg: []const u8) bool {
|
pub fn handleMessage(self: *CDP, msg: []const u8) bool {
|
||||||
// if there's an error, it's already been logged
|
// if there's an error, it's already been logged
|
||||||
self.processMessage(msg) catch return false;
|
self.processMessage(msg) catch return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn processMessage(self: *Self, msg: []const u8) !void {
|
pub fn processMessage(self: *CDP, msg: []const u8) !void {
|
||||||
const arena = &self.message_arena;
|
const arena = &self.message_arena;
|
||||||
defer _ = arena.reset(.{ .retain_with_limit = 1024 * 16 });
|
defer _ = arena.reset(.{ .retain_with_limit = 1024 * 16 });
|
||||||
return self.dispatch(arena.allocator(), self, msg);
|
return self.dispatch(arena.allocator(), self, msg);
|
||||||
@@ -130,7 +129,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
// A bit hacky right now. The main server loop doesn't unblock for
|
// A bit hacky right now. The main server loop doesn't unblock for
|
||||||
// scheduled task. So we run this directly in order to process any
|
// scheduled task. So we run this directly in order to process any
|
||||||
// timeouts (or http events) which are ready to be processed.
|
// timeouts (or http events) which are ready to be processed.
|
||||||
pub fn pageWait(self: *Self, ms: u32) !Session.Runner.CDPWaitResult {
|
pub fn pageWait(self: *CDP, ms: u32) !Session.Runner.CDPWaitResult {
|
||||||
const session = &(self.browser.session orelse return error.NoPage);
|
const session = &(self.browser.session orelse return error.NoPage);
|
||||||
var runner = try session.runner(.{});
|
var runner = try session.runner(.{});
|
||||||
return runner.waitCDP(.{ .ms = ms });
|
return runner.waitCDP(.{ .ms = ms });
|
||||||
@@ -139,12 +138,12 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
// Called from above, in processMessage which handles client messages
|
// Called from above, in processMessage which handles client messages
|
||||||
// but can also be called internally. For example, Target.sendMessageToTarget
|
// but can also be called internally. For example, Target.sendMessageToTarget
|
||||||
// calls back into dispatch to capture the response.
|
// calls back into dispatch to capture the response.
|
||||||
pub fn dispatch(self: *Self, arena: Allocator, sender: anytype, str: []const u8) !void {
|
pub fn dispatch(self: *CDP, arena: Allocator, sender: anytype, str: []const u8) !void {
|
||||||
const input = json.parseFromSliceLeaky(InputMessage, arena, str, .{
|
const input = json.parseFromSliceLeaky(InputMessage, arena, str, .{
|
||||||
.ignore_unknown_fields = true,
|
.ignore_unknown_fields = true,
|
||||||
}) catch return error.InvalidJSON;
|
}) catch return error.InvalidJSON;
|
||||||
|
|
||||||
var command = Command(Self, @TypeOf(sender)){
|
var command = Command(CDP, @TypeOf(sender)){
|
||||||
.input = .{
|
.input = .{
|
||||||
.json = str,
|
.json = str,
|
||||||
.id = input.id,
|
.id = input.id,
|
||||||
@@ -264,26 +263,26 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
return error.UnknownDomain;
|
return error.UnknownDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isValidSessionId(self: *const Self, input_session_id: []const u8) bool {
|
fn isValidSessionId(self: *const CDP, input_session_id: []const u8) bool {
|
||||||
const browser_context = &(self.browser_context orelse return false);
|
const browser_context = &(self.browser_context orelse return false);
|
||||||
const session_id = browser_context.session_id orelse return false;
|
const session_id = browser_context.session_id orelse return false;
|
||||||
return std.mem.eql(u8, session_id, input_session_id);
|
return std.mem.eql(u8, session_id, input_session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createBrowserContext(self: *Self) ![]const u8 {
|
pub fn createBrowserContext(self: *CDP) ![]const u8 {
|
||||||
if (self.browser_context != null) {
|
if (self.browser_context != null) {
|
||||||
return error.AlreadyExists;
|
return error.AlreadyExists;
|
||||||
}
|
}
|
||||||
const id = self.browser_context_id_gen.next();
|
const id = self.browser_context_id_gen.next();
|
||||||
|
|
||||||
self.browser_context = @as(BrowserContext(Self), undefined);
|
self.browser_context = @as(BrowserContext(CDP), undefined);
|
||||||
const browser_context = &self.browser_context.?;
|
const browser_context = &self.browser_context.?;
|
||||||
|
|
||||||
try BrowserContext(Self).init(browser_context, id, self);
|
try BrowserContext(CDP).init(browser_context, id, self);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disposeBrowserContext(self: *Self, browser_context_id: []const u8) bool {
|
pub fn disposeBrowserContext(self: *CDP, browser_context_id: []const u8) bool {
|
||||||
const bc = &(self.browser_context orelse return false);
|
const bc = &(self.browser_context orelse return false);
|
||||||
if (std.mem.eql(u8, bc.id, browser_context_id) == false) {
|
if (std.mem.eql(u8, bc.id, browser_context_id) == false) {
|
||||||
return false;
|
return false;
|
||||||
@@ -297,7 +296,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
const SendEventOpts = struct {
|
const SendEventOpts = struct {
|
||||||
session_id: ?[]const u8 = null,
|
session_id: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
pub fn sendEvent(self: *Self, method: []const u8, p: anytype, opts: SendEventOpts) !void {
|
pub fn sendEvent(self: *CDP, method: []const u8, p: anytype, opts: SendEventOpts) !void {
|
||||||
return self.sendJSON(.{
|
return self.sendJSON(.{
|
||||||
.method = method,
|
.method = method,
|
||||||
.params = if (comptime @typeInfo(@TypeOf(p)) == .null) struct {}{} else p,
|
.params = if (comptime @typeInfo(@TypeOf(p)) == .null) struct {}{} else p,
|
||||||
@@ -305,13 +304,11 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendJSON(self: *Self, message: anytype) !void {
|
pub fn sendJSON(self: *CDP, message: anytype) !void {
|
||||||
return self.client.sendJSON(message, .{
|
return self.client.sendJSON(message, .{
|
||||||
.emit_null_optional_fields = false,
|
.emit_null_optional_fields = false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn BrowserContext(comptime CDP_T: type) type {
|
pub fn BrowserContext(comptime CDP_T: type) type {
|
||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
@@ -958,7 +955,7 @@ fn asUint(comptime T: type, comptime string: []const u8) T {
|
|||||||
|
|
||||||
const testing = @import("testing.zig");
|
const testing = @import("testing.zig");
|
||||||
test "cdp: invalid json" {
|
test "cdp: invalid json" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try testing.expectError(error.InvalidJSON, ctx.processMessage("invalid"));
|
try testing.expectError(error.InvalidJSON, ctx.processMessage("invalid"));
|
||||||
@@ -983,7 +980,7 @@ test "cdp: invalid json" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp: invalid sessionId" {
|
test "cdp: invalid sessionId" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1008,7 +1005,7 @@ test "cdp: invalid sessionId" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp: STARTUP sessionId" {
|
test "cdp: STARTUP sessionId" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1021,13 +1018,13 @@ test "cdp: STARTUP sessionId" {
|
|||||||
// we have a brower context but no session_id
|
// we have a brower context but no session_id
|
||||||
_ = try ctx.loadBrowserContext(.{});
|
_ = try ctx.loadBrowserContext(.{});
|
||||||
try ctx.processMessage(.{ .id = 3, .method = "Hi", .sessionId = "STARTUP" });
|
try ctx.processMessage(.{ .id = 3, .method = "Hi", .sessionId = "STARTUP" });
|
||||||
try ctx.expectSentResult(null, .{ .id = 3, .index = 0, .session_id = "STARTUP" });
|
try ctx.expectSentResult(null, .{ .id = 3, .index = 1, .session_id = "STARTUP" });
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// we have a brower context with a different session_id
|
// we have a brower context with a different session_id
|
||||||
_ = try ctx.loadBrowserContext(.{ .session_id = "SESS-2" });
|
_ = try ctx.loadBrowserContext(.{ .session_id = "SESS-2" });
|
||||||
try ctx.processMessage(.{ .id = 4, .method = "Hi", .sessionId = "STARTUP" });
|
try ctx.processMessage(.{ .id = 4, .method = "Hi", .sessionId = "STARTUP" });
|
||||||
try ctx.expectSentResult(null, .{ .id = 4, .index = 0, .session_id = "STARTUP" });
|
try ctx.expectSentResult(null, .{ .id = 4, .index = 2, .session_id = "STARTUP" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ fn resetPermissions(cmd: anytype) !void {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.browser: getVersion" {
|
test "cdp.browser: getVersion" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -131,7 +131,7 @@ test "cdp.browser: getVersion" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.browser: getWindowForTarget" {
|
test "cdp.browser: getWindowForTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
|
|||||||
@@ -547,7 +547,7 @@ fn requestNode(cmd: anytype) !void {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.dom: getSearchResults unknown search id" {
|
test "cdp.dom: getSearchResults unknown search id" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -559,7 +559,7 @@ test "cdp.dom: getSearchResults unknown search id" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: search flow" {
|
test "cdp.dom: search flow" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
||||||
@@ -614,7 +614,7 @@ test "cdp.dom: search flow" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: querySelector unknown search id" {
|
test "cdp.dom: querySelector unknown search id" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
||||||
@@ -635,7 +635,7 @@ test "cdp.dom: querySelector unknown search id" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: querySelector Node not found" {
|
test "cdp.dom: querySelector Node not found" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
||||||
@@ -663,7 +663,7 @@ test "cdp.dom: querySelector Node not found" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: querySelector Nodes found" {
|
test "cdp.dom: querySelector Nodes found" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.html" });
|
||||||
@@ -693,7 +693,7 @@ test "cdp.dom: querySelector Nodes found" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: getBoxModel" {
|
test "cdp.dom: getBoxModel" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.html" });
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ fn waitForSelector(cmd: anytype) !void {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.lp: getMarkdown" {
|
test "cdp.lp: getMarkdown" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -271,12 +271,12 @@ test "cdp.lp: getMarkdown" {
|
|||||||
.method = "LP.getMarkdown",
|
.method = "LP.getMarkdown",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
const result = (try ctx.getSentMessage(0)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("markdown") != null);
|
try testing.expect(result.get("markdown") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.lp: getInteractiveElements" {
|
test "cdp.lp: getInteractiveElements" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -287,13 +287,13 @@ test "cdp.lp: getInteractiveElements" {
|
|||||||
.method = "LP.getInteractiveElements",
|
.method = "LP.getInteractiveElements",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
const result = (try ctx.getSentMessage(0)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("elements") != null);
|
try testing.expect(result.get("elements") != null);
|
||||||
try testing.expect(result.get("nodeIds") != null);
|
try testing.expect(result.get("nodeIds") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.lp: getStructuredData" {
|
test "cdp.lp: getStructuredData" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -304,12 +304,12 @@ test "cdp.lp: getStructuredData" {
|
|||||||
.method = "LP.getStructuredData",
|
.method = "LP.getStructuredData",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
const result = (try ctx.getSentMessage(0)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("structuredData") != null);
|
try testing.expect(result.get("structuredData") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.lp: action tools" {
|
test "cdp.lp: action tools" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -370,7 +370,7 @@ test "cdp.lp: action tools" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.lp: waitForSelector" {
|
test "cdp.lp: waitForSelector" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -386,9 +386,8 @@ test "cdp.lp: waitForSelector" {
|
|||||||
.method = "LP.waitForSelector",
|
.method = "LP.waitForSelector",
|
||||||
.params = .{ .selector = "#existing", .timeout = 2000 },
|
.params = .{ .selector = "#existing", .timeout = 2000 },
|
||||||
});
|
});
|
||||||
var result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
var result = (try ctx.getSentMessage(0)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("backendNodeId") != null);
|
try testing.expect(result.get("backendNodeId") != null);
|
||||||
ctx.client.?.sent.clearRetainingCapacity();
|
|
||||||
|
|
||||||
// 2. Delayed element
|
// 2. Delayed element
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -396,9 +395,8 @@ test "cdp.lp: waitForSelector" {
|
|||||||
.method = "LP.waitForSelector",
|
.method = "LP.waitForSelector",
|
||||||
.params = .{ .selector = "#delayed", .timeout = 5000 },
|
.params = .{ .selector = "#delayed", .timeout = 5000 },
|
||||||
});
|
});
|
||||||
result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
result = (try ctx.getSentMessage(1)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("backendNodeId") != null);
|
try testing.expect(result.get("backendNodeId") != null);
|
||||||
ctx.client.?.sent.clearRetainingCapacity();
|
|
||||||
|
|
||||||
// 3. Timeout error
|
// 3. Timeout error
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -406,6 +404,6 @@ test "cdp.lp: waitForSelector" {
|
|||||||
.method = "LP.waitForSelector",
|
.method = "LP.waitForSelector",
|
||||||
.params = .{ .selector = "#nonexistent", .timeout = 100 },
|
.params = .{ .selector = "#nonexistent", .timeout = 100 },
|
||||||
});
|
});
|
||||||
const err_obj = ctx.client.?.sent.items[0].object.get("error").?.object;
|
const err_obj = (try ctx.getSentMessage(2)).?.object.get("error").?.object;
|
||||||
try testing.expect(err_obj.get("code") != null);
|
try testing.expect(err_obj.get("code") != null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ fn idFromRequestId(request_id: []const u8) !u64 {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.network setExtraHTTPHeaders" {
|
test "cdp.network setExtraHTTPHeaders" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "NID-A", .session_id = "NESI-A" });
|
_ = try ctx.loadBrowserContext(.{ .id = "NID-A", .session_id = "NESI-A" });
|
||||||
@@ -465,7 +465,7 @@ test "cdp.Network: cookies" {
|
|||||||
const ResCookie = CdpStorage.ResCookie;
|
const ResCookie = CdpStorage.ResCookie;
|
||||||
const CdpCookie = CdpStorage.CdpCookie;
|
const CdpCookie = CdpStorage.CdpCookie;
|
||||||
|
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-S" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-S" });
|
||||||
|
|
||||||
|
|||||||
@@ -642,7 +642,7 @@ fn getLayoutMetrics(cmd: anytype) !void {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.page: getFrameTree" {
|
test "cdp.page: getFrameTree" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -712,7 +712,7 @@ test "cdp.page: captureScreenshot" {
|
|||||||
const filter: LogFilter = .init(&.{.not_implemented});
|
const filter: LogFilter = .init(&.{.not_implemented});
|
||||||
defer filter.deinit();
|
defer filter.deinit();
|
||||||
|
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
{
|
{
|
||||||
try ctx.processMessage(.{ .id = 10, .method = "Page.captureScreenshot", .params = .{ .format = "jpg" } });
|
try ctx.processMessage(.{ .id = 10, .method = "Page.captureScreenshot", .params = .{ .format = "jpg" } });
|
||||||
@@ -728,7 +728,7 @@ test "cdp.page: captureScreenshot" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.page: getLayoutMetrics" {
|
test "cdp.page: getLayoutMetrics" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ fn setIgnoreCertificateErrors(cmd: anytype) !void {
|
|||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
|
||||||
test "cdp.Security: setIgnoreCertificateErrors" {
|
test "cdp.Security: setIgnoreCertificateErrors" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ pub fn writeCookie(cookie: *const Cookie, w: anytype) !void {
|
|||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
|
||||||
test "cdp.Storage: cookies" {
|
test "cdp.Storage: cookies" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-S" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-S" });
|
||||||
|
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ const TargetInfo = struct {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.target: getBrowserContexts" {
|
test "cdp.target: getBrowserContexts" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
// {
|
// {
|
||||||
@@ -536,7 +536,7 @@ test "cdp.target: getBrowserContexts" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: createBrowserContext" {
|
test "cdp.target: createBrowserContext" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -554,7 +554,7 @@ test "cdp.target: createBrowserContext" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: disposeBrowserContext" {
|
test "cdp.target: disposeBrowserContext" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -585,7 +585,7 @@ test "cdp.target: disposeBrowserContext" {
|
|||||||
|
|
||||||
test "cdp.target: createTarget" {
|
test "cdp.target: createTarget" {
|
||||||
{
|
{
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .url = "about:blank" } });
|
try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .url = "about:blank" } });
|
||||||
|
|
||||||
@@ -595,7 +595,7 @@ test "cdp.target: createTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
// active auto attach to get the Target.attachedToTarget event.
|
// active auto attach to get the Target.attachedToTarget event.
|
||||||
try ctx.processMessage(.{ .id = 9, .method = "Target.setAutoAttach", .params = .{ .autoAttach = true, .waitForDebuggerOnStart = false } });
|
try ctx.processMessage(.{ .id = 9, .method = "Target.setAutoAttach", .params = .{ .autoAttach = true, .waitForDebuggerOnStart = false } });
|
||||||
@@ -607,7 +607,7 @@ test "cdp.target: createTarget" {
|
|||||||
try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = bc.session_id.?, .targetInfo = .{ .url = "about:blank", .title = "", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{});
|
try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = bc.session_id.?, .targetInfo = .{ .url = "about:blank", .title = "", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
@@ -624,7 +624,7 @@ test "cdp.target: createTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: closeTarget" {
|
test "cdp.target: closeTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -655,7 +655,7 @@ test "cdp.target: closeTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: attachToTarget" {
|
test "cdp.target: attachToTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -686,7 +686,7 @@ test "cdp.target: attachToTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: getTargetInfo" {
|
test "cdp.target: getTargetInfo" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -737,7 +737,7 @@ test "cdp.target: getTargetInfo" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: issue#474: attach to just created target" {
|
test "cdp.target: issue#474: attach to just created target" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
@@ -752,7 +752,7 @@ test "cdp.target: issue#474: attach to just created target" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: detachFromTarget" {
|
test "cdp.target: detachFromTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
@@ -775,19 +775,19 @@ test "cdp.target: detachFromTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: detachFromTarget without session" {
|
test "cdp.target: detachFromTarget without session" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
// detach when no session is attached should not send event
|
// detach when no session is attached should not send event
|
||||||
try ctx.processMessage(.{ .id = 10, .method = "Target.detachFromTarget" });
|
try ctx.processMessage(.{ .id = 10, .method = "Target.detachFromTarget" });
|
||||||
try ctx.expectSentResult(null, .{ .id = 10 });
|
try ctx.expectSentResult(null, .{ .id = 10 });
|
||||||
try ctx.expectSentCount(0);
|
try ctx.expectSentCount(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: setAutoAttach false sends detachedFromTarget" {
|
test "cdp.target: setAutoAttach false sends detachedFromTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,12 +18,14 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
|
const posix = std.posix;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
const Testing = @This();
|
const Testing = @This();
|
||||||
|
|
||||||
const main = @import("cdp.zig");
|
const CDP = @import("CDP.zig");
|
||||||
|
const Server = @import("../Server.zig");
|
||||||
|
|
||||||
const base = @import("../testing.zig");
|
const base = @import("../testing.zig");
|
||||||
pub const allocator = base.allocator;
|
pub const allocator = base.allocator;
|
||||||
@@ -35,61 +37,27 @@ pub const expectEqualSlices = base.expectEqualSlices;
|
|||||||
pub const pageTest = base.pageTest;
|
pub const pageTest = base.pageTest;
|
||||||
pub const newString = base.newString;
|
pub const newString = base.newString;
|
||||||
|
|
||||||
const Client = struct {
|
|
||||||
allocator: Allocator,
|
|
||||||
send_arena: ArenaAllocator,
|
|
||||||
sent: std.ArrayList(json.Value) = .{},
|
|
||||||
serialized: std.ArrayList([]const u8) = .{},
|
|
||||||
|
|
||||||
fn init(alloc: Allocator) Client {
|
|
||||||
return .{
|
|
||||||
.allocator = alloc,
|
|
||||||
.send_arena = ArenaAllocator.init(alloc),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendAllocator(self: *Client) Allocator {
|
|
||||||
return self.send_arena.allocator();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendJSON(self: *Client, message: anytype, opts: json.Stringify.Options) !void {
|
|
||||||
var opts_copy = opts;
|
|
||||||
opts_copy.whitespace = .indent_2;
|
|
||||||
const serialized = try json.Stringify.valueAlloc(self.allocator, message, opts_copy);
|
|
||||||
try self.serialized.append(self.allocator, serialized);
|
|
||||||
|
|
||||||
const value = try json.parseFromSliceLeaky(json.Value, self.allocator, serialized, .{});
|
|
||||||
try self.sent.append(self.allocator, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendJSONRaw(self: *Client, buf: std.ArrayList(u8)) !void {
|
|
||||||
const value = try json.parseFromSliceLeaky(json.Value, self.allocator, buf.items, .{});
|
|
||||||
try self.sent.append(self.allocator, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const TestCDP = main.CDPT(struct {
|
|
||||||
pub const Client = *Testing.Client;
|
|
||||||
});
|
|
||||||
|
|
||||||
const TestContext = struct {
|
const TestContext = struct {
|
||||||
client: ?Client = null,
|
read_at: usize = 0,
|
||||||
cdp_: ?TestCDP = null,
|
read_buf: [1024 * 32]u8 = undefined,
|
||||||
arena: ArenaAllocator,
|
cdp_: ?CDP = null,
|
||||||
|
client: Server.Client,
|
||||||
|
socket: posix.socket_t,
|
||||||
|
received: std.ArrayList(json.Value) = .empty,
|
||||||
|
received_raw: std.ArrayList([]const u8) = .empty,
|
||||||
|
|
||||||
pub fn deinit(self: *TestContext) void {
|
pub fn deinit(self: *TestContext) void {
|
||||||
if (self.cdp_) |*c| {
|
if (self.cdp_) |*c| {
|
||||||
c.deinit();
|
c.deinit();
|
||||||
}
|
}
|
||||||
self.arena.deinit();
|
self.client.deinit();
|
||||||
|
posix.close(self.socket);
|
||||||
|
base.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cdp(self: *TestContext) *TestCDP {
|
pub fn cdp(self: *TestContext) *CDP {
|
||||||
if (self.cdp_ == null) {
|
if (self.cdp_ == null) {
|
||||||
self.client = Client.init(self.arena.allocator());
|
self.cdp_ = CDP.init(&self.client) catch |err| @panic(@errorName(err));
|
||||||
// Don't use the arena here. We want to detect leaks in CDP.
|
|
||||||
// The arena is only for test-specific stuff
|
|
||||||
self.cdp_ = TestCDP.init(base.test_app, base.test_http, &self.client.?) catch unreachable;
|
|
||||||
}
|
}
|
||||||
return &self.cdp_.?;
|
return &self.cdp_.?;
|
||||||
}
|
}
|
||||||
@@ -100,7 +68,7 @@ const TestContext = struct {
|
|||||||
session_id: ?[]const u8 = null,
|
session_id: ?[]const u8 = null,
|
||||||
url: ?[:0]const u8 = null,
|
url: ?[:0]const u8 = null,
|
||||||
};
|
};
|
||||||
pub fn loadBrowserContext(self: *TestContext, opts: BrowserContextOpts) !*main.BrowserContext(TestCDP) {
|
pub fn loadBrowserContext(self: *TestContext, opts: BrowserContextOpts) !*CDP.BrowserContext(CDP) {
|
||||||
var c = self.cdp();
|
var c = self.cdp();
|
||||||
if (c.browser_context) |bc| {
|
if (c.browser_context) |bc| {
|
||||||
_ = c.disposeBrowserContext(bc.id);
|
_ = c.disposeBrowserContext(bc.id);
|
||||||
@@ -130,7 +98,7 @@ const TestContext = struct {
|
|||||||
}
|
}
|
||||||
const page = try bc.session.createPage();
|
const page = try bc.session.createPage();
|
||||||
const full_url = try std.fmt.allocPrintSentinel(
|
const full_url = try std.fmt.allocPrintSentinel(
|
||||||
self.arena.allocator(),
|
base.arena_allocator,
|
||||||
"http://127.0.0.1:9582/src/browser/tests/{s}",
|
"http://127.0.0.1:9582/src/browser/tests/{s}",
|
||||||
.{url},
|
.{url},
|
||||||
0,
|
0,
|
||||||
@@ -143,19 +111,20 @@ const TestContext = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn processMessage(self: *TestContext, msg: anytype) !void {
|
pub fn processMessage(self: *TestContext, msg: anytype) !void {
|
||||||
var json_message: []const u8 = undefined;
|
const json_message: []const u8 = blk: {
|
||||||
if (@typeInfo(@TypeOf(msg)) != .pointer) {
|
if (@typeInfo(@TypeOf(msg)) != .pointer) {
|
||||||
json_message = try std.json.Stringify.valueAlloc(self.arena.allocator(), msg, .{});
|
break :blk try std.json.Stringify.valueAlloc(base.arena_allocator, msg, .{});
|
||||||
} else {
|
}
|
||||||
// assume this is a string we want to send as-is, if it isn't, we'll
|
// assume this is a string we want to send as-is, if it isn't, we'll
|
||||||
// get a compile error, so no big deal.
|
// get a compile error, so no big deal.
|
||||||
json_message = msg;
|
break :blk msg;
|
||||||
}
|
};
|
||||||
return self.cdp().processMessage(json_message);
|
return self.cdp().processMessage(json_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expectSentCount(self: *TestContext, expected: usize) !void {
|
pub fn expectSentCount(self: *TestContext, expected: usize) !void {
|
||||||
try expectEqual(expected, self.client.?.sent.items.len);
|
try self.read();
|
||||||
|
try expectEqual(expected, self.received.items.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpectResultOpts = struct {
|
const ExpectResultOpts = struct {
|
||||||
@@ -203,37 +172,135 @@ const TestContext = struct {
|
|||||||
index: ?usize = null,
|
index: ?usize = null,
|
||||||
};
|
};
|
||||||
pub fn expectSent(self: *TestContext, expected: anytype, opts: SentOpts) !void {
|
pub fn expectSent(self: *TestContext, expected: anytype, opts: SentOpts) !void {
|
||||||
const serialized = try json.Stringify.valueAlloc(self.arena.allocator(), expected, .{
|
const serialized = try json.Stringify.valueAlloc(base.arena_allocator, expected, .{
|
||||||
.whitespace = .indent_2,
|
.whitespace = .indent_2,
|
||||||
.emit_null_optional_fields = false,
|
.emit_null_optional_fields = false,
|
||||||
});
|
});
|
||||||
|
for (0..5) |_| {
|
||||||
for (self.client.?.sent.items, 0..) |sent, i| {
|
for (self.received.items, 0..) |received, i| {
|
||||||
if (try compareExpectedToSent(serialized, sent) == false) {
|
if (try compareExpectedToSent(serialized, received) == false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.index) |expected_index| {
|
if (opts.index) |expected_index| {
|
||||||
if (expected_index != i) {
|
if (expected_index != i) {
|
||||||
|
std.debug.print("Expected message at index: {d}, was at index: {d}\n", .{ expected_index, i });
|
||||||
|
self.dumpReceived();
|
||||||
return error.ErrorAtWrongIndex;
|
return error.ErrorAtWrongIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = self.client.?.sent.orderedRemove(i);
|
return;
|
||||||
_ = self.client.?.serialized.orderedRemove(i);
|
}
|
||||||
|
std.Thread.sleep(5 * std.time.ns_per_ms);
|
||||||
|
try self.read();
|
||||||
|
}
|
||||||
|
self.dumpReceived();
|
||||||
|
return error.ErrorNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dumpReceived(self: *const TestContext) void {
|
||||||
|
std.debug.print("CDP Message Received ({d})\n", .{self.received_raw.items.len});
|
||||||
|
for (self.received_raw.items, 0..) |received, i| {
|
||||||
|
std.debug.print("===Message: {d}===\n{s}\n\n", .{ i, received });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSentMessage(self: *TestContext, index: usize) !?json.Value {
|
||||||
|
for (0..5) |_| {
|
||||||
|
if (index < self.received.items.len) {
|
||||||
|
return self.received.items[index];
|
||||||
|
}
|
||||||
|
std.Thread.sleep(5 * std.time.ns_per_ms);
|
||||||
|
try self.read();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(self: *TestContext) !void {
|
||||||
|
while (true) {
|
||||||
|
const n = posix.read(self.socket, self.read_buf[self.read_at..]) catch |err| switch (err) {
|
||||||
|
error.WouldBlock => return,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (n == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std.debug.print("Error not found. Expecting:\n{s}\n\nGot:\n", .{serialized});
|
self.read_at += n;
|
||||||
for (self.client.?.serialized.items, 0..) |sent, i| {
|
|
||||||
std.debug.print("#{d}\n{s}\n\n", .{ i, sent });
|
// Try to parse complete WebSocket frames
|
||||||
|
var pos: usize = 0;
|
||||||
|
while (pos < self.read_at) {
|
||||||
|
// Need at least 2 bytes for header
|
||||||
|
if (self.read_at - pos < 2) break;
|
||||||
|
|
||||||
|
const opcode = self.read_buf[pos] & 0x0F;
|
||||||
|
const payload_len_byte = self.read_buf[pos + 1] & 0x7F;
|
||||||
|
|
||||||
|
var header_size: usize = 2;
|
||||||
|
var payload_len: usize = payload_len_byte;
|
||||||
|
|
||||||
|
if (payload_len_byte == 126) {
|
||||||
|
if (self.read_at - pos < 4) break;
|
||||||
|
payload_len = std.mem.readInt(u16, self.read_buf[pos + 2 ..][0..2], .big);
|
||||||
|
header_size = 4;
|
||||||
|
}
|
||||||
|
// Skip 8-byte length case (127) - not needed
|
||||||
|
|
||||||
|
const frame_size = header_size + payload_len;
|
||||||
|
if (self.read_at - pos < frame_size) break;
|
||||||
|
|
||||||
|
// We have a complete frame - process text (1) or binary (2), skip others
|
||||||
|
if (opcode == 1 or opcode == 2) {
|
||||||
|
const payload = self.read_buf[pos + header_size ..][0..payload_len];
|
||||||
|
const parsed = try std.json.parseFromSliceLeaky(json.Value, base.arena_allocator, payload, .{});
|
||||||
|
try self.received.append(base.arena_allocator, parsed);
|
||||||
|
try self.received_raw.append(base.arena_allocator, try base.arena_allocator.dupe(u8, payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move remaining partial data to beginning of buffer
|
||||||
|
if (pos > 0 and pos < self.read_at) {
|
||||||
|
std.mem.copyForwards(u8, &self.read_buf, self.read_buf[pos..self.read_at]);
|
||||||
|
self.read_at -= pos;
|
||||||
|
} else if (pos == self.read_at) {
|
||||||
|
self.read_at = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return error.ErrorNotFound;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn context() TestContext {
|
pub fn context() !TestContext {
|
||||||
|
var pair: [2]posix.socket_t = undefined;
|
||||||
|
const rc = std.c.socketpair(posix.AF.LOCAL, posix.SOCK.STREAM, 0, &pair);
|
||||||
|
if (rc != 0) {
|
||||||
|
return error.SocketPairFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
posix.close(pair[0]);
|
||||||
|
posix.close(pair[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = std.mem.toBytes(posix.timeval{ .sec = 0, .usec = 5_000 });
|
||||||
|
try posix.setsockopt(pair[0], posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout);
|
||||||
|
try posix.setsockopt(pair[0], posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout);
|
||||||
|
try posix.setsockopt(pair[1], posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout);
|
||||||
|
try posix.setsockopt(pair[1], posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout);
|
||||||
|
|
||||||
|
try posix.setsockopt(pair[0], posix.SOL.SOCKET, posix.SO.RCVBUF, &std.mem.toBytes(@as(c_int, 32_768)));
|
||||||
|
try posix.setsockopt(pair[0], posix.SOL.SOCKET, posix.SO.SNDBUF, &std.mem.toBytes(@as(c_int, 32_768)));
|
||||||
|
try posix.setsockopt(pair[1], posix.SOL.SOCKET, posix.SO.RCVBUF, &std.mem.toBytes(@as(c_int, 32_768)));
|
||||||
|
try posix.setsockopt(pair[1], posix.SOL.SOCKET, posix.SO.SNDBUF, &std.mem.toBytes(@as(c_int, 32_768)));
|
||||||
|
|
||||||
|
const client = try Server.Client.init(pair[1], base.arena_allocator, base.test_app, "json-version", 2000);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.arena = ArenaAllocator.init(std.testing.allocator),
|
.client = client,
|
||||||
|
.socket = pair[0],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,9 +192,9 @@ fn dumpWPT(page: *Page, writer: *std.Io.Writer) !void {
|
|||||||
|
|
||||||
pub inline fn assert(ok: bool, comptime ctx: []const u8, args: anytype) void {
|
pub inline fn assert(ok: bool, comptime ctx: []const u8, args: anytype) void {
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
if (comptime IS_DEBUG) {
|
// if (comptime IS_DEBUG) {
|
||||||
unreachable;
|
// unreachable;
|
||||||
}
|
// }
|
||||||
assertionFailure(ctx, args);
|
assertionFailure(ctx, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,7 +324,9 @@ pub const WsConnection = struct {
|
|||||||
pub fn init(socket: posix.socket_t, allocator: Allocator, json_version_response: []const u8, timeout_ms: u32) !WsConnection {
|
pub fn init(socket: posix.socket_t, allocator: Allocator, json_version_response: []const u8, timeout_ms: u32) !WsConnection {
|
||||||
const socket_flags = try posix.fcntl(socket, posix.F.GETFL, 0);
|
const socket_flags = try posix.fcntl(socket, posix.F.GETFL, 0);
|
||||||
const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true }));
|
const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true }));
|
||||||
|
if (builtin.is_test == false) {
|
||||||
assert(socket_flags & nonblocking == nonblocking, "WsConnection.init blocking", .{});
|
assert(socket_flags & nonblocking == nonblocking, "WsConnection.init blocking", .{});
|
||||||
|
}
|
||||||
|
|
||||||
var reader = try Reader(true).init(allocator);
|
var reader = try Reader(true).init(allocator);
|
||||||
errdefer reader.deinit();
|
errdefer reader.deinit();
|
||||||
|
|||||||
@@ -445,10 +445,6 @@ pub fn pageTest(comptime test_file: []const u8) !*Page {
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
|
||||||
std.testing.refAllDecls(@This());
|
|
||||||
}
|
|
||||||
|
|
||||||
const log = @import("log.zig");
|
const log = @import("log.zig");
|
||||||
const TestHTTPServer = @import("TestHTTPServer.zig");
|
const TestHTTPServer = @import("TestHTTPServer.zig");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user