June 11, 2026
Playwright vs Selenium for File Upload and Download Testing: Where Each Tool Gets Messy
A practical comparison of Playwright vs Selenium file upload download testing, including hidden inputs, OS dialogs, download events, CI constraints, and when Endtest is the lower-maintenance alternative.
File upload and download flows look simple in product demos, but they are often where browser automation becomes surprisingly awkward. A form field that accepts a CSV, a hidden <input type="file">, a download triggered by JavaScript, a browser permission prompt, or a CI environment with no writable disk can turn a basic end-to-end test into a maintenance task.
If you are evaluating Playwright or Selenium for Playwright vs Selenium file upload download testing, the real question is not which one can technically do the job. Both can. The question is how much custom handling, browser-specific workarounds, and pipeline discipline each approach forces on your team.
This article breaks down where each tool is straightforward, where it gets messy, and what changes when file transfer testing has to run reliably in CI, across browsers, and in real apps with imperfect UI patterns. It also looks at Endtest as a lower-maintenance alternative for teams that want editable upload and download workflows without building and maintaining custom file-handling code.
Why file transfer testing is harder than it first appears
Upload and download tests sit at the intersection of browser behavior, operating system behavior, application logic, and test runner constraints. That means a failure can originate in several different layers:
- the app hides the real file input behind custom UI
- the browser blocks a native dialog from automation
- the app validates MIME type, extension, size, or content bytes
- the test runner cannot easily inspect the downloaded file
- the CI container has a temporary file system with cleanup or permission issues
- the file path is valid locally, but not inside Docker or a remote grid
File transfer tests are less about clicking buttons and more about proving that the browser, the app, and the runtime all agree on what a file is.
A good file upload test should answer questions like these:
- Can the user select the correct file through the UI?
- Does the application accept or reject the file for the right reason?
- Is the uploaded data persisted correctly after submission?
- Does a download produce the right filename, size, and content?
- Can the test run in CI without special privileges or manual intervention?
The basic mechanics, upload versus download
Upload and download are handled very differently by automation tools.
Upload testing
Upload testing usually involves a file input. Modern test tools can bypass the native OS file picker and inject a path directly into the browser-controlled input. That is ideal because native file dialogs are outside normal DOM automation.
The hard part is not choosing the file, it is the UI around the file picker. Many apps use custom buttons, drag-and-drop zones, or hidden inputs styled away from view.
Download testing
Download testing is more complicated because the browser decides when a download starts, where it goes, and what name it gets. Some flows trigger a real file download. Others open a new tab with a PDF. Others send a blob URL from client-side code. Your test has to know which behavior is expected and then inspect the result.
That is why file download testing frequently needs extra configuration for:
- download directories
- accepting downloads in browser context
- waiting for the download event
- reading the file contents after the browser saves it
Playwright, strong primitives with a few sharp edges
Playwright is usually the smoother option for browser file handling because it offers first-class APIs for uploads and downloads. It was designed with modern browser automation in mind, so a lot of common file scenarios feel intentional rather than hacked together.
File upload in Playwright
For standard file inputs, Playwright is direct:
import { test, expect } from '@playwright/test';
import path from 'path';
test('uploads a CSV file', async ({ page }) => {
await page.goto('https://example.com/import');
const filePath = path.resolve(__dirname, 'fixtures/users.csv');
await page.locator('input[type="file"]').setInputFiles(filePath);
await page.getByRole('button', { name: 'Import' }).click();
await expect(page.getByText('Import complete')).toBeVisible();
});
This works well because Playwright does not need the native picker. It talks to the browser input directly.
If the app hides the input behind a custom button, Playwright still usually handles it well as long as the input exists in the DOM. If the app relies on drag-and-drop, Playwright can simulate the upload, but you may need to build a little more logic around the drop target.
File download in Playwright
Playwright also gives you a download event, which is useful because you can wait for the browser to initiate the file and then inspect the result:
import { test, expect } from '@playwright/test';
import fs from 'fs';
test('downloads an export file', async ({ page }) => {
await page.goto('https://example.com/reports');
const downloadPromise = page.waitForEvent(‘download’); await page.getByRole(‘button’, { name: ‘Export CSV’ }).click(); const download = await downloadPromise;
const filePath = await download.path(); expect(filePath).not.toBeNull(); const contents = fs.readFileSync(filePath!, ‘utf-8’); expect(contents).toContain(‘user_id’); });
This is one of the reasons many teams prefer Playwright for browser file handling. You can inspect the download object, save it, and assert against the file contents instead of only checking whether a click happened.
Where Playwright gets messy
Playwright is good, but not magic. The pain usually shows up in these cases:
- the app uses an actual OS file dialog, which Playwright does not automate the way a human would
- the file download is generated by a browser plugin or external application
- the test needs to validate file contents across multiple browsers and operating systems
- the CI environment has strict sandboxing or ephemeral storage limits
- the app uses nonstandard behavior, such as opening a blob URL in a separate context or downloading from an authenticated S3 link that expires quickly
You may end up writing custom helper functions for temporary directories, retries, file cleanup, and content validation. That is not a Playwright weakness exactly, it is just what happens when a flexible low-level tool meets messy real-world file flows.
Selenium, capable but more manual
Selenium can absolutely handle uploads and downloads, but it often requires more plumbing. The core issue is that Selenium is lower level for these workflows, so you may spend more time stitching together browser settings, filesystem paths, and explicit waits.
File upload in Selenium
For a visible or hidden file input, Selenium typically uses send_keys() on the input element:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome() driver.get(‘https://example.com/import’)
file_input = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, ‘input[type=”file”]’)) ) file_input.send_keys(‘/home/ci/fixtures/users.csv’) driver.find_element(By.CSS_SELECTOR, ‘button[type=”submit”]’).click()
This is simple on paper, but the surrounding app can make it difficult. If the file input is detached from the DOM, replaced dynamically, or embedded in a widget that expects drag-and-drop, you may have to fight the UI more aggressively than you would with Playwright.
File download in Selenium
Selenium does not have a built-in download API comparable to Playwright. Download testing usually means configuring the browser profile, setting a download directory, and then checking the filesystem yourself.
In Chrome, that often looks like this kind of setup:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options() prefs = { ‘download.default_directory’: ‘/tmp/downloads’, ‘download.prompt_for_download’: False, ‘safebrowsing.enabled’: True, } options.add_experimental_option(‘prefs’, prefs)
driver = webdriver.Chrome(options=options)
Then your test needs to wait for the file to appear on disk, verify that the browser finished writing it, and assert on the contents.
That extra waiting logic is where Selenium download testing often becomes brittle. If the app generates large files, or if the browser writes temporary partial files before renaming them, your assertion has to account for that behavior.
Where Selenium gets messy
Selenium’s flexibility is useful, but it often shifts the burden to the test author. Common pain points include:
- browser-specific download preferences
- differences between local runs, Docker, and remote grids
- manual handling of file completion and cleanup
- more custom helper code for file assertions
- more dependence on stable locators for hidden inputs and surrounding UI
If your team already maintains a Selenium framework, file testing may fit into your existing patterns. If not, file transfer scenarios can become the place where “just one more helper” turns into a framework of its own.
Hidden inputs, custom upload widgets, and drag-and-drop zones
A lot of product teams avoid native file inputs in the visible UI. Instead, they render a button or drop zone that triggers a hidden input behind the scenes.
This is one of the clearest dividing lines in browser file handling:
- Playwright is usually comfortable targeting the hidden input directly, or interacting with the visible proxy UI and then setting files
- Selenium can do this too, but the test is more likely to rely on DOM structure staying stable
A subtle issue here is that the visible button is often not the thing that matters. What matters is whether the hidden input exists, whether it is wired correctly, and whether the application reacts to a file selection event.
For drag-and-drop upload zones, both tools can work, but you may need to generate a DataTransfer object or fire custom events. That starts to move away from “test the user flow” and toward “mock the browser behavior,” which is a sign that the app may be harder to automate than it should be.
If your upload test needs a lot of JavaScript just to look like a file drop, the product UI is probably doing too much work in the browser layer.
CI restrictions are where the nice demo ends
The biggest difference between local testing and CI is that CI is often less forgiving.
Common CI constraints
- ephemeral workspace, files disappear after the job
- no real desktop session, so native dialogs are unavailable
- containerized browser with limited permissions
- locked-down download paths
- parallel jobs sharing directories if you are careless
- artifact collection that removes files before the test inspects them
Playwright handles many of these better out of the box because its APIs make it easier to control file paths and capture downloads programmatically. Selenium can still work, but you tend to own more of the surrounding setup.
A simple GitHub Actions example for file tests usually needs explicit artifacts and cleanup discipline:
name: e2e
on: [push]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npm test - if: failure() uses: actions/upload-artifact@v4 with: name: downloads path: ./test-artifacts/downloads
That setup is not unusual, but it reveals the hidden cost of file download testing, your test suite now cares about directories, artifacts, and job lifecycle.
What to verify beyond the click
A mature file test should validate more than whether the upload button or download link was clicked.
For uploads, verify:
- the correct file name is displayed
- the file type is accepted or rejected correctly
- validation errors appear for wrong format or excessive size
- the backend persisted the expected record or metadata
- the page behaves correctly after refresh or navigation
For downloads, verify:
- the download starts
- the filename is correct or normalized as expected
- the file contents match the expected schema or header row
- the file is not empty or truncated
- the export respects filters, locale, and encoding
A common bug class is UI success with corrupted file contents. For example, the app can show “Export ready” while the generated CSV contains the wrong delimiter, incorrect timestamp formatting, or rows out of order.
Choosing between Playwright and Selenium for file workflows
The right choice depends on what kind of team you have and where the complexity lives.
Choose Playwright if:
- you want strong built-in support for file upload and download testing
- your app uses modern browsers and you can standardize around a code-based stack
- you want fewer browser profile hacks for download paths
- you need good control over browser events and file assertions
- your team is comfortable writing and maintaining helper utilities
Choose Selenium if:
- your organization already has a mature Selenium suite
- you need broad language support and existing framework investment
- your test team already owns browser profile configuration and filesystem assertions
- you are comfortable adding custom utilities for download handling
- you need to support an older automation model and can absorb the maintenance cost
If you want a broader comparison beyond file handling, see Endtest vs Selenium and Endtest vs Playwright.
Where Endtest makes sense for upload and download workflows
For teams that need editable upload and download workflows without building custom file-handling code, Endtest is often the lower-maintenance path.
The key advantage is not that it invents a different kind of file system. The advantage is that the workflow stays platform-native and editable inside the tool, so your team spends less time maintaining browser-specific glue code and more time describing the behavior that matters.
Endtest is also useful when file workflows are part of a larger test suite that keeps changing. Its self-healing approach can reduce the maintenance burden around locators that often surround file inputs, upload buttons, or export controls. According to Endtest’s self-healing documentation, the platform can recover from broken locators when the UI changes, which is especially relevant when upload and download buttons are frequently redesigned.
That matters because file-transfer tests often fail for reasons adjacent to the file itself:
- renamed button labels
- DOM reshuffles around upload zones
- changed CSS classes for custom file pickers
- new wrapper elements inserted by a frontend framework
Endtest’s agentic AI test creation and maintenance model fits teams that do not want to keep rewriting helper code every time a file control changes shape. If your organization is migrating from an existing Selenium estate, the migrating from Selenium documentation is also worth a look, especially if file workflows are one of the more painful parts of your current suite.
A practical decision matrix
Use this short checklist when deciding how to test file transfer flows:
The app is simple and the team is code-first
Use Playwright if you want direct APIs for uploads and downloads, clean assertions, and a modern developer experience.
The app is complex, legacy-heavy, or browser-profile dependent
Selenium can work, but expect more helper code and more ongoing maintenance.
The team wants less code ownership and more editable workflows
Endtest is often the better fit, especially if file upload and download flows keep changing and you do not want to maintain custom automation plumbing.
The browser behavior is inconsistent across environments
Prioritize the tool that makes the file path, download directory, and event handling most explicit. In many cases that is Playwright, but if the bottleneck is maintenance rather than raw control, a managed platform may be the smarter tradeoff.
Final take
For Playwright vs Selenium file upload download testing, Playwright usually gives you the cleaner developer experience and the better primitives for upload and download assertions. Selenium can absolutely do the job, but you are more likely to own browser-specific configuration, filesystem polling, and extra helper code.
The more custom your file UI becomes, hidden inputs, drag-and-drop widgets, browser-mediated downloads, CI artifact rules, the more these differences matter. If your team is happy to write and maintain that plumbing, Playwright is a solid choice. If your organization already lives in Selenium, it can still be made to work, but with more friction.
If the real goal is reliable file transfer coverage without building a mini framework around it, Endtest is worth serious consideration. It is especially appealing when your QA or product team wants editable workflows, lower maintenance, and self-healing around the UI elements that file tests depend on most.
For a broader perspective on how these tools compare outside file flows, explore Endtest vs Playwright and Endtest vs Selenium, then evaluate which model matches your team’s actual maintenance budget, not just the demo scenario.