drashland / sinco

Browser Automation and Testing Tool for Deno, written in full TypeScript
https://drash.land/sinco
MIT License
57 stars 3 forks source link

question: How to access global variable on window inside page.evaluate() #139

Closed thomaswormann closed 2 years ago

thomaswormann commented 2 years ago

Summary

I'm trying to get access to a global variable on the window object inside a page.evaluate() function call. I've seen the example in page.ts#L267 but I can't make it work.

I'm getting this error about a circular conversion:

error: TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Window'
    --- property 'window' closes the circle
    this.socket.send(JSON.stringify(data));
                          ^
    at JSON.stringify (<anonymous>)
    at Protocol.send (https://deno.land/x/sinco@v4.1.0/src/protocol.ts:88:27)
    at Page.evaluate (https://deno.land/x/sinco@v4.1.0/src/page.ts:328:40)
    at async fn

I'm wondering if it's just the example being outdated or something else?

ebebbington commented 2 years ago

@thomaswormann can you show your whole code?

thomaswormann commented 2 years ago

I'm trying to expose some global objects in my tests that are encapsulated in ES modules. So I'm trying to inject them through a "globals.js" file I dynamically add to the page. This file contains just "window.foo = "bar";" - so I'm expecting the console.log output to contain the string "bar".

Deno.test({
  name: "FooBar",
  fn: async (t) => {

    const { browser, page } = await buildFor("chrome", {
      defaultUrl: "about:blank",
      binaryPath: Deno.env.get('DENO_CHROME_EXECUTABLE') || '/usr/bin/chromium'
    });

    const url = Deno.env.get('DENO_FOOBAR_URL') || 'http://localhost:8181/?locale=en_US';
    await page.location(url);

    const injectGlobals = await page.evaluate((window) => {
      const globalsScript = document.createElement('script');
      globalsScript.setAttribute("type", "module");
      globalsScript.setAttribute("src", "./tests/ui/globals.js");
      globalsScript.setAttribute("async", "false");
      globalsScript.setAttribute("defer", "false");
      document.body.appendChild(globalsScript);

      return { window };
    }, window);

    console.log(window.foo);
  }
});
ebebbington commented 2 years ago

@thomaswormann this won’t work, you’re still referencing the global window object in the Deno runtime and not accessing the one returned (`injectGlobals’)

ebebbington commented 2 years ago

You’re also passing in the window object from the Deno namespace, I think to make it work, remove the 2nd parameter in evaluate() and remove the window parameter in your callback, that way your returned value will be a reference to the page’s window object

thomaswormann commented 2 years ago

That makes a lot of sense, I confused the global Deno "window" object with the browser one. I updated the code:

Deno.test({
    // [...]
    const injectGlobals = await page.evaluate(() => {
      const globalsScript = document.createElement('script');
      globalsScript.setAttribute("type", "module");
      globalsScript.setAttribute("src", "./tests/ui/globals.js");
      globalsScript.setAttribute("async", "false");
      globalsScript.setAttribute("defer", "false");
      document.body.appendChild(globalsScript);

      return window;
    });

    console.log(injectGlobals.foo);
  }
});

But now I get:

error: TypeError: Cannot read properties of undefined (reading 'value')
      return res.result.value;
                        ^
    at Page.evaluate (https://deno.land/x/sinco@v4.1.0/src/page.ts:343:25)

I checked the document.body.innerHTML btw. and the script tag gets added, I just don't know if it gets parsed properly (I guess it should).

thomaswormann commented 2 years ago

I wrote the res variable in page.ts:343 to the console and it says:

------- output -------
{ code: -32000, message: "Object reference chain is too long" }
----- output end -----

So maybe the window object is just to large to be passed back via the websocket connection?

ebebbington commented 2 years ago

It seems like it :/

There nothing we can do about that on our side i dont think, if you need the whole window, maybe return JSON.stringify(window), then parse JSON.parse(onjectGlobals)? or just return the data you need:

return {
  parser: window.parser,
  stringHelper: window.stringHelper
}
thomaswormann commented 2 years ago

Yes, I'll do that. Thank you for your help!

ebebbington commented 2 years ago

@thomaswormann let us know how it goes! feel free to re-open this issue if you still have questions/problems, maybe there is always something we can do on our end