Whenever I try to pass a function, like this:
var myFunc = function() { console.log(\"lol\"); };
await page.evaluate(func => {
func();
return true;
},
The error is thrown because you execute func();
but func
is not a function. I update my answer to answer your updated question:
Option 1: execute your function in page context:
var myFunc = function(element) { element.innerHTML = "baz" };
await page.evaluate(func => {
var foo = document.querySelector('.bar');
myFunc(foo);
return true;
});
Option 2: pass element handle as arguments
const myFunc = (element) => {
innerHTML = "baz";
return true;
}
const barHandle = await page.$('.bar');
const result = await page.evaluate(myFunc, barHandle);
await barHandle.dispose();
`
// External function to run inside evaluate context
function getData() {
return document.querySelector('title').textContent;
}
function mainFunction(url, extractFunction){
let browser = await puppeteer.launch({});
let page = await browser.newPage();
await page.goto(url);
let externalFunction = Object.assign(extractFunction);
let res = await this.page.evaluate(externalFunction)
console.log(res);
}
// call it here
mainFunction('www.google.com',getData);
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.
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;
});
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;
});
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);
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.
Pass function with parameter
// add it manually and expose to window
await page.evaluate(() => {
window.myFunc = function(element) { element.innerHTML = "baz" };
});
// and then call function declared above
await page.evaluate((param) => {
myFunc (param);
}, param);
Created a helper function that wraps page.evaluate
:
const evaluate = (page, ...params) => browserFn => {
const fnIndexes = [];
params = params.map((param, i) => {
if (typeof param === "function") {
fnIndexes.push(i);
return param.toString();
}
return param;
});
return page.evaluate(
(fnIndexes, browserFnStr, ...params) => {
for (let i = 0; i < fnIndexes.length; i++) {
params[fnIndexes[i]] = new Function(
" return (" + params[fnIndexes[i]] + ").apply(null, arguments)"
);
}
browserFn = new Function(
" return (" + browserFnStr + ").apply(null, arguments)"
);
return browserFn(...params);
},
fnIndexes,
browserFn.toString(),
...params
);
};
export default evaluate;
Takes all parameters and converts functions to string.
Then recreates functions in browser context.
See https://github.com/puppeteer/puppeteer/issues/1474
You can use this function like so:
const featuredItems = await evaluate(page, _getTile, selector)((get, s) => {
const items = Array.from(document.querySelectorAll(s));
return items.map(node => get(node));
});
You cannot pass a function directly into page.evaluate()
, but you can call another special method (page.exposeFunction
), which expose your function as a global function (also available in as an attribute of your page window
object), so you can call it when you are inside page.evaluate()
:
var myFunc = function() { console.log("lol"); };
await page.exposeFunction("myFunc", myFunc);
await page.evaluate(async () => {
await myFunc();
return true;
});
Just remember that page.exposeFunction()
will make your function return a Promise
, then, you need to use async
and await
. This happens because your function will not be running inside your browser, but inside your nodejs
application.