-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
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:
- ✅ Resolves when download was triggered before calling
waitForDownload() - ✅ Resolves when download is triggered after calling
waitForDownload()(existing flow) - ✅ Rejects with timeout error when no download occurs
PR: #560