How to pass a function in Puppeteers .evaluate() method?

chitzui picture chitzui · Nov 15, 2017 · Viewed 10.8k times · Source

Whenever I try to pass a function, like this:

var myFunc = function() { console.log("lol"); };

await page.evaluate(func => {
 func();
 return true;
}, myFunc);

I get:

(node:13108) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Evaluation failed: TypeError: func is not a function
at func (<anonymous>:9:9)
(node:13108) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Why? How to do it correctly?

Thank you!

€: let me clarify: I am doing it this way because I want to find some DOM elements first and use them inside of that function, more like this (simplified):

var myFunc = function(element) { element.innerHTML = "baz" };

await page.evaluate(func => {
  var foo = document.querySelector('.bar');
  func(foo);
  return true;
}, myFunc);

Answer

Md. Abu Taher picture Md. Abu Taher · Nov 15, 2017

Similar problems have been discussed in a puppeteer issue.

There are several way to deal with your problem. First rule is to keep it simple.

Evaluate the function

This is the fastest way to do things, you can just pass the function and execute it.

await page.evaluate(() => {
  var myFunc = function(element) { element.innerHTML = "baz" };
  var foo = document.querySelector('.bar');
  myFunc(foo);
  return true;
});

Expose the function beforehand

You can expose the function beforehand using a page.evaluate, or a page.addScriptTag

// add it manually and expose to window
await page.evaluate(() => {
  window.myFunc = function(element) { element.innerHTML = "baz" };
});

// add some scripts
await page.addScriptTag({path: "myFunc.js"});

// Now I can evaluate as many times as I want
await page.evaluate(() => {
  var foo = document.querySelector('.bar');
  myFunc(foo);
  return true;
});

Use ElementHandle

page.$(selector)

You can pass an element handle to .evaluate and make changes as you seem fit.

const bodyHandle = await page.$('body');
const html = await page.evaluate(body => body.innerHTML, bodyHandle);

page.$eval

You can target one element and make changes as you want.

const html = await page.$eval('.awesomeSelector', e => {
e.outerHTML = "whatever"
});

The trick is to read the docs and keep it simple.