Resource library

QA How-To

How to Upload a file in Playwright (2026)

Learn playwright how to upload a file with setInputFiles, file chooser handling, buffers, assertions, fixtures, and reliable CI-ready test patterns now.

19 min read | 3,123 words

TL;DR

playwright how to upload a file works best when the test synchronizes with the exact application event, uses stable locators or request predicates, and asserts the resulting business state. Start with the simplest public Playwright API, then add a focused fallback only when the application requires it.

Key Takeaways

  • Start with Playwright's public, action-oriented APIs and stable user-facing locators.
  • Begin waiting before the action that triggers asynchronous work.
  • Assert the final business outcome, not only that an automation call returned.
  • Keep test data isolated and make cleanup safe for retries and parallel workers.
  • Use traces, screenshots, and targeted diagnostics to explain failures.
  • Treat fallback techniques as documented exceptions with focused coverage.

playwright how to upload a file is a practical testing workflow that verifies user-visible behavior, not merely that automation commands completed. This guide gives you runnable patterns, assertion strategies, debugging methods, and design choices suitable for a maintained 2026 test suite.

The examples emphasize deterministic synchronization, accessible locators, useful failure evidence, and tests that stay readable as the application evolves. Use the quick answer first, then work through the numbered sections for production details and interview reasoning.

Playwright file upload uploads one or more files by setting the value of an HTML file input through a locator. The recommended 2026 pattern is locator.setInputFiles(), usually with getByLabel() or another stable locator, followed by assertions for the selected file, upload request, server result, and visible application state.

The method supports file paths, arrays of paths, in-memory payloads, directory selection for compatible inputs, and an empty array to clear files. It works without opening the operating system file dialog. This guide covers reliable path handling, dynamic file choosers, hidden inputs, multiple uploads, request verification, cleanup, security, and debugging in Playwright Test with TypeScript.

TL;DR

import path from 'node:path';
import { fileURLToPath } from 'node:url';

const currentDir = path.dirname(fileURLToPath(import.meta.url));
const resume = path.join(currentDir, 'fixtures', 'resume.pdf');

await page.getByLabel('Upload resume').setInputFiles(resume);
Need Value passed to setInputFiles()
One file from disk Absolute or relative path string
Multiple files Array of path strings
Generated file { name, mimeType, buffer }
Multiple generated files Array of FilePayload objects
Clear the input []
Upload a directory One directory path for an input with webkitdirectory
Dynamic chooser fileChooser.setFiles() after waiting for filechooser

1. playwright how to upload a file: Core Concepts

locator.setInputFiles() sets files on an <input type=file> element and dispatches the browser events the application expects. It was added to the Locator API in Playwright 1.14 and is the preferred form. The older selector-based page.setInputFiles(selector, files) API still exists but is discouraged in current documentation because locator-based operations provide stronger retry behavior and clearer composition.

The locator should resolve to the file input. If it resolves to a label that has an associated control, Playwright targets that control. An accessible label is therefore a readable choice when the markup connects <label for> and <input id>, or exposes an appropriate accessible name.

Supported input values are:

  • A path string for one disk file.
  • An array of path strings for multiple files.
  • A FilePayload object with name, mimeType, and buffer.
  • An array of FilePayload objects.
  • An empty array to remove selected files.
  • A single directory path for an input with the webkitdirectory attribute.

Relative file paths are resolved from the current working directory. That can differ between a local command, monorepo script, editor, and CI job. Resolve an absolute path from the spec module, a known fixture directory, or configuration so the test does not depend on where Node.js was launched.

setInputFiles() does not prove that the server accepted the content. It controls browser selection. The application may still reject type, size, scanning, authorization, or business rules, so assertions must cover the boundary relevant to the test.

2. playwright how to upload a file: Setup and First Working Test

Place a small synthetic fixture under a test fixture directory and compute an absolute path. In an ESM TypeScript project, derive the module directory from import.meta.url. This avoids CommonJS-only __dirname assumptions.

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

const currentDir = path.dirname(fileURLToPath(import.meta.url));
const resumePath = path.join(currentDir, 'fixtures', 'resume.pdf');

