Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/widgetastic/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1619,6 +1619,46 @@ def expect_response(self, url_or_predicate, *, timeout: int = 10000):
self.logger.debug("expect_response(timeout=%d)", timeout)
return self.page.expect_response(url_or_predicate, timeout=timeout)

def expect_download(self, predicate=None, *, timeout: int = 30000):
"""Context manager that waits for a download triggered by a page action.

Performs an action inside the ``with`` block and waits for the browser
to start a file download. An optional *predicate* can narrow the match
to a specific download.

Args:
predicate: Optional callable ``(Download) -> bool``. When given,
the ``Download`` object is passed to *predicate* and only a
truthy return is accepted as a match.
timeout: Maximum time to wait for the download to start in
milliseconds. Defaults to 30 000 ms (30 s).

Returns:
A context manager whose ``.value`` attribute holds the matched
``Download`` object after the block exits.

Usage::

with browser.expect_download() as download_info:
view.download_button.click()

download = download_info.value
assert ".csv" in download.suggested_filename

# With a predicate
with browser.expect_download(
lambda d: d.suggested_filename.endswith(".pdf"),
) as download_info:
view.export_pdf_button.click()

download_info.value.save_as("/tmp/report.pdf")
"""
self.logger.debug("expect_download(timeout=%d)", timeout)
kwargs = {"timeout": timeout}
if predicate is not None:
kwargs["predicate"] = predicate
return self.page.expect_download(**kwargs)

# ====================== ALERT HANDLING (TODO/FUTURE) ======================
# TODO: Implement alert handling
# def get_alert(self) -> Alert:
Expand Down
35 changes: 35 additions & 0 deletions testing/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,41 @@ def test_expect_response(browser):
browser.page.unroute(mock_url)


def test_expect_download(browser):
"""Test expect_download captures a file download triggered by JS."""
with browser.expect_download() as download_info:
browser.execute_script(
"""
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob(['test content'], {type: 'text/plain'}));
a.download = 'test_file.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
"""
)
download = download_info.value
assert download.suggested_filename == "test_file.txt"


def test_expect_download_with_predicate(browser):
"""Test expect_download with a predicate to match specific downloads."""
with browser.expect_download(
predicate=lambda d: d.suggested_filename.endswith(".csv"),
) as download_info:
browser.execute_script(
"""
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob(['a,b,c'], {type: 'text/csv'}));
a.download = 'report.csv';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
"""
)
assert download_info.value.suggested_filename == "report.csv"


# =================== OVERALL FUNCTIONALITY & BrowserParentWrapper TESTS ===================


Expand Down
Loading