ScreenshotNeo

BlogHow-to

Puppeteer Screenshot on Windows with a Custom Chrome User Data Directory

Set Puppeteer’s userDataDir on Windows, save a screenshot, and fix profile, Chrome installation, and launch errors.

By the ScreenshotNeo team4 October 20269 min read

Set Puppeteer’s userDataDir launch option to a writable Windows directory, navigate to a page, and call page.screenshot(). Use a dedicated directory for automation: Chrome locks a user data directory while it is in use, so sharing it with a separately running Chrome process can prevent Puppeteer from launching.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    userDataDir: 'C:\\Users\\YourName\\AppData\\Local\\Puppeteer\\ChromeProfile',
  });

  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });
    await page.screenshot({ path: 'capture.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Install Puppeteer in your project with npm install puppeteer, save the script as screenshot.js, and run node screenshot.js. The example path is illustrative: replace YourName and choose a directory that the Windows account running Node.js can write to. See the Puppeteer LaunchOptions API and Screenshots guide.

1. Understand the user data directory and profile

userDataDir points Puppeteer to the Chrome user data directory root: the directory where browser user data is stored. It is not a Chrome executable path, and it is not a setting for choosing the screenshot output folder. Chromium documents the equivalent command-line option as --user-data-dir, including a Windows example. Puppeteer exposes the setting directly in launch().

For a reliable automation workflow, give Puppeteer its own directory. Chrome processes use a lock associated with the profile data, so do not point automation at a directory that a regular Chrome session is using at the same time. If you need a particular account state, configure that state in a dedicated automation profile rather than assuming the browser can safely share a live profile.

2. Set up Puppeteer on Windows

  1. Install a current Node.js version and create or open a project directory.
  2. Install Puppeteer with npm install puppeteer. The standard Puppeteer package downloads a compatible Chrome for Testing browser.
  3. Create a dedicated user data directory, or let Chrome create it when Puppeteer launches. Ensure its parent directory exists and the account running the script can write there.
  4. Save the script below and run it from the project directory. The screenshot is written relative to the current working directory.
const puppeteer = require('puppeteer');

async function main() {
  const browser = await puppeteer.launch({
    headless: true,
    userDataDir: 'C:\\Users\\YourName\\AppData\\Local\\Puppeteer\\ScreenshotProfile',
  });

  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
    await page.goto('https://example.com', {
      waitUntil: 'networkidle2',
      timeout: 60_000,
    });
    await page.screenshot({ path: 'example.png', fullPage: true });
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

headless: true makes the browser run without a visible window. You can set headless: false while diagnosing navigation or page state. Puppeteer’s default install downloads a compatible browser; its install guide explains that install scripts can be blocked and documents npx puppeteer browsers install as a manual browser-install step.

3. Choose the right Windows path

In JavaScript string literals, backslashes need escaping, as in 'C:\\Users\\YourName\\...'. You can also use forward slashes in a Windows path, or construct a path with Node’s path.join() to avoid hand-building separators.

const path = require('node:path');
const profileDir = path.join(
  process.env.LOCALAPPDATA,
  'Puppeteer',
  'ScreenshotProfile',
);

const browser = await puppeteer.launch({ userDataDir: profileDir });

LOCALAPPDATA is available in typical Windows user sessions. If the script runs as a service or scheduled task, confirm the environment variable and account are the ones you expect. A relative directory is resolved from the process working directory, which can differ between an interactive terminal and a scheduled task; an explicit absolute path is easier to diagnose.

4. Select a browser executable only when needed

For most projects, use Puppeteer’s downloaded Chrome for Testing. If you manage Chrome separately, or use puppeteer-core, set executablePath to the browser executable as well as userDataDir:

const browser = await puppeteer.launch({
  executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
  userDataDir: 'C:\\Users\\YourName\\AppData\\Local\\Puppeteer\\ScreenshotProfile',
  headless: true,
});

The executable path and user data directory serve different purposes. The former selects which browser binary to start; the latter selects where that browser stores its profile data. Puppeteer says it guarantees compatibility with its bundled browser, so a separately managed Chrome may require more version and update coordination. The puppeteer-core package does not download Chrome; install or provide the browser yourself. See the Puppeteer installation documentation.

5. Pick screenshot dimensions and output

Puppeteer’s Page.screenshot() captures the page. With path, it writes the image to disk; without it, the method returns image bytes. PNG is the documented default image type. quality applies to image formats other than PNG.

Option Use Notes
path Save an image to a file Use an absolute path or know the process working directory. Ensure the destination is writable.
fullPage: true Capture the full document Useful for long pages; very tall pages can create large images and take longer to render.
clip Capture a rectangular region Specify a region when you need a particular part of the page.
type Select an image format PNG is the default. Set a supported non-PNG format to use quality.
quality Adjust lossy image output Applies to formats other than PNG.
omitBackground Keep transparency where supported Useful for image output that should not include the default page background.
captureBeyondViewport Control capture outside the viewport Consult the ScreenshotOptions API for behavior with clipping and full-page capture.

For a clipped capture, pass a clip rectangle with coordinates and dimensions:

await page.screenshot({
  path: 'region.png',
  clip: { x: 0, y: 0, width: 900, height: 600 },
});

For a specific element, locate it and use the element screenshot method. Puppeteer’s screenshots guide says it attempts to scroll a hidden element into view:

const card = await page.waitForSelector('.product-card', { timeout: 15_000 });
if (!card) throw new Error('Product card was not found');
await card.screenshot({ path: 'product-card.png' });

See the complete ScreenshotOptions API for current options and types.

6. Wait for the page state you need

The example uses networkidle2, which is convenient for many pages but is not a guarantee that every visual element has finished changing. Some sites keep network connections open, defer content, or load images after scrolling. Choose the wait strategy for the page rather than assuming one event fits every site.

await page.goto('https://example.com', {
  waitUntil: 'domcontentloaded',
  timeout: 60_000,
});
await page.waitForSelector('main', { timeout: 15_000 });
await page.screenshot({ path: 'main.png', fullPage: true });

Use a selector wait when a known element indicates that the content you need is present. Use a deliberate delay only when the site has a known client-side transition that cannot be observed with a selector. Keep navigation and selector timeouts bounded so a broken page does not leave a job running indefinitely.

7. Or skip the browser setup

ScreenshotNeo accepts a URL in one GET request and returns an image or PDF. Its API parameter names used by other screenshot APIs also work. The examples below use the documented API endpoint and show the basic request; see the ScreenshotNeo API documentation for output and capture 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 require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, inspect page information, and capture PDFs. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 screenshots. ScreenshotNeo is a website screenshot API and MCP server by Yorker Media.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

8. Troubleshooting Windows launches and captures

Symptom Likely cause What to do
Browser says the profile is already in use Another Chrome process has locked the same user data directory. Close the process using that directory, or give Puppeteer a separate automation directory. Do not run concurrent jobs against one profile directory.
Profile cannot be written or Chrome exits at startup The Windows account lacks permission, the path is invalid, or the directory is unavailable. Check the resolved path, create the parent directory, and run under an account with write access. Prefer a dedicated directory under a user-writable location.
Chrome executable not found puppeteer-core does not download Chrome, or a configured executable path is wrong. Install the required browser or set executablePath to the actual chrome.exe. If using standard Puppeteer with install scripts blocked, run npx puppeteer browsers install.
Browser install completed but launch fails The package manager may have blocked Puppeteer’s browser download or setup scripts. Follow Puppeteer’s install instructions and install the browser manually. Confirm the browser build is compatible with the Puppeteer package.
Chrome launch conflicts with organization policy Puppeteer disables extensions by default, while some Chrome policies require them. Check whether the reported issue is an enforced extension policy. Puppeteer documents enableExtensions: true as a workaround for that policy conflict.
Windows sandbox permission error Chrome sandbox files may not have the permissions expected by the installed Puppeteer version or environment. Check your Puppeteer version and the exact error first. Since v22.14.0, installation attempts to configure Windows sandbox permissions with Chrome’s setup.exe. For older installs or persistent errors, follow Puppeteer’s documented permission guidance, including its more restrictive SID recommendation for high-security environments.
Navigation times out The site is slow, unreachable, or never reaches the chosen lifecycle condition. Check URL reachability, set a sensible timeout, and use a more suitable readiness condition such as domcontentloaded followed by a selector wait.
Screenshot is blank or content is missing The page may not have rendered the needed content when capture ran, or content may be lazy-loaded. Wait for a relevant selector or page state. For lazy content, scroll through the page before a full-page capture if the site requires it.
Screenshot file is missing path is relative to the process working directory or points to a non-writable folder. Log process.cwd(), use an absolute output path, and verify the destination directory permissions.

Puppeteer’s Troubleshooting guide documents the Windows extension-policy and sandbox-permission cases. Its launcher source also handles Windows profile locks and distinguishes unwritable profiles from an already-running browser: BrowserLauncher.ts.

9. Reliability, performance, and cost considerations

  • Profile isolation: Use one dedicated profile directory per independently running browser process. For parallel capture jobs, use separate directories to avoid lock conflicts.
  • Cleanup: Always close the browser in a finally block so failures do not leave processes holding the profile lock.
  • Rendering time: Full-page images and pages with substantial client-side rendering can take longer and consume more memory than viewport captures. Capture only the region or element you need when a full page is unnecessary.
  • Browser versions: Bundled Chrome for Testing reduces browser/Puppeteer version coordination. A separately managed Chrome provides control over the binary and update schedule, with additional compatibility responsibility.
  • Storage: Screenshots are files when a path is supplied; plan for output locations and cleanup in recurring jobs. If you omit the path, retain or process the returned bytes.
  • Cost: Puppeteer itself is an open-source browser automation library, but running it has infrastructure costs: Windows compute, browser installation and updates, storage, and engineering time for failures and maintenance. There is no relevant benchmark in the cited documentation, so runtime should be measured on the actual pages and machine.

10. Frequently asked questions

Does userDataDir select a named Chrome profile such as “Profile 1”?

It sets the browser’s user data directory. Treat it as the directory supplied to Chrome for its user data and use a dedicated automation directory; do not assume a live desktop profile is safe to share with another process.

Can I reuse the same directory on the next run?

Yes, sequential runs can use the same dedicated directory after the previous browser has closed. Avoid simultaneous browser processes using that directory.

Where does the screenshot get saved?

The path passed to page.screenshot() controls the output file. A relative path is resolved from Node’s current working directory.

Can I capture an element instead of the whole page?

Yes. Find the element and call its screenshot method, or use clip for a rectangular region. Puppeteer’s element screenshot workflow attempts to scroll a hidden element into view.

What if I need to keep the browser visible while debugging?

Launch with headless: false and inspect the browser state. Return to headless mode for unattended runs once the problem is understood.