File upload flows look simple until you automate them. A product may expose a clean <input type="file">, then hide it behind a custom button, embed the widget in an iframe, allow drag-and-drop only, or wrap the whole thing in a third-party component that behaves differently in each browser. Once the flow touches the operating system’s native file picker, the problem changes again, because browser automation frameworks are not general-purpose desktop automation tools.

That is why playwright vs selenium file upload testing is less about which tool has a single better API and more about which one handles the mix of upload patterns with less friction. In practice, teams need to cover hidden inputs, iframe-embedded upload widgets, browser file picker automation limits, and drag and drop file upload scenarios without turning every test into a fragile special case.

What actually makes file upload tests hard

Upload tests fail for reasons that are easy to underestimate:

  • The visible button is not the real file input.
  • The input is hidden, disabled, or detached from the DOM until a modal opens.
  • The upload widget lives inside an iframe from a third-party vendor.
  • The app uses drag-and-drop dropzones, not a clickable input.
  • The flow depends on a native OS picker, which browser automation cannot reliably control.
  • The app validates file size, MIME type, image dimensions, or server-side antivirus scanning after selection.
  • The upload runs asynchronously, so the UI changes after the file is chosen.

The first useful rule is simple:

If the app ultimately uses a real HTML file input, automation is straightforward. If it relies on the OS picker UI, the test becomes much harder, and sometimes you should redesign the application or the test strategy instead of fighting the tool.

That is the key lens for comparing Playwright and Selenium. Both can handle the DOM-level upload path well. Their differences show up in ergonomics, synchronization, iframe handling, and how much custom work you need to cover edge cases.

The short version

If your upload widget exposes a real file input in the DOM, Playwright is usually easier and more reliable to work with. It has a more direct API for setting files, auto-waiting, and dealing with frame-scoped elements. Selenium can do the same job, but it often takes more code, more waiting logic, and more debugging.

If your team needs broad browser coverage, existing Selenium infrastructure, or language flexibility, Selenium still has a place. If you care about deterministic file attachment in modern web apps, especially SPAs with iframes and custom components, Playwright tends to be the more practical choice.

