Path: blob/trunk/py/selenium/webdriver/chromium/options.py
4012 views
# Licensed to the Software Freedom Conservancy (SFC) under one1# or more contributor license agreements. See the NOTICE file2# distributed with this work for additional information3# regarding copyright ownership. The SFC licenses this file4# to you under the Apache License, Version 2.0 (the5# "License"); you may not use this file except in compliance6# with the License. You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing,11# software distributed under the License is distributed on an12# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13# KIND, either express or implied. See the License for the14# specific language governing permissions and limitations15# under the License.1617import base6418import os19from typing import BinaryIO2021from selenium.webdriver.common.desired_capabilities import DesiredCapabilities22from selenium.webdriver.common.options import ArgOptions232425class ChromiumOptions(ArgOptions):26KEY = "goog:chromeOptions"2728def __init__(self) -> None:29"""Initialize ChromiumOptions with default settings."""30super().__init__()31self._binary_location: str = ""32self._extension_files: list[str] = []33self._extensions: list[str] = []34self._experimental_options: dict[str, str | int | dict | list[str]] = {}35self._debugger_address: str | None = None36self._enable_webextensions: bool = False3738@property39def binary_location(self) -> str:40"""Returns the location of the binary, otherwise an empty string."""41return self._binary_location4243@binary_location.setter44def binary_location(self, value: str) -> None:45"""Allows you to set where the chromium binary lives.4647Args:48value: Path to the Chromium binary.49"""50if not isinstance(value, str):51raise TypeError(self.BINARY_LOCATION_ERROR)52self._binary_location = value5354@property55def debugger_address(self) -> str | None:56"""Returns the address of the remote devtools instance."""57return self._debugger_address5859@debugger_address.setter60def debugger_address(self, value: str) -> None:61"""Set the address of the remote devtools instance for active wait connection.6263Args:64value: Address of remote devtools instance if any (hostname[:port]).65"""66if not isinstance(value, str):67raise TypeError("Debugger Address must be a string")68self._debugger_address = value6970@property71def extensions(self) -> list[str]:72"""Returns a list of encoded extensions that will be loaded."""7374def _decode(file_data: BinaryIO) -> str:75# Should not use base64.encodestring() which inserts newlines every76# 76 characters (per RFC 1521). Chromedriver has to remove those77# unnecessary newlines before decoding, causing performance hit.78return base64.b64encode(file_data.read()).decode("utf-8")7980encoded_extensions = []81for extension in self._extension_files:82with open(extension, "rb") as f:83encoded_extensions.append(_decode(f))8485return encoded_extensions + self._extensions8687def add_extension(self, extension: str) -> None:88"""Add the path to an extension to be extracted to ChromeDriver.8990Args:91extension: Path to the *.crx file.92"""93if extension:94extension_to_add = os.path.abspath(os.path.expanduser(extension))95if os.path.exists(extension_to_add):96self._extension_files.append(extension_to_add)97else:98raise OSError("Path to the extension doesn't exist")99else:100raise ValueError("argument can not be null")101102def add_encoded_extension(self, extension: str) -> None:103"""Add Base64-encoded string with extension data to be extracted to ChromeDriver.104105Args:106extension: Base64 encoded string with extension data.107"""108if extension:109self._extensions.append(extension)110else:111raise ValueError("argument can not be null")112113@property114def experimental_options(self) -> dict:115"""Returns a dictionary of experimental options for chromium."""116return self._experimental_options117118def add_experimental_option(self, name: str, value: str | int | dict | list[str]) -> None:119"""Adds an experimental option which is passed to chromium.120121Args:122name: The experimental option name.123value: The option value.124"""125self._experimental_options[name] = value126127@property128def enable_webextensions(self) -> bool:129"""Return whether webextension support is enabled for Chromium-based browsers."""130return self._enable_webextensions131132@enable_webextensions.setter133def enable_webextensions(self, value: bool) -> None:134"""Enables or disables webextension support for Chromium-based browsers.135136Args:137value: True to enable webextension support, False to disable.138139Notes:140- When enabled, this automatically adds the required Chromium flags:141- --enable-unsafe-extension-debugging142- --remote-debugging-pipe143- When disabled, this removes BOTH flags listed above, even if they were manually added via add_argument()144before enabling webextensions.145- Enabling --remote-debugging-pipe makes the connection b/w chromedriver146and the browser use a pipe instead of a port, disabling many CDP functionalities147like devtools148"""149self._enable_webextensions = value150if value:151# Add required flags for Chromium webextension support152required_flags = ["--enable-unsafe-extension-debugging", "--remote-debugging-pipe"]153for flag in required_flags:154if flag not in self._arguments:155self.add_argument(flag)156else:157# Remove webextension flags if disabling158flags_to_remove = ["--enable-unsafe-extension-debugging", "--remote-debugging-pipe"]159for flag in flags_to_remove:160if flag in self._arguments:161self._arguments.remove(flag)162163def to_capabilities(self) -> dict:164"""Creates a capabilities with all the options that have been set.165166Returns:167A dictionary with all set options.168"""169caps = self._caps170chrome_options = self.experimental_options.copy()171if self.mobile_options:172chrome_options.update(self.mobile_options)173chrome_options["extensions"] = self.extensions174if self.binary_location:175chrome_options["binary"] = self.binary_location176chrome_options["args"] = self._arguments177if self.debugger_address:178chrome_options["debuggerAddress"] = self.debugger_address179180caps[self.KEY] = chrome_options181182return caps183184@property185def default_capabilities(self) -> dict:186return DesiredCapabilities.CHROME.copy()187188189