Skip to content

wait --download times out even when download already started (race condition) #561

@digitall-it

Description

@digitall-it

Summary

wait --download [path] always times out if the download was triggered before the command is sent. Only the pattern of calling wait --download first and then triggering the download works — but that's impossible in typical agent workflows where a click or navigation is what causes the download.

Root cause

handleWaitForDownload calls page.waitForEvent('download', { timeout }) lazily — only when the command arrives. By then the browser has already emitted the download event and the listener misses it.

This is the same race condition that was fixed for network requests in #559, where request tracking was moved from lazy (startRequestTracking()) to eager (setupPageTracking()).

Reproduction

# Open any page with a downloadable link
agent-browser open https://example.com

# Trigger a download (the download event fires immediately after the click)
agent-browser click @e5          # e.g. a "Download PDF" button

# NOW try to wait for it — always times out:
agent-browser wait --download /tmp/file.pdf
# ✗ page.waitForEvent: Timeout 25000ms exceeded while waiting for event "download"

The only pattern that accidentally works is when the round-trip latency between click and wait --download is long enough that waitForEvent is registered before the download fires — which is never reliable.

Verified on v0.15.1

✗ page.waitForEvent: Timeout 25000ms exceeded while waiting for event "download"

Fix

Register an eager page.on('download', ...) listener in setupPageTracking() (called at page creation, before any user interaction):

  • Downloads are dispatched directly to any waiting waitForDownload() caller
  • If no caller is waiting, the download is buffered
  • waitForDownload() checks the buffer first → resolves immediately if download already started

This mirrors the fix in #559 for network requests.

Tests

Three regression tests added to src/browser.test.ts:

  1. ✅ Resolves when download was triggered before calling waitForDownload()
  2. ✅ Resolves when download is triggered after calling waitForDownload() (existing flow)
  3. ✅ Rejects with timeout error when no download occurs

PR: #560

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions