Pierre Olivier Tran
Pierre Olivier Tran

Reputation: 1875

How to wait for generated page to be fully loaded before taking screenshot?

I'm using puppeteer to convert some HTML to PNG using the screenshot method.

First, I fetch some SVG, then I create a page, and set the SVG as page content.


fetch(url)
  .then(data => data.text())
  .then((svgText) => {
    // res.set('Content-Type', 'text/html');
    const $ = cheerio.load(svgText)
    return $.html()
  })
  .then(async (html) => {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()
    await page.setContent(html)

    const file = await page.screenshot()
    res.set('Content-Type', 'image/png');
    await browser.close()
    res.send(file)
  })
  .catch((err) => {
    console.log(err);
    logger.log({
      level: 'error', message: 'GET /product', err
    })
  })
})

The problem is, texts in my SVG includes a specific font. This font is loaded using the @import CSS tag. If I set my method to return the HTML, the fonts are loaded, then, after a slight delay, they get applied to my texts. Unfortunately, when using the screenshot method, my texts are not styled anymore. I suppose it is because the screenshot is taken before the fonts are loaded and applied, therefore rendering a text with a fallback font.

Is there a way to make sure that the page is completely rendered before taking the screenshot ?

I tried using the page.on('load') event listener, but this doesn't change anything the script just runs forever.

Upvotes: 7

Views: 2434

Answers (1)

Edi Imanto
Edi Imanto

Reputation: 2509

Of course you can waitForNetworkIdle(), but I'm pretty sure this method won't work for detecting font loading properly or not. You can definitely check all request for the page using page.on('request'), and filter this font using resourceType as font.

But still you'll have to delay some more time, as there's can be some FOUC (Flash Of Unloaded Content), if the font file is big enough (large TTF sometimes may failed to be loaded into the browser). You can set the time to proper timing, as the font loaded properly. So test this script before use for production/testing.

And if you're not sure what type of font will be loaded by the browser itself, you can filter page request using fonts URL directly, like EOT, WOFF, WOFF2, SVG, TTF, OTF, etc.

Then after font file successfully downloaded, don't forget to wait some time, you may use your own delay function. In this example, I've put a variable named fontSuccessLoaded as a boolean for marking the timing, fontDownloadFailed as boolean for marking download failed and a delayFor function for waiting purpose.

// Method for waiting delay timing
async delayFor(time) {
    return new Promise(function(resolve) {
        setTimeout(resolve, time)
    })
}

fetch(url)
  .then(data => data.text())
  .then((svgText) => {
    // res.set('Content-Type', 'text/html');
    const $ = cheerio.load(svgText)
    return $.html()
  })
  .then(async (html) => {
    let fontSuccessLoaded = 0
    let fontDownloadFailed = 0
    const browser = await puppeteer.launch()
    const page = await browser.newPage()
    const pathsUrl = 'https://fonts.gstatic.com/' // or any URLs
    const fontsExt = ['eot', 'otf', 'ttf', 'svg', 'woff', 'woff2']
    page.setRequestInterception(true)
    page.on('request', async (request) => {

        // Filter using resource type
        if (request.resourceType() === 'font') {
            console.log('Font starting to be requested')
        }
 
        // Filter using fonts URL directly
        if (request.url().search(pathsUrl) > -1) {
            fontsExt.forEach(extension => {
                if (request.url().toLowerCase().search(extension) > -1) {
                    console.log('Font type requested:', extension)
                }
            })
        }
        // You'll have to use this continue,
        // or all if your page requests will be blocked
        await request.continue()
    })
    page.on('response', async (response) => {
        if (response.request().resourceType() === 'font') {
            console.log('Font download starting')
        }
    })
    page.on('requestfinished', async (request) => {
        if (request.resourceType() === 'font') {
            console.log('Font download finished')
            await delayFor(1000)
            fontSuccessLoaded = 1
        }
    })
    page.on('requestfailed', async (request) => {
        if (request.resourceType() === 'font') {
            console.log('Font request failed')
            fontDownloadFailed = 1
        }
    })

    await page.setContent(html)

    // This will delay the screenshot process
    // before font file download and loaded
    // and not failed to download (aborted)
    while (!fontSuccessLoaded && !fontDownloadFailed) {
        await delayFor(500)
    }

    const file = await page.screenshot()
    res.set('Content-Type', 'image/png');
    await browser.close()
    res.send(file)
  })
  .catch((err) => {
    console.log(err);
    logger.log({
      level: 'error', message: 'GET /product', err
    })
  })
})

Then this can be shown in console.log like below:

Font starting to be requested
Font type requested: woff
Font download starting
Font download finished

Upvotes: 0

Related Questions