Why can't I access 'window' in an exposeFunction() function with Puppeteer?

▼魔方 西西 提交于 2019-12-21 04:34:10

问题


I have a very simple Puppeteer script that uses exposeFunction() to run something inside headless Chrome.

(async function(){

    var log = console.log.bind(console),
        puppeteer = require('puppeteer');


    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    var functionToInject = function(){
        return window.navigator.appName;
    }

    await page.exposeFunction('functionToInject', functionToInject);

    var data = await page.evaluate(async function(){
        console.log('woo I run inside a browser')
        return await functionToInject();
    });

    console.log(data);

    await browser.close();

})()

This fails with:

ReferenceError: window is not defined

Which refers to the injected function. How can I access window inside the headless Chrome?

I know I can do evaluate() instead, but this doesn't work with a function I pass dynamically:

(async function(){

    var log = console.log.bind(console),
        puppeteer = require('puppeteer');

    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    var data = await page.evaluate(async function(){
        console.log('woo I run inside a browser')
        return window.navigator.appName;
    });

    console.log(data);

    await browser.close();

})()

回答1:


evaluate the function

You can pass the dynamic script using evaluate.

(async function(){
    var puppeteer = require('puppeteer');
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    var functionToInject = function(){
        return window.navigator.appName;
    }

    var data = await page.evaluate(functionToInject); // <-- Just pass the function
    console.log(data); // outputs: Netscape

    await browser.close();
})()

addScriptTag and readFileSync

You can save the function to a seperate file and use the function using addScriptTag.

await page.addScriptTag({path: 'my-script.js'});

or evaluate with readFileSync.

await page.evaluate(fs.readFileSync(filePath, 'utf8'));

or, pass a parameterized funciton as a string to page.evaluate.

await page.evaluate(new Function('foo', 'console.log(foo);'), {foo: 'bar'});

Make a new function dynamicly

Do you need some other more way to deal with this? How about making it into a runnable function :D ?

function runnable(fn) {
  return new Function("arguments", `return ${fn.toString()}(arguments)`);
}

the above will create a new function with provided arguments. We can pass any function we want.

Such as the following function with window, along with arguments,

function functionToInject() {
  return window.location.href;
};

works flawlessly with promises too,

function functionToInject() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(window.location.href);
    }, 5000);
  });
}

and with arguments,

async function functionToInject(someargs) {
  return someargs; // {bar: 'foo'}
};

Call the desired function with evaluate,

var data = await page.evaluate(runnable(functionToInject), {bar: "foo"});
console.log(data); // shows the location



回答2:


exposeFunction() isn't the right tool for this job.

From the Puppeteer docs

page.exposeFunction(name, puppeteerFunction)

puppeteerFunction Callback function which will be called in Puppeteer's context.

'In puppeteer's context' is a little vague, but check out the docs for evaluate():

page.evaluateHandle(pageFunction, ...args)

pageFunction Function to be evaluated in the page context

exposeFunction() doesn't expose a function to run inside the page, but exposes a function to be be run in node to be called from the page.

I have to use evaluate():



来源:https://stackoverflow.com/questions/48281130/why-cant-i-access-window-in-an-exposefunction-function-with-puppeteer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!