From 460cf1a684ff175ee51b6c05355840ddee2f9054 Mon Sep 17 00:00:00 2001 From: titusfortner Date: Fri, 14 Jul 2023 10:52:00 -0500 Subject: [PATCH] [py] implement proxy and keep alive with client config --- .../webdriver/chrome/remote_connection.py | 7 +- py/selenium/webdriver/chrome/webdriver.py | 10 +- .../webdriver/chromium/remote_connection.py | 8 +- py/selenium/webdriver/chromium/webdriver.py | 19 +++- py/selenium/webdriver/common/options.py | 19 +++- .../webdriver/edge/remote_connection.py | 7 +- py/selenium/webdriver/edge/webdriver.py | 8 +- .../webdriver/firefox/remote_connection.py | 12 +- py/selenium/webdriver/firefox/webdriver.py | 17 ++- py/selenium/webdriver/ie/webdriver.py | 15 ++- py/selenium/webdriver/remote/client_config.py | 58 ++++++++++ .../webdriver/remote/remote_connection.py | 106 ++++++++++++------ py/selenium/webdriver/remote/webdriver.py | 31 +++-- .../webdriver/safari/remote_connection.py | 12 +- py/selenium/webdriver/safari/webdriver.py | 20 +++- 15 files changed, 268 insertions(+), 81 deletions(-) create mode 100644 py/selenium/webdriver/remote/client_config.py diff --git a/py/selenium/webdriver/chrome/remote_connection.py b/py/selenium/webdriver/chrome/remote_connection.py index f289ff1f5e400..7f2fbf642a05a 100644 --- a/py/selenium/webdriver/chrome/remote_connection.py +++ b/py/selenium/webdriver/chrome/remote_connection.py @@ -18,6 +18,7 @@ from selenium.webdriver import DesiredCapabilities from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection +from selenium.webdriver.remote.client_config import ClientConfig class ChromeRemoteConnection(ChromiumRemoteConnection): @@ -26,8 +27,9 @@ class ChromeRemoteConnection(ChromiumRemoteConnection): def __init__( self, remote_server_addr: str, - keep_alive: bool = True, - ignore_proxy: typing.Optional[bool] = False, + keep_alive: typing.Optional[bool] = None, + ignore_proxy: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: super().__init__( remote_server_addr=remote_server_addr, @@ -35,4 +37,5 @@ def __init__( browser_name="chrome", keep_alive=keep_alive, ignore_proxy=ignore_proxy, + client_config=client_config, ) diff --git a/py/selenium/webdriver/chrome/webdriver.py b/py/selenium/webdriver/chrome/webdriver.py index 517f14a1d7097..09f363e3b2781 100644 --- a/py/selenium/webdriver/chrome/webdriver.py +++ b/py/selenium/webdriver/chrome/webdriver.py @@ -14,9 +14,11 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import typing from selenium.webdriver.chromium.webdriver import ChromiumDriver +from ..remote.client_config import ClientConfig from .options import Options from .service import Service @@ -28,7 +30,8 @@ def __init__( self, options: Options = None, service: Service = None, - keep_alive: bool = True, + keep_alive: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: """Creates a new instance of the chrome driver. Starts the service and then creates new instance of chrome driver. @@ -36,9 +39,10 @@ def __init__( :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. + - keep_alive - Deprecated: Whether to configure ChromeRemoteConnection to use HTTP keep-alive. + - client_config - configuration values for the http client """ service = service if service else Service() options = options if options else Options() - super().__init__(options=options, service=service, keep_alive=keep_alive) + super().__init__(options=options, service=service, keep_alive=keep_alive, client_config=client_config) diff --git a/py/selenium/webdriver/chromium/remote_connection.py b/py/selenium/webdriver/chromium/remote_connection.py index c2e7c9a614d46..ee896dd0067a5 100644 --- a/py/selenium/webdriver/chromium/remote_connection.py +++ b/py/selenium/webdriver/chromium/remote_connection.py @@ -16,6 +16,7 @@ # under the License. import typing +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -25,10 +26,11 @@ def __init__( remote_server_addr: str, vendor_prefix: str, browser_name: str, - keep_alive: bool = True, - ignore_proxy: typing.Optional[bool] = False, + keep_alive: typing.Optional[bool] = None, + ignore_proxy: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: - super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy) + super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy, client_config=client_config) self.browser_name = browser_name self._commands["launchApp"] = ("POST", "/session/$sessionId/chromium/launch_app") self._commands["setPermissions"] = ("POST", "/session/$sessionId/permissions") diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py index 0c12cad51c3ea..864cb67be1d8d 100644 --- a/py/selenium/webdriver/chromium/webdriver.py +++ b/py/selenium/webdriver/chromium/webdriver.py @@ -14,12 +14,14 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import typing import warnings from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection from selenium.webdriver.common.driver_finder import DriverFinder from selenium.webdriver.common.options import ArgOptions from selenium.webdriver.common.service import Service +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver @@ -31,10 +33,11 @@ def __init__( self, browser_name: str = None, vendor_prefix: str = None, - options: ArgOptions = ArgOptions(), + options: ArgOptions = None, service: Service = None, - keep_alive: bool = True, - remote_connection: ChromiumRemoteConnection = None, + keep_alive: typing.Optional[bool] = None, + remote_connection: typing.Optional[ChromiumRemoteConnection] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: """Creates a new WebDriver instance of the ChromiumDriver. Starts the service and then creates new WebDriver instance of ChromiumDriver. @@ -44,7 +47,8 @@ def __init__( - 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. + - keep_alive - Deprecated: Whether to configure ChromiumRemoteConnection to use HTTP keep-alive. + - client_config - configuration values for the http client """ if browser_name: @@ -73,7 +77,12 @@ def __init__( command_executor = remote_connection if remote_connection else self.service.service_url try: - super().__init__(command_executor=command_executor, options=options, keep_alive=keep_alive) + super().__init__( + command_executor=command_executor, + options=options, + keep_alive=keep_alive, + client_config=client_config, + ) except Exception: self.quit() raise diff --git a/py/selenium/webdriver/common/options.py b/py/selenium/webdriver/common/options.py index 6a4bea0801bed..e92c4501e7635 100644 --- a/py/selenium/webdriver/common/options.py +++ b/py/selenium/webdriver/common/options.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. import typing +import warnings from abc import ABCMeta from abc import abstractmethod @@ -31,6 +32,7 @@ def __init__(self) -> None: self._proxy = None self.set_capability("pageLoadStrategy", "normal") self.mobile_options = None + self._ignore_local_proxy = False @property def capabilities(self): @@ -225,12 +227,22 @@ def to_capabilities(self): def default_capabilities(self): """Return minimal capabilities necessary as a dictionary.""" + 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.""" + warnings.warn( + "setting ignore proxy in Options has been deprecated, " + "set ProxyType.DIRECT in ClientConfig and pass to WebDriver constructor instead", + DeprecationWarning, + stacklevel=2, + ) + self._ignore_local_proxy = True + class ArgOptions(BaseOptions): def __init__(self) -> None: super().__init__() self._arguments = [] - self._ignore_local_proxy = False @property def arguments(self): @@ -250,11 +262,6 @@ def add_argument(self, argument): else: raise ValueError("argument can not be null") - 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.""" - self._ignore_local_proxy = True - def to_capabilities(self): return self._caps diff --git a/py/selenium/webdriver/edge/remote_connection.py b/py/selenium/webdriver/edge/remote_connection.py index c91fb030a5da1..8ee7625bd0e58 100644 --- a/py/selenium/webdriver/edge/remote_connection.py +++ b/py/selenium/webdriver/edge/remote_connection.py @@ -18,6 +18,7 @@ from selenium.webdriver import DesiredCapabilities from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection +from selenium.webdriver.remote.client_config import ClientConfig class EdgeRemoteConnection(ChromiumRemoteConnection): @@ -26,8 +27,9 @@ class EdgeRemoteConnection(ChromiumRemoteConnection): def __init__( self, remote_server_addr: str, - keep_alive: bool = True, - ignore_proxy: typing.Optional[bool] = False, + keep_alive: typing.Optional[bool] = None, + ignore_proxy: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: super().__init__( remote_server_addr=remote_server_addr, @@ -35,4 +37,5 @@ def __init__( browser_name="MicrosoftEdge", keep_alive=keep_alive, ignore_proxy=ignore_proxy, + client_config=client_config, ) diff --git a/py/selenium/webdriver/edge/webdriver.py b/py/selenium/webdriver/edge/webdriver.py index 6fc1116e894a2..e7742f0ecad16 100644 --- a/py/selenium/webdriver/edge/webdriver.py +++ b/py/selenium/webdriver/edge/webdriver.py @@ -14,9 +14,11 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import typing from selenium.webdriver.chromium.webdriver import ChromiumDriver +from ..remote.client_config import ClientConfig from .options import Options from .service import Service @@ -28,7 +30,8 @@ def __init__( self, options: Options = None, service: Service = None, - keep_alive: bool = True, + keep_alive: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: """Creates a new instance of the edge driver. Starts the service and then creates new instance of edge driver. @@ -37,8 +40,9 @@ def __init__( - options - this takes an instance of EdgeOptions - service - Service object for handling the browser driver if you need to pass extra details - keep_alive - Whether to configure EdgeRemoteConnection to use HTTP keep-alive. + - client_config - configuration values for the http client """ service = service if service else Service() options = options if options else Options() - super().__init__(options=options, service=service, keep_alive=keep_alive) + super().__init__(options=options, service=service, keep_alive=keep_alive, client_config=client_config) diff --git a/py/selenium/webdriver/firefox/remote_connection.py b/py/selenium/webdriver/firefox/remote_connection.py index b99a369cd3bea..e9dcdacc74ccf 100644 --- a/py/selenium/webdriver/firefox/remote_connection.py +++ b/py/selenium/webdriver/firefox/remote_connection.py @@ -14,16 +14,24 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import typing from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection class FirefoxRemoteConnection(RemoteConnection): browser_name = DesiredCapabilities.FIREFOX["browserName"] - def __init__(self, remote_server_addr, keep_alive=True, ignore_proxy=False) -> None: - super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy) + def __init__( + self, + remote_server_addr, + keep_alive: typing.Optional[bool] = None, + ignore_proxy: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, + ) -> None: + super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy, client_config=client_config) self._commands["GET_CONTEXT"] = ("GET", "/session/$sessionId/moz/context") self._commands["SET_CONTEXT"] = ("POST", "/session/$sessionId/moz/context") diff --git a/py/selenium/webdriver/firefox/webdriver.py b/py/selenium/webdriver/firefox/webdriver.py index e0121dfa9e6ba..3d5600a1425d5 100644 --- a/py/selenium/webdriver/firefox/webdriver.py +++ b/py/selenium/webdriver/firefox/webdriver.py @@ -17,6 +17,7 @@ import base64 import logging import os +import typing import warnings import zipfile from contextlib import contextmanager @@ -25,6 +26,7 @@ from selenium.webdriver.common.driver_finder import DriverFinder from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from ..remote.client_config import ClientConfig from .options import Options from .service import Service @@ -41,15 +43,17 @@ def __init__( self, options: Options = None, service: Service = None, - keep_alive: bool = True, + keep_alive: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: """Creates a new instance of the Firefox driver. Starts the service and then creates new instance of Firefox driver. :Args: - - options - Instance of ``options.Options``. + - options - (Optional) Instance of ``options.Options``. - service - (Optional) service instance for managing the starting and stopping of the driver. - - keep_alive - Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive. + - keep_alive - Deprecated: Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive. + - client_config - configuration values for the http client """ self.service = service if service else Service() @@ -59,7 +63,12 @@ def __init__( self.service.start() try: - super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive) + super().__init__( + command_executor=self.service.service_url, + options=options, + keep_alive=keep_alive, + client_config=client_config, + ) except Exception: self.quit() raise diff --git a/py/selenium/webdriver/ie/webdriver.py b/py/selenium/webdriver/ie/webdriver.py index def009d4c6710..0fd6f35159918 100644 --- a/py/selenium/webdriver/ie/webdriver.py +++ b/py/selenium/webdriver/ie/webdriver.py @@ -14,10 +14,12 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import typing from selenium.webdriver.common.driver_finder import DriverFinder from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from ..remote.client_config import ClientConfig from .options import Options from .service import Service @@ -30,16 +32,18 @@ def __init__( self, options: Options = None, service: Service = None, - keep_alive=True, + keep_alive: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: """Creates a new instance of the Ie driver. Starts the service and then creates new instance of Ie driver. :Args: - - options - IE Options instance, providing additional IE options + - options - (Optional) IE Options instance, providing additional IE options - service - (Optional) service instance for managing the starting and stopping of the driver. - keep_alive - Deprecated: Whether to configure RemoteConnection to use HTTP keep-alive. + - client_config - configuration values for the http client """ self.service = service if service else Service() @@ -49,7 +53,12 @@ def __init__( self.service.start() try: - super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive) + super().__init__( + command_executor=self.service.service_url, + options=options, + keep_alive=keep_alive, + client_config=client_config, + ) except Exception: self.quit() raise diff --git a/py/selenium/webdriver/remote/client_config.py b/py/selenium/webdriver/remote/client_config.py new file mode 100644 index 0000000000000..350e9fc8f00cc --- /dev/null +++ b/py/selenium/webdriver/remote/client_config.py @@ -0,0 +1,58 @@ +# 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. +import typing + +from selenium.webdriver.common.proxy import Proxy +from selenium.webdriver.common.proxy import ProxyType + + +class ClientConfig: + def __init__( + self, + keep_alive: bool = True, + proxy: Proxy = Proxy({"proxyType": ProxyType.SYSTEM}), + ) -> None: + self.keep_alive = keep_alive + self.proxy = proxy + + @property + def keep_alive(self) -> bool: + """:Returns: The keep alive value""" + return self._keep_alive + + @keep_alive.setter + def keep_alive(self, value: bool) -> None: + """Toggles the keep alive value. + + :Args: + - value: whether to keep the http connection alive + """ + self._keep_alive = value + + @property + def proxy(self) -> Proxy: + """:Returns: The proxy used for communicating to the driver/server""" + return self._proxy + + @proxy.setter + def proxy(self, proxy: Proxy) -> None: + """Provides the information for communicating with the driver or server. + + :Args: + - value: the proxy information to use to communicate with the driver or server + """ + self._proxy = proxy diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index 54da8cdbc43a3..1b49ccc9f90da 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -20,6 +20,8 @@ import platform import socket import string +import typing +import warnings from base64 import b64encode from urllib import parse @@ -28,7 +30,10 @@ from selenium import __version__ +from ..common.proxy import Proxy +from ..common.proxy import ProxyType from . import utils +from .client_config import ClientConfig from .command import Command from .errorhandler import ErrorCode @@ -128,8 +133,8 @@ class RemoteConnection: """A connection with the Remote WebDriver server. - Communicates with the server using the WebDriver wire protocol: - https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol + Communicates with the server using the w3c WebDriver protocol: + https://w3c.github.io/webdriver """ browser_name = None @@ -205,11 +210,19 @@ def get_remote_connection_headers(cls, parsed_url, keep_alive=False): return headers - def _get_proxy_url(self): - if self._url.startswith("https://"): - return os.environ.get("https_proxy", os.environ.get("HTTPS_PROXY")) - if self._url.startswith("http://"): - return os.environ.get("http_proxy", os.environ.get("HTTP_PROXY")) + def _get_proxy_url(self, proxy=None): + proxy = Proxy({"proxyType": ProxyType.SYSTEM}) if proxy is None else proxy + + if proxy.proxyType == ProxyType.SYSTEM: + if self._url.startswith("https://"): + return os.environ.get("https_proxy", os.environ.get("HTTPS_PROXY")) + if self._url.startswith("http://"): + return os.environ.get("http_proxy", os.environ.get("HTTP_PROXY")) + else: + if self._url.startswith("https://"): + return proxy.sslProxy + if self._url.startswith("http://"): + return proxy.http_proxy def _identify_http_proxy_auth(self): url = self._proxy_url @@ -242,31 +255,37 @@ def _get_connection_manager(self): return urllib3.PoolManager(**pool_manager_init_args) - def __init__(self, remote_server_addr: str, keep_alive: bool = False, ignore_proxy: bool = False): - self.keep_alive = keep_alive + def __init__( + self, + remote_server_addr: str, + keep_alive: typing.Optional[bool] = None, + ignore_proxy: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, + ) -> None: self._url = remote_server_addr - - # Env var NO_PROXY will override this part of the code - _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY")) - if _no_proxy: - for npu in _no_proxy.split(","): - npu = npu.strip() - if npu == "*": - ignore_proxy = True - break - n_url = parse.urlparse(npu) - remote_add = parse.urlparse(self._url) - if n_url.netloc: - if remote_add.netloc == n_url.netloc: - ignore_proxy = True - break - else: - if n_url.path in remote_add.netloc: - ignore_proxy = True - break - - self._proxy_url = self._get_proxy_url() if not ignore_proxy else None - if keep_alive: + client_config = ClientConfig() if client_config is None else client_config + + if keep_alive is not None: + warnings.warn( + "setting keep_alive in RemoteConnection has been deprecated, pass it in with ClientConfig instead", + DeprecationWarning, + stacklevel=2, + ) + client_config.keep_alive = keep_alive + + if ignore_proxy is not None: + warnings.warn( + "setting ignore_proxy in RemoteConnection has been deprecated, pass it in with ClientConfig instead", + DeprecationWarning, + stacklevel=2, + ) + if ignore_proxy: + client_config.ignore_proxy = Proxy({"proxyType": ProxyType.DIRECT}) + + self._keep_alive = client_config.keep_alive + + self._proxy_url = self._get_proxy_url(client_config.proxy) if self._use_proxy(client_config.proxy) else None + if self._keep_alive: self._conn = self._get_connection_manager() self._commands = remote_commands @@ -303,12 +322,12 @@ def _request(self, method, url, body=None): """ LOGGER.debug(f"{method} {url} {body}") parsed_url = parse.urlparse(url) - headers = self.get_remote_connection_headers(parsed_url, self.keep_alive) + headers = self.get_remote_connection_headers(parsed_url, self._keep_alive) response = None if body and method not in ("POST", "PUT"): body = None - if self.keep_alive: + if self._keep_alive: response = self._conn.request(method, url, body=body, headers=headers) statuscode = response.status else: @@ -347,6 +366,27 @@ def _request(self, method, url, body=None): LOGGER.debug("Finished Request") response.close() + def _use_proxy(self, proxy: Proxy): + if proxy.proxyType == ProxyType.DIRECT: + return False + + _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY")) + if _no_proxy: + for npu in _no_proxy.split(","): + npu = npu.strip() + if npu == "*": + return False + n_url = parse.urlparse(npu) + remote_add = parse.urlparse(self._url) + if n_url.netloc: + if remote_add.netloc == n_url.netloc: + return False + else: + if n_url.path in remote_add.netloc: + return False + + return True + def close(self): """Clean up resources when finished with the remote_connection.""" if hasattr(self, "_conn"): diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 281aa853607d7..ba48c1a912b72 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -50,7 +50,10 @@ ) from selenium.webdriver.support.relative_locator import RelativeBy +from ..common.proxy import Proxy +from ..common.proxy import ProxyType from .bidi_connection import BidiConnection +from .client_config import ClientConfig from .command import Command from .errorhandler import ErrorHandler from .file_detector import FileDetector @@ -90,7 +93,7 @@ def _create_caps(caps): return {"capabilities": {"firstMatch": [{}], "alwaysMatch": always_match}} -def get_remote_connection(capabilities, command_executor, keep_alive, ignore_local_proxy=False): +def get_remote_connection(capabilities, command_executor, client_config): from selenium.webdriver.chrome.remote_connection import ChromeRemoteConnection from selenium.webdriver.edge.remote_connection import EdgeRemoteConnection from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection @@ -99,7 +102,7 @@ def get_remote_connection(capabilities, command_executor, keep_alive, ignore_loc candidates = [ChromeRemoteConnection, EdgeRemoteConnection, SafariRemoteConnection, FirefoxRemoteConnection] handler = next((c for c in candidates if c.browser_name == capabilities.get("browserName")), RemoteConnection) - return handler(command_executor, keep_alive=keep_alive, ignore_proxy=ignore_local_proxy) + return handler(command_executor, client_config=client_config) def create_matches(options: List[BaseOptions]) -> Dict: @@ -163,9 +166,10 @@ class WebDriver(BaseWebDriver): def __init__( self, command_executor="http://127.0.0.1:4444", - keep_alive=True, + keep_alive: typing.Optional[bool] = None, file_detector=None, options: Union[BaseOptions, List[BaseOptions]] = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: """Create a new driver that will issue commands using the wire protocol. @@ -173,26 +177,37 @@ def __init__( :Args: - command_executor - Either a string representing URL of the remote server or a custom remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'. - - keep_alive - Whether to configure remote_connection.RemoteConnection to use + - keep_alive - Deprecated: Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive. Defaults to True. - file_detector - Pass custom file detector object during instantiation. If None, then default LocalFileDetector() will be used. - options - instance of a driver options.Options class + - client_config - configuration values for the http client """ + if client_config is None: + client_config = ClientConfig() + + if keep_alive is not None: + warnings.warn( + "setting keep_alive directly in WebDriver has been deprecated, pass it in with ClientConfig instead", + DeprecationWarning, + stacklevel=2, + ) + client_config.keep_alive = keep_alive if isinstance(options, list): capabilities = create_matches(options) - _ignore_local_proxy = False else: capabilities = options.to_capabilities() - _ignore_local_proxy = options._ignore_local_proxy + if options._ignore_local_proxy: + client_config.proxy = Proxy({"proxyType": ProxyType.DIRECT}) + self.command_executor = command_executor if isinstance(self.command_executor, (str, bytes)): self.command_executor = get_remote_connection( capabilities, command_executor=command_executor, - keep_alive=keep_alive, - ignore_local_proxy=_ignore_local_proxy, + client_config=client_config, ) self._is_remote = True self.session_id = None diff --git a/py/selenium/webdriver/safari/remote_connection.py b/py/selenium/webdriver/safari/remote_connection.py index a7929e98d7831..360ff92a113f7 100644 --- a/py/selenium/webdriver/safari/remote_connection.py +++ b/py/selenium/webdriver/safari/remote_connection.py @@ -14,16 +14,24 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import typing from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection class SafariRemoteConnection(RemoteConnection): browser_name = DesiredCapabilities.SAFARI["browserName"] - def __init__(self, remote_server_addr: str, keep_alive: bool = True, ignore_proxy: bool = False) -> None: - super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy) + def __init__( + self, + remote_server_addr: str, + keep_alive: typing.Optional[bool] = None, + ignore_proxy: typing.Optional[bool] = None, + client_config: typing.Optional[ClientConfig] = None, + ) -> None: + super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy, client_config=client_config) self._commands["GET_PERMISSIONS"] = ("GET", "/session/$sessionId/apple/permissions") self._commands["SET_PERMISSIONS"] = ("POST", "/session/$sessionId/apple/permissions") self._commands["ATTACH_DEBUGGER"] = ("POST", "/session/$sessionId/apple/attach_debugger") diff --git a/py/selenium/webdriver/safari/webdriver.py b/py/selenium/webdriver/safari/webdriver.py index d29b1521a9246..f5c61042d034a 100644 --- a/py/selenium/webdriver/safari/webdriver.py +++ b/py/selenium/webdriver/safari/webdriver.py @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - +import typing import warnings from selenium.common.exceptions import WebDriverException @@ -22,6 +22,7 @@ from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver from ..common.driver_finder import DriverFinder +from ..remote.client_config import ClientConfig from .options import Options from .service import Service @@ -34,19 +35,21 @@ class WebDriver(RemoteWebDriver): def __init__( self, reuse_service=False, - keep_alive=True, + keep_alive: typing.Optional[bool] = None, options: Options = None, service: Service = None, + client_config: typing.Optional[ClientConfig] = None, ) -> None: """Creates a new Safari driver instance and launches or finds a running safaridriver service. :Args: - - reuse_service - If True, do not spawn a safaridriver instance; instead, connect to an already-running service that was launched externally. - - keep_alive - Whether to configure SafariRemoteConnection to use - HTTP keep-alive. Defaults to True. + - reuse_service - Deprecated: If True, do not spawn a safaridriver instance; + instead, connect to an already-running service that was launched externally. + - keep_alive - Deprecated: Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive. - options - Instance of ``options.Options``. - service - Service object for handling the browser driver if you need to pass extra details + - client_config - configuration values for the http client """ if reuse_service: warnings.warn( @@ -65,7 +68,12 @@ def __init__( self.service.start() try: - super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive) + super().__init__( + command_executor=self.service.service_url, + options=options, + keep_alive=keep_alive, + client_config=client_config, + ) except Exception: self.quit() raise