269

I'm trying to pass a variable into a page.evaluate() function in Puppeteer, but when I use the following very simplified example, the variable evalVar is undefined.

I can't find any examples to build on, so I need help passing that variable into the page.evaluate() function so I can use it inside.

const puppeteer = require('puppeteer');

(async() => {

  const browser = await puppeteer.launch({headless: false});
  const page = await browser.newPage();

  const evalVar = 'WHUT??';

  try {

    await page.goto('https://www.google.com.au');
    await page.waitForSelector('#fbar');
    const links = await page.evaluate((evalVar) => {

      console.log('evalVar:', evalVar); // appears undefined

      const urls = [];
      hrefs = document.querySelectorAll('#fbar #fsl a');
      hrefs.forEach(function(el) {
        urls.push(el.href);
      });
      return urls;
    })
    console.log('links:', links);

  } catch (err) {

    console.log('ERR:', err.message);

  } finally {

    // browser.close();

  }

})();
starball
  • 20,030
  • 7
  • 43
  • 238
Cat Burston
  • 2,833
  • 2
  • 12
  • 10

7 Answers7

421

You have to pass the variable as an argument to the pageFunction like this:

const links = await page.evaluate((evalVar) => {

  console.log(evalVar); // 2. should be defined now
  …

}, evalVar); // 1. pass variable as an argument

You can pass in multiple variables by passing more arguments to page.evaluate():

await page.evaluate((a, b c) => { console.log(a, b, c) }, a, b, c)

