Skip to content
Merged
13 changes: 7 additions & 6 deletions py/selenium/webdriver/chrome/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ def __init__(
service: Optional[Service] = None,
keep_alive: bool = True,
) -> None:
"""Creates a new instance of the chrome driver. Starts the service and
then creates new instance of chrome driver.
"""Creates a new instance of the chrome driver.

:Args:
- options - this takes an instance of ChromeOptions
- service - Service object for handling the browser driver if you need to pass extra details
- keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
Starts the service and then creates new instance of chrome driver.

Args:
options: This takes an instance of ChromeOptions.
service: Service object for handling the browser driver if you need to pass extra details.
keep_alive: Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
"""
service = service if service else Service()
options = options if options else Options()
Expand Down
75 changes: 38 additions & 37 deletions py/selenium/webdriver/chromium/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ChromiumOptions(ArgOptions):
KEY = "goog:chromeOptions"

def __init__(self) -> None:
"""Initialize ChromiumOptions with default settings."""
super().__init__()
self._binary_location: str = ""
self._extension_files: list[str] = []
Expand All @@ -37,42 +38,46 @@ def __init__(self) -> None:

@property
def binary_location(self) -> str:
""":Returns: The location of the binary, otherwise an empty string."""
"""Returns:
The location of the binary, otherwise an empty string.
"""
return self._binary_location

@binary_location.setter
def binary_location(self, value: str) -> None:
"""Allows you to set where the chromium binary lives.

Parameters:
----------
value: path to the Chromium binary
Args:
value: Path to the Chromium binary.
"""
if not isinstance(value, str):
raise TypeError(self.BINARY_LOCATION_ERROR)
self._binary_location = value

@property
def debugger_address(self) -> Optional[str]:
""":Returns: The address of the remote devtools instance."""
"""Returns:
The address of the remote devtools instance.
"""
return self._debugger_address

@debugger_address.setter
def debugger_address(self, value: str) -> None:
"""Allows you to set the address of the remote devtools instance that
the ChromeDriver instance will try to connect to during an active wait.

Parameters:
----------
value: address of remote devtools instance if any (hostname[:port])
Args:
value: Address of remote devtools instance if any (hostname[:port]).
"""
if not isinstance(value, str):
raise TypeError("Debugger Address must be a string")
self._debugger_address = value

@property
def extensions(self) -> list[str]:
""":Returns: A list of encoded extensions that will be loaded."""
"""Returns:
A list of encoded extensions that will be loaded.
"""

def _decode(file_data: BinaryIO) -> str:
# Should not use base64.encodestring() which inserts newlines every
Expand All @@ -91,9 +96,8 @@ def add_extension(self, extension: str) -> None:
"""Adds the path to the extension to a list that will be used to
extract it to the ChromeDriver.

Parameters:
----------
extension: path to the \\*.crx file
Args:
extension: Path to the \\*.crx file.
"""
if extension:
extension_to_add = os.path.abspath(os.path.expanduser(extension))
Expand All @@ -108,9 +112,8 @@ def add_encoded_extension(self, extension: str) -> None:
"""Adds Base64 encoded string with extension data to a list that will
be used to extract it to the ChromeDriver.

Parameters:
----------
extension: Base64 encoded string with extension data
Args:
extension: Base64 encoded string with extension data.
"""
if extension:
self._extensions.append(extension)
Expand All @@ -119,22 +122,24 @@ def add_encoded_extension(self, extension: str) -> None:

@property
def experimental_options(self) -> dict:
""":Returns: A dictionary of experimental options for chromium."""
"""Returns:
A dictionary of experimental options for chromium.
"""
return self._experimental_options

def add_experimental_option(self, name: str, value: Union[str, int, dict, list[str]]) -> None:
"""Adds an experimental option which is passed to chromium.