test('uploads a PDF resume', async ({ page }) => {
  await page.goto('https://app.example.test/profile');

  await page.getByLabel('Upload resume').setInputFiles(resumePath);

  await expect(page.getByText('resume.pdf')).toBeVisible();
  await page.getByRole('button', { name: 'Save profile' }).click();
  await expect(page.getByRole('status')).toHaveText('Profile saved');
});

Replace the illustrative site URL and fixture with an application you control. The file should be intentionally small and safe to commit. Do not use a real applicant resume or personal document. A synthetic fixture can contain enough structure to pass the server's file validation without exposing private data.

Prefer the user-facing label to a CSS input selector. If the page displays a button that is actually a label for a hidden input, getByLabel() may still locate the control correctly depending on markup. Inspect the accessibility tree and DOM when it does not. Do not add force because setInputFiles() does not offer a force option. Fix the locator or chooser flow instead of inventing options.

The first assertion checks that the browser-side UI recognized the file. The final assertion checks the application action. Add a response assertion when upload acceptance by the service is critical.

3. Playwright file upload multiple files example

A file input must include the multiple attribute to accept more than one file. Pass an array in the order the user would select files. The application may preserve or reorder that list, so assert ordering only if it is part of the requirement.

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

const fixtureDir = path.join(
  path.dirname(fileURLToPath(import.meta.url)),
  'fixtures',
);

test('attaches two evidence files', async ({ page }) => {
  await page.goto('https://app.example.test/issues/QA-17');

  await page.getByLabel('Add attachments').setInputFiles([
    path.join(fixtureDir, 'console-log.txt'),
    path.join(fixtureDir, 'failure.png'),
  ]);

  await expect(page.getByRole('listitem').filter({
    hasText: 'console-log.txt',
  })).toBeVisible();
  await expect(page.getByRole('listitem').filter({
    hasText: 'failure.png',
  })).toBeVisible();

  await page.getByRole('button', { name: 'Upload 2 files' }).click();
  await expect(page.getByRole('status')).toHaveText('2 files uploaded');
});

If a non-multiple input receives an array, the browser cannot represent the requested selection correctly and Playwright reports an error. Confirm markup rather than working around it. Tests should also cover a mixed-validity selection if the product supports it: for example, one accepted PNG and one rejected executable. Assert whether the application uploads valid files, rejects the entire batch, or asks the user to remove the invalid item.

Avoid broad list-item assertions on pages with other lists. Scope to an attachment region or use test IDs if accessible structure cannot uniquely identify it. The goal is stable user semantics, not avoiding all CSS at any cost.

4. Create in-memory files with FilePayload

An in-memory payload removes the need for a disk fixture when content is small and generated specifically for a scenario. Provide all three fields: a displayed file name, a MIME type, and a Node.js Buffer. This pattern is excellent for CSV, JSON, plain text, and minimal binary test files generated by a trusted helper.

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

test('imports generated CSV data', async ({ page }) => {
  const csv = [
    'email,role',
    'alex@example.test,viewer',
    'morgan@example.test,editor',
  ].join('\n');

  await page.goto('https://app.example.test/admin/import');
  await page.getByLabel('Choose CSV file').setInputFiles({
    name: 'users.csv',
    mimeType: 'text/csv',
    buffer: Buffer.from(csv, 'utf8'),
  });

  await page.getByRole('button', { name: 'Validate import' }).click();
  await expect(page.getByText('2 valid rows')).toBeVisible();
});

The MIME type in FilePayload is what the browser exposes for the selected File. It does not magically convert data or guarantee the backend trusts it. Real systems should validate bytes, extension, content, size, and security policy server-side. Your negative tests can deliberately mismatch the extension and MIME type to verify that the product rejects suspicious input, but use harmless content.

In-memory files make tests self-contained and easy to review. Disk fixtures are better when the actual structure is complex, such as a valid PDF, spreadsheet, image metadata case, or signed archive. Choose the representation that keeps the scenario accurate and maintainable.

You can pass an array of FilePayload objects for multiple generated files. Avoid megabytes of inline base64 or giant buffers in source code. Large upload and streaming behavior is better exercised with generated output in the test artifact directory or a dedicated test-data builder.

5. Handle a dynamic file chooser correctly

