Skip to content

Commit a083343

Browse files
authored
Merge 4b1b35c into c14d399
2 parents c14d399 + 4b1b35c commit a083343

File tree

6 files changed

+139
-6
lines changed

6 files changed

+139
-6
lines changed

py/conftest.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ def fin():
120120
options = get_options(driver_class, request.config)
121121
if driver_class == "Remote":
122122
options = get_options("Firefox", request.config) or webdriver.FirefoxOptions()
123+
options.set_capability("moz:firefoxOptions", {})
124+
options.enable_downloads = True
123125
if driver_class == "WebKitGTK":
124126
options = get_options(driver_class, request.config)
125127
if driver_class == "Edge":
@@ -246,7 +248,18 @@ def wait_for_server(url, timeout):
246248
except Exception:
247249
print("Starting the Selenium server")
248250
process = subprocess.Popen(
249-
["java", "-jar", _path, "standalone", "--port", "4444", "--selenium-manager", "true"]
251+
[
252+
"java",
253+
"-jar",
254+
_path,
255+
"standalone",
256+
"--port",
257+
"4444",
258+
"--selenium-manager",
259+
"true",
260+
"--enable-managed-downloads",
261+
"true",
262+
]
250263
)
251264
print(f"Selenium server running as process: {process.pid}")
252265
assert wait_for_server(url, 10), f"Timed out waiting for Selenium server at {url}"

py/selenium/webdriver/common/options.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(self, name):
2727
self.name = name
2828

2929
def __get__(self, obj, cls):
30-
if self.name in ("acceptInsecureCerts", "strictFileInteractability", "setWindowRect"):
30+
if self.name in ("acceptInsecureCerts", "strictFileInteractability", "setWindowRect", "se:downloadsEnabled"):
3131
return obj._caps.get(self.name, False)
3232
return obj._caps.get(self.name)
3333

@@ -322,6 +322,28 @@ class BaseOptions(metaclass=ABCMeta):
322322
- `None`
323323
"""
324324

325+
enable_downloads = _BaseOptionsDescriptor("se:downloadsEnabled")
326+
"""Gets and Sets whether session can download files.
327+
328+
Usage
329+
-----
330+
- Get
331+
- `self.enable_downloads`
332+
- Set
333+
- `self.enable_downloads` = `value`
334+
335+
Parameters
336+
----------
337+
`value`: `bool`
338+
339+
Returns
340+
-------
341+
- Get
342+
- `bool`
343+
- Set
344+
- `None`
345+
"""
346+
325347
def __init__(self) -> None:
326348
super().__init__()
327349
self._caps = self.default_capabilities

py/selenium/webdriver/remote/command.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ class Command:
2626
https://w3c.github.io/webdriver/
2727
"""
2828

29-
# Keep in sync with org.openqa.selenium.remote.DriverCommand
30-
3129
NEW_SESSION: str = "newSession"
3230
DELETE_SESSION: str = "deleteSession"
3331
NEW_WINDOW: str = "newWindow"
@@ -49,7 +47,6 @@ class Command:
4947
CLEAR_ELEMENT: str = "clearElement"
5048
CLICK_ELEMENT: str = "clickElement"
5149
SEND_KEYS_TO_ELEMENT: str = "sendKeysToElement"
52-
UPLOAD_FILE: str = "uploadFile"
5350
W3C_GET_CURRENT_WINDOW_HANDLE: str = "w3cGetCurrentWindowHandle"
5451
W3C_GET_WINDOW_HANDLES: str = "w3cGetWindowHandles"
5552
SET_WINDOW_RECT: str = "setWindowRect"
@@ -119,3 +116,9 @@ class Command:
119116
REMOVE_CREDENTIAL: str = "removeCredential"
120117
REMOVE_ALL_CREDENTIALS: str = "removeAllCredentials"
121118
SET_USER_VERIFIED: str = "setUserVerified"
119+
120+
# Remote File Management
121+
UPLOAD_FILE: str = "uploadFile"
122+
GET_DOWNLOADABLE_FILES: str = "getDownloadableFiles"
123+
DOWNLOAD_FILE: str = "downloadFile"
124+
DELETE_DOWNLOADABLE_FILES: str = "deleteDownloadableFiles"

