node.jsexpresspuppeteerpdf-generationejs

Puppeteer TimeoutError: Timed out after waiting 30000ms


I am trying to achieve a very simple pdf generation through Express JS, puppeteer and ejs. I have used the following code on multiple occasions and have achieved similar results.

const router = require('express').Router();
const puppeteer = require('puppeteer');
const {_generateHtml} = require("./utils");

router.post('/offer-letter', async (req, res) => {
    const browser = await puppeteer.launch({headless: true});
    const page = await browser.newPage();
    const html = await _generateHtml({
        doj: req.body.doj,
        name: req.body.name,
        role: req.body.role,
    }, 'offer-template.ejs');
    await page.setContent(html, {waitUntil: 'load', timeout: 0});
    const pdfBuffer = await page.pdf({
        printBackground: true,
    });

    res.set('Content-Type', 'application/pdf');
    res.send(pdfBuffer);

});

module.exports = router;

Here's the generateHtml function as well

const {compile} = require("ejs");
const fs = require("fs");

async function _generateHtml(data, templatePath) {
    const template = fs.readFileSync(templatePath, 'utf8');
    let html = compile(template);
    return html(data);
}

module.exports = {
    _generateHtml
}

Here's the folder structure

.env
index.js
node_modules
offer-letter-template-html.html
offer-template.ejs
package-lock.json
package.json
pdfRoutes.js
utils

However, I now find myself with an error message that's quite the headscratcher. How exactly do you work out this one?

            throw new Errors_js_1.TimeoutError(`Timed out after waiting ${ms}ms`, { cause });
                  ^

TimeoutError: Timed out after waiting 30000ms
    at C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\puppeteer\common\util.js:289:19
    at C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:3986:35
    at OperatorSubscriber2._this._next (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1055:13)
    at Subscriber2.next (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:678:16)
    at AsyncAction2.<anonymous> (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:4863:24)
    at AsyncAction2._execute (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1974:16)
    at AsyncAction2.execute (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1963:26)
    at AsyncScheduler2.flush (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:2235:30)
    at listOnTimeout (node:internal/timers:573:17)
    at process.processTimers (node:internal/timers:514:7) {
  [cause]: undefined
}

Node.js v20.12.1

Here's a sample of ejs file you can use

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
</head>
<body>

<div id="page-container">
    test
</div>


</body>
</html>

22nd May 2024 - Edit --------------------------------

This is the minimal failing code for everyone trying to replicate the issue.  

const puppeteer = require("puppeteer");

async function generatePDF() {
  const browser = await puppeteer.launch({headless: true, timeout:0});
  const page = await browser.newPage();
  await page.setContent("<h1>Hello World</h1>", { waitUntil: 'domcontentloaded' });
  console.log("Content set");
  const pdfBuffer = await page.pdf({ printBackground: true });
  console.log("PDF generated");
  await browser.close();
}

generatePDF().catch(console.error);

package.json for the project

{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "puppeteer": "^22.9.0"
  }
}

Solution

  • I'm unable to reproduce your problem. Here's my code that works just fine on Node 20 and Ubuntu 22.04.

    server.js:

    // Note: error handling is not included. I don't recommend this setup;
    // it's purely for reproduction and doesn't follow best practices.
    
    const fs = require("fs");
    const {compile} = require("ejs"); // ^3.1.10
    const express = require("express"); // ^4.19.2
    const puppeteer = require("puppeteer"); // ^22.7.1
    
    const app = express();
    
    async function _generateHtml(data, templatePath) {
      const template = fs.readFileSync(templatePath, "utf8");
      const html = compile(template);
      return html(data);
    }
    
    app.get("/", async (req, res) => {
      const browser = await puppeteer.launch({headless: true});
      const page = await browser.newPage();
      const html = await _generateHtml({}, "offer-template.ejs");
      await page.setContent(html);
      const pdfBuffer = await page.pdf({
        printBackground: true,
      });
      res.set("Content-Type", "application/pdf");
      res.send(pdfBuffer);
    
      // browser should be closed in a finally
      // block here if this were a real script.
    });
    
    app.listen(3000);
    

    offer-template.ejs:

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
    </head>
    <body>
    
    <div id="page-container">
        test
    </div>
    
    
    </body>
    </html>
    

    Run node server, visit your browser at http://localhost:3000 and you'll see the PDF correctly rendered. Since the POST data isn't used in the template (and templating should be irrelevant as far as a hang is concerned--it ultimately returns a string, which you could hardcode for minimal reproducibility), I removed that. Same for the exported module--it's irrelevant.

    Please provide more details and steps to reproduce the issue. There's nothing obviously wrong with the code you've shown, other than using timeout: 0, which stifles errors and can cause an infinite hang.