Sometimes clicking an Upload button creates the input dynamically or opens a chooser that cannot be located beforehand. In that case, start waiting for the filechooser event before the click. The ordering prevents a race in which the event occurs before the waiter exists.

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

test('uploads through a dynamic chooser', async ({ page }) => {
  await page.goto('https://app.example.test/assets');

  const chooserPromise = page.waitForEvent('filechooser');
  await page.getByRole('button', { name: 'Choose asset' }).click();
  const chooser = await chooserPromise;

  await chooser.setFiles(path.resolve('tests/fixtures/logo.png'));
  await expect(page.getByText('logo.png')).toBeVisible();
});

FileChooser.setFiles() accepts the same general file representations. Use this path when a stable input is not available before the user gesture. If the input exists in the DOM, a locator is simpler and gives a direct contract with the control.

Do not click first and then call waitForEvent(). That creates intermittent timeouts because the file chooser event is fast. Do not automate the native operating system dialog with keyboard coordinates or desktop automation. Playwright controls the browser input or chooser directly, which is cross-platform and suitable for headless CI.

If no event fires, verify that the click actually reached an enabled control and that the page did not use a drag-and-drop-only widget. A drag target is a different interaction model. Current Playwright supports file drop APIs for appropriate targets, and Playwright drag and drop examples cover the broader gesture and DataTransfer concepts.

6. Work with hidden file inputs and custom upload buttons

Many design systems hide the actual <input type=file> and style a label or button. setInputFiles() operates on the input without requiring a visible native chooser, so locate the real control when it exists. An associated label is ideal. A stable test ID on the input is acceptable when the control has no useful accessible relationship.

const validPngBytes = Buffer.from(
  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9WlZ3QAAAABJRU5ErkJggg==',
  'base64',
);
const fileInput = page.getByTestId('avatar-file-input');
await fileInput.setInputFiles({
  name: 'avatar.png',
  mimeType: 'image/png',
  buffer: Buffer.from(validPngBytes),
});

validPngBytes must be defined by a fixture helper or imported data that represents an actual safe PNG if the application validates file signatures. Buffering the text fake png with MIME type image/png is not a valid image. A shallow client that accepts it may still be rejected by the server.

Do not change the DOM with page.evaluate() merely to unhide the input. That tests a state the user never experiences and can bypass application listeners. If the page uses a label correctly, target the associated input. If the input is genuinely created only after interaction, use the file chooser event.

A hidden input that cannot be associated with the visible Upload control may signal an accessibility issue. Report it where appropriate. Test automation should not normalize inaccessible markup by default. Semantic locators are both a reliability strategy and a useful product-quality signal.

7. Clear a selected file and test replacement behavior

Pass an empty array to clear the file input. This simulates removing the current browser selection, but application UI behavior depends on its event handlers. Assert that the displayed file chip, validation state, and submit control update.

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

test('replaces an invalid attachment', async ({ page }) => {
  const input = page.getByLabel('Attachment');

  await page.goto('https://app.example.test/contact');
  await input.setInputFiles({
    name: 'script.exe',
    mimeType: 'application/octet-stream',
    buffer: Buffer.from('harmless test content'),
  });
  await expect(page.getByRole('alert')).toContainText('File type is not allowed');

  await input.setInputFiles([]);
  await expect(page.getByText('script.exe')).toBeHidden();

  await input.setInputFiles({
    name: 'details.txt',
    mimeType: 'text/plain',
    buffer: Buffer.from('Steps to reproduce'),
  });
  await expect(page.getByText('details.txt')).toBeVisible();
  await expect(page.getByRole('button', { name: 'Send' })).toBeEnabled();
});

Use harmless bytes even in security-oriented tests. An .exe name is enough to exercise an extension rule in many systems, and there is no reason to place executable content in a test repository. Coordinate deeper malware-scanning tests with security controls and isolated infrastructure.

For replacement, a single-file input normally replaces its previous selection when another file is set. A multi-file widget may append through application logic only after another user gesture. Model the actual control rather than assuming every second call appends.

8. Upload a directory with webkitdirectory

For an input with the webkitdirectory attribute, setInputFiles() accepts one directory path. Playwright reads the directory selection as the browser would expose it, including relative path metadata used by upload code. Only a single directory path is supported for such an input.

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

