Direct Javascript access in Puppeteer
I accidentally ran into this when I started using headless-chrome with the puppeteer project.
I love using puppeteer for testing browser code, and many times I use it to test Javascript libraries with a rich API.
But I got a bit bummed by the evaluate API. The environment inside the evaluate function and the connection with the environment outside of it feel too much disjointed and verbose, I have to pass too many parameters and remember their order.
For example, to use the scrollBy method, the following code needs to be written:
await page.evaluate((x, y) => window.scrollBy(x, y), x, y)
I have to repeat the parameters three times! This doesn’t feel right… I wanted the code to look more like:
await page.window.scrollBy(x, y)
So I played with ES6 proxies, trying to find the hack that would achieve this.
Doing that, I found an interesting feature of proxies when used with async/await code: the proxy trap would receive an “apply” call for the “then” function.
This was the bit I needed, and from there code similar to the above is actually possible! Also, you can now await regular getters inside the window, like this:
const window = directJSHandle(page.evaluate(() => window))
const tagName = await window.document.body.tagName
I use the proxy to chain and store all the getters, setters, and calls, and then break the chain when I receive the magic function call to “then”.
This is all available a small github project. It’s not yet ready to be maintained as an open source project (maybe it will if we start using it internally at Wix), but more like a concept.
Caveats: debugging code like this is a bit cumbersome and requires tools to ease the pain, and seeing “window” in a node context is confusing. But it was fun enough to share.
To be continued…