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
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 2060msThree 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
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 1552msA 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:
node -e "const s=require('fs').readFileSync('report.pdf','latin1'); console.log(((s.match(/\/Type *\/Page[^s]/g)||[]).length) + ' pages')"2 pagesIf 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:
{
"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.