const currentDir = path.dirname(fileURLToPath(import.meta.url));
const importDir = path.join(currentDir, 'fixtures', 'project-import');

test('selects a project directory', async ({ page }) => {
  await page.goto('https://app.example.test/projects/import');
  await page.getByLabel('Project directory').setInputFiles(importDir);

  await expect(page.getByText('3 files selected')).toBeVisible();
  await expect(page.getByText('project-import/config.json')).toBeVisible();
});

Keep the directory fixture small and deterministic. Do not point at a developer's home directory, repository root, or generated dependency tree. That can leak sensitive data, create huge uploads, and behave differently in CI.

Directory uploads are not identical to selecting several independent files. Applications can use each file's relative path to reconstruct hierarchy. Assert one or two representative relative paths and the final import result. Cross-browser product support for directory selection should be part of the application's compatibility decision, so run this scenario in the browser projects the product officially supports.

If the directory input is created dynamically, combine the file chooser event with chooser.setFiles(directoryPath). Confirm the actual markup and Playwright behavior in the supported project rather than adding browser-specific workarounds prematurely.

9. Verify the multipart upload request and response

A visible filename proves selection, not transmission. For high-risk uploads, wait for the exact response before clicking Submit, then assert status and final UI. Keep the predicate method-aware and path-aware.

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

const validPngBytes = Buffer.from(
  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9WlZ3QAAAABJRU5ErkJggg==',
  'base64',
);

test('sends and confirms an avatar upload', async ({ page }) => {
  await page.goto('https://app.example.test/profile/avatar');

  await page.getByLabel('Avatar image').setInputFiles({
    name: 'avatar.png',
    mimeType: 'image/png',
    buffer: Buffer.from(validPngBytes),
  });

  const responsePromise = page.waitForResponse(response => {
    const url = new URL(response.url());
    return url.pathname === '/api/profile/avatar' &&
      response.request().method() === 'POST';
  });

  await page.getByRole('button', { name: 'Upload avatar' }).click();
  const response = await responsePromise;

  expect(response.status()).toBe(201);
  await expect(page.getByRole('img', { name: 'Profile avatar' })).toBeVisible();
});

Avoid manually parsing raw multipart bytes in every browser test. That duplicates protocol libraries and produces brittle assertions around boundaries. If the server must receive an exact part name, use a controlled test endpoint, server log designed for testing, or API-level contract test. The UI test can assert method, endpoint, service response, and user outcome.

To isolate frontend behavior, fulfill the upload endpoint with a controlled response using Playwright route fulfill. Validate request metadata that is practical, then assert progress, success, or error UI. Keep at least one live integration test so a mock does not conceal multipart contract drift.

Do not log raw request bodies for uploads. They may contain personal documents and can make CI logs enormous. Record safe metadata such as synthetic filename, MIME type, response status, and a generated test identifier.

10. Generate temporary files safely in parallel tests

A test may need a dynamically generated file that a library writes to disk. Use testInfo.outputPath() so each test receives a unique artifact path under Playwright's output structure. This prevents workers from overwriting a shared temp.csv.

import { test, expect } from '@playwright/test';
import { writeFile } from 'node:fs/promises';

test('uploads a generated audit file', async ({ page }, testInfo) => {
  const auditPath = testInfo.outputPath('audit.csv');
  await writeFile(auditPath, 'event,result\nlogin,passed\n', 'utf8');

  await page.goto('https://app.example.test/audits/import');
  await page.getByLabel('Audit CSV').setInputFiles(auditPath);
  await page.getByRole('button', { name: 'Import' }).click();

  await expect(page.getByRole('status')).toHaveText('1 audit event imported');
});

Using Node's file API in test code is appropriate because file generation is part of the scenario. For simple text, an in-memory FilePayload is even cleaner. Use disk output when a generator library requires a path or the artifact is helpful for debugging.

Never use one fixed filename in the repository root for parallel tests. Also avoid deleting shared directories in teardown. Playwright manages test output retention according to configuration, and CI can publish failure artifacts. If uploaded content is sensitive even when synthetic, configure retention carefully.

Generated data should be deterministic. Avoid current timestamps inside file bodies unless time is the requirement, because snapshots and server calculations can vary. Use a fixed clock or explicit date value and include a unique external record ID separately when collision prevention is needed.

