How do I expose an object with a bunch of methods to puppeteer? I am trying to retain the definition of the parent object and method (i.e. foo.one) within page.evaluate, if possible. In other words, I am looking for console.log(foo.one('world')), typed as such, to return world.
foo is a library container which returns a whole bunch of (relatively) pure functions. These functions are required both in the main script context AND within the puppeteer browser. I would prefer to not have to redefine each of them within page.evaluate and instead pass this entire "package" to page.evaluate for repository readability/maintenance. Nonetheless, as one answer suggests below, iterating over the methods from foo and exposing them individually to puppeteer with a different name isn't a terrible option. It just would require redefinitions within page.evaluate which I am trying to avoid.
Let's assume an immediately invoked function which returns an object with a series of function definitions as properties. When trying to pass this IIFE (or object) to puppeteer page, I receive the following error:
import puppeteer from 'puppeteer'
const foo = (()=>{
const one = (msg) => console.log('1) ' + msg)
const two = (msg) => console.log('2) ' + msg)
const three = (msg) => console.log('3) ' + msg)
return {one, two, three}
})()
const browser = await puppeteer.launch().catch(err => `Browser not launched properly: ${err}`)
const page = await browser.newPage()
page.on('console', (msg) => console.log('PUPPETEER:', msg._text)); // Pipe puppeteer console to local console
await page.evaluate((foo)=>{
console.log('hello')
console.log(foo.one('world'))
},foo)
browser.close()
// Error: Evaluation failed: TypeError: foo.one is not a function
When I try to use page.exposeFunction I receive an error. This is to be expected because foo is an object.
page.exposeFunction('foo',foo)
// Error: Failed to add page binding with name foo: [object Object] is not a function or a module with a default export.
The control case, defining the function within the browser page, works as expected:
import puppeteer from 'puppeteer'
const browser = await puppeteer.launch().catch(err => `Browser not launched properly: ${err}`)
const page = await browser.newPage()
page.on('console', (msg) => console.log('PUPPETEER:', msg._text)); // Pipe puppeteer console to local console
await page.evaluate(()=>{
const bar = (()=>{
const one = (msg) => console.log('1) ' + msg)
const two = (msg) => console.log('2) ' + msg)
const three = (msg) => console.log('3) ' + msg)
return {one, two, three}
})()
console.log('hello')
console.log(bar.one('world'))
})
browser.close()
// PUPPETEER: hello
// PUPPETEER: 1) world
Adding a quick update after testing the below solutions given my use case
Reminder: I am trying to pass an externally defined utilities.js library to the browser so that it can conditionally interact with page data and navigate accordingly.
I'm open to any ideas or feedback!
Unfortunately, passing a node.js module of utility functions is very difficult in my situation. When the module contains export statements or objects, addScriptTag() fails.
I get Error: Evaluation failed: ReferenceError: {x} is not defined in this case. I created an intermediary function to remove the export statements. That is messy but it seemed to work. However, some of my functions are IIFE which return an object with methods. And objects are proving very hard to work with via addScriptTag(), to say the least.
I think for smaller projects the simplest and best option is to just re-declare the objects/functions in the puppeteer context. I hate redefining things but it works as expected.
As @ggorlen suggests, I was able to host the utilities function on another server. This can be sourced by both the node.js and puppeteer environments. I still had to import the library twice: once in the node.js environment and once in the browser context. But it's probably better in my case than redeclaring dozens of functions and objects.
It might be repetitive when calling, but you could iterate over the object and call page.exposeFunction for each.
page.exposeFunction('fooOne', foo.one);
// ...
or
for (const [fnName, fn] of Object.entries(foo)) {
page.exposeFunction(fnName, fn);
}
If the functions can all be executed in the context of the page, simply defining them inside a page.evaluate would work too.
page.evaluate(() => {
window.foo = (()=>{
const one = (msg) => console.log('1) ' + msg)
const two = (msg) => console.log('2) ' + msg)
const three = (msg) => console.log('3) ' + msg)
return {one, two, three}
})();
});
If you have to have only a single object containing the functions in the context of the page, you could first put an object on the window with page.evaluate, then in the main script, have an serial async loop over the keys and values of the object that:
page.exposeFunction('fnToMove' with the functionpage.evaluate which assigns fnToMove to the desired property on the object created earlierBut that's somewhat convoluted. I wouldn't recommend it unless you really need it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With