The reliable way to test an upload in Puppeteer with Jest is to select the page’s real input[type="file"], resolve a fixture to an absolute path, call uploadFile(), and then assert the application’s actual upload result. If a button launches a native chooser instead, register waitForFileChooser() before clicking and pass the file to accept(). Selecting a file changes browser state; it does not prove that your server received or stored it.
Choose the upload path your page actually exposes
Start by inspecting the DOM, not the visual design. A styled drop zone often wraps a genuine file input. If that input is present and addressable, use ElementHandle.uploadFile(); this is Puppeteer’s normal upload API. Use the chooser API only when the action that starts selection is indirect or the input is not practical to access.
| Page behavior | Puppeteer approach | What you must verify |
|---|---|---|
A usable input[type="file"] |
Find the element and call uploadFile(path) |
The application submits or starts its upload request |
| A button opens a file chooser | Start waitForFileChooser() and the click in one Promise.all, then call accept() |
The chooser is accepted or cancelled and the UI reports the result |
| Several files | Pass an array of paths (or the documented variadic form for your installed version) | The input has the application’s multiple behavior and all files are handled |
Do not add or alter the multiple attribute merely to make a test pass. Your test should represent the production control.
Set up Jest, Puppeteer, and a deterministic fixture
Keep browser creation and cleanup in Jest hooks. Store a small fixture in the test project (for example, test/fixtures/report.pdf) or create one in a temporary directory during setup. The file must exist on the machine running Puppeteer, including a CI runner or remote test host.
Recommended Free Tools
#1 Best Overall
import puppeteer from 'puppeteer';
import path from 'node:path';
describe('file upload', () => {
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch({ headless: true });
page = await browser.newPage();
});
afterAll(async () => {
await browser.close();
});
test('uploads the fixture', async () => {
await page.goto('http://localhost:3000/upload');
const fixture = path.resolve(process.cwd(), 'test/fixtures/report.pdf');
const input = await page.waitForSelector('input[type="file"]');
await input.uploadFile(fixture);
await page.click('button[type="submit"]');
await page.waitForSelector('[data-testid="upload-success"]');
});
});
Use selectors that are stable in your application, such as data-testid values. Replace the success selector with the signal your product actually provides: a success message, a completed row, a redirect, or another state change.
Upload through a real file input
The direct method has four distinct stages: locate the input, provide an existing path, trigger the page’s upload action, and assert the result. uploadFile() selects the file for the page; it does not click Submit and does not wait for an API response.
- Resolve the fixture. Use
path.resolve()so the path does not depend on the directory from which Jest was invoked. - Wait for the actual input. A visible label or drop-zone container is not a file input. Target
input[type="file"]or an application-specific selector on that element. - Select the file. Call
await input.uploadFile(absolutePath). - Perform the application action. Click the submit button or invoke the UI operation that starts an asynchronous upload.
- Assert an application-level outcome. Wait for a success state, inspect a response, or verify the resulting navigation. A selected filename alone is only a client-side assertion.
If your page uploads immediately when the input changes, omit the submit click and wait for the UI or network-backed state that indicates completion. Avoid arbitrary sleeps when a selector or response can express readiness.
Testing the request as well as the UI
For a stronger test, wait for the request your application sends and assert its response status, then check the visible result. The exact endpoint and payload are application-specific, so keep those assertions next to the form’s contract rather than assuming a universal URL or success element.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsHandle a button-launched file chooser
When a button launches a chooser, the order of operations is critical. Puppeteer’s chooser reference requires waitForFileChooser() to be called before the chooser is launched. Register the wait and perform the click concurrently:
Rank #2
import path from 'node:path';
const filePath = path.resolve(process.cwd(), 'test/fixtures/report.pdf');
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
page.click('#upload-file-button'),
]);
await fileChooser.accept([filePath]);
await page.waitForSelector('[data-testid="upload-success"]');
The Promise.all prevents a race in which the click opens the chooser before Puppeteer starts listening. Always finish the chooser interaction: call accept() with the intended files or cancel() when testing cancellation. An unresolved chooser can block later interactions.
Chooser cancellation
const [chooser] = await Promise.all([
page.waitForFileChooser(),
page.click('#upload-file-button'),
]);
await chooser.cancel();
await page.waitForSelector('[data-testid="upload-cancelled"]');
This tests the page’s cancel path without selecting a file.
Upload multiple files
For an input that supports multiple selection, provide every fixture path. Puppeteer versions document both an array form and, in some versions, a variadic form; use the form documented for the version installed in your project.
const first = path.resolve(process.cwd(), 'test/fixtures/one.txt');
const second = path.resolve(process.cwd(), 'test/fixtures/two.txt');
const input = await page.waitForSelector('input[type="file"][multiple]');
await input.uploadFile([first, second]);
The chooser equivalent is:
const [chooser] = await Promise.all([
page.waitForFileChooser(),
page.click('#upload-files-button'),
]);
await chooser.accept([first, second]);
After selection, assert that the application displays both names or, preferably, that the server reports both uploads completed. If the production input does not permit multiple files, a test that supplies several paths is testing behavior users cannot perform.
Paths, fixtures, and CI environments
Relative paths resolve from the Node process’s current working directory, which can differ between a local shell, an IDE, and a CI job. Absolute paths are safer, especially when Puppeteer connects to remote Chrome: the file must be readable where the Puppeteer process runs, not merely on your workstation.
Rank #3
- Resolve with
path.resolve(process.cwd(), ...)or an equivalent path based on the test module. - Commit small, non-sensitive fixtures, or create them in Jest setup and remove them afterward.
- Confirm the fixture is included in the CI checkout and is not excluded by ignore rules or artifact packaging.
- Log the resolved path when diagnosing failures, but avoid logging confidential fixture contents.
- If the browser and test runner are separated, place the file on the runner that calls Puppeteer and follow your remote-browser setup’s file-transfer requirements.
Assertions that prove a real upload
Separate three facts that are often conflated:
- Selection: the input or chooser accepted a path.
- Submission: the page made the intended form submission or upload request.
- Completion: the server accepted and processed the file, and the UI reflects that state.
A robust test waits for the page’s completion signal after submission. Depending on the product, that may be a success element, a status transition from “Uploading” to “Complete,” a navigation to a result page, or a request whose response you inspect. Do not invent a universal upload-success selector; define one in the application or adapt the assertion to the existing contract.
Common failures and precise fixes
Chooser timeout
Symptom: waitForFileChooser() times out. Cause: the click happened before the wait was registered, or the clicked control does not launch a native chooser. Fix: use the Promise.all ordering shown above and verify the control’s event actually opens a chooser. If a real input exists, use uploadFile() instead.
No file selected
Symptom: the test finds an element but the form remains empty. Cause: the selector matched a styled wrapper, label, or drop-zone rather than the file input. Fix: inspect the DOM and target the actual input[type="file"], or use chooser handling for the button that owns the interaction.
Selection passes, server upload is absent
Symptom: the filename appears, but no record is stored. Cause: the test asserted selection only and never submitted, or the upload request failed. Fix: perform the page’s submit/change action and assert the response or completed UI state separately.
CI cannot find the file
Symptom: a local run passes but CI reports a missing path. Cause: a relative path resolved from another working directory, or the fixture was not checked out. Fix: resolve an absolute path, print it during diagnosis, and ensure the fixture is part of the CI workspace.
Unsupported picker API
Symptom: a page using window.showOpenFilePicker does not respond to the documented chooser interception. Cause: that API is outside the documented waitForFileChooser() support described for this workflow. Fix: test the application through a real file input where possible, or provide a testable input path rather than assuming chooser interception covers the picker API.
Make the test maintainable and reliable
- Use one fixture per behavior. Keep files small and choose types that exercise validation (for example, an allowed PDF and a rejected extension).
- Wait on conditions, not time. Prefer selectors, navigation completion, or request promises to fixed delays.
- Keep lifecycle hooks isolated. Launch once per suite when state can be reset safely; use a fresh page or browser when tests would otherwise share upload state.
- Exercise negative paths. Add cases for cancellation, size/type rejection, duplicate names, and server errors using the same selection mechanism.
- Preserve diagnostics. On failure, capture the resolved path, current URL, visible error text, and (where appropriate) the failed response status.
Or skip the browser setup
If your goal is a rendered screenshot of an upload result or another page state rather than an end-to-end file-selection test, ScreenshotNeo can return a screenshot or PDF from one request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo documentation for all options. A direct call looks like this:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in Python:
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)
And 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}`);
The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Sign up for ScreenshotNeo if that fits your capture workflow.
FAQ
Can Puppeteer upload a file that exists only on my laptop?
No. The path must exist where the Puppeteer process runs. Copy or generate the fixture in the runner’s workspace, especially in CI or remote-browser arrangements.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Does calling uploadFile() submit the form?
No. It selects the file for the page. Trigger the application’s submit or change-driven upload and assert its resulting response or UI state.
Best Value
Should I intercept window.showOpenFilePicker with waitForFileChooser()?
Not on the basis of the documented chooser API. That picker API is outside the documented interception support, so use a real file input or a dedicated test seam.
Frequently Asked Questions
Can Puppeteer upload a file that exists only on my laptop?
No. The path must exist where the Puppeteer process runs, including a CI runner or remote test host.
Does calling uploadFile() submit the form?
No. It selects the file; your test must trigger the application upload and verify the resulting state.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchShould waitForFileChooser() intercept window.showOpenFilePicker?
That picker API is outside the documented chooser interception support; use a real file input or a test-specific seam.
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.




