Skip to content

Commit ee708e6

Browse files
committed
[py] implement file downloads
1 parent 0d04d2e commit ee708e6

File tree

6 files changed

+122
-5
lines changed

6 files changed

+122
-5
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

+3
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@
122122
"/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials",
123123
),
124124
Command.SET_USER_VERIFIED: ("POST", "/session/$sessionId/webauthn/authenticator/$authenticatorId/uv"),
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

+15
Original file line numberDiff line numberDiff line change
@@ -1132,3 +1132,18 @@ def set_user_verified(self, verified: bool) -> None:
11321132
verified: True if the authenticator will pass user verification, False otherwise.
11331133
"""
11341134
self.execute(Command.SET_USER_VERIFIED, {"authenticatorId": self._authenticator_id, "isUserVerified": verified})
1135+
1136+
def get_downloadable_files(self) -> dict:
1137+
"""Retrieves the downloadable files as a map of file names and their corresponding URLs."""
1138+
return self.execute(Command.GET_DOWNLOADABLE_FILES)["value"]
1139+
1140+
def download_file(self, file_name: str) -> str:
1141+
"""Downloads a file with the specified file name
1142+
1143+
file_name: The name of the file to download.
1144+
"""
1145+
return self.execute(Command.DOWNLOAD_FILE, {"name": file_name})["value"]
1146+
1147+
def delete_downloadable_files(self) -> None:
1148+
"""Deletes all downloadable files."""
1149+
self.execute(Command.DELETE_DOWNLOADABLE_FILES)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 base64
18+
import io
19+
import zipfile
20+
from time import sleep
21+
22+
import pytest
23+
24+
from selenium import webdriver
25+
from selenium.webdriver.common.by import By
26+
27+
28+
def test_get_downloadable_files(driver, pages):
29+
pages.load("downloads/download.html")
30+
driver.find_element(By.ID, "file-1").click()
31+
driver.find_element(By.ID, "file-2").click()
32+
sleep(3)
33+
34+
file_names = driver.get_downloadable_files()["names"]
35+
assert "file_1.txt" in file_names
36+
assert "file_2.jpg" in file_names
37+
38+
39+
def test_download_file(driver, pages):
40+
pages.load("downloads/download.html")
41+
driver.find_element(By.ID, "file-1").click()
42+
sleep(3)
43+
44+
file_contents = driver.download_file("file_1.txt")["contents"]
45+
file_byte_data = base64.b64decode(file_contents)
46+
zip_memory = io.BytesIO(file_byte_data)
47+
48+
with zipfile.ZipFile(zip_memory, 'r') as zip_ref:
49+
for name in zip_ref.namelist():
50+
with zip_ref.open(name) as file:
51+
file_content = file.read()
52+
assert "Hello, World!" in file_content.decode("utf-8")
53+
54+
55+
def test_delete_downloadable_files(driver, pages):
56+
pages.load("downloads/download.html")
57+
driver.find_element(By.ID, "file-1").click()
58+
sleep(3)
59+
60+
driver.delete_downloadable_files()
61+
assert not driver.get_downloadable_files()["names"]

0 commit comments

Comments
 (0)