Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use imported function inside page.evaluate in Puppeteer with Jest?

I have a TypeScript project that uses Jest for unit tests and have just added Puppeteer to the mix with the intention to run some tests on the client. It works fine, unless I try use imported functions inside page.evaluate.

For example, I have the following in HdpiCanvas.test.ts:

import { createHdpiCanvas } from "./HdpiCanvas";

test("createHdpiCanvas", async () => {
    await page.setViewport({ width: 800, height: 600, deviceScaleFactor: 2 });
    let size = await page.evaluate(() => {
        const canvas = createHdpiCanvas(); // document.createElement('canvas');
        return [canvas.width, canvas.height];
    });
    console.log(size); // [600, 300] for HDPI canvas and [ 300, 150 ] for a regular one
});

With the commented out document.createElement('canvas') the test runs just fine and logs [ 300, 150 ]. But with the createHdpiCanvas() the following error is thrown by the page.evaluate function:

Error: Evaluation failed: ReferenceError: HdpiCanvas_1 is not defined
    at __puppeteer_evaluation_script__:2:24

The actual createHdpiCanvas inside HdpiCanvas.ts is defined as follows:

export function createHdpiCanvas(width = 300, height = 150): HTMLCanvasElement {
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    applyHdpiOverrides(canvas);
    return canvas;
}

and itself depends on other functions defined in HdpiCanvas.ts, like applyHdpiOverrides.

like image 438
Vitaly Avatar asked Dec 07 '18 20:12

Vitaly


2 Answers

The solution suggested by Meni Roytenburd is correct. If you don't like the fact that every function needs to be exposed to the browser separately, the only idea that comes to mind is transpiling your project to a single JavaScript file first and then injecting it as a <script> tag — just like you would in real life.

The first step would be to generate a single JavaScript file with your bundle. Sometimes this can be accomplished with the TypeScript compiler alone, but a tool like Webpack can be used, too.

Once it's done, you can move your bundle to the client from within Puppeteer:

await page.addScriptTag({ path: 'path/to/the/bundle' });

Keep in mind this still may expose your functions to the global scope, hence accessing them via window.

  let size = await page.evaluate(() => {
      const canvas = window.createHdpiCanvas();
      return [canvas.width, canvas.height];
  });

  console.log(size); // [ 300, 150 ]

Another downside to this approach to having to deal with the warnings generated by TypeScript — at this point it doesn't know that createHdpiCanvas exists on window, so accessing window.createHdpiCanvas will cause an error.

like image 178
Karol Majewski Avatar answered Oct 08 '22 19:10

Karol Majewski


you can do like this before you invoke the evaluate function :

await page.exposeFunction("applyHdpiOverrides",applyHdpiOverrides);
await page.exposeFunction("createHdpiCanvas",createHdpiCanvas);

and now your window will recognize these functions

like image 39
Meni Roytenburd Avatar answered Oct 08 '22 18:10

Meni Roytenburd