For teams that want stable upload coverage without writing a lot of custom iframe and picker handling, a lower-code platform such as Endtest, an agentic AI [Test automation](https://en.wikipedia.org/wiki/Test_automation) platform, can be a relevant alternative, especially when different people on the team need to author and maintain tests without living in code.

How browser upload automation works under the hood

Most upload automation works by bypassing the native file picker and assigning files directly to the input element. This is important because browser automation tools generally cannot interact with the operating system file dialog the way a human can. The browser security model is intentionally restrictive here.

A typical upload flow looks like this:

  1. Find the file input element.
  2. Attach one or more local files to it.
  3. Wait for the UI and network to confirm the upload.
  4. Assert on the filename, preview, progress bar, or server response.

If the page uses drag-and-drop, many frameworks still rely on a hidden file input or a synthetic DataTransfer event. If the widget lives inside an iframe, you first need to scope into the frame and then target the relevant element.

Playwright for file upload testing

Playwright’s strength is that file attachment is built into the core API and works cleanly with modern web apps. The most common path is setInputFiles, which attaches a file directly to an input element.

Official docs: Playwright documentation

import { test, expect } from '@playwright/test';
test('uploads a file', async ({ page }) => {
  await page.goto('https://example.com/upload');
  await page.locator('input[type="file"]').setInputFiles('fixtures/report.pdf');
  await expect(page.getByText('report.pdf')).toBeVisible();
});

Why Playwright is strong here

  • Built-in auto-waiting, fewer manual waits around dynamic upload widgets.
  • Frame support is direct, so iframe-embedded upload controls are easier to address.
  • Good locator ergonomics, which matters when the visible UI is not the input.
  • Handles multiple files cleanly, including tests for multi-upload validation.
  • Works well with modern component libraries, where upload inputs are hidden behind custom controls.

Playwright also makes it easier to inspect frame-scoped elements when the upload widget is inside an iframe.

typescript

const frame = page.frameLocator('iframe[title="Uploader"]');
await frame.locator('input[type="file"]').setInputFiles(['fixtures/a.png', 'fixtures/b.png']);

Where Playwright still has limits

Playwright does not magically automate a native file picker dialog. If the application flow truly requires interacting with the OS picker UI, Playwright generally cannot click around inside that dialog as a user would. In most web testing, that is not a defect in Playwright, it is a consequence of browser security and test architecture.

The practical workaround is to make the test target the DOM element that receives the file, not the OS dialog. If your app or third-party widget hides that input too aggressively, you may need to expose a test-only hook or use a drag-and-drop simulation path.

Selenium for file upload testing

Selenium can also handle file uploads, but the shape of the solution depends on the language binding and the app design. For a standard file input, Selenium typically uses send_keys with a local file path. That works because the browser sees the path as the file selection input, not because Selenium is controlling the native dialog.

Official docs: Selenium documentation

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome() driver.get(‘https://example.com/upload’)

file_input = driver.find_element(By.CSS_SELECTOR, ‘input[type=”file”]’) file_input.send_keys(‘/path/to/report.pdf’)

Why Selenium can be more work

Selenium’s upload support is fine for the basic case, but teams often run into more ceremony when the UI is dynamic:

  • You may need explicit waits for the input to exist, become visible, or become enabled.
  • Frame switching is more manual.
  • Upload widgets often require extra JavaScript execution for drag-and-drop simulation.
  • Native picker workarounds tend to be brittle or environment-specific.

Selenium remains valuable when you need it for existing stack reasons, broader legacy browser support, or because your team already has mature Selenium patterns. But for upload-heavy test suites, it is usually the more verbose option.

Hidden inputs, custom buttons, and why locators matter

A common pattern is a custom button that opens a hidden file input. Test authors often click the button, then try to interact with the dialog. That approach is usually the wrong layer.

Instead, target the hidden input directly, if the app uses a real DOM file input.

Playwright example

typescript

await page.locator('input[type="file"]').setInputFiles('fixtures/avatar.jpg');

Selenium example

file_input = driver.find_element(By.CSS_SELECTOR, 'input[type="file"]')
file_input.send_keys('/path/to/avatar.jpg')

This direct approach is more stable because it avoids depending on CSS transitions, click interception, or opening a dialog that automation cannot inspect.

If the input is conditionally rendered, Playwright’s auto-waiting and locator model usually makes the code less brittle. Selenium can do it too, but the test author needs to be more deliberate about waits and stale element handling.

iframe upload testing, the common failure point

Iframe-embedded upload widgets are one of the most common places where upload tests become unreliable. Third-party services for document upload, image cropper widgets, and embedded forms frequently isolate the control inside an iframe.

Playwright with an iframe

typescript

const uploader = page.frameLocator('iframe#upload-widget');
await uploader.locator('input[type="file"]').setInputFiles('fixtures/invoice.pdf');

Selenium with an iframe

iframe = driver.find_element(By.CSS_SELECTOR, 'iframe#upload-widget')
driver.switch_to.frame(iframe)
driver.find_element(By.CSS_SELECTOR, 'input[type="file"]').send_keys('/path/to/invoice.pdf')
driver.switch_to.default_content()

The Selenium version works, but the test now has more context management to maintain. That matters in real suites where the upload flow is one step in a longer scenario and other page objects assume the driver is back in default content.

For iframe upload testing, the key comparison is not raw capability. Both tools can do it. The difference is how quickly your suite becomes maintainable as the number of embedded widgets grows.

Native file picker limitations, and what not to test

Many teams discover too late that “click upload, choose a file” is not a browser automation problem if the selection happens entirely in the native dialog. The browser security model prevents normal DOM-level scripts from reaching into the OS picker.

So what should you test?

  • That clicking upload opens the expected UI.
  • That a real file input accepts a path or file object.
  • That the app shows the chosen filename, preview, or count.
  • That validation errors appear for invalid files.
  • That the uploaded file reaches the backend and can be retrieved later.

What should you usually avoid?

  • Trying to automate every click in the OS file dialog.
  • Relying on system-specific picker behavior in cross-platform tests.
  • Using image recognition or coordinate clicks for the picker unless you are explicitly building desktop automation, not browser automation.

If your application design forces a native picker into the critical path, that is often a signal to add a test hook or expose a DOM-based upload control for automation.

Drag-and-drop file upload: the tricky but testable case

Drag-and-drop file upload zones are popular in modern UX, but they add another layer of simulation. The dropzone often listens for dragenter, dragover, and drop events, then reads files from a DataTransfer object.

Playwright approach

Playwright can often target a hidden input behind the dropzone, which is the simplest path. If the component truly requires drag events, you may need to dispatch them with files attached, or use the component’s fallback input if it exists.

Selenium approach

Selenium typically requires a JavaScript helper to create a DataTransfer object and dispatch a drop event. That is possible, but it is more custom code and more dependent on the component’s implementation.

The hardest part is not the event dispatch itself. It is that different libraries implement drag-and-drop differently. Some accept real files only, some accept synthetic events, and some expect browser-specific behavior that is not faithfully reproduced in automation.

If a drag-and-drop area has no fallback input, consider adding one for accessibility and testability. It helps users with assistive technology too.

When Playwright is the better choice

Playwright tends to win when your app has most of these traits:

  • Modern frontend framework with dynamic rendering
  • Frequent iframe-embedded widgets
  • Hidden file inputs behind custom UI
  • Multiple file attachments in one control
  • Need for stable, concise tests
  • Need to run in CI with low flake rates

It is especially strong when the upload flow is part of a broader end-to-end test that also covers routing, API-driven state changes, and visual verification.

When Selenium is still the better fit

Selenium is still a good choice when:

  • Your organization already has a large Selenium suite and the upload tests fit that ecosystem.
  • You need a language binding your team already uses heavily.
  • Legacy browser support or grid infrastructure is a requirement.
  • The test architecture is already built around page objects, explicit waits, and shared utilities.

Selenium becomes less attractive when the upload tests are highly dynamic and the team spends more time on synchronization than on assertions.

What Cypress changes, briefly

Since this topic often comes up in framework comparisons, Cypress deserves a quick note. Cypress can handle file uploads with plugins or helper libraries, but iframe-heavy flows and cross-origin constraints can make it less convenient for complex upload widgets. If your upload component lives in an iframe from another domain, the friction can be higher than with Playwright or Selenium.

That does not make Cypress a bad tool, it just means upload-heavy apps with embedded widgets deserve extra evaluation.

A practical decision table

Scenario Playwright Selenium
Standard <input type="file"> Excellent Good
Hidden input behind custom button Excellent Good, but more manual
Iframe-embedded upload widget Very good Good, more context switching
Native OS picker automation Limited, as browser tools are Limited, as browser tools are
Drag-and-drop upload zone Good, especially with fallback inputs Good, but often more custom JS
Large legacy suite already on Selenium Can integrate, but may be a migration effort Strong fit
Test maintainability for modern SPAs Usually better Depends on team discipline

Implementation patterns that reduce flakiness

A few habits make upload tests far more stable, regardless of framework:

  1. Prefer direct file input attachment over clicking through dialogs.
  2. Assert on an outcome, not just the upload action, for example the filename, progress state, or persisted record.
  3. Use small, deterministic fixture files that are checked into the repo.
  4. Test negative cases separately, such as invalid extensions or oversized files.
  5. Keep iframe selectors stable, ideally with test-specific attributes.
  6. Wait for the backend confirmation, not just the UI animation.
  7. Avoid relying on local desktop state, downloads folder content, or picker UI screenshots.

Here is a Playwright example that checks both the attachment and the visible result.

typescript

await page.locator('input[type="file"]').setInputFiles('fixtures/photo.png');
await expect(page.getByText('photo.png')).toBeVisible();
await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();

And a Selenium example that waits for upload completion.

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

file_input.send_keys(‘/path/to/photo.png’) WebDriverWait(driver, 10).until( EC.visibility_of_element_located((By.XPATH, “//*[contains(text(), ‘photo.png’)]”) ) )

CI considerations for upload tests

Upload tests often pass locally and fail in CI because of environment differences, especially when files are large, network latency is variable, or the app uses temp storage, virus scanning, or background jobs.

A good CI setup should:

  • Keep fixtures small and deterministic.
  • Run uploads against the same browser versions used in production support matrices.
  • Separate fast unit-level checks from true end-to-end upload flows.
  • Capture logs and screenshots when upload state gets stuck.
  • Make sure test machines have stable filesystem access to fixture files.

For background reading on CI, see continuous integration.

Where Endtest fits for upload-heavy teams

For teams that need stable upload coverage but do not want to hand-code every iframe and picker edge case, Endtest can be worth a look as a lower-code alternative. Its AI Test Creation Agent is designed to create editable platform-native steps, which can help teams standardize common file workflow tests without building a lot of custom framework glue.

The useful takeaway is not that low-code replaces code-based testing. It is that upload flows often involve enough UI variation that giving non-developers a maintainable way to author or adjust tests can reduce bottlenecks. That is especially true when the team wants broader coverage across file workflows, validations, and form submissions, not just one narrow path.

If you are evaluating tradeoffs more broadly, the broader comparisons on Endtest vs Selenium and Endtest vs Playwright can help frame the maintenance and ownership question from a team perspective.

Recommendation by team profile

Choose Playwright if

  • You are starting fresh on modern web testing.
  • Your app uses a lot of iframes or custom upload widgets.
  • You want concise tests and strong auto-waiting.
  • Your team values code-level control but wants less boilerplate.

Choose Selenium if

  • You already have a mature Selenium ecosystem.
  • You need the language or grid setup you already run in production-like CI.
  • Your upload tests are part of a larger legacy suite and do not justify a rewrite.

Consider Endtest if

  • You want upload coverage without maintaining a lot of custom iframe logic.
  • Different team members need to author tests, not just developers.
  • You prefer a managed platform and platform-native test steps over maintaining framework plumbing.

Final take

For playwright vs selenium file upload testing, the real question is how your app exposes the upload path. If you can attach files directly to a DOM input, both tools work. If the flow is buried in iframes, custom components, and drag-and-drop zones, Playwright usually gives you a cleaner path with less waiting and less framework noise.

Selenium is still capable, especially in established environments, but it tends to require more explicit management of frames, waits, and custom drag-and-drop helpers. Neither tool solves the native file picker problem, because that is mostly a browser and OS boundary rather than a framework gap.

If your upload tests are becoming fragile, the best fix is often not a more clever selector. It is better test design: expose a real file input, keep iframe selectors stable, use deterministic fixtures, and assert on real outcomes. That approach pays off whether you use Playwright, Selenium, or a lower-code platform like Endtest.