py/selenium/webdriver/remote/remote_connection.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
Command.CLEAR_ELEMENT: ("POST", "/session/$sessionId/element/$id/clear"),
6060
Command.GET_ELEMENT_TEXT: ("GET", "/session/$sessionId/element/$id/text"),
6161
Command.SEND_KEYS_TO_ELEMENT: ("POST", "/session/$sessionId/element/$id/value"),
62-
Command.UPLOAD_FILE: ("POST", "/session/$sessionId/se/file"),
6362
Command.GET_ELEMENT_TAG_NAME: ("GET", "/session/$sessionId/element/$id/name"),
6463
Command.IS_ELEMENT_SELECTED: ("GET", "/session/$sessionId/element/$id/selected"),
6564
Command.IS_ELEMENT_ENABLED: ("GET", "/session/$sessionId/element/$id/enabled"),
@@ -122,6 +121,10 @@
122121
"/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials",
123122
),
124123
Command.SET_USER_VERIFIED: ("POST", "/session/$sessionId/webauthn/authenticator/$authenticatorId/uv"),
124+
Command.UPLOAD_FILE: ("POST", "/session/$sessionId/se/file"),
125+
Command.GET_DOWNLOADABLE_FILES: ("GET", "/session/$sessionId/se/files"),
126+
Command.DOWNLOAD_FILE: ("POST", "/session/$sessionId/se/files"),
127+
Command.DELETE_DOWNLOADABLE_FILES: ("DELETE", "/session/$sessionId/se/files"),
125128
}
126129

127130

py/selenium/webdriver/remote/webdriver.py

+36
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717
"""The WebDriver implementation."""
18+
import base64
1819
import contextlib
1920
import copy
21+
import os
2022
import pkgutil
2123
import types
2224
import typing
@@ -1132,3 +1134,37 @@ def set_user_verified(self, verified: bool) -> None:
11321134
verified: True if the authenticator will pass user verification, False otherwise.
11331135
"""
11341136
self.execute(Command.SET_USER_VERIFIED, {"authenticatorId": self._authenticator_id, "isUserVerified": verified})
1137+
1138+
def get_downloadable_files(self) -> dict:
1139+
"""Retrieves the downloadable files as a map of file names and their
1140+
corresponding URLs."""
1141+
if "se:downloadsEnabled" not in self.capabilities:
1142+
raise WebDriverException("You must enable downloads in order to work with downloadable files.")
1143+
1144+
return self.execute(Command.GET_DOWNLOADABLE_FILES)["value"]["names"]
1145+
1146+
def download_file(self, file_name: str, target_directory: str) -> None:
1147+
"""Downloads a file with the specified file name to the target
1148+
directory.
1149+
1150+
file_name: The name of the file to download.
1151+
target_directory: The path to the directory to save the downloaded file.
1152+
"""
1153+
if "se:downloadsEnabled" not in self.capabilities:
1154+
raise WebDriverException("You must enable downloads in order to work with downloadable files.")
1155+
1156+
if not os.path.exists(target_directory):
1157+
os.makedirs(target_directory)
1158+
1159+
contents = self.execute(Command.DOWNLOAD_FILE, {"name": file_name})["value"]["contents"]
1160+
1161+
target_file = os.path.join(target_directory, file_name)
1162+
with open(target_file, "wb") as file:
1163+
file.write(base64.b64decode(contents))
1164+
1165+
def delete_downloadable_files(self) -> None:
1166+
"""Deletes all downloadable files."""
1167+
if "se:downloadsEnabled" not in self.capabilities:
1168+
raise WebDriverException("You must enable downloads in order to work with downloadable files.")
1169+
1170+
self.execute(Command.DELETE_DOWNLOADABLE_FILES)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
import os
18+
import tempfile
19+
20+
from selenium.webdriver.common.by import By
21+
from selenium.webdriver.support.wait import WebDriverWait
22+
23+
24+
def test_get_downloadable_files(driver, pages):
25+
pages.load("downloads/download.html")
26+
driver.find_element(By.ID, "file-1").click()
27+
driver.find_element(By.ID, "file-2").click()
28+
WebDriverWait(driver, 3).until(lambda d: len(d.get_downloadable_files()) == 2)
29+
30+
file_names = driver.get_downloadable_files()
31+
32+
assert "file_1.txt" in file_names
33+
assert "file_2.jpg" in file_names
34+
35+
36+
def test_download_file(driver, pages):
37+
pages.load("downloads/download.html")
38+
driver.find_element(By.ID, "file-1").click()
39+
WebDriverWait(driver, 3).until(lambda d: d.get_downloadable_files())
40+
41+
file_name = driver.get_downloadable_files()[0]
42+
with tempfile.TemporaryDirectory() as target_directory:
43+
driver.download_file(file_name, target_directory)
44+
45+
target_file = os.path.join(target_directory, file_name)
46+
with open(target_file, "r") as file:
47+
assert "Hello, World!" in file.read()
48+
49+
50+
def test_delete_downloadable_files(driver, pages):
51+
pages.load("downloads/download.html")
52+
driver.find_element(By.ID, "file-1").click()
53+
WebDriverWait(driver, 3).until(lambda d: d.get_downloadable_files())
54+
55+
driver.delete_downloadable_files()
56+
assert not driver.get_downloadable_files()

0 commit comments

Comments
 (0)