Tutorial October 12, 2026 5 min read

Playwright File Upload & Download: Complete Guide

Uploads and downloads are where many UI tests get stuck: hidden inputs, native file dialogs, drag-and-drop zones, and downloads that vanish when the test ends. This guide covers every case with working code in TypeScript, Python and Java — and how to check the downloaded file's contents.

Playwright never opens the operating system's file dialog. Instead, it sets files directly on the page's <input type="file"> and captures downloads as objects you can save and inspect. That makes file tests fast and reliable in headless mode and CI — once you know which API to use.


Upload Files with setInputFiles

If the page has a file input (even a hidden one), call setInputFiles on it:

upload.spec.ts
import path from 'path';

// one file
await page.getByLabel('Upload file').setInputFiles(path.join(__dirname, 'myfile.pdf'));

// several files (the input needs the "multiple" attribute)
await page.getByLabel('Upload files').setInputFiles([
  path.join(__dirname, 'file1.txt'),
  path.join(__dirname, 'file2.txt'),
]);

// a whole directory (input with "webkitdirectory")
await page.getByLabel('Upload directory').setInputFiles(path.join(__dirname, 'mydir'));

// clear the selection
await page.getByLabel('Upload file').setInputFiles([]);

Upload a file built in memory

No fixture file needed — pass a buffer with a name and MIME type. Great for generating CSVs or JSON on the fly:

upload.spec.ts
await page.getByLabel('Upload file').setInputFiles({
  name: 'file.txt',
  mimeType: 'text/plain',
  buffer: Buffer.from('this is test'),
});

Hidden input? Many styled upload buttons hide the real <input>. Target it with page.locator('input[type=file]') — setInputFiles works even when it is not visible.

Upload When There Is No Input: the filechooser Event

Some apps create the file input dynamically when you click a button. Wait for the filechooser event, then set files on it:

upload.spec.ts
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: 'Upload file' }).click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, 'myfile.pdf'));

Note the order: start waiting before the click, then await the promise. This pattern is the same for downloads, popups and other events.

Drag-and-Drop Uploads

Most drop zones still contain a hidden file input — try setInputFiles on it first. If the component only listens for drop events, create a DataTransfer in the page and dispatch the event:

dropzone.spec.ts
const dataTransfer = await page.evaluateHandle((content) => {
  const dt = new DataTransfer();
  dt.items.add(new File([content], 'notes.txt', { type: 'text/plain' }));
  return dt;
}, 'file contents');

await page.locator('#dropzone').dispatchEvent('drop', { dataTransfer });

Download Files with waitForEvent('download')

download.spec.ts
const downloadPromise = page.waitForEvent('download');
await page.getByText('Download file').click();
const download = await downloadPromise;

// keep the file: save it with the name the server suggested
await download.saveAs(test.info().outputPath(download.suggestedFilename()));
  • download.suggestedFilename() — the file name from the server or the download attribute.
  • download.path() — path of the temporary file Playwright saved.
  • download.saveAs(path) — copy it somewhere permanent.

Downloaded files are deleted when the browser context that produced them closes. Read or saveAs the file inside the test. Saving to test.info().outputPath() keeps it with the test results.

Download a File Straight from a URL

When you only need to check the file — not the click flow — skip the browser and use the request fixture. It shares cookies with the page context when you use page.request:

download-api.spec.ts
import { test, expect } from '@playwright/test';
import fs from 'fs';

test('report CSV is served', async ({ page }) => {
  const response = await page.request.get('/reports/monthly.csv');
  expect(response.ok()).toBeTruthy();
  expect(response.headers()['content-type']).toContain('text/csv');

  const body = await response.body();
  fs.writeFileSync(test.info().outputPath('monthly.csv'), body);
});

More on the request API in our Playwright API testing guide.

Verify the Downloaded File's Contents

A download that “happened” is not the same as a correct download. Read the file and assert on it:

download.spec.ts
import fs from 'fs';