Parameters:
----------
name: The experimental option name.
value: The option value.
Args:
name: The experimental option name.
value: The option value.
"""
self._experimental_options[name] = value

@property
def enable_webextensions(self) -> bool:
""":Returns: Whether webextension support is enabled for Chromium-based browsers.
"""Returns:
Whether webextension support is enabled for Chromium-based browsers.
True if webextension support is enabled, False otherwise.
"""
return self._enable_webextensions
Expand All @@ -143,21 +148,18 @@ def enable_webextensions(self) -> bool:
def enable_webextensions(self, value: bool) -> None:
"""Enables or disables webextension support for Chromium-based browsers.

Parameters:
----------
value : bool
True to enable webextension support, False to disable.
Args:
value: True to enable webextension support, False to disable.

Notes:
-----
- When enabled, this automatically adds the required Chromium flags:
- --enable-unsafe-extension-debugging
- --remote-debugging-pipe
- When disabled, this removes BOTH flags listed above, even if they were manually added via add_argument()
before enabling webextensions.
- Enabling --remote-debugging-pipe makes the connection b/w chromedriver
and the browser use a pipe instead of a port, disabling many CDP functionalities
like devtools
- When enabled, this automatically adds the required Chromium flags:
- --enable-unsafe-extension-debugging
- --remote-debugging-pipe
- When disabled, this removes BOTH flags listed above, even if they were manually added via add_argument()
before enabling webextensions.
- Enabling --remote-debugging-pipe makes the connection b/w chromedriver
and the browser use a pipe instead of a port, disabling many CDP functionalities
like devtools
"""
self._enable_webextensions = value
if value:
Expand All @@ -174,11 +176,10 @@ def enable_webextensions(self, value: bool) -> None:
self._arguments.remove(flag)

def to_capabilities(self) -> dict:
"""Creates a capabilities with all the options that have been set
"""Creates a capabilities with all the options that have been set.

Returns:
-------
dict : a dictionary with all set options
A dictionary with all set options.
"""
caps = self._caps
chrome_options = self.experimental_options.copy()
Expand Down
107 changes: 53 additions & 54 deletions py/selenium/webdriver/chromium/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ def __init__(
"""Creates a new WebDriver instance of the ChromiumDriver. Starts the
service and then creates new WebDriver instance of ChromiumDriver.

:Args:
- browser_name - Browser name used when matching capabilities.
- vendor_prefix - Company prefix to apply to vendor-specific WebDriver extension commands.
- options - this takes an instance of ChromiumOptions
- service - Service object for handling the browser driver if you need to pass extra details
- keep_alive - Whether to configure ChromiumRemoteConnection to use HTTP keep-alive.
Args:
browser_name: Browser name used when matching capabilities.
vendor_prefix: Company prefix to apply to vendor-specific WebDriver extension commands.
options: This takes an instance of ChromiumOptions.
service: Service object for handling the browser driver if you need to pass extra details.
keep_alive: Whether to configure ChromiumRemoteConnection to use HTTP keep-alive.
"""

self.service = service if service else ChromiumService()
Expand Down Expand Up @@ -76,33 +76,34 @@ def __init__(
self._is_remote = False

def launch_app(self, id):
"""Launches Chromium app specified by id."""
"""Launches Chromium app specified by id.

Args:
id: The id of the Chromium app to launch.
"""
return self.execute("launchApp", {"id": id})

def get_network_conditions(self):
"""Gets Chromium network emulation settings.

:Returns:
A dict.
For example: {'latency': 4, 'download_throughput': 2, 'upload_throughput': 2, 'offline': False}
Returns:
A dict. For example: {'latency': 4, 'download_throughput': 2, 'upload_throughput': 2}
"""
return self.execute("getNetworkConditions")["value"]

def set_network_conditions(self, **network_conditions) -> None:
"""Sets Chromium network emulation settings.

:Args:
- network_conditions: A dict with conditions specification.

:Usage:
::
Args:
**network_conditions: A dict with conditions specification.

driver.set_network_conditions(
offline=False,
latency=5, # additional latency (ms)
download_throughput=500 * 1024, # maximal throughput
upload_throughput=500 * 1024,
) # maximal throughput
Example:
driver.set_network_conditions(
offline=False,
latency=5, # additional latency (ms)
download_throughput=500 * 1024, # maximal throughput
upload_throughput=500 * 1024,
) # maximal throughput

