File upload testing with Selenium WebDriver

Uploading with send_keys in Python and Java, remote grids, hidden inputs, multiple files and downloads.

Published by SampleTestFiles. About 4 minutes to read.

Selenium cannot operate the operating system's file dialog, and it does not need to. A file input accepts a path typed into it, so the standard technique is to find the <input type="file"> element and send it the absolute path of the file. Everything else in this guide is a variation on that.

Prepare the fixtures

Download a few small samples and store them with your tests: the 10 KB PDF, a 100 KB JPG and the 10 KB CSV cover most cases. Commit them to the repository so that every machine and CI agent uses identical files.

Upload a file in Python

from pathlib import Path

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

FIXTURES = Path(__file__).parent / "fixtures"

driver = webdriver.Chrome()
driver.get("https://your-app.example/documents")

file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
file_input.send_keys(str((FIXTURES / "sample-pdf-10kb.pdf").resolve()))
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

WebDriverWait(driver, 10).until(
    EC.text_to_be_present_in_element((By.ID, "uploaded-files"), "sample-pdf-10kb.pdf")
)
driver.quit()

The path must be absolute. A relative path is resolved by the browser driver, not by your test, and usually points nowhere.

Upload a file in Java

import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class UploadTest {
    public static void main(String[] args) {
        Path fixture = Paths.get("src/test/resources/fixtures/sample-pdf-10kb.pdf").toAbsolutePath();

        WebDriver driver = new ChromeDriver();
        try {
            driver.get("https://your-app.example/documents");
            driver.findElement(By.cssSelector("input[type='file']")).sendKeys(fixture.toString());
            driver.findElement(By.cssSelector("button[type='submit']")).click();

            new WebDriverWait(driver, Duration.ofSeconds(10)).until(
                ExpectedConditions.textToBePresentInElementLocated(By.id("uploaded-files"), "sample-pdf-10kb.pdf"));
        } finally {
            driver.quit();
        }
    }
}

Do not click the input

A common mistake is to click the "Browse" button first. That opens the native dialog, which Selenium cannot see or close, and the test hangs. Send the path straight to the input element without clicking anything.

Hidden inputs

Many designs hide the real file input and show a styled button or a drop zone. Selenium refuses to type into an element it considers not interactable. Make the input visible for the duration of the test:

file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
driver.execute_script(
    "arguments[0].style.display = 'block'; arguments[0].style.visibility = 'visible'; arguments[0].style.opacity = 1;",
    file_input,
)
file_input.send_keys(str(path))

This changes only how the element is displayed. The application's own upload code still runs when the file is set.

Remote browsers and grids

When the browser runs on another machine, such as Selenium Grid, a container or a cloud testing service, the path on your machine means nothing to it. A file detector tells the driver to transfer the file to the remote end first.

Python:

from selenium.webdriver.remote.file_detector import LocalFileDetector

driver.file_detector = LocalFileDetector()
file_input.send_keys(str(path))

Java:

import org.openqa.selenium.remote.LocalFileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;

((RemoteWebDriver) driver).setFileDetector(new LocalFileDetector());

Without it, an upload that passes locally fails on the grid with a "file not found" error.

Multiple files

If the input has the multiple attribute, send the paths separated by newline characters:

paths = [FIXTURES / "sample-pdf-10kb.pdf", FIXTURES / "sample-jpg-100kb.jpg"]
file_input.send_keys("\n".join(str(p.resolve()) for p in paths))

What to test

With the mechanics in place, the valuable part is the choice of files. A compact set:

  • A valid file of each accepted type, to confirm the happy path.
  • A file of a type that is not accepted, with an assertion on the error message.
  • A file over the size limit. Use a real sample, such as the 10 MB PDF, since applications that inspect content will reject a fake.
  • The zero-byte file.
  • A file with spaces in its name, with an assertion that the displayed name is intact.

A parameterised test in pytest keeps this tidy:

import pytest

CASES = [
    ("sample-pdf-10kb.pdf", True),
    ("sample-jpg-100kb.jpg", False),
    ("empty-file.txt", False),
]

@pytest.mark.parametrize("name,accepted", CASES)
def test_upload_validation(driver, name, accepted):
    driver.get("https://your-app.example/documents")
    driver.find_element(By.CSS_SELECTOR, "input[type='file']").send_keys(str((FIXTURES / name).resolve()))
    driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
    selector = ".upload-success" if accepted else ".upload-error"
    WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector)))

Verifying downloads

Selenium has no download API, so the test configures the browser to save files to a known folder without prompting, then watches that folder.

import time
from pathlib import Path

from selenium import webdriver

download_dir = Path("downloads").resolve()
download_dir.mkdir(exist_ok=True)

options = webdriver.ChromeOptions()
options.add_experimental_option("prefs", {
    "download.default_directory": str(download_dir),
    "download.prompt_for_download": False,
})
driver = webdriver.Chrome(options=options)

driver.get("https://your-app.example/documents")
driver.find_element(By.LINK_TEXT, "Download").click()

target = download_dir / "sample-pdf-10kb.pdf"
deadline = time.time() + 30
while time.time() < deadline and not target.exists():
    time.sleep(0.25)

assert target.stat().st_size == 10240

Chrome writes to a temporary .crdownload file and renames it when the download is finished, so waiting for the final name is a reliable completion signal. Asserting on the exact byte count, which is published for every sample, catches truncated downloads.

Troubleshooting

  • ElementNotInteractableException. The input is hidden. Use the script above to reveal it.
  • InvalidArgumentException: File not found. The path is relative, or the browser is remote and no file detector is set.
  • The test hangs. Something clicked the input and opened the native dialog. Remove the click.
  • The upload never starts. Some pages upload on the input's change event and some wait for a button. Check which your application does.

Files used in this guide

Sample files referred to in this guide
FileFormatSizeContentsDownload
10 KB PDF samplesample-pdf-10kb.pdf PDF 10 KB10,240 bytes Pages: 2 Download PDF
100 KB JPG samplesample-jpg-100kb.jpg JPG 100 KB102,400 bytes 580 × 434 px Download JPG
10 KB CSV samplesample-csv-10kb.csv CSV 10 KB10,240 bytes Data rows: 78 Download CSV
Empty file (0 bytes)empty-file.txt TXT 0 B0 bytes Lines: 0 Download TXT
File name with spacessample file name with spaces.txt TXT 34 B34 bytes File name length: 32 characters Download TXT

Related guides