Как использовать импортированную функцию внутри page.evaluate в Puppeteer с Jest? - PullRequest
0 голосов
/ 07 декабря 2018

У меня есть проект TypeScript, который использует Jest для модульных тестов, и я только что добавил Puppeteer в микс с намерением запустить некоторые тесты на клиенте.Это работает нормально, если я не попытаюсь использовать импортированные функции внутри page.evaluate.

Например, у меня есть 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
});

С закомментированным document.createElement('canvas')Тест проходит нормально и протоколирует [ 300, 150 ].Но с createHdpiCanvas() функция page.evaluate выдает следующую ошибку:

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

Фактический createHdpiCanvas внутри HdpiCanvas.ts определяется следующим образом:

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

и сам по себе зависит от других функций, определенных в HdpiCanvas.ts, например applyHdpiOverrides.

Ответы [ 3 ]

0 голосов
/ 15 декабря 2018

Как вы, возможно, уже поняли, причина сбоя page.evaluate заключается в том, что функция оценивается в другом контексте, а текущая область (где createHdpiCanvas доступна вне функции) теряется.

Я вообще не знаком с pupeteer, но, посмотрев документацию по этой функции, вы можете попробовать кое-что.Это не красиво, но, возможно, стоит попробовать:

Вы можете передать createHdpiCanvas в качестве параметра, однако функция не сериализуема, поэтому вам придется сделать это самостоятельно:

page.evaluate((createHdpiCanvasCode) => {
  const createHdpiCanvas = new Function(`return ${createHdpiCanvasCode}`)();
  const canvas = createHdpiCanvas();
  return [canvas.width, canvas.height];
}, createHdpiCanvas.toString());

Однако, вам придется вводить свои зависимости таким же образом.

0 голосов
/ 19 декабря 2018

Решение, предложенное Мени Ройтенбурдом, является правильным.Если вам не нравится тот факт, что каждая функция должна быть представлена ​​браузеру отдельно, единственная идея, которая приходит на ум, - сначала перенести ваш проект в один файл JavaScript, а затем вставить его в виде тега <script> - точно так же, каквы бы в реальной жизни.

Первым шагом было бы создать один файл JavaScript с вашим пакетом.Иногда это может быть достигнуто только с помощью компилятора TypeScript, но можно использовать и такой инструмент, как Webpack.

Как только это будет сделано, вы можете переместить свой пакет на клиент из Puppeteer:

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

Имейте в виду, что это все еще может открыть ваши функции для глобальной области видимости, следовательно, доступ к ним через window.

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

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

Еще один недостаток этого подхода к необходимости иметь дело с предупреждениями, генерируемыми TypeScript -на данный момент он не знает, что createHdpiCanvas существует на window, поэтому доступ к window.createHdpiCanvas вызовет ошибку.

0 голосов
/ 13 декабря 2018

вы можете сделать это, прежде чем вызывать функцию evaluate:

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

и теперь ваше окно будет распознавать эти функции

...