Wait for a File Download to Complete
Configure a download directory, trigger the download, and wait until the file exists and is no longer a partial file.
Medium Verified against Selenium 4.48.0
Problem
Clicking “Export CSV” starts a download. The test needs to assert on the file, but the download happens outside the DOM: there is no element to wait for, and the browser writes a temporary .crdownload or .part file first.
Solution
Point the browser at a known empty directory, click, then poll the filesystem until the expected file exists and no temporary files remain.
Local download with a filesystem wait
Selenium 4 Medium
Path downloads = Files.createTempDirectory("dl");
ChromeOptions options = new ChromeOptions();options.setExperimentalOption("prefs", Map.of( "download.default_directory", downloads.toAbsolutePath().toString(), "download.prompt_for_download", false, "safebrowsing.enabled", true));// Firefox equivalent:// FirefoxOptions ff = new FirefoxOptions();// ff.addPreference("browser.download.folderList", 2);// ff.addPreference("browser.download.dir", downloads.toString());// ff.addPreference("browser.helperApps.neverAsk.saveToDisk", "text/csv,application/pdf");
WebDriver driver = new ChromeDriver(options);driver.get("https://app.example.com/reports");driver.findElement(By.id("export-csv")).click();
Path file = new WebDriverWait(driver, Duration.ofSeconds(30)).until(d -> { try (Stream<Path> files = Files.list(downloads)) { List<Path> all = files.toList(); boolean partial = all.stream().anyMatch(p -> p.toString().endsWith(".crdownload") || p.toString().endsWith(".part")); return all.stream() .filter(p -> p.getFileName().toString().equals("report.csv")) .filter(p -> !partial) .findFirst().orElse(null); } catch (IOException e) { return null; }});assertTrue(Files.size(file) > 0);import tempfile, osfrom pathlib import Path
downloads = Path(tempfile.mkdtemp("dl"))
options = webdriver.ChromeOptions()options.add_experimental_option("prefs", { "download.default_directory": str(downloads), "download.prompt_for_download": False, "safebrowsing.enabled": True,})# Firefox:# options.set_preference("browser.download.folderList", 2)# options.set_preference("browser.download.dir", str(downloads))# options.set_preference("browser.helperApps.neverAsk.saveToDisk", "text/csv,application/pdf")
driver = webdriver.Chrome(options=options)driver.get("https://app.example.com/reports")driver.find_element(By.ID, "export-csv").click()
def downloaded(_): files = list(downloads.iterdir()) if any(f.suffix in (".crdownload", ".part", ".tmp") for f in files): return False target = downloads / "report.csv" return target if target.exists() and target.stat().st_size > 0 else False
file = WebDriverWait(driver, 30).until(downloaded)assert file.read_text().startswith("order_id,")const fs = require('fs');const os = require('os');const path = require('path');
const downloads = fs.mkdtempSync(path.join(os.tmpdir(), 'dl-'));
const options = new chrome.Options().setUserPreferences({'download.default_directory': downloads,'download.prompt_for_download': false,});const driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build();
await driver.get('https://app.example.com/reports');await driver.findElement(By.id('export-csv')).click();
const file = await driver.wait(async () => {const files = fs.readdirSync(downloads);if (files.some((f) => f.endsWith('.crdownload') || f.endsWith('.part'))) return null;const target = path.join(downloads, 'report.csv');return fs.existsSync(target) && fs.statSync(target).size > 0 ? target : null;}, 30000);var downloads = Directory.CreateTempSubdirectory("dl").FullName;
var options = new ChromeOptions();options.AddUserProfilePreference("download.default_directory", downloads);options.AddUserProfilePreference("download.prompt_for_download", false);
var driver = new ChromeDriver(options);driver.Navigate().GoToUrl("https://app.example.com/reports");driver.FindElement(By.Id("export-csv")).Click();
var file = new WebDriverWait(driver, TimeSpan.FromSeconds(30)).Until(_ =>{ var files = Directory.GetFiles(downloads); if (files.Any(f => f.EndsWith(".crdownload") || f.EndsWith(".part"))) return null; var target = Path.Combine(downloads, "report.csv"); return File.Exists(target) && new FileInfo(target).Length > 0 ? target : null;});Why It Works
Browsers write to a temporary name and rename on completion. Checking for the absence of temporary files plus the presence of the final name with non-zero size is the reliable completion signal. A fresh temporary directory per test avoids collisions and stale files.
Gotchas
- On a Grid or cloud, the file lands on the remote machine. Use Selenium 4’s managed downloads instead (
se:downloadsEnabledcapability,getDownloadableFiles()anddownloadFile()); see Grid on Docker and Kubernetes. - Headless Chrome allows downloads by default in the new headless mode; the old headless mode required a CDP
Browser.setDownloadBehaviorcall. - If the file name is dynamic, wait for “exactly one non-temporary file” instead of a fixed name.