32

I'm trying to inject jQuery into my Puppeteer page because document.querySelector doesn't cut it for me:

async function inject_jquery(page){
  await page.evaluate(() => {
    var jq = document.createElement("script")
    jq.src = "https://code.jquery.com/jquery-3.2.1.min.js"
    document.querySelector("head").appendChild(jq)
  })
  const watchDog = page.waitForFunction('window.jQuery !== undefined');
  await watchDog;
}

The result is it mostly times out. Does anyone have a solution?

Grant Miller
  • 27,532
  • 16
  • 147
  • 165
pguardiario
  • 53,827
  • 19
  • 119
  • 159
  • did you try `document.getElementsByTagName('head')[0].appendChild(jq)` ? Also, since you are setting `src`, listening to the `load` event of the script element and then returning might be more robust. – TDreama Oct 28 '17 at 08:56

10 Answers10

53

I have used page.addScriptTag to inject js files.

...
await page.addScriptTag({url: 'https://code.jquery.com/jquery-3.2.1.min.js'})
...

page.addScriptTag - documentation

Working example using puppeteer: 0.12.0

import { launch } from 'puppeteer'
(async () => {
    const browser = await launch({headless: false});
    const page = await browser.newPage();
    await page.goto('https://example.com', {waitUntil: 'networkidle'});
    await page.addScriptTag({url: 'https://code.jquery.com/jquery-3.2.1.min.js'});
    await page.close();
    await browser.close();
})();
nilobarp
  • 3,806
  • 2
  • 29
  • 37
  • This also gives me: Unhandled Rejection at: Promise Promise { Error: Protocol error (Runtime.callFunctionOn): Execution context was destroyed. undefined – pguardiario Oct 28 '17 at 09:40
  • Nevermind, I needed to add a `await page.waitForNavigation()` before that part. This is the best solution. – pguardiario Oct 28 '17 at 09:49
25

I am doing this:

await page.addScriptTag({ url: 'https://code.jquery.com/jquery-3.2.1.min.js' });
const title = await page.evaluate(() => {
  const $ = window.$; //otherwise the transpiler will rename it and won't work
  return $('h1 > span').text();
});
Tudor Morar
  • 3,720
  • 2
  • 27
  • 25
25

For those looking to inject a local copy of jQuery:

await page.addScriptTag({path: require.resolve('jquery')})

subeebot
  • 271
  • 2
  • 2
6

Some sites won't allow you to inject a script tag, so you'll have to inject the contents of it before it'll allow you to do so. If that's the case you can use the evaluate method to grab the scripts contents from a CDN and inject them manually:

const jquery = await page.evaluate(() => window.fetch('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js').then((res) => res.text()));
await page.goto(YOUR_PAGE_HERE);
await page.evaluate(jquery);

This is used in scraping puppeteer's docs for browserless here (I'm the author of this tool) if you'd like to see an example in the wild.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
browserless
  • 2,090
  • 16
  • 16
4

This works for me.

async function inject_jquery(page){
      await page.evaluate(() => {
        var jq = document.createElement("script")
        jq.setAttribute('type','text/javascript');
        jq.src = "https://code.jquery.com/jquery-3.2.1.min.js"
        return new Promise( (resolve) => {
            jq.addEventListener("load", ()=> {
                resolve();
            });
            document.getElementsByTagName("head")[0].appendChild(jq);
        });
      })
      const watchDog = page.waitForFunction('window.jQuery !== undefined');
      await watchDog;
    }
TDreama
  • 420
  • 3
  • 8
  • This gives me: Unhandled Rejection at: Promise Promise { Error: Protocol error (Runtime.callFunctionOn): Execution context was destroyed. undefined – pguardiario Oct 28 '17 at 09:39
  • 1
    Is your page object still valid when invoking `inject_jquery` ?. Can you sharing the invoking code ? Atleast for academic reasons, this answer might explain why your script was failing. I believe the solution by nilobarp is more simpler – TDreama Oct 28 '17 at 09:42
3

The best solution found so far, URL insertion may not work due to browser origin policies, so we insert local file contents instead of URL.

const fs = require('fs');
const path = require('path');

const addJQueryToPage = async (page) => {
    const file = fs.readFileSync(path.resolve(__dirname, 'jquery-file-in-same-directory.min.js'), 'utf8');
    await page.addScriptTag({ content: file });
    await page.evaluate(_ => {
        $.noConflict();
    });
}
Tim Kozak
  • 4,026
  • 39
  • 44
1

To inject jQuery from CDN (inspired by @browserless answer above):

// go to page
await page.goto(url_str);
        
// inject jQuery
var jquery_ev_fn = await page.evaluate(function(){
    return window.fetch('https://code.jquery.com/jquery-3.4.1.min.js').then(function(res){
        return res.text();
    });
});
await page.evaluate(jquery_ev_fn);

To inject local jQuery:

// get local jQuery and inject it
var jquery_code_str = fs.readFileSync('/path/to/local/jquery.js', 'utf8');

// go to page
await page.goto(url_str);
        
// inject jQuery
var jquery_ev_fn = await page.evaluate(function(code_str){
    return code_str;
}, jquery_code_str);
await page.evaluate(jquery_ev_fn);
andromeda
  • 4,433
  • 5
  • 32
  • 42
ObiHill
  • 11,448
  • 20
  • 86
  • 135
1

I had just publish my puppeteer jquery integration here

code sample:

let browser = await launch({headless: true});
let pageOrg = await browser.newPage();
let page = pageExtand(pageOrg);
// append a <H1>
await page.jQuery('body').append(`<h1>Title</h1>`);
// get the H1 value
let title = await page.jQuery('h1').text();
// chain calls
let text = await page.jQuery('body button:last')
          .closest('div')
          .find('h3')
          .css('color', 'yellow')
          .parent()
          .find(':last')
          .text();

Not all jQuery function are mapped yet, so open issue if you need some more function (Each call need to be add by name and with the number of parameter they take).

Uriel
  • 121
  • 1
  • 1
  • 8
0

It becomes more easier to manage if you inject your script into the your html page header if you have got like this

<script type="text/javascript" src="abc.min.js"></script>

and now you can easily call its function in your page.evaluate(function(){ })

Ahsam Aslam
  • 151
  • 9
0

You can use the following method to add jQuery to the page using page.evaluate():

await page.evaluate(async () => {
  const script = document.createElement('script');
  script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js';
  const promise = new Promise((resolve, reject) => {
    script.onload = resolve;
    script.onerror = reject;
  });
  document.head.appendChild(script);
  await promise;
});
Grant Miller
  • 27,532
  • 16
  • 147
  • 165