Chapter 13 – Handling Downloads and File Uploads

File operations are a key part of end-to-end testing. Whether it’s uploading a document, exporting a report, or verifying that a downloaded file exists, Playwright provides robust APIs to handle these tasks seamlessly.

In this chapter, we’ll cover everything from basic file uploads and downloads to advanced automation techniques, including verifying file contents, managing temporary directories, and simulating drag-and-drop interactions.

1. Uploading files

File uploads are common in forms. Playwright makes it easy using the setInputFiles() method, which simulates selecting files in a file picker.

Example: Simple file upload

import { test, expect } from '@playwright/test';
import path from 'path';

test('upload a single file', async ({ page }) => {
  await page.goto('https://the-internet.herokuapp.com/upload');

  // Path to your local file
  const filePath = path.join(__dirname, 'test-data/sample.txt');

  // Upload the file
  await page.setInputFiles('input[type="file"]', filePath);
  await page.click('#file-submit');

  // Verify upload success
  const uploadedFile = page.locator('#uploaded-files');
  await expect(uploadedFile).toHaveText('sample.txt');
});

What’s happening here:

  • Playwright directly interacts with the file input element.
  • No native OS file dialogs are needed.
  • The file path points to a file on your system.

Pro Tip: Always store test files in a dedicated /test-data/ directory for consistency.

2. Uploading multiple files

You can upload multiple files at once by passing an array of file paths.

const files = [
  path.join(__dirname, 'test-data/sample1.txt'),
  path.join(__dirname, 'test-data/sample2.txt')
];

await page.setInputFiles('input[type="file"]', files);

Playwright automatically handles multiple file uploads if the input element allows it (has the multiple attribute).

3. Removing uploaded files

To clear the selected files from an input element, simply call setInputFiles([]).

await page.setInputFiles('input[type="file"]', []);

This resets the input, simulating a user clearing their file selection.

4. Uploading files without visible input

Some web apps hide the <input> element and use a custom button instead. Playwright can still upload files by directly targeting the hidden element.

Example:

await page.setInputFiles('input#hiddenFileInput', 'test-data/image.png');

If your upload button triggers JavaScript validation, make sure to trigger a change event after uploading:

await page.evaluate(() => {
  document.querySelector('input#hiddenFileInput').dispatchEvent(new Event('change'));
});

5. Drag-and-drop file uploads (advanced)

Some modern UIs use drag-and-drop areas for uploads. Playwright supports simulating drag-and-drop actions with JavaScript events.

Example:

const filePath = path.join(__dirname, 'test-data/sample.png');
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
await page.locator('input[type="file"]').setInputFiles(filePath);
await page.dispatchEvent('#drop-zone', 'drop', { dataTransfer });

This mimics a real drag-and-drop upload scenario.

6. Handling file downloads

Playwright automatically detects downloads and gives you control to track and validate them.

Example: Simple download

import fs from 'fs';
import path from 'path';
import { test, expect } from '@playwright/test';

test('download a file', async ({ page }) => {
  await page.goto('https://the-internet.herokuapp.com/download');

  const [download] = await Promise.all([
    page.waitForEvent('download'), // Wait for the download to start
    page.click('a[href="download/sample.png"]') // Trigger download
  ]);

  // Save the file to a specific location
  const downloadPath = await download.path();
  const fileName = download.suggestedFilename();
  console.log(`Downloaded: ${fileName}`);

  // Optionally save to a custom path
  const savePath = path.join(__dirname, 'downloads', fileName);
  await download.saveAs(savePath);

  // Verify the file exists
  expect(fs.existsSync(savePath)).toBeTruthy();
});

What’s happening here:

  • page.waitForEvent('download') listens for a download trigger.
  • Playwright saves the file to a temporary location.
  • You can save it permanently using download.saveAs().

7. Accessing download metadata

You can extract metadata like file name, MIME type, and URL.

console.log(await download.url());
console.log(download.suggestedFilename());
console.log(await download.failure()); // Returns error if download failed

Pro Tip: Use download.failure() to verify whether the download succeeded, it is helpful in negative test cases.

8. Verifying downloaded file contents

You can verify downloaded files by reading them from the filesystem.

Example:

import fs from 'fs';

const filePath = await download.path();
const content = fs.readFileSync(filePath, 'utf8');
expect(content).toContain('Expected Text');

This ensures the downloaded file contains expected data, not just the correct file name.

9. Setting default download directories

You can configure the browser context to store all downloads in a specific folder.

Example:

const context = await browser.newContext({
  acceptDownloads: true,
  downloadsPath: 'downloads/'
});

Now all downloads automatically save to that directory.

10. Handling failed or interrupted downloads

If a file fails to download, you can capture that using Playwright’s built-in failure detection.

Example:

const [download] = await Promise.all([
  page.waitForEvent('download'),
  page.click('#export-button')
]);

const error = await download.failure();
if (error) {
  console.error('Download failed:', error);
}

This helps diagnose network issues or broken download links.

11. Cleaning up temporary files

When Playwright downloads files, they’re stored temporarily. You can manage these files manually for clean test runs.

Example:

const tempPath = await download.path();
if (fs.existsSync(tempPath)) {
  fs.unlinkSync(tempPath); // Delete temporary file
}

12. Best practices for handling files

  1. Use a dedicated test-data/ directory for uploads.
  2. Use acceptDownloads: true when testing downloads.
  3. Always verify downloaded file existence or content.
  4. Avoid hardcoded file paths, use path.join() for cross-platform compatibility.
  5. Clean up downloaded or temporary files after tests.
  6. Mock file upload/download APIs when testing offline.

Exercise

Task 1: Upload a text file and verify success.
Task 2: Upload multiple files using setInputFiles().
Task 3: Download a file and verify it exists.
Task 4: Read a downloaded file and assert its content.
Bonus: Simulate a drag-and-drop upload.

Cheat Sheet

TaskMethod
Upload single filepage.setInputFiles('input', 'file.txt')
Upload multiple filespage.setInputFiles('input', ['a.txt', 'b.txt'])
Clear uploadpage.setInputFiles('input', [])
Wait for downloadpage.waitForEvent('download')
Save filedownload.saveAs('path/file.txt')
Get file pathawait download.path()
Verify file existsfs.existsSync(path)
Set download directorybrowser.newContext({ downloadsPath })

Summary

In this chapter, you learned how to:

  • Upload single and multiple files using setInputFiles().
  • Handle downloads and track them using waitForEvent('download').
  • Save, verify, and clean up downloaded files.
  • Simulate advanced interactions like drag-and-drop uploads.

With Playwright’s file-handling capabilities, you can easily automate workflows involving file uploads and downloads, it’s a critical part of real-world end-to-end testing.