Improve async tests

testing.async(...) is pretty lame. It works for simple cases, where the
microtask is very quickly resolved, but otherwise can't block the test from
exiting.

This adds an overload to testing.async and leverages the new Runner
https://github.com/lightpanda-io/browser/pull/1958 to "tick" until completion
(or timeout).

The overloaded version of testing.async() (called without a callback) will
increment a counter which is only decremented with the promise is resolved. The
test runner will now `tick` until the counter == 0.
This commit is contained in:
Karl Seguin
2026-03-23 17:09:37 +08:00
parent d517488158
commit 35be9f897f
6 changed files with 80 additions and 35 deletions

View File

@@ -3585,12 +3585,7 @@ test "WebApi: Page" {
} }
test "WebApi: Frames" { test "WebApi: Frames" {
// TOO FLAKY, disabled for now try testing.htmlRunner("frames", .{});
// const filter: testing.LogFilter = .init(&.{.js});
// defer filter.deinit();
// try testing.htmlRunner("frames", .{});
} }
test "WebApi: Integration" { test "WebApi: Integration" {

View File

@@ -118,23 +118,24 @@
} }
</script> </script>
<script id=link_click> <script id=link_click type=module>
testing.async(async (restore) => { const state = await testing.async();
await new Promise((resolve) => {
let count = 0; let count = 0;
let f6 = document.createElement('iframe'); let f6 = document.createElement('iframe');
f6.id = 'f6'; f6.id = 'f6';
f6.addEventListener('load', () => { f6.addEventListener('load', () => {
if (++count == 2) { if (++count == 2) {
resolve(); state.resolve();
return; return;
} }
f6.contentDocument.querySelector('#link').click(); f6.contentDocument.querySelector('#link').click();
}); });
f6.src = "support/with_link.html";
f6.src = 'support/with_link.html';
document.documentElement.appendChild(f6); document.documentElement.appendChild(f6);
});
restore(); await state.done(() => {
testing.expectEqual("<html><head></head><body>It was clicked!\n</body></html>", f6.contentDocument.documentElement.outerHTML); testing.expectEqual("<html><head></head><body>It was clicked!\n</body></html>", f6.contentDocument.documentElement.outerHTML);
}); });
</script> </script>

View File

@@ -7,7 +7,6 @@
{ {
let reply = null; let reply = null;
window.addEventListener('message', (e) => { window.addEventListener('message', (e) => {
console.warn('reply')
reply = e.data; reply = e.data;
}); });

View File

@@ -1,7 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<script> <script>
window.addEventListener('message', (e) => { window.addEventListener('message', (e) => {
console.warn('Frame Message', e.data);
if (e.data === 'ping') { if (e.data === 'ping') {
window.top.postMessage({data: 'pong', origin: e.origin}, '*'); window.top.postMessage({data: 'pong', origin: e.origin}, '*');
} }

View File

@@ -4,6 +4,7 @@
let eventuallies = []; let eventuallies = [];
let async_capture = null; let async_capture = null;
let current_script_id = null; let current_script_id = null;
let async_pending = 0;
function expectTrue(actual) { function expectTrue(actual) {
expectEqual(true, actual); expectEqual(true, actual);
@@ -64,6 +65,25 @@
} }
async function async(cb) { async function async(cb) {
if (cb == undefined) {
let resolve = null
const promise = new Promise((r) => { resolve = r});
async_pending += 1;
return {
promise: promise,
resolve: resolve,
capture: {script_id: document.currentScript.id, stack: new Error().stack},
done: async function(cb) {
await this.promise;
async_pending -= 1;
async_capture = this.capture;
cb();
async_capture = false;
}
};
}
let capture = {script_id: document.currentScript.id, stack: new Error().stack}; let capture = {script_id: document.currentScript.id, stack: new Error().stack};
await cb(() => { async_capture = capture; }); await cb(() => { async_capture = capture; });
async_capture = null; async_capture = null;
@@ -74,6 +94,10 @@
throw new Error('Failed'); throw new Error('Failed');
} }
if (async_pending > 0) {
return false;
}
for (let e of eventuallies) { for (let e of eventuallies) {
current_script_id = e.script_id; current_script_id = e.script_id;
e.callback(); e.callback();
@@ -97,6 +121,8 @@
throw new Error(`script id: '${script_id}' failed: ${status || 'no assertions'}`); throw new Error(`script id: '${script_id}' failed: ${status || 'no assertions'}`);
} }
} }
return true;
} }
const IS_TEST_RUNNER = window.navigator.userAgent.startsWith("Lightpanda/"); const IS_TEST_RUNNER = window.navigator.userAgent.startsWith("Lightpanda/");

View File

@@ -410,21 +410,46 @@ fn runWebApiTest(test_file: [:0]const u8) !void {
page.js.localScope(&ls); page.js.localScope(&ls);
defer ls.deinit(); defer ls.deinit();
{
var try_catch: js.TryCatch = undefined; var try_catch: js.TryCatch = undefined;
try_catch.init(&ls.local); try_catch.init(&ls.local);
defer try_catch.deinit(); defer try_catch.deinit();
try page.navigate(url, .{}); try page.navigate(url, .{});
}
var runner = try test_session.runner(.{}); var runner = try test_session.runner(.{});
try runner.wait(.{ .ms = 2000 }); try runner.wait(.{ .ms = 2000 });
test_browser.runMicrotasks(); var wait_ms: u32 = 2000;
var timer = try std.time.Timer.start();
while (true) {
var try_catch: js.TryCatch = undefined;
try_catch.init(&ls.local);
defer try_catch.deinit();
ls.local.eval("testing.assertOk()", "testing.assertOk()") catch |err| { const js_val = ls.local.exec("testing.assertOk()", "testing.assertOk()") catch |err| {
const caught = try_catch.caughtOrError(arena_allocator, err); const caught = try_catch.caughtOrError(arena_allocator, err);
std.debug.print("{s}: test failure\nError: {f}\n", .{ test_file, caught }); std.debug.print("{s}: test failure\nError: {f}\n", .{ test_file, caught });
return err; return err;
}; };
if (js_val.isTrue()) {
return;
}
switch (try runner.tick(.{ .ms = 20 })) {
.done => return error.TestNeverSignaledCompletion,
.ok => |next_ms| {
const ms_elapsed = timer.lap() / 1_000_000;
if (ms_elapsed >= wait_ms) {
return error.TestTimedOut;
}
wait_ms -= @intCast(ms_elapsed);
if (next_ms > 0) {
std.Thread.sleep(std.time.ns_per_ms * next_ms);
}
},
}
}
} }
// Used by a few CDP tests - wouldn't be sad to see this go. // Used by a few CDP tests - wouldn't be sad to see this go.