Source code for selenium_driverless.types.options

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
# modified by kaliiiiiiiiii | Aurin Aegerter
# all modifications are licensed under the license provided at LICENSE.md

import os
import pathlib
import warnings
from abc import ABCMeta
import typing
from typing import Union, Optional, List
from selenium_driverless.scripts.prefs import prefs_to_json


# noinspection PyUnreachableCode,PyUnusedLocal
[docs]class Options(metaclass=ABCMeta): """ the `webdriver.ChromeOptions` class .. warning:: options should not be reused """ use_extension: bool = True """ don't add the chrome-extension by default .. warning:: setting proxies and auth while running requires the extension to be added. As an alternative, you might use ``--proxy-server=host:port`` and `Requests-interception <https://kaliiiiiiiiii.github.io/Selenium-Driverless/api/RequestInterception/#request-interception>`_ to provide auth """ def __init__(self) -> None: self._single_proxy = None from selenium_driverless.utils.utils import IS_POSIX super().__init__() self._proxy = None # self.set_capability("pageLoadStrategy", "normal") self.mobile_options = None self._binary_location = None self._env = os.environ self._extension_paths = [] self._extensions = [] self._experimental_options = {} self._debugger_address = None self._user_data_dir = None self._downloads_dir = None self._arguments = [] self._prefs = { 'devtools': { 'preferences': { # always open devtools in undocked 'currentDockState': '"undocked"', # always open devtools with console open 'panel-selectedTab': '"console"'} }, "download_bubble": { # don't Show downloads when they're done "partial_view_enabled": False, }, "in_product_help": { "snoozed_feature": { "IPH_HighEfficiencyMode": { # disable "memory saver" # instead, limit number of open tabs # https://github.com/milahu/aiohttp_chromium/blob/61fe3150ed032ef8aa99b23dddbedaa1929c229c/src/aiohttp_chromium/client.py#L1017-L1025 "is_dismissed": True, } } }, # disable password manager popup https://stackoverflow.com/a/46602329/20443541 "credentials_enable_service": False, "profile": {"password_manager_enabled": False} } self._ignore_local_proxy = False self._auto_clean_dirs = True self._headless = False self._startup_url = "about:blank" self.add_arguments( "--no-first-run", # disable first run page # '--disable-component-update', # disable updates, breaks widevine '--no-service-autorun', # don't start a service # don't auto-reload pages on network errors, https://github.com/milahu/aiohttp_chromium/blob/61fe3150ed032ef8aa99b23dddbedaa1929c229c/src/aiohttp_chromium/client.py#L1116C9-L1118 "--disable-auto-reload", # some backgrounding tweaking '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-background-timer-throttling', '--disable-background-networking', '--no-pings', '--disable-infobars', '--disable-breakpad', # some bars tweak "--no-default-browser-check", # disable default browser message '--homepage=about:blank' # set homepage "--wm-window-animations-disabled", "--animation-duration-scale=0", # disable animations "--enable-privacy-sandbox-ads-apis", # ensure window.Fence, window.SharedStorage etc. exist, looks like chrome disables them when using automation "--disable-search-engine-choice-screen", # for chrome>=127, # "--enable-field-trial-config" # https://source.chromium.org/chromium/chromium/src/+/main:components/variations/variations_url_constants.cc # "--variations-server-url=https://clientservices.googleapis.com/chrome-variations/seed" ) if IS_POSIX: self.add_argument("--password-store=basic") self._is_remote = True @property def arguments(self) -> typing.List[str]: """ used arguments for the chrome executable """ return self._arguments
[docs] def add_argument(self, argument: str): """Adds an argument for launching chrome :param argument: argument to add """ import os if type(argument) is str: if argument[:16] == "--user-data-dir=": user_data_dir = argument[16:] if not os.path.isdir(user_data_dir): os.makedirs(user_data_dir, exist_ok=True) self._user_data_dir = user_data_dir elif argument[:24] == "--remote-debugging-port=": port = int(argument[24:]) if not self._debugger_address: self._debugger_address = f"127.0.0.1:{port}" self._is_remote = False elif argument[:17] == "--load-extension=": extensions = argument[17:].split(",") self._extension_paths.extend(extensions) return elif argument[:10] == "--headless": self._headless = True if not (len(argument) > 10 and argument[11:] == "new"): warnings.warn( 'headless without "--headless=new" might be buggy, makes you detectable & breaks proxies', DeprecationWarning) self._arguments.append(argument) else: raise ValueError("argument has to be str")
[docs] def add_arguments(self, *arguments: str): """add multiple arguments :param arguments: arguments to add """ for arg in arguments: self.add_argument(arg)
@property def prefs(self) -> dict: """the preferences as json""" return self._prefs
[docs] def update_pref(self, pref: str, value): """update a preference :param pref: name of the preference ("." dot path) :param value: the value to set the preference to """ self._prefs.update(prefs_to_json({pref: value}))
@property def user_data_dir(self) -> str: """the directory to save all browser data in. ``None`` (default) will temporarily create a directory in $temp """ return self._user_data_dir @user_data_dir.setter def user_data_dir(self, _dir: str): self._user_data_dir = _dir if _dir: self.add_argument(f"--user-data-dir={_dir}") @property def downloads_dir(self): """the default directory to download files to. .. code-block:: python _dir = os.getcwd()+"/downloads" if not os.path.isdir(_dir): os.mkdir(_dir) options = webdriver.ChromeOptions() options.downloads_dir = _dir options.update_pref("plugins.always_open_pdf_externally", True) async with webdriver.Chrome(options=options) as driver: download_data = await driver.get('https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', timeout=5) print(download_data.get("guid_file")) .. warning:: path has to be absolute """ return self._downloads_dir @downloads_dir.setter def downloads_dir(self, directory_path: str): if directory_path is None: self._downloads_dir = None else: _dir = str(pathlib.Path(directory_path)) if os.path.isfile(_dir): raise OSError("path can't point to a file") elif os.path.isdir(_dir): pass else: os.mkdir(_dir) self._downloads_dir = _dir @property def headless(self) -> bool: """ Whether chrome starts headless. defaults to ``False`` """ return self._headless @headless.setter def headless(self, value: bool) -> None: if (value is False) and self._headless: raise NotImplementedError("setting headless=True can't be undone in options atm") if value is True: self.add_argument("--headless=new") @property def startup_url(self) -> str: """ the url the first tab loads. Defaults to ``about:blank`` """ return self._startup_url @startup_url.setter def startup_url(self, url: typing.Union[str, None]): if url is None: url = "" self._startup_url = url @property def single_proxy(self): """ Set a single proxy to be applied. .. code-block:: python options = webdriver.ChromeOptions() options.single_proxy = "http://user1:passwrd1@example.proxy.com:5001/" .. warning:: - Only supported when Chrome has been started with driverless or the extension at ``selenium_driverless/files/mv3_extension`` has been loaded into the browser. - ``Socks5`` doesn't support authentication due to `crbug#1309413 <https://bugs.chromium.org/p/chromium/issues/detail?id=1309413>`__. """ return self._single_proxy @property def binary_location(self) -> str: """ path to the Chromium binary """ from selenium_driverless.utils.utils import find_chrome_executable if self._binary_location is None: self._binary_location = find_chrome_executable() return self._binary_location @binary_location.setter def binary_location(self, value: str) -> None: self._binary_location = value @property def env(self): """the env for ``subprocess.Popen, ``os.environ`` by default""" return self._env @env.setter def env(self, env): self._env = env
[docs] def add_extension(self, path: str) -> None: """Adds an extension to Chrome The extension can either be a compressed file (zip, crx, etc.) or extracted in a directory :param path: path to the extension """ extension_to_add = os.path.abspath(os.path.expanduser(path)) if os.path.exists(extension_to_add): self._extension_paths.append(extension_to_add) else: raise OSError("Path to the extension doesn't exist")
@property def debugger_address(self) -> str: """ The address of the remote devtools instance in format "host:port" Setting this value makes the driver connect to a remote browser instance (unless you set user-data-dir as well) """ return self._debugger_address @debugger_address.setter def debugger_address(self, value: str) -> None: self._debugger_address = value @single_proxy.setter def single_proxy(self, proxy: str): self._single_proxy = proxy @property def auto_clean_dirs(self) -> bool or None: """if user-data-dir should be cleaned automatically defaults to True """ return self._auto_clean_dirs @auto_clean_dirs.setter def auto_clean_dirs(self, enabled: bool = True) -> None: self._auto_clean_dirs = enabled
[docs] def enable_mobile( self, android_package: str = "com.android.chrome", android_activity: Optional[str] = None, device_serial: Optional[str] = None, ) -> None: """Enables mobile browser use for browsers that support it. :param android_activity: The name of the android package to start :param android_package: :param device_serial: .. warning:: Not Implemented yet """ raise NotImplementedError() if not android_package: raise AttributeError("android_package must be passed in") self.mobile_options = {"androidPackage": android_package} if android_activity: self.mobile_options["androidActivity"] = android_activity if device_serial: self.mobile_options["androidDeviceSerial"] = device_serial
@property def accept_insecure_certs(self) -> bool: """ :returns: whether the session accepts insecure certificates .. warning:: NotImplemented yet """ raise NotImplementedError() return self._caps.get("acceptInsecureCerts", False)
[docs] def ignore_local_proxy_environment_variables(self) -> None: """By calling this you will ignore HTTP_PROXY and HTTPS_PROXY from being picked up and used. .. warning:: NotImplemented yet """ raise NotImplementedError() self._ignore_local_proxy = True
[docs] def add_experimental_option(self, name: str, value: Union[str, int, dict, List[str]]) -> None: """Adds an experimental option which is passed to chromium. .. warning:: only ``name="prefs"`` supported. This method is deprecated and will be removed. Use :obj:`ChromeOptions.update_pref` instead. :param name: The experimental option name. :param value: The option value. """ if name == "prefs": self.prefs.update(prefs_to_json(value)) else: raise NotImplementedError()