11. Playwright file upload best practices for stable suites

Treat file upload as a layered behavior. Selection, client validation, request creation, server processing, scanning, persistence, and later download are distinct boundaries. One end-to-end test may cover the happy path, while focused routed tests cover size, type, service error, and retry states.

Practice Why it matters
Prefer locator.setInputFiles() Locator API is the current recommended form
Resolve absolute fixture paths Local and CI working directories can differ
Use synthetic minimal files Faster, reviewable, and privacy-safe
Register event waiters first Prevent chooser, request, and download races
Use per-test output paths Parallel workers cannot overwrite each other
Verify visible outcome Selection alone does not prove application success
Keep live boundary coverage Mocks can drift from multipart and server rules

Name fixtures by scenario, such as valid-avatar.png, oversize-metadata.jpg, or malformed-import.csv. Do not rely on extension alone when the product validates file signatures. Document how each special fixture was generated so maintainers can reproduce it safely.

Test the negative cases required by the product: unsupported extension, wrong MIME, empty file, duplicate filename, maximum boundary, server 413, scanning rejection, permission denial, and interrupted upload. Avoid enormous checked-in files. Generate boundary-size files efficiently under the test output directory or test size validation at an API/component layer when a full browser transfer adds little value.

Keep uploads isolated from real user data and production storage. Test buckets and records need lifecycle cleanup. Never place secrets in filenames, file bodies, traces, or reports. File upload automation often handles the most sensitive class of user input, so security and retention are part of test design.

Interview Questions and Answers

Q: What is the recommended way to upload a file in Playwright?

Use a locator for the file input, usually getByLabel(), and call locator.setInputFiles(pathOrPayload). The selector-based page.setInputFiles() form is discouraged. Resolve paths reliably and assert the application outcome after selection.

Q: How do you upload a file that does not exist on disk?

Pass a FilePayload with name, mimeType, and a Node.js Buffer. This is useful for generated CSV, JSON, or text data and keeps the scenario self-contained.

Q: How do you upload several files?

Pass an array of paths or FilePayload objects to an input that supports multiple. Assert each file and the batch outcome, including mixed-validity behavior if the product defines it.

Q: When should you use a filechooser event?

Use it when clicking a control dynamically creates or opens a file input that cannot be targeted beforehand. Create page.waitForEvent('filechooser') before clicking, then call chooser.setFiles().

Q: Can Playwright upload through a hidden input?

Yes. Target the actual file input with a locator or its associated label and call setInputFiles(). Do not mutate the page to make the input visible unless DOM mutation itself is the test.

Q: How do you clear a file input?

Call locator.setInputFiles([]). Then assert that filename chips, validation errors, preview state, and submit controls update as required.

Q: How do you validate that a file reached the server?

Create a method-aware waitForResponse() promise before submitting, await the exact upload response, and assert its status plus final UI. Use a server-side test hook or contract test for detailed multipart-part validation.

Q: How do you prevent file tests from colliding in parallel?

Prefer in-memory payloads or create disk files with testInfo.outputPath(). Never share one mutable temporary path across workers, and keep backend records and storage keys unique as well.

Common Mistakes

  • Using discouraged page.setInputFiles(selector, files) in new code instead of a locator.
  • Passing a relative path that works locally but resolves from a different CI working directory.
  • Waiting for the file chooser after clicking, which creates a race.
  • Automating the native operating system dialog instead of the input or chooser API.
  • Assuming a MIME label turns arbitrary bytes into a valid image, PDF, or spreadsheet.
  • Uploading multiple paths to an input that does not have the multiple capability.
  • Proving only that a filename appeared, without checking upload response or application state.
  • Writing generated files to one shared path during parallel execution.
  • Using real customer documents, credentials, or personal data as fixtures.
  • Parsing large multipart bodies in every UI test instead of validating at a suitable boundary.
  • Using executable or dangerous content for security-negative tests when harmless synthetic content is sufficient.
  • Pointing a directory upload at a home folder, repository root, or dependency tree.

Production Review Checklist: How to Upload a file in Playwright

