Skip to content

Commit 48c8ff1

Browse files
authored
[py][bidi]: add set_download_behavior command (#16556)
1 parent 3da2883 commit 48c8ff1

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

py/selenium/webdriver/common/bidi/browser.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
# KIND, either express or implied. See the License for the
1515
# specific language governing permissions and limitations
1616
# under the License.
17-
18-
17+
import os
1918
from typing import Any, Optional
2019

2120
from selenium.webdriver.common.bidi.common import command_builder
@@ -236,3 +235,46 @@ def get_client_windows(self) -> list[ClientWindowInfo]:
236235
"""
237236
result = self.conn.execute(command_builder("browser.getClientWindows", {}))
238237
return [ClientWindowInfo.from_dict(window) for window in result["clientWindows"]]
238+
239+
def set_download_behavior(
240+
self,
241+
*,
242+
allowed: Optional[bool] = None,
243+
destination_folder: Optional[str | os.PathLike] = None,
244+
user_contexts: Optional[list[str]] = None,
245+
) -> None:
246+
"""Set the download behavior for the browser or specific user contexts.
247+
248+
Args:
249+
allowed: True to allow downloads, False to deny downloads, or None to
250+
clear download behavior (revert to default).
251+
destination_folder: Required when allowed is True. Specifies the folder
252+
to store downloads in.
253+
user_contexts: Optional list of user context IDs to apply this
254+
behavior to. If omitted, updates the default behavior.
255+
256+
Raises:
257+
ValueError: If allowed=True and destination_folder is missing, or if
258+
allowed=False and destination_folder is provided.
259+
"""
260+
params: dict[str, Any] = {}
261+
262+
if allowed is None:
263+
params["downloadBehavior"] = None
264+
else:
265+
if allowed:
266+
if not destination_folder:
267+
raise ValueError("destination_folder is required when allowed=True.")
268+
params["downloadBehavior"] = {
269+
"type": "allowed",
270+
"destinationFolder": os.fspath(destination_folder),
271+
}
272+
else:
273+
if destination_folder:
274+
raise ValueError("destination_folder should not be provided when allowed=False.")
275+
params["downloadBehavior"] = {"type": "denied"}
276+
277+
if user_contexts is not None:
278+
params["userContexts"] = user_contexts
279+
280+
self.conn.execute(command_builder("browser.setDownloadBehavior", params))

py/test/selenium/webdriver/common/bidi_browser_tests.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,21 @@
1616
# under the License.
1717

1818
import http.server
19+
import os
1920
import socketserver
2021
import threading
2122

2223
import pytest
2324

25+
from selenium.common.exceptions import TimeoutException
2426
from selenium.webdriver.common.bidi.browser import ClientWindowInfo, ClientWindowState
27+
from selenium.webdriver.common.bidi.browsing_context import ReadinessState
2528
from selenium.webdriver.common.bidi.session import UserPromptHandler, UserPromptHandlerType
2629
from selenium.webdriver.common.by import By
2730
from selenium.webdriver.common.proxy import Proxy, ProxyType
2831
from selenium.webdriver.common.utils import free_port
2932
from selenium.webdriver.common.window import WindowTypes
33+
from selenium.webdriver.support.ui import WebDriverWait
3034

3135

3236
class FakeProxyHandler(http.server.SimpleHTTPRequestHandler):
@@ -262,3 +266,96 @@ def test_create_user_context_with_unhandled_prompt_behavior(driver, pages):
262266

263267
# Clean up
264268
driver.browser.remove_user_context(user_context)
269+
270+
271+
@pytest.mark.xfail_firefox
272+
def test_set_download_behavior_allowed(driver, pages, tmp_path):
273+
print(f"Driver info: {driver.capabilities}")
274+
try:
275+
driver.browser.set_download_behavior(allowed=True, destination_folder=tmp_path)
276+
277+
context_id = driver.current_window_handle
278+
url = pages.url("downloads/download.html")
279+
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
280+
281+
driver.find_element(By.ID, "file-1").click()
282+
283+
WebDriverWait(driver, 5).until(lambda d: "file_1.txt" in os.listdir(tmp_path))
284+
285+
files = os.listdir(tmp_path)
286+
assert "file_1.txt" in files, f"Expected file_1.txt in {tmp_path}, but found: {files}"
287+
finally:
288+
driver.browser.set_download_behavior(allowed=None)
289+
290+
291+
@pytest.mark.xfail_firefox
292+
def test_set_download_behavior_denied(driver, pages, tmp_path):
293+
try:
294+
driver.browser.set_download_behavior(allowed=False)
295+
296+
context_id = driver.current_window_handle
297+
url = pages.url("downloads/download.html")
298+
driver.browsing_context.navigate(context=context_id, url=url, wait=ReadinessState.COMPLETE)
299+
300+
driver.find_element(By.ID, "file-1").click()
301+
302+
try:
303+
WebDriverWait(driver, 3, poll_frequency=0.2).until(lambda _: len(os.listdir(tmp_path)) > 0)
304+
files = os.listdir(tmp_path)
305+
pytest.fail(f"A file was downloaded unexpectedly: {files}")
306+
except TimeoutException:
307+
pass # Expected, no file downloaded
308+
finally:
309+
driver.browser.set_download_behavior(allowed=None)
310+
311+
312+
@pytest.mark.xfail_firefox
313+
def test_set_download_behavior_user_context(driver, pages, tmp_path):
314+
user_context = driver.browser.create_user_context()
315+
316+
try:
317+
bc = driver.browsing_context.create(type=WindowTypes.WINDOW, user_context=user_context)
318+
driver.switch_to.window(bc)
319+
320+
try:
321+
driver.browser.set_download_behavior(
322+
allowed=True, destination_folder=tmp_path, user_contexts=[user_context]
323+
)
324+
325+
url = pages.url("downloads/download.html")
326+
driver.browsing_context.navigate(context=bc, url=url, wait=ReadinessState.COMPLETE)
327+
328+
driver.find_element(By.ID, "file-1").click()
329+
330+
WebDriverWait(driver, 5).until(lambda d: "file_1.txt" in os.listdir(tmp_path))
331+
332+
files = os.listdir(tmp_path)
333+
assert "file_1.txt" in files, f"Expected file_1.txt in {tmp_path}, but found: {files}"
334+
335+
initial_file_count = len(files)
336+
337+
driver.browser.set_download_behavior(allowed=False, user_contexts=[user_context])
338+
339+
driver.find_element(By.ID, "file-2").click()
340+
341+
try:
342+
WebDriverWait(driver, 3, poll_frequency=0.2).until(
343+
lambda _: len(os.listdir(tmp_path)) > initial_file_count
344+
)
345+
files_after = os.listdir(tmp_path)
346+
pytest.fail(f"A file was downloaded unexpectedly: {files_after}")
347+
except TimeoutException:
348+
pass # Expected, no file downloaded
349+
finally:
350+
driver.browser.set_download_behavior(allowed=None, user_contexts=[user_context])
351+
finally:
352+
driver.browser.remove_user_context(user_context)
353+
354+
355+
@pytest.mark.xfail_firefox
356+
def test_set_download_behavior_validation(driver):
357+
with pytest.raises(ValueError, match="destination_folder is required when allowed=True"):
358+
driver.browser.set_download_behavior(allowed=True)
359+
360+
with pytest.raises(ValueError, match="destination_folder should not be provided when allowed=False"):
361+
driver.browser.set_download_behavior(allowed=False, destination_folder="/tmp")

0 commit comments

Comments
 (0)