How to ensure Puppeteer waits for full page load before generating PDF?

I’m trying to create PDFs from a single-page web app using Puppeteer. The tricky part is waiting for the page to fully load, including charts and graphs that render after calculations.

Here’s what I’ve tried:

const browser = await puppeteer.launch({
  executablePath: '/path/to/chrome',
  headless: true,
  args: ['--no-sandbox']
});

const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });

await page.type('#user', 'admin');
await page.type('#pass', 'secret');
await page.click('#submit');

// This feels hacky:
await page.waitFor(2000);

await page.pdf({
  path: 'output.pdf',
  format: 'A4'
});

I don’t want to use fixed delays like waitFor(2000). And waitForSelector won’t work because the content I need isn’t tied to specific elements.

Any ideas on how to reliably wait for everything to load before generating the PDF? Thanks!

Having worked extensively with Puppeteer for similar tasks, I suggest a more robust solution. Instead of relying on arbitrary timeouts or brittle selectors, consider implementing a custom event in your web application that signals when all critical content has finished loading.

For instance, modify your application to dispatch a custom event (e.g., ‘appFullyLoaded’) when charts, graphs, and dynamic content are ready. In your Puppeteer script, wait for this event by evaluating a Promise that resolves once the event triggers. This approach avoids fixed delays and inflexible selectors, and although it requires a minor change in your application, it enhances the reliability and consistency of your PDF generation.

I’ve dealt with similar issues when generating PDFs from dynamic web apps. One approach that’s worked well for me is using a custom function to check for specific conditions that indicate the page is fully loaded.

Here’s a technique I’ve used:

const isPageLoaded = async (page) => {
  return await page.evaluate(() => {
    // Check for presence of key elements
    if (!document.querySelector('#main-chart')) return false;
    
    // Verify data has loaded
    if (document.querySelectorAll('.data-point').length < 10) return false;
    
    // Check if any spinners/loaders are still visible
    if (document.querySelector('.loading-spinner')) return false;
    
    return true;
  });
};

// In your main code
await page.goto(url, { waitUntil: 'networkidle2' });
await page.waitForFunction(isPageLoaded);

// Now proceed with PDF generation

This method lets you define custom logic based on your app’s specific behavior. It’s more reliable than fixed timeouts and more flexible than waiting for a single element. You might need to adjust the conditions based on your particular app, but this approach has served me well in production environments.

yo, i had a similar issue. try using page.waitForFunction() with a custom function that checks for specific stuff on ur page. like this:

await page.waitForFunction(() => {
return document.querySelector(‘#chart’) && document.querySelectorAll(‘.data-row’).length > 5;
});

this waits for ur chart and some data rows. adjust it for ur specific page elements. works better than fixed timeouts imo