Capture console output from custom workers
Capture console output from custom workers
Summary
This PR adds wasm_bindgen_test::forward_console_to_test_runner(), a public API that allows console output from user-spawned dedicated workers to be captured by the test harness.
Problem
Currently, wasm-bindgen-test-runner only captures console output from:
- The main browser thread
- The test runner's own worker (when using
run_in_dedicated_worker, etc.)
Console output from custom workers spawned by user code is not captured - it goes directly to browser DevTools and is invisible to the test harness.
This means they do not appear as seen from the command-line output of cargo test, wasm-bindgen-test-runner etc
as they would be on other runners and other platforms.
Solution
This PR
Add a public API that users should call from their custom worker (or from their crate that spawns workers):
// In a custom worker's entry point:
wasm_bindgen_test::forward_console_to_test_runner();
// Now console.log, console.error, etc. will be captured
console_log!("This will appear in test output");
The function wraps console.debug/log/info/warn/error to send messages via postMessage in the format ["__wbgtest_<method>", [args...]], which the test runner already listens for.
Pros:
- Simple, explicit, no magic
- Less risk of breaking existing code
- a PR in the hand is worth two in the bush
Cons:
- Requires user (or ecosystem crates) to remember to call the function to get these logs
- Only works for custom dedicated workers at present
Testing
Two new tests verify the functionality:
-
forward_console_to_test_runner_sends_correct_message- Verifies console.log frmo dedicated worker sends correct postMessage format -
forward_console_all_methods- Verifies all five console methods are forwarded
Alternative idea: Monkeypatching
I think the best way to evaluate this is by comparison to the main alternative design.
I suspect we could automatically capture console output from all spawned workers without requiring new API or user code changes. That architecture would instead be along the lines of:
-
Monkey-patch
Workerconstructor in the test runner's HTML to add a query parameter:const OriginalWorker = Worker; Worker = function(url, options) { const absoluteUrl = new URL(url, location.href).href; const patchedUrl = absoluteUrl + (absoluteUrl.includes('?') ? '&' : '?') + '__wbgtest_worker=1'; return new OriginalWorker(patchedUrl, options); }; -
Server-side script prepending - when the test server sees
__wbgtest_worker=1, prepend console forwarding code:// In server.rs if request.url().contains("__wbgtest_worker=1") { let content = read_file(path); let wrapped = format!("{}\n{}", CONSOLE_FORWARD_BOOTSTRAP, content); return Response::from_data("application/javascript", wrapped); } -
Bootstrap code prepended to worker scripts:
(function() { const methods = ['debug', 'log', 'info', 'warn', 'error']; for (const method of methods) { const original = console[method].bind(console); console[method] = function(...args) { self.postMessage(["__wbgtest_" + method, args]); original(...args); }; } })(); // ... original worker script follows ...
Pros:
- Fully transparent - works without any user code changes
- Captures output from all workers, including those in dependencies
Cons:
- More likely to break user code without any code changes
- Much more complex implementation, touches many wasm-bindgen components
- Modifies scripts in flight
- Many more edge cases than I've explained here (blob URLs, modules, relative imports...)
Comparison
It seemed simpler to start with the simple approach and get feedback.
CodSpeed Performance Report
Merging #4856 will not alter performance
Comparing drewcrawford:capture-worker-logs (4123bc0) with main (ce43616)
Summary
✅ 4 untouched
I like the solution, but would like to hear what others think about this as well.
In comparison to (3) that you present it seems to mainly save extra typing. But I'm all for that personally.
mainly save extra typing
IMO the bigger tradeoff is that
- Incomplete logs are dangerous; they send folks down some very wild debugging goose chases. Once one realizes that one is using custom workers at all, and that custom workers aren't being logged, and figures out there's an API that will escape log jail, and figures out where is the right place to call it, then this is a great solution to the problem. But getting to this point assumes a certain level of sophistication with wasm's logging quirks.
- But we're already missing stdio logs anyway, so a certain awareness of wasm's logging quirks should be assumed. It would take a lot of implementation complexity to actually collect worker logs by default without breaking anything. Atomics users are more sophisticated anyway, so it's not unreasonable to ask them to learn 1 API.
I think they're both good arguments. I'm kind of persuaded by the idea that we could start with the simple thing now and see how that goes.
I'd be interested to hear from any users of custom workers and what their experience has been with missing logs.