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 == 10240Chrome 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
changeevent and some wait for a button. Check which your application does.