PaperPony

Content drawn by JavaScript missing from a PDF

A chart is on the page in the browser and is a blank space in the PDF. The answer given almost everywhere is a wait strategy, usually networkidle. It is a real answer to one half of this and blind to the other, and when it is blind it reports success.

Measured on Chromium 151.0.7922.34, printing a document whose script adds enough content to need a second page, so the question is a page count rather than a judgement about what a picture looks like.

networkidle waits for the network, not for your code

Measured
content added by a script, in a document that then needs two pages

  written by     after    waitUntil       in the pdf  the wait took

  a timer        200ms    networkidle     2 pages       545ms
  a timer        1500ms   networkidle     1 page        538ms
  a fetch        200ms    networkidle     2 pages       770ms
  a fetch        1500ms   networkidle     2 pages      2060ms

Three of those four are fine, and the second line is the whole problem. The content arrived 1500 ms in, on a timer, with nothing on the network to wait for. The wait returned at 538 ms, reported that the page was ready, and printed one page.

Nothing failed and nothing was slow. The request the strategy is named after was never made, so the network was idle from the beginning and the wait ended as soon as its own quiet period elapsed.

When the network is what the content waits on, it works, and the last line shows it working properly: 2060 ms of waiting for a response due at 1500 ms, and the content in the file.

So which documents does it miss

Everything whose last step is not a response arriving. A chart library that animates in. A layout that measures itself and reflows. A font that triggers a redraw. Code behind a setTimeout, which is most libraries’ way of waiting for the browser to be free.

All of these share one property: the network went quiet long before the page was finished, and every strategy that watches the network is watching the wrong thing. load and domcontentloaded are earlier still.

Waiting for the content instead

Measured
the same four, waiting for the content instead

  written by     after    strategy            in the pdf  took

  a timer        1500ms   load + 3s fixed     2 pages     3039ms
  a timer        1500ms   load + a condition  2 pages     1553ms
  a fetch        1500ms   load + 3s fixed     2 pages     3054ms
  a fetch        1500ms   load + a condition  2 pages     1552ms

A fixed delay catches everything and costs the delay every time, including on the documents that were ready in 200 ms. Waiting for a condition catches the same cases and returns when the work is done: 1553 ms against 3039 ms for the same document.

If your renderer can wait on a selector or an expression, that is the setting to reach for. If it cannot, a delay is the honest fallback, and the number to pick is the slowest your document has ever been rather than the usual one.

The other direction is worth naming because it removes the question rather than answering it: if the chart can be drawn on the server and sent as markup or an image, nothing has to be waited for at all.

Checking it on your own file

If the missing content changes the length of the document, the page count settles it without opening anything:

Counts pages
node -e "const s=require('fs').readFileSync('report.pdf','latin1'); console.log(((s.match(/\/Type *\/Page[^s]/g)||[]).length) + ' pages')"
With the content present
2 pages

If it does not change the length, print the document twice with the wait raised the second time. Two files of different sizes mean the first was printed early.

How we handle this

PaperPony takes wait_for with the three states above, and the default is the one measured here:

The default
{
  "options": {
    "wait_for": "networkidle"
  }
}

Which means our default carries the same blind spot: a document whose last step is a timer rather than a response can print early, and the render will report success because as far as the renderer is concerned it did wait. We do not offer a wait on a selector today. If your document has that shape, the reliable options are to draw it before you send it, or to send it already finished as markup.

On the free plan the question does not arise, because scripts do not run at all there. That is a different reason for the same empty space, and the job says so in your dashboard rather than leaving you to guess. The plans say which is which.