File upload and download are common in enterprise apps. Upload a report, download an invoice, attach a document. In Selenium, file upload requires sending file paths to input elements. In Playwright, it is cleaner. setInputFiles() for uploads, waitForEvent('download') for downloads.
import { test, expect } from '@playwright/test';
test('upload a single file', async ({ page }) => {
await page.goto('/topics/file-upload');
// Set a file on the input[type="file"] element
await page.getByLabel('Upload document').setInputFiles('test-data/sample.pdf');
// Verify the filename appears
await expect(page.getByText('sample.pdf')).toBeVisible();
// Click upload button
await page.getByRole('button', { name: 'Upload' }).click();
await expect(page.getByText('Upload successful')).toBeVisible();
});
test('upload multiple files', async ({ page }) => {
await page.goto('/topics/file-upload');
await page.getByLabel('Upload files').setInputFiles([
'test-data/report.pdf',
'test-data/photo.png',
'test-data/data.csv',
]);
await expect(page.getByText('3 files selected')).toBeVisible();
});
test('clear file selection', async ({ page }) => {
await page.goto('/topics/file-upload');
// Upload a file
await page.getByLabel('Upload document').setInputFiles('test-data/sample.pdf');
// Clear the selection by passing empty array
await page.getByLabel('Upload document').setInputFiles([]);
await expect(page.getByText('No file selected')).toBeVisible();
});Some apps use drag-and-drop zones or custom buttons instead of a visible input[type="file"]. The input element exists but is hidden. Playwright handles this with the fileChooser event.
import { test, expect } from '@playwright/test';
test('upload via file chooser dialog', async ({ page }) => {
await page.goto('/topics/file-upload');
// Wait for the file chooser AND click the custom button
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.getByRole('button', { name: 'Choose File' }).click(),
]);
// Set the file through the file chooser
await fileChooser.setFiles('test-data/sample.pdf');
// Verify
await expect(page.getByText('sample.pdf')).toBeVisible();
});import { test, expect } from '@playwright/test';
import fs from 'fs';
test('download a file and verify', async ({ page }) => {
await page.goto('/topics/downloads');
// Wait for download AND click the download link
const [download] = await Promise.all([
page.waitForEvent('download'),
page.getByRole('link', { name: 'Download Report' }).click(),
]);
// Check the suggested filename
expect(download.suggestedFilename()).toBe('monthly-report.pdf');
// Save to a specific path
const filePath = 'downloads/monthly-report.pdf';
await download.saveAs(filePath);
// Verify the file exists and is not empty
const fileExists = fs.existsSync(filePath);
expect(fileExists).toBeTruthy();
const fileSize = fs.statSync(filePath).size;
expect(fileSize).toBeGreaterThan(0);
});
test('get download as a stream', async ({ page }) => {
await page.goto('/topics/downloads');
const [download] = await Promise.all([
page.waitForEvent('download'),
page.getByRole('link', { name: 'Download CSV' }).click(),
]);
// Read the download directly without saving
const path = await download.path();
if (path) {
const content = fs.readFileSync(path, 'utf-8');
expect(content).toContain('Name,Email,Amount');
}
});| Method | Purpose |
|---|---|
| setInputFiles(path) | Upload a file to input[type="file"] |
| setInputFiles([path1, path2]) | Upload multiple files |
| setInputFiles([]) | Clear file selection |
| waitForEvent('filechooser') | Handle non-input upload buttons |
| waitForEvent('download') | Catch a file download |
| download.suggestedFilename() | Get the filename from Content-Disposition |
| download.saveAs(path) | Save the downloaded file to disk |
| download.path() | Get the temporary path of the download |
setInputFiles() only works on input[type="file"] elements. If the upload button is a custom component, use the fileChooser event instead. Trying to call setInputFiles() on a regular button will throw an error.
Put test files in a test-data/ folder at the project root. Use relative paths like 'test-data/sample.pdf'. Playwright resolves paths relative to the project root (where playwright.config.ts lives).
Q: How do you handle file uploads and downloads in Playwright?
A: For uploads, I use setInputFiles() on the file input element. If the input is hidden behind a custom button, I use waitForEvent('filechooser') to catch the file dialog. For downloads, I use waitForEvent('download') with Promise.all, then check suggestedFilename() and save with saveAs(). I always verify the file exists and has content after download. For clearing a file selection, I pass an empty array to setInputFiles([]).
Key Point: setInputFiles() for uploads, waitForEvent('download') for downloads. Use fileChooser event for custom upload buttons. Always verify file contents after download.