The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →In headless chromedp, the return from chromedp.Run after a click does not mean the downloaded file is ready. Configure Chrome’s download behavior, enable download events, register a chromedp.ListenTarget listener before triggering the download, then wait for browser.EventDownloadProgress to report Completed. Use the event’s GUID to find the file and verify it on disk.
Why chromedp can return before a download finishes
A browser action and a file transfer are separate events. A click may successfully start a download and return while Chrome is still receiving or writing the file. Treating the return from chromedp.Run as proof that the file is complete creates a race: the next step may try to read a partial or nonexistent file.
Chrome reports download progress through the Browser domain. The generated cdproto API exposes behavior settings and progress states; the canonical chromedp project test configures downloads, listens for a completion event, clicks a link, and checks the resulting file. chromedp package documentation
Use download events and wait for completion
- Choose a writable directory. Create or select a dedicated directory so you can tell which files belong to the job.
- Set Chrome’s behavior before triggering the download. Use
browser.SetDownloadBehaviorwithallowAndName, the directory path, and events enabled. - Register the listener before the click or navigation. Listen for
*browser.EventDownloadProgressand react to its state. - Trigger the download. A click, navigation, or another browser action may start it.
- Wait for a completion signal or context cancellation. Handle both completed and canceled states; apply a practical deadline.
- Verify the file. With
allowAndName, use the event GUID as the filename, then check the path and any application-specific integrity requirements.
The order matters: enabling events and installing the listener before the action prevents missing a fast completion event. The chromedp download test
#1 Best Overall
Runnable Go example
This example assumes the download link is available at the supplied page and can be selected with #download. It uses a buffered channel so the listener callback does not block if the download finishes while the browser action is returning. Give the context a deadline appropriate for the expected file size and network conditions.
package main
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"time"
"github.com/chromedp/chromedp"
"github.com/chromedp/cdproto/browser"
)
func download(ctx context.Context, pageURL, downloadDir string) (string, error) {
if err := os.MkdirAll(downloadDir, 0755); err != nil {
return "", fmt.Errorf("create download directory: %w", err)
}
done := make(chan struct {
guid string
err error
}, 1)
chromedp.ListenTarget(ctx, func(v any) {
ev, ok := v.(*browser.EventDownloadProgress)
if !ok {
return
}
switch ev.State {
case browser.DownloadProgressStateCompleted:
select {
case done <- struct {
guid string
err error
}{guid: ev.GUID}:
default:
}
case browser.DownloadProgressStateCanceled:
select {
case done <- struct {
guid string
err error
}{err: errors.New("download canceled")}:
default:
}
}
})
err := chromedp.Run(ctx,
browser.SetDownloadBehavior(browser.SetDownloadBehaviorBehaviorAllowAndName).
WithDownloadPath(downloadDir).
WithEventsEnabled(true),
chromedp.Navigate(pageURL),
chromedp.Click("#download", chromedp.ByQuery),
)
if err != nil {
return "", fmt.Errorf("start download: %w", err)
}
select {
case result := <-done:
if result.err != nil {
return "", result.err
}
path := filepath.Join(downloadDir, result.guid)
if _, err := os.Stat(path); err != nil {
return "", fmt.Errorf("download completed but file is unavailable at %q: %w", path, err)
}
return path, nil
case <-ctx.Done():
return "", fmt.Errorf("waiting for download: %w", ctx.Err())
}
}
func main() {
allocCtx, cancelAlloc := chromedp.NewContext(context.Background())
defer cancelAlloc()
ctx, cancel := context.WithTimeout(allocCtx, 2*time.Minute)
defer cancel()
path, err := download(ctx, "https://example.com/page-with-download", "./downloads")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("Downloaded:", path)
}
Replace the example page URL and selector with the target page and its download control. The code uses allowAndName, so Chrome writes the file under its download GUID rather than the URL’s apparent basename. The example follows the official test pattern; the project test is the reference for the sequence, not a guarantee that a site’s own download flow uses the same selector or behavior. chromedp download test
Dependencies and context setup
Import both github.com/chromedp/chromedp and github.com/chromedp/cdproto/browser. The context passed to ListenTarget and chromedp.Run must be the same target context. In production, use a context deadline for the entire operation or derive a per-download deadline from a longer-lived browser context. A timeout bounds how long a worker can remain stuck if the site stalls or no event arrives.
Why the channel uses a result type
A completion event carries a GUID; a canceled event should be distinguishable from success. A result with an error preserves that distinction instead of using an empty GUID as an implicit cancellation marker. In a system that intentionally handles only one download at a time, a simpler buffered channel can carry the GUID, with a separate cancellation signal.
Choosing the Chrome download behavior
The Browser-domain API supports four behavior values. The generated cdproto documentation says a download path is required with allow and allowAndName; progress events are disabled by default, so explicitly enabling them is essential for this event-based method. cdproto browser API documentation
| Behavior | What it means for this workflow |
|---|---|
deny |
Disallow downloads. |
allow |
Allow downloads; provide a download path. |
allowAndName |
Allow downloads and name files using their download GUIDs; provide a download path. Useful for deterministic lookup from the completion event. |
default |
Use Chrome’s default download behavior. |
For a workflow that needs to correlate an event with a file, allowAndName avoids guessing from the source URL. Redirects, server-provided filenames, and browser naming behavior can make the original URL basename unreliable.
Rank #3
Handle multiple or concurrent downloads
Do not assume the next progress event belongs to the click you just issued if a page can start more than one download. Progress events include a GUID; retain the GUID of each download and associate its state with the right job. If your workflow can identify an expected GUID only after progress begins, maintain a small map keyed by GUID rather than consuming the first completion as a global success signal.
- One download at a time: a buffered result channel and a dedicated directory are usually enough.
- Several downloads from one page: track each GUID and completion or cancellation independently.
- Several browser jobs at once: isolate download directories per job and use separate contexts/listeners to reduce accidental file association.
After a completion event, verify the corresponding path. If correctness is important, also validate expected size, MIME type, file signature, or checksum. The event indicates Chrome’s download completed; it does not establish that the file is the content your application expected.
Filesystem polling as a fallback
If progress events are unavailable in a particular integration, polling the directory can work, but a file’s first appearance is not proof that writing has finished. Use a timeout and require file size to remain unchanged over multiple intervals before treating it as stable. This is a fallback, not as precise as receiving Chrome’s completion state, and it still needs cancellation and partial-file handling.
Rank #4
Troubleshooting common failures
No progress event arrives
- Confirm the listener was registered before the click or navigation.
- Confirm the behavior call used
WithEventsEnabled(true); the API field defaults to false. - Check that the event callback is listening on the target context used for the download.
- Keep the context alive until the completion wait ends.
The click succeeds but the file is missing
- Do not treat the click or
chromedp.Runreturn as completion. - Check that the path exists and is writable by the browser process.
- With
allowAndName, inspect the GUID path rather than expecting the URL filename. - Verify the page actually initiated a download rather than opening content in a tab or returning an error page.
The operation hangs indefinitely
Set a context deadline and return its error when the wait expires. A timeout is a safety boundary for a stalled request or a missed event, not proof that Chrome canceled the network transfer. Decide whether the caller should retry, cancel the browser context, or inspect the download directory before retrying to avoid confusing a late file with a new attempt. chromedp issue discussing download behavior and waiting
The browser reports cancellation
Handle DownloadProgressStateCanceled explicitly and surface it as a failed download. Inspect the site response and browser logs, confirm the target remains available, and avoid reading a partial file as a successful result.
Performance, reliability, and cost considerations
- Bound each wait: choose a deadline based on expected download size and the slowest acceptable network, rather than allowing a worker to block forever.
- Limit concurrency deliberately: concurrent downloads need per-GUID state and directory isolation, and consume browser, disk, and network resources.
- Clean up intentionally: remove temporary files after validation or retain them according to the application’s needs; do not reuse a shared directory without a strategy for identifying stale files.
- Validate before consuming: completion is a transport-level signal, not a guarantee of file type, content, or business correctness.
- Use the right mechanism: events are the direct completion signal; polling adds filesystem checks and stability delays.
Or skip the browser setup
If the job is to capture a page rather than download a file through Chrome, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. For example, the cURL call below saves a WebP screenshot; see the ScreenshotNeo documentation for parameters and response details.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
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
ScreenshotNeo accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
Sign up for 1,000 free screenshots a month—no card required.
Frequently Asked Questions
Which event state means Chrome finished downloading?
Use browser.DownloadProgressStateCompleted from browser.EventDownloadProgress.
Does allowAndName preserve the downloaded filename?
No. It names the file with the download GUID, which you can use to locate it deterministically.
Free tools Windows power users keep installed
One-click scans. No signup required.
Can I use a different language with ScreenshotNeo?
Yes. The API accepts a GET request, and the documentation includes request options for integrating it with your application.
Quick 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.

