How to Accept a File Chooser Dialog in Puppeteer
Intercept Puppeteer’s file chooser before triggering it, select local files reliably, and troubleshoot paths, timeouts, and multiple-file uploads.
Use page.waitForFileChooser() before the action that opens the chooser, then call accept() with the file path or paths. Start the wait and triggering action together with Promise.all so the chooser event cannot fire before Puppeteer is listening.
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
page.click('#upload-file-button'),
]);
await fileChooser.accept(['/absolute/path/to/file.pdf']);
This handles a user-facing upload control that launches a file chooser. If the page has an accessible <input type="file">, you can often upload directly through that element instead. The distinction is whether your test needs to exercise the control that opens the chooser.
1. Intercept a file chooser step by step
- Find the button or other page control that opens the file chooser.
- Call
page.waitForFileChooser()before activating that control. - Run the wait and the click together with
Promise.all. - Call
fileChooser.accept(paths)with the file path or paths your test should select. - Wait for the application’s upload or submission result, such as a success message or completed request.
A complete example using an existing Puppeteer page looks like this:
const path = require('node:path');
const filePath = path.resolve('fixtures/report.pdf');
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
page.click('#upload-file-button'),
]);
console.log('Multiple selection enabled:', fileChooser.isMultiple());
await fileChooser.accept([filePath]);
await page.waitForSelector('.upload-success');
The example assumes page is already open on the page under test and the fixture exists. Puppeteer does not verify that a supplied path exists, so validate the fixture in your test setup. Relative paths resolve against the Node.js process’s current working directory. When Chrome is remote, use absolute paths for files available to the browser connection.
2. Runnable Node.js example
Install Puppeteer in a Node.js project, save this as upload.cjs, and provide a page URL, a selector, and a real local fixture file. The control selector below is a placeholder that must match the page under test.
const path = require('node:path');
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com/upload', {
waitUntil: 'domcontentloaded',
});
const filePath = path.resolve('fixtures/report.pdf');
const [fileChooser] = await Promise.all([
page.waitForFileChooser({ timeout: 10_000 }),
page.click('#upload-file-button'),
]);
if (fileChooser.isMultiple()) {
console.log('The chooser permits multiple files.');
}
await fileChooser.accept([filePath]);
await page.waitForSelector('.upload-success', { timeout: 15_000 });
console.log('Upload completed.');
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
For an application that uploads after file selection, replace .upload-success with a signal that reflects the actual result. Selecting a file and completing the server-side upload are separate events.
3. Choose between chooser interception and direct upload
| Approach | Use it when | Example |
|---|---|---|
| Chooser interception | The test should activate the user-facing control that opens the chooser. | waitForFileChooser(), trigger the control, then accept(paths). |
| Direct file input | A normal file input is available and exercising the chooser-opening interaction is unnecessary. | uploadFile(paths) on the input element. |
Puppeteer’s file guide documents direct upload through an input element:
const fileElement = await page.waitForSelector('input[type="file"]');
await fileElement.uploadFile('/absolute/path/to/file.pdf');
Use chooser interception when the button interaction itself matters. Use direct upload when a file input is accessible and the test only needs to provide a file. Puppeteer documents that interception of picker dialogs opened through DOM APIs such as window.showOpenFilePicker() is unsupported. Check the Page.waitForFileChooser API and Puppeteer file guide for the version of Puppeteer in your project.
4. Multiple files, cancellation, and chooser state
fileChooser.isMultiple() reports whether the chooser allows multiple selections. Pass the intended file paths as an array to accept(). The application may still impose its own limits on file count, type, or size, so test those rules at the application layer too.
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
page.click('#upload-files-button'),
]);
if (!fileChooser.isMultiple()) {
throw new Error('This control does not allow multiple files');
}
await fileChooser.accept([
'/absolute/path/to/first.pdf',
'/absolute/path/to/second.pdf',
]);
If the test should exercise cancellation, call fileChooser.cancel() after the chooser opens and assert that the page remains in the expected state. Accept or cancel every chooser you open: only one chooser can be open at a time, and an unresolved chooser blocks a later one.
5. Timeouts and wait options
page.waitForFileChooser() accepts wait options. Puppeteer’s general wait settings use a 30-second default unless changed through page timeout configuration; set a timeout that fits the control and test environment. Waits can also accept an abort signal. A longer timeout cannot fix a control that never opens a supported chooser.
const controller = new AbortController();
const [fileChooser] = await Promise.all([
page.waitForFileChooser({
timeout: 12_000,
signal: controller.signal,
}),
page.click('#upload-file-button'),
]);
await fileChooser.accept(['/absolute/path/to/file.pdf']);
Use abort signals when the surrounding test needs to cancel a pending wait as part of its own cleanup. Avoid starting a wait without a matching action or allowing an opened chooser to remain unresolved.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The chooser wait times out. | The listener started after the click, the selector targets the wrong control, or the control does not launch a supported chooser. | Register the wait before the action in Promise.all; verify the selector and page state. If the site uses showOpenFilePicker(), use a supported input-based route where available. |
| The page reports an empty or missing file. | The path is wrong, relative to a different working directory, or unavailable to remote Chrome. | Resolve the path and check it exists before calling accept(). Use an absolute path to a file accessible to the browser environment. |
| A second chooser never opens. | The previous chooser is still open or unresolved. | Accept or cancel the first chooser before triggering another. |
| The selection succeeds but the test fails. | The application upload is asynchronous, or its validation rejected the file. | Wait for an application-level completion signal and inspect validation behavior, file type, size, and server response. |
| The click fails before a chooser appears. | The control is hidden, covered, disabled, or not yet ready. | Wait for the correct control to become available, then trigger it while the chooser wait is active. |
| A JavaScript alert is mistaken for a file chooser. | Alerts, confirms, and prompts use Puppeteer’s Dialog API, not FileChooser. |
Handle JavaScript dialogs with the dialog event and Dialog.accept(); use chooser APIs only for file selection. |
7. Performance, reliability, and cost
File chooser interception adds no separate upload service: it automates the browser interaction in your Puppeteer test. Runtime is primarily determined by page readiness, file transfer, application processing, and the waits your test uses. Keep fixture files local to the browser process when possible, and choose timeouts based on the workflow rather than increasing them to mask a missed event.
For reliable tests, use stable selectors, absolute fixture paths, explicit chooser cleanup, and an application-level completion assertion. Keep test files small enough for the scenario unless size handling is what the test covers. This workflow has no per-shot API cost; infrastructure and browser runtime costs depend on where and how the test suite runs.
8. Or skip the browser setup
If your goal is to capture a page rather than test its upload flow, ScreenshotNeo returns a screenshot or PDF from one API request, without setting up Puppeteer. See the ScreenshotNeo API documentation.
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 banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
9. FAQ
Does the native file picker appear during a headful run?
Puppeteer handles the chooser request, so the native picker does not appear for a person to interact with.
Does accept() check that the file exists?
No. Check the path yourself before passing it to Puppeteer.
Can I use this for a JavaScript prompt?
No. A prompt is a JavaScript dialog and uses Puppeteer’s Dialog handling.
What if my page only exposes a file input?
Wait for the input and call uploadFile() with the file path or paths.


