Puppeteer Screenshot Fails with ENOENT: Check the Output Directory
Puppeteer resolves relative screenshot paths from the process working directory. Find the missing path component and fix it by creating the output directory before capture.
If Puppeteer’s page.screenshot() fails with ENOENT, check the destination path and create its parent directory before saving. A relative screenshot path is resolved from Node.js’s current working directory, process.cwd(); it is not automatically relative to the JavaScript file. If any directory component in the resolved path is missing, the filesystem can report ENOENT. See Puppeteer’s ScreenshotOptions documentation and Node.js’s error reference.
1. What ENOENT means for a Puppeteer screenshot
ENOENT means “no such file or directory”: a filesystem operation could not find a path component it needed. For example, saving to screenshots/run/page.png requires the screenshots/run directory to exist under the working directory. Puppeteer documents the screenshot path as the output filename and says relative paths resolve from the current working directory. If path is omitted, the screenshot is returned as data and is not saved to disk. The screenshot guide shows page.screenshot() and element screenshots, but does not say it creates missing parent folders automatically. Puppeteer screenshot guide.
The failure is about locating or writing the output path. Diagnose it separately from browser launch, browser installation, and page loading. An ENOENT from the screenshot write does not, by itself, indicate a browser executable problem.
2. Diagnose the path before changing browser settings
- Log
process.cwd()from the same process that runs Puppeteer. - Resolve the screenshot destination to an absolute path and print it.
- Check that the parent directory exists, including each nested component.
- Check spelling, capitalization, path separators, and whether the process is running from the project directory you expected.
- If the error persists, inspect
error.pathin the complete error object. Node.js documents this as the relevant pathname when present.
These checks are especially useful in test runners, containers, scheduled jobs, and deployment environments, where the process working directory may differ from the directory containing the script.
3. Fix: create the parent directory before capture
This complete example uses Puppeteer with Node.js ES modules. Install Puppeteer in your project with npm install puppeteer, save the code as screenshot.mjs, then run node screenshot.mjs. It creates nested output directories recursively, logs the resolved path, captures the page, and closes the browser even if capture fails.
import puppeteer from 'puppeteer';
import { mkdir } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
const url = process.argv[2] ?? 'https://example.com';
const outputPath = resolve(process.cwd(), 'screenshots', 'run', 'page.png');
console.log('Working directory:', process.cwd());
console.log('Screenshot destination:', outputPath);
await mkdir(dirname(outputPath), { recursive: true });
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
await page.screenshot({ path: outputPath, fullPage: true });
console.log('Screenshot saved:', outputPath);
} finally {
await browser.close();
}
mkdir(..., { recursive: true }) creates the missing parent directories and does not fail just because the directory already exists. Creating the directory before launching the browser can also make path mistakes fail early in a larger workflow. The snippet is a practical pattern based on the documented path behavior; adapt URL, output name, and capture options to your application.
Choose a path strategy
| Approach | Example | Good fit | Watch for |
|---|---|---|---|
| Resolve from a known working directory | resolve(process.cwd(), 'screenshots', 'page.png') |
Applications that deliberately set their working directory at startup. | The working directory can vary between local runs, test runners, and deployment. |
| Resolve from the script location | resolve(import.meta.dirname, 'screenshots', 'page.png') |
When output should follow the script’s location in supported Node.js versions. | Confirm the Node.js version and module format you run; still create the parent directory. |
| Use a configured absolute destination | resolve(configuredOutputDirectory, 'page.png') |
Deployments with an explicitly configured writable output folder. | Ensure the configured directory exists and is writable in that environment. |
The first option makes the current working directory explicit; the others can make the destination more predictable for a particular deployment. Puppeteer’s documented rule is that relative paths use the process working directory. Choose the base intentionally and log the resulting absolute path.
4. Screenshot path and capture options
The path option controls where Puppeteer writes the screenshot. The file extension is used to infer the image format. If you need to keep the screenshot in memory instead, omit path and handle the returned bytes; this avoids a disk destination for that operation.
| Option | Use | ENOENT relevance |
|---|---|---|
path |
Save to a file. Relative paths use the current working directory; the extension determines the format. | Primary option to inspect. Ensure the parent directory exists. |
No path |
Receive screenshot bytes instead of writing a file. | Avoids writing the screenshot to a path; your later file-writing code must still use a valid destination. |
fullPage |
Capture beyond the viewport. | Does not create an output directory. |
type, quality |
Select an image type or lossy-image quality where supported; quality does not apply to PNG. | Does not repair a missing path component. |
clip, captureBeyondViewport |
Capture a selected region or area beyond the viewport. | Does not change how the output path is resolved. |
omitBackground |
Hide the default white background to allow transparency. | Unrelated to directory creation. |
encoding |
Choose binary or base64 output representation. | Does not change the destination directory. |
See the complete, version-specific Puppeteer ScreenshotOptions reference for available options. Element screenshots use the same screenshot options; the screenshot guide demonstrates ElementHandle.screenshot().
5. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
ENOENT mentions a nested output path |
A parent folder such as screenshots/run does not exist under the resolved base. |
Call mkdir(dirname(outputPath), { recursive: true }) before screenshot writing. |
| The path exists locally but fails in deployment | The deployed process has a different working directory, or the expected folder was not included or created. | Log process.cwd() and the absolute destination in that environment; create the directory at runtime. |
| The screenshot appears in an unexpected folder | A relative path was resolved from a different current working directory than expected. | Resolve from a deliberate base and log the full output path. |
| The screenshot returns but no file appears | path may be omitted, so Puppeteer returns screenshot data without saving it. |
Pass a destination in path, or explicitly write the returned bytes yourself. |
ENOTDIR instead of ENOENT |
A path component exists but is a file where a directory is expected. | Inspect each component of the destination and choose a valid directory path. |
EACCES or another permission error |
The process cannot write to the destination. This is different from a missing directory. | Use a directory writable by the process and check the runtime’s filesystem permissions. |
| Browser launch fails before capture | This is a separate browser setup or launch failure, not evidence that the screenshot output directory is missing. | Read the launch error and diagnose browser setup independently; do not treat changing launch flags as the ENOENT path fix. |
Node.js distinguishes ENOENT (a path component cannot be found) from ENOTDIR (a component is not a directory) and permission errors. The precise error and its path help identify which operation failed. Node.js error documentation.
6. Reliability, performance, and cost considerations
- Reliability: Create the directory before capture and use an absolute resolved path in logs. In jobs that can overlap, give each run a distinct filename to avoid multiple captures writing to the same destination.
- Deployment: Verify the destination is available and writable in the actual runtime. A local project folder may not be the deployment process’s working directory.
- Performance: Directory creation is small filesystem work compared with navigating and rendering a page, but no benchmark is implied here. For many captures, create a shared destination once during startup or use recursive creation safely before each capture.
- Storage: Full-page images can take more disk space than viewport captures. Pick output formats and retention practices appropriate to your workflow; Puppeteer infers format from the extension when using
path. - Cost: Puppeteer itself is a browser automation approach, so the relevant operational costs depend on where and how you run the browser and store files. No fixed price or runtime benchmark follows from the cited documentation.
7. Or skip the browser setup
If you want a screenshot without installing and managing a browser, ScreenshotNeo offers a website screenshot API and MCP server. One GET request can return a PNG, JPEG, WebP, or PDF. The API call below saves the response body as an image; see the ScreenshotNeo API documentation for options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
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(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Before a capture, ScreenshotNeo accepts cookie or consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up free for 1,000 screenshots a month, with no card required.
8. Frequently asked questions
Is ENOENT caused by the page URL being unavailable?
Not usually when the error is from writing the screenshot path. Check the full error and error.path to identify the failing filesystem operation; a navigation failure has a different context.
Does Puppeteer create the screenshot folder automatically?
The cited screenshot documentation describes the output path and capture methods, but does not say that missing parent directories are created. Create them explicitly before saving.
Can I save the screenshot next to my script?
Yes. Build the destination from the script’s directory using a path API appropriate to your Node.js version and module system, then create its parent directory. A relative Puppeteer path by itself is based on process.cwd().
Why is the screenshot not saved when there is no error?
Puppeteer returns screenshot data when path is not provided; it does not write a file in that case. Pass a path or write the returned bytes yourself.


