Q21 of 42 · Playwright

How do you handle file uploads in Playwright?

PlaywrightMidplaywrightuploadsfilesmid

Short answer

Short answer: Use `locator.setInputFiles(path)` on the `<input type=file>` element. Pass a path, multiple paths, an in-memory `{ name, mimeType, buffer }`, or an empty array to clear. For drag-and-drop or programmatic uploads without a visible file input, intercept the `filechooser` event.

Detail

Playwright's first-class API for uploads is setInputFiles:

await page.getByLabel('Profile photo').setInputFiles('fixtures/avatar.png');

The file picker doesn't open; Playwright assigns the file directly to the input. This works whether the input is visible or hidden behind a styled <label>.

Variations:

// Multiple files
await fileInput.setInputFiles(['a.pdf', 'b.pdf']);

// In-memory file (no disk roundtrip)
await fileInput.setInputFiles({
  name: 'report.csv',
  mimeType: 'text/csv',
  buffer: Buffer.from('id,name\n1,Alice'),
});

// Clear the selection
await fileInput.setInputFiles([]);

For UIs that hide the input behind a custom button or drag-and-drop zone, listen for the filechooser event:

const [fileChooser] = await Promise.all([
  page.waitForEvent('filechooser'),
  page.getByRole('button', { name: 'Upload photo' }).click(),
]);
await fileChooser.setFiles('fixtures/avatar.png');

For drag-and-drop uploads, you typically need to dispatch a synthetic drop event with a constructed DataTransfer containing the file. This is fiddlier; setInputFiles should be your first choice if the input exists in the DOM at all.

Common asserts after upload:

await fileInput.setInputFiles('fixtures/avatar.png');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByTestId('avatar')).toHaveAttribute('src', /avatar\.png/);

Watch-outs:

  • File paths are relative to the test's working directory (project root, typically). Use absolute paths or path.join(__dirname, ...) for clarity.
  • Browsers limit upload sizes; very large files in tests may need splitting or stubbing.

// EXAMPLE

upload.spec.ts

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

test('uploads a profile photo', async ({ page }) => {
  await page.goto('/profile');

  const file = path.join(__dirname, '../fixtures/avatar.png');
  await page.getByLabel('Profile photo').setInputFiles(file);

  await page.getByRole('button', { name: 'Save' }).click();
  await expect(page.getByTestId('avatar-img'))
    .toHaveAttribute('src', /avatar\.png$/);
});

test('uploads from in-memory buffer', async ({ page }) => {
  await page.goto('/import');

  await page.getByLabel('CSV file').setInputFiles({
    name: 'orders.csv',
    mimeType: 'text/csv',
    buffer: Buffer.from('id,name\n1,Alice\n2,Bob'),
  });

  await page.getByRole('button', { name: 'Import' }).click();
  await expect(page.getByTestId('import-status')).toHaveText('2 rows imported');
});

// WHAT INTERVIEWERS LOOK FOR

Knowing `setInputFiles` for visible/hidden inputs, the in-memory buffer variant, and `filechooser` event for custom upload buttons.

// COMMON PITFALL

Trying to drive the OS file picker — Playwright bypasses it entirely with `setInputFiles`.