Note: 'throughput' can be used to set both (for download and upload).
"""
Expand All @@ -115,14 +116,12 @@ def delete_network_conditions(self) -> None:
def set_permissions(self, name: str, value: str) -> None:
"""Sets Applicable Permission.

:Args:
- name: The item to set the permission on.
- value: The value to set on the item

:Usage:
::
Args:
name: The item to set the permission on.
value: The value to set on the item

driver.set_permissions("clipboard-read", "denied")
Example:
driver.set_permissions("clipboard-read", "denied")
"""
self.execute("setPermissions", {"descriptor": {"name": name}, "state": value})

Expand All @@ -132,26 +131,26 @@ def execute_cdp_cmd(self, cmd: str, cmd_args: dict):
domains/commands, refer to link
https://chromedevtools.github.io/devtools-protocol/

:Args:
- cmd: A str, command name
- cmd_args: A dict, command args. empty dict {} if there is no command args
:Usage:
::
Args:
cmd: A str, command name
cmd_args: A dict, command args. empty dict {} if there is no command args

Example:
driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})

driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})
:Returns:
Returns:
A dict, empty dict {} if there is no result to return.
For example to getResponseBody:
{'base64Encoded': False, 'body': 'response body string'}
"""
return super().execute_cdp_cmd(cmd, cmd_args)

def get_sinks(self) -> list:
""":Returns: A list of sinks available for Cast."""
"""Returns: A list of sinks available for Cast."""
return self.execute("getSinks")["value"]

def get_issue_message(self):
""":Returns: An error message when there is any issue in a Cast
"""Returns: An error message when there is any issue in a Cast
session."""
return self.execute("getIssueMessage")["value"]

Expand All @@ -168,50 +167,47 @@ def log_types(self):
def get_log(self, log_type):
"""Gets the log for a given log type.

Parameters:
-----------
log_type : str
- Type of log that which will be returned
Args:
log_type: Type of log that which will be returned

Example:
--------
>>> driver.get_log("browser")
>>> driver.get_log("driver")
>>> driver.get_log("client")
>>> driver.get_log("server")
>>> driver.get_log("browser")
>>> driver.get_log("driver")
>>> driver.get_log("client")
>>> driver.get_log("server")
"""
return self.execute(Command.GET_LOG, {"type": log_type})["value"]

def set_sink_to_use(self, sink_name: str) -> dict:
"""Sets a specific sink, using its name, as a Cast session receiver
target.

:Args:
- sink_name: Name of the sink to use as the target.
Args:
sink_name: Name of the sink to use as the target.
"""
return self.execute("setSinkToUse", {"sinkName": sink_name})

def start_desktop_mirroring(self, sink_name: str) -> dict:
"""Starts a desktop mirroring session on a specific receiver target.

:Args:
- sink_name: Name of the sink to use as the target.
Args:
sink_name: Name of the sink to use as the target.
"""
return self.execute("startDesktopMirroring", {"sinkName": sink_name})

def start_tab_mirroring(self, sink_name: str) -> dict:
"""Starts a tab mirroring session on a specific receiver target.

:Args:
- sink_name: Name of the sink to use as the target.
Args:
sink_name: Name of the sink to use as the target.
"""
return self.execute("startTabMirroring", {"sinkName": sink_name})

def stop_casting(self, sink_name: str) -> dict:
"""Stops the existing Cast session on a specific receiver target.

:Args:
- sink_name: Name of the sink to stop the Cast session.
Args:
sink_name: Name of the sink to stop the Cast session.
"""
return self.execute("stopCasting", {"sinkName": sink_name})

Expand All @@ -226,10 +222,13 @@ def quit(self) -> None:
self.service.stop()

def download_file(self, *args, **kwargs):
"""Download file functionality is not implemented for Chromium driver."""
raise NotImplementedError

def get_downloadable_files(self, *args, **kwargs):
"""Get downloadable files functionality is not implemented for Chromium driver."""
raise NotImplementedError

def delete_downloadable_files(self, *args, **kwargs):
"""Delete downloadable files functionality is not implemented for Chromium driver."""
raise NotImplementedError
Loading