const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export CSV' }).click();
const download = await downloadPromise;

expect(download.suggestedFilename()).toMatch(/^orders-\d{4}-\d{2}-\d{2}\.csv$/);

const content = fs.readFileSync(await download.path(), 'utf-8');
const rows = content.trim().split('\n');
expect(rows[0]).toBe('id,customer,total');
expect(rows.length).toBeGreaterThan(1);

For PDFs or spreadsheets, parse the file with a library such as pdf-parse or xlsx and assert on the parsed data. Our assertions guide covers the matchers.

Playwright Python and Java Examples

Python

test_files.py
from playwright.sync_api import Page

def test_upload(page: Page):
    page.get_by_label("Upload file").set_input_files("myfile.pdf")

def test_upload_filechooser(page: Page):
    with page.expect_file_chooser() as fc_info:
        page.get_by_role("button", name="Upload file").click()
    fc_info.value.set_files("myfile.pdf")

def test_download(page: Page):
    with page.expect_download() as download_info:
        page.get_by_text("Download file").click()
    download = download_info.value
    download.save_as("downloads/" + download.suggested_filename)

Java

FilesTest.java
import java.nio.file.Paths;

// upload
page.getByLabel("Upload file").setInputFiles(Paths.get("myfile.pdf"));

// filechooser
FileChooser fileChooser = page.waitForFileChooser(() -> {
  page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Upload file")).click();
});
fileChooser.setFiles(Paths.get("myfile.pdf"));

// download
Download download = page.waitForDownload(() -> {
  page.getByText("Download file").click();
});
download.saveAs(Paths.get("downloads", download.suggestedFilename()));

Full setups: Python tutorial · Java tutorial.

Troubleshooting File Upload and Download

ProblemFix
Error: Non-multiple file input can only accept single fileThe input has no multiple attribute — pass one file, or upload in separate steps.
ENOENT on uploadRelative paths resolve from the working directory. Build paths with path.join(__dirname, ...).
waitForEvent('download') times outThe click opened the file in a new tab instead of downloading it (common for PDFs). Wait for popup instead, or fetch the URL with page.request.
Download file is missing after the testFiles are deleted when the context closes. Call saveAs during the test.
Upload works locally but not in CIFixture files are missing from the repo or Docker image, or paths are OS-specific — use path.join.
Native OS dialog appears in headed modeYou clicked a button without handling filechooser. Start waiting for the event before the click.

Frequently Asked Questions

Where does Playwright save downloaded files?

In a temporary folder (configurable with downloadsPath when launching the browser). The files are deleted when the browser context closes, so call download.saveAs() to keep them.

How do I upload a file without a file on disk?

Pass an object to setInputFiles with name, mimeType and buffer, for example Buffer.from('id,total\n1,10').

How do I handle a native file upload dialog in Playwright?

Playwright never shows the native dialog. Use setInputFiles on the file input, or wait for the filechooser event before clicking and call fileChooser.setFiles().

Can Playwright upload multiple files at once?

Yes. Pass an array of paths to setInputFiles. The input must have the multiple attribute.

How do I check the contents of a downloaded file?

Read it with fs.readFileSync(await download.path()) and assert on the text, or parse it with a CSV, PDF or Excel library first.


Asim Noaman - Playwright and Claude AI course instructor

Asim Noaman

Senior QA Automation Engineer & AI Testing Specialist

With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.

Udemy Instructor Published course author
Playwright + AI Expert Specialized in AI-powered QA
Production Experience Enterprise-grade frameworks
Connect on LinkedIn

Complete Course

Master Real-World Playwright Automation

Uploads, downloads, APIs and tricky UI flows are covered hands-on in the complete Udemy course — along with AI-assisted test writing using Claude and MCP Server.

  • Real-world UI scenarios
  • API + UI testing together
  • Stable locators and assertions
  • AI test generation with Claude
Enroll Now on Udemy →