How to ensure complete page rendering before PDF generation in Puppeteer

I’m trying to convert a web application into PDF format using Puppeteer. The app I’m dealing with is a single page application that has dynamic content.

I’ve looked at various solutions online but none seem to work for my case. Here’s what I’m currently using:

const chromeInstance = await puppeteer.launch({
    executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
    ignoreHTTPSErrors: true,
    headless: true,
    devtools: false,
    args: ['--no-sandbox', '--disable-setuid-sandbox']
});

const webPage = await chromeInstance.newPage();

await webPage.goto(targetUrl, {
    waitUntil: 'networkidle2'
});

await webPage.type('#user_input', 'john');
await webPage.type('#pass_input', 'secret');

await webPage.click('#submit_btn');
await webPage.waitFor(2000);

await webPage.pdf({
    path: pdfFile,
    displayHeaderFooter: true,
    headerTemplate: '',
    footerTemplate: '',
    printBackground: true,
    format: 'A4'
});

My goal is to create the PDF only when everything on the page has finished loading completely. I want to avoid using fixed delays like waitFor(2000) because they’re unreliable.

Using waitForSelector isn’t an option since my page contains charts and graphs that render dynamically after data processing is complete.

Any suggestions would be helpful.

Had the same problem with dynamic charts taking forever to render. Here’s what fixed it for me: inject a custom function that actually monitors the rendering state instead of just waiting for DOM elements. Use page.evaluate() to check if your charts library is done rendering. Most libraries have completion events or status flags you can tap into. With Chart.js, wait for the animation complete callback. D3 has its own completion indicators. The approach that really saved me was adding a custom flag in my app code that flips to true when all dynamic content finishes loading. Then use page.waitForFunction() to wait for that flag. Way more reliable than guessing with timeouts and works regardless of page load speed.

I’ve hit this same issue with PDFs from complex SPAs. Here’s what worked for me: combine multiple wait strategies instead of relying on one. After login, use page.waitForLoadState('networkidle') then page.waitForFunction() to check for specific conditions that show your content’s actually ready. For charts and dynamic stuff, I monitor the browser’s performance timeline with page.evaluate() to see if rendering has stabilized. You can also listen for events your app fires when data processing wraps up, or check if certain CSS classes/data attributes show up once rendering finishes. The trick is figuring out your app’s rendering lifecycle and building custom wait conditions around that - don’t just rely on generic page load events.

Try page.waitForFunction(() => document.readyState === 'complete' && window.performance.now() > 0) and check if your elements are visible. I add a custom script that sets window.appReady = true when charts finish loading, then wait for that with puppeteer. Way better than fixed delays and handles slow connections.