Download tests fail silently when the click works but the browser saves the file somewhere unexpected or exits before the transfer finishes. A Selenium test should control the browser download directory, trigger one known link, and assert the final file on disk instead of only checking that the page click succeeded.
Chrome and ChromeDriver accept download preferences through ChromeOptions. The test needs an absolute, unique download folder, prompts disabled, and a wait that watches for the final filename after the temporary .crdownload state disappears.
Use browser download tests for UI wiring, Content-Disposition behavior, or flows where cookies and redirects are part of the user path. When the file contents alone are the target, Selenium upstream guidance recommends finding the link with Selenium and downloading it with an HTTP client because WebDriver does not expose local browser download progress.
Steps to test file downloads with Selenium:
- Create a local download test script.
$ cat > selenium-download-file-test.py <<'PY' from functools import partial from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from shutil import which from tempfile import TemporaryDirectory from threading import Thread from time import monotonic, sleep from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.common.by import By class QuietHandler(SimpleHTTPRequestHandler): def log_message(self, format, *args): return def wait_for_download(download_dir, filename, timeout=10): target = download_dir / filename partial = download_dir / f"{filename}.crdownload" deadline = monotonic() + timeout while monotonic() < deadline: if target.exists() and not partial.exists(): return target sleep(0.2) raise TimeoutError(f"{filename} did not finish downloading") with TemporaryDirectory() as tmpdir: root = Path(tmpdir) site_dir = root / "site" download_dir = root / "downloads" site_dir.mkdir() download_dir.mkdir() (site_dir / "report.csv").write_text("name,total\nalpha,42\n", encoding="utf-8") (site_dir / "index.html").write_text( """<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Selenium download demo</title> </head> <body> <a id="download-report" href="report.csv" download>Download report</a> </body> </html> """, encoding="utf-8", ) handler = partial(QuietHandler, directory=site_dir) server = ThreadingHTTPServer(("127.0.0.1", 0), handler) server_thread = Thread(target=server.serve_forever, daemon=True) server_thread.start() options = Options() browser_path = which("google-chrome") or which("chromium") or which("chromium-browser") if browser_path: options.binary_location = browser_path options.add_argument("--headless=new") options.add_experimental_option( "prefs", { "download.default_directory": str(download_dir), "download.prompt_for_download": False, "download.directory_upgrade": True, "safebrowsing.enabled": True, }, ) driver_path = which("chromedriver") service = ChromeService(executable_path=driver_path) if driver_path else ChromeService() driver = webdriver.Chrome(service=service, options=options) try: driver.get(f"http://127.0.0.1:{server.server_port}/index.html") driver.find_element(By.ID, "download-report").click() downloaded = wait_for_download(download_dir, "report.csv") contents = downloaded.read_text(encoding="utf-8").strip() assert contents == "name,total\nalpha,42" print(f"Downloaded file: {downloaded.name}") print(f"Saved in: {downloaded.parent.name}/") print("Contents verified: name,total | alpha,42") finally: driver.quit() server.shutdown() PYThe script uses a local HTTP server and a temporary download directory so the test does not touch a real application or leave files behind. When chromedriver is on PATH, ChromeService uses that binary; otherwise Selenium can fall back to its normal driver discovery.
- Run the script to verify the browser download path.
$ python3 selenium-download-file-test.py Downloaded file: report.csv Saved in: downloads/ Contents verified: name,total | alpha,42
- Reuse the download preferences in the project test fixture.
options = Options() options.add_argument("--headless=new") options.add_experimental_option( "prefs", { "download.default_directory": str(download_dir), "download.prompt_for_download": False, "download.directory_upgrade": True, "safebrowsing.enabled": True, }, )Use a fresh absolute directory for each test run. Shared download folders can hide stale files from earlier runs.
- Trigger the real download and wait for the final filename.
driver.get("https://app.example.net/reports") driver.find_element(By.CSS_SELECTOR, "a.download-report").click() downloaded = wait_for_download(download_dir, "report.csv")ChromeDriver does not wait for downloads before driver.quit(). Keep the browser open until the expected file exists and the temporary .crdownload file is gone.
- Assert the downloaded content before the test passes.
contents = downloaded.read_text(encoding="utf-8") assert "name,total" in contents assert "alpha,42" in contents
When the browser runs on a remote Selenium Grid node, the download directory is on the remote node. Use managed downloads for remote sessions instead of checking a local filesystem path.
Related: How to connect to a remote Selenium WebDriver - Remove the demo script if it was only used to prove the download flow.
$ rm selenium-download-file-test.py
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.