Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsIf a headless Chrome download suspends, fix it in this order: create a dedicated absolute directory that Chrome can write to, configure that path before the browser starts, grant download permission when your Selenium mode requires it, wait for the completed file, and only then call driver.quit(). ChromeDriver does not wait for downloads to finish, so closing the session can interrupt an otherwise valid transfer.
The reliable local Selenium fix
Use a directory created by your Python process, but remember that the browser process—not Selenium itself—must be able to write there. Avoid the desktop and other special system locations; ChromeDriver specifically warns against some such directories, including the home directory on Linux. Resolve the path to an absolute value before placing it in Chrome preferences.
from pathlib import Path
from selenium import webdriver
out_dir = Path.cwd() / "downloads"
out_dir.mkdir(parents=True, exist_ok=True)
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_experimental_option("prefs", {
"download.default_directory": str(out_dir.resolve()),
"download.prompt_for_download": False,
"download.directory_upgrade": True,
})
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com/report")
# Locate and click the site's download control here.
finally:
# Do not put driver.quit() here until your completion check has run.
pass
The directory preference is configuration, not proof that a file was downloaded. The page may open a new tab, require authentication, return an error document, or assign a different filename. Confirm the result on disk.
Wait for the file before quitting
Chrome commonly writes a temporary partial file while a transfer is in progress. The suffix is often .crdownload, but it is not a universal contract, so treat it as a useful signal rather than the only test. Check for the expected completed file, ensure no partial files remain, apply a timeout, and print the directory contents when the wait fails.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
import time
expected = out_dir / "report.csv"
deadline = time.monotonic() + 60
while time.monotonic() < deadline:
partials = list(out_dir.glob("*.crdownload"))
if expected.exists() and not partials:
break
time.sleep(0.25)
else:
files = [p.name for p in out_dir.iterdir()]
raise TimeoutError(
f"Download did not complete: {expected}; directory contains {files}"
)
driver.quit()
Place the wait immediately after the click or download-triggering action. A successful click only means that the browser accepted the action; it does not mean the response finished. If the server chooses names dynamically, snapshot the directory before the click, then identify the new completed file after the partial file disappears.
Diagnose a suspended download step by step
1. Record the execution mode and versions
Write down the Python version, Selenium package version, Chrome version, ChromeDriver version, operating system, container image (if any), and whether the driver is local or remote. Selenium’s Chrome guidance requires matching Chrome and ChromeDriver major versions. Selenium 4 supports Chrome v75 and later, subject to that matching-major-version requirement.
import platform
import selenium
from selenium import webdriver
print("Python/OS:", platform.python_version(), platform.platform())
print("Selenium:", selenium.__version__)
print("Chrome capabilities:", webdriver.Chrome().capabilities)
Do not leave that diagnostic browser running in production; use a short-lived session or inspect capabilities from the session you already create. In continuous integration, pin compatible browser and driver versions so an unattended update does not change download behavior.
2. Prove that the path is writable
- Create the directory before constructing
webdriver.Chrome. - Use
Path.resolve()(or an equivalent absolute path). - Give it a unique name per job when parallel runs could collide.
- Check write access as the same operating-system user that launches Chrome.
- On Windows, follow ChromeDriver’s guidance to use Windows backslash path separators.
A path on your host may not be a valid path inside a container. If Chrome runs under a service account, a directory that your interactive user can write may still be denied to the browser.
3. Check download permission for your Selenium mode
Selenium’s Python Chrome options reference documents enable_downloads for sessions that require an explicit download capability. Set it before creating the driver when it is available in your installed Selenium version, in addition to configuring Chrome’s destination:
from pathlib import Path
from selenium import webdriver
out_dir = (Path.cwd() / "downloads").resolve()
out_dir.mkdir(parents=True, exist_ok=True)
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.enable_downloads = True
options.add_experimental_option("prefs", {
"download.default_directory": str(out_dir),
"download.prompt_for_download": False,
})
driver = webdriver.Chrome(options=options)
If your installed Selenium release does not expose that option, do not silently assume it is required for every ordinary local Chrome session. Keep the preference configuration, run a minimal test, and consult the API reference for the exact version installed.
4. Confirm that the click really starts a file transfer
Inspect the result after clicking. A login redirect, an authorization error, a JavaScript exception, a new tab, or a generated filename can look like a suspended download when no download was initiated. Save the page URL and title, and list the output directory on timeout. If the site requires a user gesture or an authenticated cookie, reproduce those steps before clicking.
5. Keep the browser alive
Never put driver.quit() directly after the click unless you have an explicit completion event. ChromeDriver states that it does not wait for downloads, so quitting the session can terminate the browser while the network transfer is still active. A bounded polling loop is safer than an arbitrary long sleep because it fails clearly and does not delay successful jobs.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #3
6. Treat remote execution as two filesystems
With Selenium Grid, a hosted driver, or a container, download.default_directory points to the browser environment. It does not automatically copy the file into the Python client’s working directory. You need the remote provider’s download-retrieval mechanism or a shared volume, and the provider’s documentation determines the exact API. First verify where the browser process actually wrote the file; then verify how that service transfers it back.
BiDi, classic WebDriver, and CDP choices
BiDi: an explicit download policy when supported
Selenium’s Python BiDi browser API exposes set_download_behavior, which allows downloads and requires a destination folder. The call belongs to a BiDi connection and is not a drop-in method on every ordinary Chrome WebDriver object. A BiDi-capable application has a shape like this:
# Illustrative BiDi-context pattern; exact async setup depends on your Selenium version.
await driver.browser.set_download_behavior(
allowed=True,
destination_folder=str(out_dir),
)
Use the API signature documented for your installed Selenium release, and still wait for the completed file before closing the session. The optional user-context list can scope behavior when your application uses multiple browser contexts.
CDP: useful but version-sensitive
Older examples call Chrome DevTools Protocol commands such as Page.setDownloadBehavior or Browser.setDownloadBehavior. Selenium describes CDP support as temporary while BiDi is implemented and notes that CDP is not designed as a stable testing API. Command names and parameters can change with the browser protocol version. If a CDP snippet is unavoidable, check it against the Chrome version actually running; do not copy a command from an old post without that verification.
Recommended Free Tools
Which route should you choose?
| Situation | Preferred approach | Reason |
|---|---|---|
| Local Selenium session with a straightforward file download | Chrome preferences plus a completion wait | Smallest setup and no protocol-specific command |
| Selenium release and browser support an established BiDi connection | BiDi download behavior plus destination folder | Explicit Selenium API designed for the forward-looking standards route |
| Existing automation depends on a browser-specific DevTools feature | CDP, pinned to the browser protocol version | Can solve version-specific needs, but requires maintenance |
| Grid or container execution | Provider download transfer or shared storage | The browser’s filesystem is separate from the Python client’s filesystem |
Modern headless Chrome details
Current headless Chrome uses the same browser implementation as regular Chrome. Chrome 112 changed headless so Chrome creates platform windows without displaying them. Since Chrome 132.0.6793.0, the old headless implementation is a separate chrome-headless-shell binary. For a normal current Selenium installation, use the standard Chrome binary with --headless=new; do not add historical workarounds solely because an old headless implementation once behaved differently.
If a CI image still launches a separate shell binary intentionally, treat that as a distinct deployment and verify its download support independently. The path, permission, waiting, and remote-filesystem checks remain necessary either way.
Common symptoms and precise fixes
| Symptom | Likely check | Fix |
|---|---|---|
| No file appears | The directory does not exist, is relative, or is unwritable | Create a unique absolute directory before startup and test permissions as the Chrome user |
| A partial file remains when the script ends | quit() ran before transfer completion |
Poll for the expected file and disappearance of partial files, then quit |
| Works locally but not in Grid | The file was written inside the browser container | Configure the provider’s retrieval or a shared volume; do not inspect only the client path |
| BiDi call raises an attribute or capability error | The session is not BiDi-enabled or Selenium is too old for that API | Use the documented setup for your installed version, or use the local preference route |
| CDP command is rejected | Protocol command or parameters do not match the Chrome version | Check the installed browser protocol, or migrate the workflow to supported BiDi APIs |
| Wait times out but the page looks normal | Filename changed, a new tab opened, or an authentication/error page replaced the download | Log URL, title, windows, and directory contents; identify the new completed file rather than assuming a fixed name |
| Downloads fail only for one account or host | Browser permissions, service-account access, cookies, or server authorization | Reproduce the authenticated flow and verify filesystem permissions for that runtime identity |
Reliability and cost considerations
- Use a per-job directory in parallel CI to prevent one transfer from satisfying another job’s filename check.
- Set a timeout based on the largest legitimate file and report the observed directory state when it expires.
- Do not treat the absence of a
.crdownloadfile alone as success; a server error may create no partial file at all. - For remote browsers, budget for the provider’s file-transfer step and storage limits separately from browser time.
- Keep Chrome and ChromeDriver major versions aligned and update them together when changing the CI image.
Or skip the browser setup
If your actual output is a screenshot or PDF of a web page rather than an arbitrary attachment, ScreenshotNeo can perform the capture with one HTTP request, so there is no Selenium session, download directory, or headless-browser lifecycle to manage. It is a website screenshot API and MCP server; it does not turn an authenticated file-download workflow into an API download.
For a screenshot, use the API documented at https://screenshotneo.com/docs/:
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Plans include 1,000 shots per month free with no card; paid plans start at $5 for 3,000 shots, with every feature on every plan.
Create a free ScreenshotNeo account to try 1,000 screenshots per month without a card.
FAQ
Does enable_downloads choose the folder automatically?
No. It grants download capability where that Selenium session requires it. You still need to configure and validate the browser’s destination directory.
Why can a download be complete even when no partial suffix is visible?
Temporary naming is browser- and response-dependent. Use the expected file or a before/after directory comparison as the authoritative check, with a timeout.
Should I replace a binary download with ScreenshotNeo?
Only when the desired artifact is a rendered screenshot or PDF. ScreenshotNeo captures pages; it is not a general replacement for authenticated file downloads initiated by a website.
Frequently Asked Questions
Does enable_downloads choose the folder automatically?
No. It grants download capability where that Selenium session requires it. You still need to configure and validate the browser’s destination directory.
Why can a download be complete even when no partial suffix is visible?
Temporary naming is browser- and response-dependent. Use the expected file or a before/after directory comparison as the authoritative check, with a timeout.
Should I replace a binary download with ScreenshotNeo?
Only when the desired artifact is a rendered screenshot or PDF. ScreenshotNeo captures pages; it is not a general replacement for authenticated file downloads initiated by a website.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteQuick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

