modernweb-dev / web

Guides, tools and libraries for modern web development.
https://modern-web.dev
MIT License
2.21k stars 281 forks source link

Tests do not finish or hangs when asserting window - structuredClone errors #2802

Open josephsintum opened 1 month ago

josephsintum commented 1 month ago

When asserting with and object that contains or reference the window, the test never finish and in the browser it show a structuredClone error in the test framework Here's a test case

import { expect } from '@esm-bundle/chai';

describe('suite', function () {
    it('window test', () => {
        let cloneWindow = window.Window;
        expect(cloneWindow).to.be.a('object');
    });
});

The test finishes but gets stuck, in the CLI, there are no errors but in the browser, you can see this error message DataCloneError: Failed to execute 'structuredClone' on 'Window': function Window() { [native code] } could not be cloned. at stable (__web-dev-server__web-socket.js:23:24)

Screenshot 2024-09-05 at 11 02 38
lucaelin commented 4 weeks ago

This is related to #2728 and #2731 , maybe some check is required for special cases like window, document and such or the other way around?

denilsonsa commented 3 weeks ago

I'm getting the same error, but with a slightly different error message:

DOMException: Failed to execute 'structuredClone' on 'Window': function proxy(a, b) {
                return p.invoke(func, this, slice(arguments));
            } could not be cloned.

A quick and dirty pseudo-solution:

--- node_modules/@web/dev-server-core/dist/web-sockets/webSocketsPlugin.js.bak  2024-09-10 13:52:55
+++ node_modules/@web/dev-server-core/dist/web-sockets/webSocketsPlugin.js  2024-09-10 13:48:32
@@ -38,7 +38,12 @@
         }

         export function stable (obj, replacer, spacer) {
-          var target = structuredClone(obj)
+          var target;
+          try {
+            target = structuredClone(obj)
+          } catch (e) {
+            target = obj;
+          }
           var tmp = deterministicDecirc(target, '', [], undefined) || target
           var res
           if (replacerStack.length === 0) {

This isn't production code, but at least it lets my tests report correctly, which is much better than waiting for two minutes for a random timeout on a test code that can't timeout.

As a side note, I see the upstream code has been updated over time. The function name stable has been replaced 6½ years ago.

laino commented 2 weeks ago

The Workaround

Before getting into an explanation of the problem, here's a workaround:

  1. Install @ungap/structured-clone
  2. Add this to your config:

export default { // ... testRunnerHtml(testFramework) {
return <html><body><script type="module">import structuredClone from '@ungap/structured-clone';window.structuredClone = (value) => structuredClone(value, { lossy: true });</script><script type="module" src="${testFramework}"></script></body></html>;
},
// ...
}



That replaces the browser's built-in structuredClone algorithm with a polyfill that is more forgiving. 

Just be aware the diffs shown could be incomplete:

![image](https://github.com/user-attachments/assets/9719def3-30a5-40ab-9896-6c1fb3acbdc5)

Here the function instances differed, but get replaced by undefined before the diff was shown. Better than nothing though.

## The Problem

The root cause appears to be that Chai's ``AssertionError`` has an ``actual`` property, which contains the actual object differing from the ``expected`` one. This ``AssertionError`` (or a shallow copy) is in the data structure that we attempt to clone and serialize:

![swappy-20240916_083339](https://github.com/user-attachments/assets/7e00835f-c46b-443a-9aa9-09e9f9b8ca62)

Obviously this is going to go badly if there's anything in there that can't be cloned/serialized. I believe this is the place creating the error object in that structure: https://github.com/modernweb-dev/web/blob/d5ae228f1f030a58995ac5ac5c51df4e02612981/packages/test-runner-mocha/src/collectTestResults.ts#L49

At the end of the day there's no good *and* easy way out of this, since afaik those properties are used to calculate diffs on the server side, and thus must be sent for that work. Fixing this either requires calculating diffs client-side, or coming up a with a representation of expected/actual objects which cleanly serialize and can be used to calculate diffs without reducing their quality.
lucaelin commented 2 weeks ago

I think this is a duplicate of #2772 which I was made aware of