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.
$ 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()
PY
The 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.
$ python3 selenium-download-file-test.py Downloaded file: report.csv Saved in: downloads/ Contents verified: name,total | alpha,42
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.
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.
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
$ rm selenium-download-file-test.py