Before merging a test, confirm that it proves an observable requirement and can fail for the right reason. Keep setup explicit, use a locator that communicates intent, and place synchronization before the action that triggers the state change. Assert the final business state, not only an intermediate implementation detail. Run the case repeatedly in a clean worker and once under CI-like resource constraints. Preserve a trace, screenshot, or response detail when a failure would otherwise be difficult to reproduce.

A reviewer should also check isolation. The test must create or identify its own data, avoid depending on execution order, and clean up server-side records when appropriate. Timeouts should express a known service-level expectation rather than hide uncertainty. If the feature has several meaningful variants, use a small data table or project matrix instead of copying the entire test. Finally, keep one clear responsibility per test so that the report names the behavior that actually broke.

Conclusion

Playwright file upload provides a direct and cross-browser way to select disk files, multiple files, generated buffers, or directories without operating the native file dialog. Prefer locator-based calls, stable absolute paths, event-first chooser handling, and safe synthetic data.

Start with one happy-path upload that asserts the server response and user-visible result. Then add focused negative scenarios for the product's file policy, using in-memory payloads or per-test output paths so the suite remains fast, private, and parallel-safe.

Interview Questions and Answers

How do you upload a file in modern Playwright?

I locate the input with a semantic Locator and call locator.setInputFiles() with an absolute path or FilePayload. I avoid the discouraged page selector form. Then I verify selection, the relevant upload response, and the user-visible result.

How do you test file upload without storing a fixture?

I use a FilePayload with name, mimeType, and Buffer. It is ideal for small generated text, CSV, or JSON inputs. For formats with signatures or complex structure, I use a valid generated or reviewed disk fixture.

What is the correct file chooser synchronization pattern?

I create `page.waitForEvent('filechooser')` before clicking the chooser trigger. After both resolve, I call fileChooser.setFiles(). Registering the waiter first prevents a race with the fast browser event.

How would you test multiple-file validation?

I use an input with multiple enabled and pass an array containing the desired valid and invalid synthetic files. I assert the product's batch policy, such as reject all, accept valid items, or request removal. I also verify the upload request and final count.

How do you make generated upload files safe for parallel workers?

I prefer in-memory payloads or create files with testInfo.outputPath(), which is unique to the test. Backend object keys and records also receive unique identifiers. No worker writes to or deletes a shared temporary path.

How do you verify server-side upload behavior?

I register a precise waitForResponse promise before submission, assert method, endpoint, and service status, then check the user outcome. Detailed multipart structure is validated through a controlled server hook, API test, or contract test rather than brittle raw-body parsing in every UI test.

How do you test a hidden file input?

I locate the real input or its properly associated label and call setInputFiles() directly. If the input is created only after interaction, I use the filechooser event. I do not alter the DOM simply to expose a hidden element.

What security concerns apply to file upload tests?

Fixtures must be synthetic and free of personal data, credentials, and executable content. Logs and traces should not capture raw uploads. Test storage needs isolation, access control, retention limits, and reliable lifecycle cleanup.

Frequently Asked Questions

How do I upload a file with Playwright setInputFiles?

Locate the file input, preferably by its accessible label, and call `setInputFiles()` with a path. Register any response waiter before submitting and assert the final application state.

Can Playwright setInputFiles upload multiple files?

Yes. Pass an array of file paths or FilePayload objects to a file input that supports multiple selection. A non-multiple input cannot accept a multi-file selection.

How do I upload a Buffer in Playwright?

Pass an object containing name, mimeType, and buffer to locator.setInputFiles(). The bytes must represent valid content if the application checks file signatures or parses the format.

How do I clear a selected file in Playwright?

Call `locator.setInputFiles([])`. Verify that the UI removes the file, resets validation, and updates submit controls according to the product requirements.

Why does setInputFiles work locally but fail in CI?

A relative path may resolve from a different working directory, the fixture may not be copied, or filename case may differ on the CI filesystem. Build an absolute path from a stable fixture location and verify the file is part of the test checkout.

Should I use page.setInputFiles or locator.setInputFiles?

Use locator.setInputFiles() for new code. The page selector form is discouraged in current Playwright documentation, while locators provide the recommended retryable targeting model.

Can Playwright upload an entire directory?

Yes, when the input has the webkitdirectory attribute. Pass one directory path and assert representative relative paths and the final import behavior.

Related Guides