Start unifying test and code

Depends on https://github.com/lightpanda-io/browser/pull/993

There's currently 3 ways to execute a page:
1 - page.navigate (as used in both the 'fetch' and 'serve' commands)
2 - jsRunner as used in unit tests
3 - main_wpt as used in the WPT runner

Both jsRunner and main_wpt replicate the page.navigate code, but in their own
hack-ish way. main_wpt re-implements the DOM walking in order to extract and
execute <script> tags, as well as the needed page lifecycle events.

This PR replaces the existing main_wpt loader with a call to page.navigate. To
support this, a test HTTP server was added. (The test HTTP server is extracted
from the existing unit test test server, and re-used between the two).

There are benefits to this approach:
1 - The code is simpler
2 - More of the actual code and flow is tested
3 - There's 1 way to do things (page.navigate)
4 - Having an HTTP server might unlock some WPT tests

Technically, we're replacing file IO with network IO i.e. http requests). This
has potential downsides:
1 - The tests might be more brittle
2 - The tests might be slower

I think we need to run it for a while to see if we get flaky behavior.

The goal for following PRs is to bring this unification to the jsRunner.
This commit is contained in:
Karl Seguin
2025-08-31 19:25:17 +08:00
parent 6c41245c73
commit 7d46e8fe80
6 changed files with 265 additions and 215 deletions

View File

@@ -719,23 +719,26 @@ test {
std.testing.refAllDecls(@This());
}
const TestHTTPServer = @import("TestHTTPServer.zig");
var test_cdp_server: ?Server = null;
var test_http_server: ?TestHTTPServer = null;
test "tests:beforeAll" {
log.opts.level = .err;
log.opts.format = .logfmt;
try testing.setup();
var wg: std.Thread.WaitGroup = .{};
wg.startMany(2);
{
const thread = try std.Thread.spawn(.{}, serveHTTP, .{&wg});
const thread = try std.Thread.spawn(.{}, serveCDP, .{&wg});
thread.detach();
}
test_http_server = TestHTTPServer.init(testHTTPHandler);
{
const thread = try std.Thread.spawn(.{}, serveCDP, .{&wg});
const thread = try std.Thread.spawn(.{}, TestHTTPServer.run, .{ &test_http_server.?, &wg });
thread.detach();
}
@@ -748,59 +751,10 @@ test "tests:afterAll" {
if (test_cdp_server) |*server| {
server.deinit();
}
testing.shutdown();
}
fn serveHTTP(wg: *std.Thread.WaitGroup) !void {
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
var listener = try address.listen(.{ .reuse_address = true });
defer listener.deinit();
wg.finish();
var buf: [1024]u8 = undefined;
while (true) {
var conn = try listener.accept();
defer conn.stream.close();
var conn_reader = conn.stream.reader(&buf);
var conn_writer = conn.stream.writer(&buf);
var http_server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface);
var request = http_server.receiveHead() catch |err| switch (err) {
error.HttpConnectionClosing => continue,
else => {
std.debug.print("Test HTTP Server error: {}\n", .{err});
return err;
},
};
const path = request.head.target;
if (std.mem.eql(u8, path, "/loader")) {
try request.respond("Hello!", .{
.extra_headers = &.{.{ .name = "Connection", .value = "close" }},
});
} else if (std.mem.eql(u8, path, "/xhr")) {
try request.respond("1234567890" ** 10, .{
.extra_headers = &.{
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
.{ .name = "Connection", .value = "Close" },
},
});
} else if (std.mem.eql(u8, path, "/xhr/json")) {
try request.respond("{\"over\":\"9000!!!\"}", .{
.extra_headers = &.{
.{ .name = "Content-Type", .value = "application/json" },
.{ .name = "Connection", .value = "Close" },
},
});
} else {
// should not have an unknown path
unreachable;
}
if (test_http_server) |*server| {
server.deinit();
}
testing.shutdown();
}
fn serveCDP(wg: *std.Thread.WaitGroup) !void {
@@ -816,3 +770,31 @@ fn serveCDP(wg: *std.Thread.WaitGroup) !void {
return err;
};
}
fn testHTTPHandler(req: *std.http.Server.Request) !void {
const path = req.head.target;
if (std.mem.eql(u8, path, "/loader")) {
return req.respond("Hello!", .{
.extra_headers = &.{.{ .name = "Connection", .value = "close" }},
});
}
if (std.mem.eql(u8, path, "/xhr")) {
return req.respond("1234567890" ** 10, .{
.extra_headers = &.{
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
},
});
}
if (std.mem.eql(u8, path, "/xhr/json")) {
return req.respond("{\"over\":\"9000!!!\"}", .{
.extra_headers = &.{
.{ .name = "Content-Type", .value = "application/json" },
},
});
}
unreachable;
}