Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteUse a real file input for uploads, and configure a writable download directory for browser downloads. Puppeteer’s uploadFile() sends local paths to an <input type="file">. If a site opens a native chooser, arm page.waitForFileChooser() before clicking. Downloads need a browser-context policy, an explicit directory, and your own completion and integrity checks: Puppeteer’s maintained files guide says it currently has no universal programmatic file-download API.
Prerequisites and version caveats
Install the package that matches your browser strategy. puppeteer downloads a compatible Chrome by default; puppeteer-core is for a browser that you manage separately. Puppeteer controls Chrome or Firefox through DevTools Protocol or WebDriver BiDi, and download behavior can differ by installed version and protocol. Check the API reference for your exact version before deploying.
npm i puppeteer
The examples below use modern Puppeteer syntax and absolute paths. Run them on the machine where Puppeteer runs; a path on your laptop is not automatically visible to a remote browser process.
Upload a file through an HTML input
Direct upload with uploadFile
Most upload forms contain an input such as <input type="file">. Wait for it, then provide one or more absolute local paths.
#1 Best Overall
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/upload', {waitUntil: 'networkidle2'});
const fileInput = await page.waitForSelector('input[type="file"]');
await fileInput.uploadFile('/absolute/path/to/report.pdf');
// Selecting a file only changes the browser input. It does not submit it.
await Promise.all([
page.waitForResponse(r => r.url().includes('/upload') && r.ok()),
page.locator('button[type="submit"]').click(),
]);
await browser.close();
})();
For a multiple-file input, pass multiple paths and leave the page’s validation attributes unchanged:
await fileInput.uploadFile(
'/absolute/path/to/one.csv',
'/absolute/path/to/two.csv'
);
Use a selector specific to your form when several file inputs exist. Confirm acceptance by waiting for the documented response, navigation, or success indicator; a changed filename label is not proof that the server received bytes.
When the page opens a native file chooser
A custom “Choose file” button may launch the operating-system dialog instead of exposing a convenient input. Register the chooser wait before the click so the event cannot be missed.
const [chooser] = await Promise.all([
page.waitForFileChooser({timeout: 5000}),
page.locator('#choose-file').click(),
]);
await chooser.accept(['/absolute/path/to/report.pdf']);
The same ordering rule applies to navigation and responses: arm the wait and perform the triggering action in one Promise.all. If the control does not launch a chooser in your browser mode, inspect the page for its underlying file input and use uploadFile instead.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #2
Submit and verify the upload
Choose the synchronization signal that represents success for the application:
- Response: wait for the upload endpoint and require
response.ok(). - Navigation: wait for the destination URL and a suitable load state.
- UI state: wait for a success element, job ID, or server-rendered status.
const [response] = await Promise.all([
page.waitForResponse(r => r.url().includes('/upload') && r.request().method() === 'POST'),
page.locator('button[type="submit"]').click(),
]);
if (!response.ok()) throw new Error(`Upload failed: HTTP ${response.status()}`);
await page.waitForSelector('[data-upload-status="success"]');
Configure a controlled browser download
Set policy and an explicit destination
For a browser-managed download, create a browser context with a writable, per-job directory. The downloadPath is required when policy is allow or allowAndName.
const fs = require('node:fs/promises');
const path = require('node:path');
const puppeteer = require('puppeteer');
(async () => {
const downloadPath = path.resolve('downloads/job-001');
await fs.rm(downloadPath, {recursive: true, force: true});
await fs.mkdir(downloadPath, {recursive: true});
const browser = await puppeteer.launch();
const context = await browser.createBrowserContext({
downloadBehavior: {
policy: 'allow',
downloadPath,
},
});
const page = await context.newPage();
await page.goto('https://example.com/reports', {waitUntil: 'networkidle2'});
await page.locator('a[data-download="report"]').click();
await browser.close();
})();
allowAndName is another policy; it names files by download GUID and has a WebDriver BiDi limitation documented in the DownloadBehavior API. Treat the directory as owned by one job to avoid collisions with stale files or another worker.
How to know a download finished
Download configuration alone is not completion proof. Puppeteer’s maintained files guide states: “Currently, Puppeteer does not offer a way to handle file downloads in a programmatic way.” Build a bounded completion check using protocol notifications where your chosen browser/protocol exposes them, or poll the job directory and then validate the result.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →A defensive filesystem poll
Start with an empty directory, record its initial contents, ignore temporary .crdownload files, require a deadline, and verify more than a nonzero length. The example below waits for a stable size and then checks a minimum expected size; production code should also validate a checksum or parse the file format.
const fs = require('node:fs/promises');
const path = require('node:path');
async function waitForCompletedFile(dir, {
timeoutMs = 60_000,
intervalMs = 250,
minBytes = 1,
} = {}) {
const deadline = Date.now() + timeoutMs;
let previous = null;
while (Date.now() < deadline) {
const names = await fs.readdir(dir);
const candidates = names.filter(name => !name.endsWith('.crdownload'));
for (const name of candidates) {
const full = path.join(dir, name);
const first = await fs.stat(full);
if (!first.isFile() || first.size < minBytes) continue;
await new Promise(r => setTimeout(r, intervalMs));
const second = await fs.stat(full);
const state = `${name}:${second.size}`;
if (second.size === first.size && state === previous) return full;
previous = state;
}
await new Promise(r => setTimeout(r, intervalMs));
}
throw new Error(`Download did not complete within ${timeoutMs} ms`);
}
const file = await waitForCompletedFile('/absolute/path/to/job-directory', {
timeoutMs: 90_000,
minBytes: 100,
});
console.log(`Candidate completed: ${file}`);
A stable size can still be the wrong file or a truncated response. Prefer an expected filename, a download notification, a known byte count, a cryptographic checksum, a valid archive/PDF parse, or an application-level job status. Keep the timeout finite so a blocked server cannot consume a worker indefinitely.
Use a direct HTTP request when the URL is suitable
If the download URL is stable and your authorization permits it, an HTTP request is often simpler than browser automation. Preserve only the required origin-scoped cookies or tokens, check status and content type, stream large responses with size limits, and write into the job directory. Keep browser interaction when authentication, navigation, a user gesture, or a JavaScript-generated URL is part of the requirement.
Upload versus download: choose the right synchronization
| Task | Primary mechanism | What proves success | Main risk |
|---|---|---|---|
| Upload input | uploadFile |
Upload response, navigation, or success state | Selection mistaken for submission |
| Native chooser | waitForFileChooser before click |
Application acknowledgement after accept | Wait registered too late |
| Browser download | Context policy plus writable downloadPath |
Notification or bounded filesystem and integrity checks | Stale, partial, or incorrectly named file |
| Direct HTTP | Authorized request to stable URL | HTTP status, headers, size and checksum/parser | Missing browser cookies, gesture, or generated authorization |
Troubleshooting Puppeteer file transfers
“File not found” or permission errors
- Resolve the path on the machine running Node, not the machine displaying your terminal.
- Use an absolute path and verify read permission for uploads and write permission for the download directory.
- Create a unique per-job directory and ensure the process user owns it.
The chooser wait times out
The click may not open a native chooser, or the wait was registered after the click. Put both operations in the same Promise.all; otherwise inspect the DOM for an input[type=file].
Recommended Free Tools
Rank #4
The upload appears selected but the server has nothing
Selection does not submit data. Click the site’s submit control and wait for its response, navigation, or success state. Check client-side validation and required fields.
No file appears in the download directory
Confirm the context was created with policy: 'allow' or 'allowAndName', that downloadPath is absolute and writable, and that the click actually triggers a download rather than navigation or an in-page viewer. Check authentication and pop-up blocking as well.
The file is present but incomplete
Do not treat existence or one nonzero size as completion. Ignore .crdownload, wait for size stability within a deadline, and validate expected bytes, checksum, or file parsing. Remove stale files before each job.
Code works in Chrome but not Firefox or BiDi
Download policy and notification support vary by browser and protocol. Consult the installed version’s API reference, especially the DownloadBehavior notes about allowAndName and WebDriver BiDi.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Best Value
- Used Book in Good Condition
Operational checklist
- Use absolute local paths and verify permissions.
- Use
uploadFilefor exposed file inputs; use chooser interception only when needed. - Arm response, navigation, or chooser waits before the triggering action.
- Submit the form and verify an application-level success signal.
- For downloads, create an empty per-job directory and configure policy plus
downloadPath. - Use bounded polling or protocol events; reject temporary and stale files.
- Validate filename, size, bytes, checksum, parser result, or downstream state.
- Clean up job directories according to your retention policy.
Or skip the browser setup
If your goal is a clean image or PDF of a page rather than automating that site’s own file control, ScreenshotNeo provides a one-request website screenshot API. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for all options, including PNG, JPEG, WebP, PDF, full-page capture, selectors, waits, custom headers and cookies, and signed links. Create a free ScreenshotNeo account to get 1,000 screenshots each month without a card.
FAQ
Where does Puppeteer save downloads by default?
Do not rely on an implicit location. Set a writable downloadPath in the browser context and treat that directory as the job’s destination.
Can I upload a remote URL with uploadFile?
No. The method reads paths local to the process running Puppeteer. Download the remote data first, enforce your own size and type checks, then pass the resulting local path.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Is a nonzero file size enough?
No. It can be a stale or truncated file. Combine bounded waiting with expected naming and an integrity or application-level validation.
Frequently Asked Questions
Does Puppeteer provide a universal download event API?
The maintained files guide does not document a universal programmatic download API, so configure download behavior and add your own bounded completion and integrity checks.
Should I use a browser download or direct HTTP?
Use direct HTTP for a stable, authorized URL when browser interaction is unnecessary; keep browser automation when authentication, navigation, JavaScript generation, or a user gesture is required.
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.




