The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →If Puppeteer does not create a PDF, first prove that Chromium launched, the page finished its own loading work, and the process can write the destination. Use an absolute path while diagnosing, wait for application data and fonts, then call page.pdf() before closing the browser. An undefined path returns PDF bytes instead of creating a file.
Start with a known-good PDF script
The smallest reliable sequence is: launch Chromium, create a page, navigate, wait for the page to be ready, print with page.pdf(), and close the browser. The Puppeteer PDF guide’s canonical instruction is: “For printing PDFs use Page.pdf().”
import puppeteer from 'puppeteer';
const url = 'https://example.com';
const output = '/tmp/output.pdf';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
// Replace this with your app's real readiness condition when needed.
await page.evaluate(() => document.fonts.ready);
await page.pdf({
path: output,
format: 'A4',
printBackground: true
});
console.log(`Wrote ${output}`);
} finally {
await browser.close();
}
Run this from a directory where Puppeteer and its compatible browser are installed. During diagnosis, keep /tmp/output.pdf as an absolute destination. Once it works, you can change the path to a directory your service owns.
Confirm what “does not generate a PDF” means
The call returns bytes but no file appears
Puppeteer only writes a disk file when the PDF options include path. If path is omitted or undefined, page.pdf() returns a buffer and does not create a file. Relative paths are resolved from the process’s current working directory, which may differ between a shell, a test runner, a container, and a service manager.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
const pdf = await page.pdf({ format: 'A4' });
await fs.promises.writeFile('/tmp/output.pdf', pdf);
For a direct file write, use an explicit absolute path:
await page.pdf({ path: '/tmp/output.pdf', format: 'A4' });
The file is written somewhere unexpected
Log both process.cwd() and the resolved destination, then inspect the parent directory’s permissions. A path such as reports/invoice.pdf is relative to the current working directory, not necessarily the directory containing your JavaScript file.
import path from 'node:path';
console.log({ cwd: process.cwd(), destination: path.resolve('reports/invoice.pdf') });
The file exists but is zero bytes or cannot be opened
Do not close the browser until the page.pdf() promise resolves. Check that the parent directory exists and is writable, and verify the process is not terminating early because an earlier navigation or selector promise rejected.
Make navigation and application readiness explicit
Choose a navigation condition that matches the site
waitUntil: 'networkidle2' is useful for pages that become quiet after loading, but it is not a substitute for an application-specific readiness check. Dashboards, streaming pages, analytics connections, and long polls may continue network activity even after the content you need is visible.
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready]', { timeout: 30000 });
If the page signals readiness through an API response, wait for that response or for a DOM marker your application sets after rendering. Increase a timeout only after identifying the operation that is legitimately slow.
Rank #2
Wait for fonts and late data
PDF generation waits for fonts by default. A web font that has not loaded, or data that is still being inserted when printing starts, can make output look different, incomplete, or appear to hang. Keep the default font wait when you need the final font, and explicitly await your application’s data readiness. You can also make the font state visible during debugging:
await page.waitForSelector('#invoice-total');
await page.evaluate(() => document.fonts.ready);
await page.pdf({ path: '/tmp/invoice.pdf', format: 'A4' });
Detect a failed page instead of printing an error screen
Check the HTTP response and capture browser console or page errors. A server-side error page can still produce a valid PDF, so navigation success alone does not prove that the intended document rendered.
page.on('console', message => console.log('[browser]', message.text()));
page.on('pageerror', error => console.error('[page error]', error));
const response = await page.goto(url, { waitUntil: 'networkidle2' });
if (!response || !response.ok()) {
throw new Error(`Navigation failed: ${response?.status() ?? 'no response'}`);
}
Separate Chromium launch failures from PDF failures
Run a minimal launch before debugging HTML, CSS, or PDF options. If this fails, page.pdf() is not the problem.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteimport puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
console.log(await browser.version());
await browser.close();
Missing Linux libraries or fonts
Minimal Debian or Ubuntu images often lack libraries required by Chrome for Testing. Puppeteer’s documented dependency set includes libatk-bridge2.0-0, libatk1.0-0, libcairo2, libgbm1, libnss3, libpango-1.0-0, libpangocairo-1.0-0, and suitable font packages. Install the packages in the image used at runtime, not just on your laptop.
When Chrome reports a shared-library error, inspect the executable with:
ldd /path/to/chrome | grep not
Any reported library marked “not found” must be installed or supplied by the runtime image. Missing fonts can also produce blank-looking text or unexpected line wrapping even when Chromium launches.
Sandbox and security policy errors
No usable sandbox! indicates an environment problem, usually user-namespace restrictions or an unsuitable container configuration. Configure a supported sandbox first. Puppeteer documents --no-sandbox only for trusted content and strongly discourages disabling the sandbox; it is not a general fix for production deployments.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →On Ubuntu, AppArmor profiles can prevent Chrome for Testing from using user namespaces. Review the host security policy and container privileges instead of immediately adding unsafe flags.
Profile and cache directories are not writable
Chrome writes profile, configuration, and cache data. Read-only containers commonly fail with permission errors after a successful local run. Point runtime directories and the browser profile at writable storage:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
userDataDir: '/tmp/.puppeteer-profile',
env: {
...process.env,
XDG_CONFIG_HOME: '/tmp/.chromium/config',
XDG_CACHE_HOME: '/tmp/.chromium/cache'
}
});
Create those directories during startup if your base image does not create them, and make sure the service account can write to them.
Rank #4
Control print media and page layout
Screen CSS versus print CSS
PDF output uses print media by default. If the design you need is the screen version, set the media type before printing:
await page.emulateMediaType('screen');
await page.pdf({
path: '/tmp/screen-styled.pdf',
format: 'A4',
printBackground: true
});
Backgrounds, CSS page size, and margins
printBackground: truepreserves background colors and images that print styles otherwise omit.preferCSSPageSize: truelets the document’s@pagerule take priority over theformatoption.- Explicit margins and paper settings prevent content from being clipped when a template assumes a particular page size.
await page.pdf({
path: '/tmp/report.pdf',
printBackground: true,
preferCSSPageSize: true,
margin: { top: '16mm', right: '14mm', bottom: '16mm', left: '14mm' }
});
If a document is unexpectedly paginated, inspect its @page rule, fixed-height containers, overflow settings, and print-only styles before changing browser launch flags.
Cloud Run, Lambda, and container deployment checks
Google Cloud Run
Cloud Run’s default Node runtime does not include all system packages needed by Headless Chrome, so use an image that installs them. Also perform PDF work before sending the HTTP response or enable CPU always: after a response, CPU is disabled by default, and starting Puppeteer afterward can become very slow.
Use writable locations such as /tmp for the PDF and Chromium profile. Keep the browser launch, navigation, and print inside the request’s active CPU window, and close the browser in a finally block.
AWS Lambda
Lambda deployment-package limits make bundling a full browser difficult. Use a Chromium packaging strategy compatible with your runtime, verify the executable path, and write temporary files under Lambda’s writable temporary directory. A local executable path that works on macOS or a development container will not automatically exist in Lambda.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Best Value
- Used Book in Good Condition
Any read-only or restricted container
Validate these items in the deployed image itself:
- Chromium launches under the service user.
- Required shared libraries and fonts are installed.
- The sandbox policy permits the chosen browser configuration.
/tmpor another configured directory is writable.- The browser profile and cache do not point to a read-only home directory.
A diagnostic workflow that avoids guesswork
- Prove launch: run
puppeteer.launch(), create a page, print the browser version, and close it. - Prove writing: use an absolute path such as
/tmp/output.pdfand test parent-directory permissions. - Prove navigation: log the response status and listen for page errors.
- Prove readiness: wait for the selector, API result, or application flag that means the document is complete.
- Prove fonts: await
document.fonts.readywhen typography matters. - Prove print settings: choose screen or print media, then set background and CSS-page-size behavior deliberately.
- Prove deployment: repeat the same checks in Cloud Run, Lambda, or the production container rather than relying on local success.
Common symptoms and precise fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| No file, but no exception | path is missing or relative to an unexpected working directory. |
Set an absolute path, or write the returned buffer yourself. |
ENOENT or permission denied when saving |
The parent directory does not exist or is not writable. | Create it or use a writable absolute destination such as /tmp/output.pdf. |
| PDF promise never finishes | Navigation, fonts, or application data never reaches readiness. | Use the right waitUntil, wait for a specific selector or response, and inspect page errors. |
| Blank or nearly blank PDF | Content is rendered after printing, a failed page was printed, or required fonts/resources are unavailable. | Wait for the rendered marker, check the response and console, and verify fonts and dependencies. |
Failed to launch the browser process |
Missing libraries, an invalid executable path, or an incompatible runtime image. | Run the minimal launch test, install dependencies, and inspect ldd output. |
No usable sandbox! |
Container or host security policy blocks the sandbox. | Configure a supported sandbox and review AppArmor/user-namespace policy; avoid disabling the sandbox for untrusted content. |
EACCES for profile or cache |
Chrome’s home, profile, or cache directory is read-only. | Set writable XDG directories and userDataDir under /tmp. |
| Screen colors or layout are missing | Print media rules are active or backgrounds are disabled. | Call emulateMediaType('screen') and set printBackground: true. |
| Works locally, slow on Cloud Run | CPU is unavailable after the response or the image lacks Chrome libraries. | Finish the job before responding or enable CPU always, and use an image with the required packages. |
| Works locally, fails on Lambda | The browser binary is too large, missing, or located elsewhere. | Use a Lambda-compatible Chromium package and verify the runtime executable path. |
Performance and reliability practices
Keep the critical path deterministic
Reuse one browser process for a batch when the runtime allows it, but create a fresh page per document and close each page in a finally block. Avoid arbitrary sleeps as the primary readiness mechanism; a selector, response, or application flag is both faster and more reliable.
Bound every external wait
Set navigation and selector timeouts that match your service’s request budget. Record which stage timed out—launch, navigation, readiness, fonts, or PDF writing—so retries address the failing stage instead of repeating an unknown failure.
Make output and retries safe
Write to a temporary filename, verify that the PDF promise completed, then move or rename the file to its final name. For retries, use a unique destination or clean up the previous temporary file so a failed attempt cannot be mistaken for a fresh document.
Or skip the browser setup
If your requirement is simply to capture a URL as an image or PDF, ScreenshotNeo provides a hosted endpoint and an MCP server instead of making you package Chromium. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsThe API base is https://api.screenshotneo.com/v1/shot. This one-call example follows the documented request format:
curl -G 'https://api.screenshotneo.com/v1/shot' -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
For Python:
import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://stripe.com'}, timeout=90)
r.raise_for_status()
open('shot.webp', 'wb').write(r.content)
For Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
See the ScreenshotNeo API documentation for PDF output and the other capture controls. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—let Claude, Cursor, or another MCP client perform captures. Every plan includes the features; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

