Puppeteer wait for all images to load then take screenshot

Javascriptnode.jsGoogle ChromeAutomationPuppeteer

Javascript Problem Overview


I am using Puppeteer to try to take a screenshot of a website after all images have loaded but can't get it to work.

Here is the code I've got so far, I am using https://www.digg.com as the example website:

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://www.digg.com/');

    await page.setViewport({width: 1640, height: 800});

    await page.evaluate(() => {
        return Promise.resolve(window.scrollTo(0,document.body.scrollHeight));
    });

    await page.waitFor(1000);

    await page.evaluate(() => {
        var images = document.querySelectorAll('img');

        function preLoad() {

            var promises = [];

            function loadImage(img) {
                return new Promise(function(resolve,reject) {
                    if (img.complete) {
                        resolve(img)
                    }
                    img.onload = function() {
                        resolve(img);
                    };
                    img.onerror = function(e) {
                        resolve(img);
                    };
                })
            }

            for (var i = 0; i < images.length; i++)
            {
                promises.push(loadImage(images[i]));
            }

            return Promise.all(promises);
        }

        return preLoad();
    });

    await page.screenshot({path: 'digg.png', fullPage: true});

    browser.close();
})();

Javascript Solutions


Solution 1 - Javascript

There is a built-in option for that:

await page.goto('https://www.digg.com/', {"waitUntil" : "networkidle0"});

> networkidle0 - consider navigation to be finished when there are no more than 0 network connections for at least 500 ms

> networkidle2 - consider navigation to be finished when there are no more than 2 network connections for at least 500 ms.

P.S. Of course it won't work if you're working with endless-scrolling-single-page-applications like Twitter.

Solution 2 - Javascript

Another option, actually evaluate to get callback when all images were loaded

This option will also work with setContent that doesn't support the wait networkidle0 option

await page.evaluate(async () => {
  const selectors = Array.from(document.querySelectorAll("img"));
  await Promise.all(selectors.map(img => {
    if (img.complete) return;
    return new Promise((resolve, reject) => {
      img.addEventListener('load', resolve);
      img.addEventListener('error', reject);
    });
  }));
})

Solution 3 - Javascript

Wait for Lazy Loading Images

You may want to consider scrolling down first using a method such as Element.scrollIntoView() to account for lazy loading images:

await page.goto('https://www.digg.com/', {
  waitUntil: 'networkidle0', // Wait for all non-lazy loaded images to load
});

await page.evaluate(async () => {
  // Scroll down to bottom of page to activate lazy loading images
  document.body.scrollIntoView(false);
  
  // Wait for all remaining lazy loading images to load
  await Promise.all(Array.from(document.getElementsByTagName('img'), image => {
    if (image.complete) {
      return;
    }
    
    return new Promise((resolve, reject) => {
      image.addEventListener('load', resolve);
      image.addEventListener('error', reject);
    });
  }));
});

Solution 4 - Javascript

I'm facing the exact same issue. I have a feeling the solution will involve using:

await page.setRequestInterceptionEnabled(true);

page.on('request', interceptedRequest => {
    //some code here that adds this request to ...
    //a list and checks whether all list items have ...
    //been successfully completed!
});

https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagesetrequestinterceptionenabledvalue

Solution 5 - Javascript

I found a solution which is applicable to multiple sites using the page.setViewPort(...) method as given below:

const puppeteer = require('puppeteer');

async(() => {
    const browser = await puppeteer.launch({
        headless: true, // Set to false while development
        defaultViewport: null,
        args: [
            '--no-sandbox',
            '--start-maximized', // Start in maximized state
        ],
    });

    const page = await = browser.newPage();
    await page.goto('https://www.digg.com/', {
        waitUntil: 'networkidle0', timeout: 0
    });

    // Get scroll width and height of the rendered page and set viewport
    const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
    const bodyHeight = await page.evaluate(() => document.body.scrollHeight);
    await page.setViewport({ width: bodyWidth, height: bodyHeight });

    await page.waitFor(1000);
    await page.screenshot({path: 'digg-example.png' });
})();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPetar VasilevView Question on Stackoverflow
Solution 1 - JavascriptVaviloffView Answer on Stackoverflow
Solution 2 - JavascriptDaniel KromView Answer on Stackoverflow
Solution 3 - JavascriptGrant MillerView Answer on Stackoverflow
Solution 4 - JavascriptWissaView Answer on Stackoverflow
Solution 5 - JavascriptAmitesh RaiView Answer on Stackoverflow