The arguments must either be serializable as JSON or JSHandles of in-browser objects: https://pptr.dev/#?show=api-pageevaluatepagefunction-args

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
flozia
  • 4,288
  • 1
  • 11
  • 8
  • 10
    Hello, how would you pass multiple variables? – chitzui Nov 15 '17 at 09:58
  • 6
    Also, I’m not really able to pass a function: var myFunction = function() { console.log("hello") }; await page.evaluate(func => func(), myFunction); gives me: `Evaluation failed: TypeError: func is not a function`.. Why? – chitzui Nov 15 '17 at 10:03
  • 2
    Don't forget to type `evalVar` both in the function argument signature, and as a passed argument to `evaluate` (at the end of the code example). – Flimm Nov 07 '19 at 15:20
  • 3
    @chitzui: You can't pass a function to `pate.evaluate()`. You can supposedly 'expose' it with `page.exposeFunction`. For more, see https://stackoverflow.com/a/58040978. – knod Jan 28 '20 at 20:58
  • since this has the highest upvotes, has anyone experienced linting errors with this? specifically with the parameter already declared in the upper scope. aside from this error, this does work. – Mix Master Mike Jul 15 '20 at 22:46
  • 1
    Late to the party, but @chitzui, `evalVar` can be an array, so you can pass there multiple values – Dominik Matis Jun 05 '21 at 19:26
  • 1
    @DominikMatis No need for an array. You can just add more commas to the end of the callback parameter list and the `evaluate` function, e.g. `page.evaluate((a, b, c) => ... , a, b, c)`. See [this answer](https://stackoverflow.com/questions/46088351/how-can-i-pass-variable-into-an-evaluate-function/52012664#52012664). – ggorlen Jun 16 '21 at 02:20
  • I am using soanrqube for static code analysis and when I use page.evaluate() and pass the variable described as above. I get this code smell - "'variable' is already declared in the upper scope." – nim118 Feb 02 '22 at 12:33
93

I encourage you to stick on this style, because it's more convenient and readable.

let name = 'jack';
let age  = 33;
let location = 'Berlin/Germany';

await page.evaluate(({name, age, location}) => {

    console.log(name);
    console.log(age);
    console.log(location);

},{name, age, location});
Mehdi Raash
  • 8,721
  • 2
  • 29
  • 42
  • 7
    It's not clear to me that this is any more convenient or readable than passing the vars directly as shown [here](https://stackoverflow.com/a/52012664/6243352). This just allocates and destructures an object, increasing garbage collection activity and adding more braces. It's not a big deal but not much of an improvement, either, when the primary issue is that OP isn't passing parameters at all. – ggorlen Feb 08 '21 at 18:28
  • 4
    Disagree, allocating and destruction of array is a big deal in this case, however VM optimizes it. The plus point of this style is that you don't care about the sequence of the variable insertion. so you can retrieve the variables(properties) as you wish. – Mehdi Raash Feb 09 '21 at 22:06
  • * of objects is not a big deal – Mehdi Raash Feb 09 '21 at 22:15
  • 2
    I hadn't thought of the reordering, but that seems like a rather pointless characteristic--it strikes me as less intuitive to call a function like `fn(({baz, foo, quux}) => ..., {foo, quux, baz})`. If I really wanted to change the ordering, I'd just do that in two places so the code reads consistently. Again, all of this is pretty minor but that's the point--the answer makes it seem like it's a big win and doesn't really explain that you could just as easily use multiple params, or that the last param is where the context args are passed into the callback, which is OP's fundamental problem. – ggorlen Feb 09 '21 at 22:51
59

Single Variable:

You can pass one variable to page.evaluate() using the following syntax:

await page.evaluate(example => { /* ... */ }, example);

Note: You do not need to enclose the variable in (), unless you are going to be passing multiple variables.

Multiple Variables:

You can pass multiple variables to page.evaluate() using the following syntax:

await page.evaluate((example_1, example_2) => { /* ... */ }, example_1, example_2);

Note: Enclosing your variables within {} is not necessary.

Community
  • 1
  • 1
Grant Miller
  • 27,532
  • 16
  • 147
  • 165
22

It took me quite a while to figure out that console.log() in evaluate() can't show in node console.

Ref: https://github.com/GoogleChrome/puppeteer/issues/1944

everything that is run inside the page.evaluate function is done in the context of the browser page. The script is running in the browser not in node.js so if you log it will show in the browsers console which if you are running headless you will not see. You also can't set a node breakpoint inside the function.

Hope this can help.

harrrrrrry
  • 13,643
  • 2
  • 23
  • 28
  • If you add a listener, you can show browser logs in the Node console. See [this](https://stackoverflow.com/questions/58089425/how-do-print-the-console-output-of-the-page-in-puppeter-as-it-would-appear-in-th). – ggorlen Apr 01 '23 at 02:52
9

For pass a function, there are two ways you can do it.

// 1. Defined in evaluationContext
await page.evaluate(() => {
  window.yourFunc = function() {...};
});
const links = await page.evaluate(() => {
  const func = window.yourFunc;
  func();
});


// 2. Transform function to serializable(string). (Function can not be serialized)
const yourFunc = function() {...};
const obj = {
  func: yourFunc.toString()
};
const otherObj = {
  foo: 'bar'
};
const links = await page.evaluate((obj, aObj) => {
   const funStr = obj.func;
   const func = new Function(`return ${funStr}.apply(null, arguments)`)
   func();

   const foo = aObj.foo; // bar, for object
   window.foo = foo;
   debugger;
}, obj, otherObj);

You can add devtools: true to the launch options for test

wolf
  • 93
  • 1
  • 5
4

I have a typescript example that could help someone new in typescript.

const hyperlinks: string [] = await page.evaluate((url: string, regex: RegExp, querySelect: string) => {
.........
}, url, regex, querySelect);
0

Slightly different version from @wolf answer above. Make code much more reusable between different context.

// util functions
export const pipe = (...fns) => initialVal => fns.reduce((acc, fn) => fn(acc), initialVal)
export const pluck = key => obj => obj[key] || null
export const map = fn => item => fn(item)
// these variables will be cast to string, look below at fn.toString()
const updatedAt = await page.evaluate(
  ([selector, util]) => {
    let { pipe, map, pluck } = util
    pipe = new Function(`return ${pipe}`)()
    map = new Function(`return ${map}`)()
    pluck = new Function(`return ${pluck}`)()

    return pipe(
      s => document.querySelector(s),
      pluck('textContent'),
      map(text => text.trim()),
      map(date => Date.parse(date)),
      map(timeStamp => Promise.resolve(timeStamp))
    )(selector)
  },
  [
    '#table-announcements tbody td:nth-child(2) .d-none',
    { pipe: pipe.toString(), map: map.toString(), pluck: pluck.toString() },
  ]
)

Also not that functions inside pipe cant used something like this

// incorrect, which is i don't know why
pipe(document.querySelector) 

// should be 
pipe(s => document.querySelector(s))
Azriz
  • 3,122
  • 2
  • 12
  • 13