Skyvern-AI / Skyvern-AI/rustwright
Downloads triggered by window.open() never reach page.expect_download()
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 890
- Forks
- 58
- Avg merge
- 20h 29m
- Merged PRs (30d)
- 12
Description
Summary
A download triggered by window.open() never reaches page.expect_download(). Playwright attributes such a download to the opener page and resolves normally; rustwright waits out the timeout.
Downloads triggered by a same-tab navigation (location.href = url), by an <a download> element, or by a blob URL all work correctly — only the window.open() path is affected.
Repro
Deterministic, local, no network. The server responds to /file.txt with Content-Disposition: attachment, so the popup never becomes a page — it becomes a download.
import asyncio, http.server, socket, sys, threading
PAGE = b"""<!doctype html><html><body>opener</body></html>"""
class H(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def do_GET(self):
if self.path.startswith("/file.txt"):
body = b"file contents"
self.send_response(200)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Content-Disposition", 'attachment; filename="hello.txt"')
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(PAGE)))
self.end_headers()
self.wfile.write(PAGE)
def log_message(self, *a): pass
def serve():
s = socket.socket(); s.bind(("127.0.0.1", 0)); p = s.getsockname()[1]; s.close()
srv = http.server.ThreadingHTTPServer(("127.0.0.1", p), H); srv.daemon_threads = True
threading.Thread(target=srv.serve_forever, daemon=True).start()
return p
async def run(module, port):
api = __import__(f"{module}.async_api", fromlist=["async_playwright"])
async with api.async_playwright() as pw:
b = await pw.chromium.launch(headless=True)
for label, js in (
("window.open(url, '_blank')", "() => window.open('/file.txt', '_blank')"),
("window.open(url)", "() => window.open('/file.txt')"),
("location.href", "() => { window.location.href = '/file.txt'; }"),
):
page = await b.new_page(accept_downloads=True)
await page.goto(f"http://127.0.0.1:{port}/")
try:
async with page.expect_download(timeout=8000) as info:
await page.evaluate(js)
dl = await info.value
print(f" {module:11s} {label:28s}: OK {dl.suggested_filename!r}")
except Exception as exc:
print(f" {module:11s} {label:28s}: {type(exc).__name__}: {str(exc)[:60]}")
await page.close()
await b.close()
PORT = serve()
for m in sys.argv[1:] or ["playwright", "rustwright"]:
asyncio.run(run(m, PORT))
Result
playwright window.open(url, '_blank') : OK 'hello.txt'
playwright window.open(url) : OK 'hello.txt'
playwright location.href : OK 'hello.txt'
rustwright window.open(url, '_blank') : TimeoutError: Timeout 8000ms exceeded while waiting for event "download"
rustwright window.open(url) : TimeoutError: Timeout 8000ms exceeded while waiting for event "download"
rustwright location.href : OK 'hello.txt'
Expected: the window.open rows resolve like the location.href row.
Environment
- rustwright 0.1.1 (PyPI) and git
ec130a64— both reproduce - playwright 1.61.0 for comparison
- Chromium headless shell 1228, Linux x86_64, Python 3.14
Why it matters
Found while evaluating rustwright as a drop-in for a Playwright suite. The app under test downloads a file by redirecting the browser to a presigned URL through window.open, which is a common pattern for "download this artifact" flows where the URL is generated server-side.
The failure is hard to attribute from the test side: the click lands, the server receives and serves the request (confirmed in the application's own logs), and only the expect_download wait times out — so it reads as a broken download feature rather than a missing event.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the async_api page.expect_download flow and trace how downloads from window.open are associated with the opener page. Compare that path with the working location.href case in the provided deterministic reproduction. Done means both window.open variants resolve page.expect_download with the expected hello.txt filename, while existing cases continue to work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, rust
- Domain
- devtools, testing-qa
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100