From 6f60ad636c1ecd1bd21edc0e1f9139e9be3d92cd Mon Sep 17 00:00:00 2001 From: titusfortner Date: Thu, 11 May 2023 16:10:25 -0500 Subject: [PATCH 1/3] [py] getting remote connection does not work since chromium remote connection does not have a browser name --- py/selenium/webdriver/remote/webdriver.py | 47 ++++++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index c1579b5653b80..276b7356696a6 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -130,15 +130,42 @@ def _make_w3c_caps(caps): return {"firstMatch": [{}], "alwaysMatch": always_match} -def get_remote_connection(capabilities, command_executor, keep_alive, ignore_local_proxy=False): - from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection - from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection - from selenium.webdriver.safari.remote_connection import SafariRemoteConnection - - candidates = [RemoteConnection, ChromiumRemoteConnection, 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) +def get_remote_connection(browser_name, command_executor, keep_alive, ignore_local_proxy=False): + if browser_name == "chrome": + from selenium.webdriver.chromium.remote_connection import ( + ChromiumRemoteConnection + ) + return ChromiumRemoteConnection(command_executor, + vendor_prefix="goog", + browser_name=browser_name, + keep_alive=keep_alive, + ignore_proxy=ignore_local_proxy) + + elif browser_name == "MicrosoftEdge": + from selenium.webdriver.chromium.remote_connection import ( + ChromiumRemoteConnection + ) + return ChromiumRemoteConnection(command_executor, + vendor_prefix="ms", + browser_name=browser_name, + keep_alive=keep_alive, + ignore_proxy=ignore_local_proxy) + + elif browser_name == "firefox": + from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection + return FirefoxRemoteConnection(command_executor, + keep_alive=keep_alive, + ignore_proxy=ignore_local_proxy) + + elif browser_name == "safari": + from selenium.webdriver.safari.remote_connection import SafariRemoteConnection + return SafariRemoteConnection(command_executor, + keep_alive=keep_alive, + ignore_proxy=ignore_local_proxy) + else: + return RemoteConnection(command_executor, + keep_alive=keep_alive, + ignore_proxy=ignore_local_proxy) def create_matches(options: List[BaseOptions]) -> Dict: @@ -268,7 +295,7 @@ def __init__( self.command_executor = command_executor if isinstance(self.command_executor, (str, bytes)): self.command_executor = get_remote_connection( - capabilities, + capabilities.get("browserName"), command_executor=command_executor, keep_alive=keep_alive, ignore_local_proxy=_ignore_local_proxy, From c545f2cac4b01f0d518939cf3500cefdc7daced5 Mon Sep 17 00:00:00 2001 From: titusfortner Date: Fri, 12 May 2023 19:44:55 -0500 Subject: [PATCH 2/3] [py] superclass starts the remote connection so subclasses do not have to --- py/selenium/webdriver/chromium/webdriver.py | 11 +---------- py/selenium/webdriver/firefox/webdriver.py | 5 +---- py/selenium/webdriver/safari/webdriver.py | 4 +--- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py index 935b782f6dcc5..a686aa5503cd3 100644 --- a/py/selenium/webdriver/chromium/webdriver.py +++ b/py/selenium/webdriver/chromium/webdriver.py @@ -101,16 +101,7 @@ def __init__( self.service.start() try: - super().__init__( - command_executor=ChromiumRemoteConnection( - remote_server_addr=self.service.service_url, - browser_name=browser_name, - vendor_prefix=vendor_prefix, - keep_alive=keep_alive, - ignore_proxy=_ignore_proxy, - ), - options=options, - ) + super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive,) except Exception: self.quit() raise diff --git a/py/selenium/webdriver/firefox/webdriver.py b/py/selenium/webdriver/firefox/webdriver.py index c526cd7acfb01..bcfc1fa234f42 100644 --- a/py/selenium/webdriver/firefox/webdriver.py +++ b/py/selenium/webdriver/firefox/webdriver.py @@ -195,10 +195,7 @@ def __init__( self.service.path = DriverFinder.get_path(self.service, options) self.service.start() - executor = FirefoxRemoteConnection( - remote_server_addr=self.service.service_url, ignore_proxy=options._ignore_local_proxy - ) - super().__init__(command_executor=executor, options=options, keep_alive=True) + super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive) self._is_remote = False diff --git a/py/selenium/webdriver/safari/webdriver.py b/py/selenium/webdriver/safari/webdriver.py index 4795b1dbe0996..40a91e5a615c2 100644 --- a/py/selenium/webdriver/safari/webdriver.py +++ b/py/selenium/webdriver/safari/webdriver.py @@ -105,9 +105,7 @@ def __init__( if not reuse_service: self.service.start() - executor = SafariRemoteConnection(remote_server_addr=self.service.service_url, keep_alive=keep_alive) - - super().__init__(command_executor=executor, options=options) + super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive) self._is_remote = False From 8602eaa8cff21b54996eae61517f88845a630d38 Mon Sep 17 00:00:00 2001 From: titusfortner Date: Sat, 13 May 2023 12:33:59 -0500 Subject: [PATCH 3/3] [py] use ClientConfig for keep_alive, proxy, timeout and certificate path settings --- py/selenium/webdriver/chrome/webdriver.py | 12 +-- .../webdriver/chromium/remote_connection.py | 7 +- py/selenium/webdriver/chromium/webdriver.py | 23 ++--- py/selenium/webdriver/common/options.py | 19 ++-- py/selenium/webdriver/edge/webdriver.py | 7 +- .../webdriver/firefox/remote_connection.py | 5 +- py/selenium/webdriver/firefox/webdriver.py | 14 ++- py/selenium/webdriver/ie/webdriver.py | 18 ++-- py/selenium/webdriver/remote/client_config.py | 89 +++++++++++++++++++ .../webdriver/remote/remote_connection.py | 32 +++++-- py/selenium/webdriver/remote/webdriver.py | 63 +++++++------ .../webdriver/safari/remote_connection.py | 5 +- py/selenium/webdriver/safari/webdriver.py | 21 +++-- py/selenium/webdriver/webkitgtk/webdriver.py | 2 +- .../remote/remote_connection_tests.py | 38 +++++--- 15 files changed, 241 insertions(+), 114 deletions(-) create mode 100644 py/selenium/webdriver/remote/client_config.py diff --git a/py/selenium/webdriver/chrome/webdriver.py b/py/selenium/webdriver/chrome/webdriver.py index eb86328174136..2be91b7fb7cc8 100644 --- a/py/selenium/webdriver/chrome/webdriver.py +++ b/py/selenium/webdriver/chrome/webdriver.py @@ -20,13 +20,13 @@ from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.driver_finder import DriverFinder +from ..remote.client_config import ClientConfig from .options import Options from .service import DEFAULT_EXECUTABLE_PATH from .service import Service DEFAULT_PORT = 0 DEFAULT_SERVICE_LOG_PATH = None -DEFAULT_KEEP_ALIVE = None class WebDriver(ChromiumDriver): @@ -46,7 +46,8 @@ def __init__( service_log_path=DEFAULT_SERVICE_LOG_PATH, chrome_options=None, service: Service = None, - keep_alive=DEFAULT_KEEP_ALIVE, + keep_alive=None, + client_config: ClientConfig = ClientConfig(), ) -> None: """Creates a new instance of the chrome driver. Starts the service and then creates new instance of chrome driver. @@ -69,12 +70,6 @@ def __init__( if chrome_options: warnings.warn("use options instead of chrome_options", DeprecationWarning, stacklevel=2) options = chrome_options - if keep_alive != DEFAULT_KEEP_ALIVE: - warnings.warn( - "keep_alive has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 - ) - else: - keep_alive = True if not options: options = self.create_options() if not service: @@ -91,6 +86,7 @@ def __init__( service_log_path, service, keep_alive, + client_config, ) def create_options(self) -> Options: diff --git a/py/selenium/webdriver/chromium/remote_connection.py b/py/selenium/webdriver/chromium/remote_connection.py index c2e7c9a614d46..54e7b6fa77937 100644 --- a/py/selenium/webdriver/chromium/remote_connection.py +++ b/py/selenium/webdriver/chromium/remote_connection.py @@ -14,8 +14,8 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import typing +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -25,10 +25,9 @@ def __init__( remote_server_addr: str, vendor_prefix: str, browser_name: str, - keep_alive: bool = True, - ignore_proxy: typing.Optional[bool] = False, + client_config: ClientConfig = ClientConfig(), ) -> None: - super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy) + super().__init__(remote_server_addr, 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 a686aa5503cd3..bcd0490495cd7 100644 --- a/py/selenium/webdriver/chromium/webdriver.py +++ b/py/selenium/webdriver/chromium/webdriver.py @@ -18,15 +18,14 @@ import warnings from selenium.webdriver.chrome.options import Options as ChromeOptions -from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection from selenium.webdriver.common.options import BaseOptions from selenium.webdriver.common.service import Service from selenium.webdriver.edge.options import Options as EdgeOptions +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver DEFAULT_PORT = 0 DEFAULT_SERVICE_LOG_PATH = None -DEFAULT_KEEP_ALIVE = None class ChromiumDriver(RemoteWebDriver): @@ -43,7 +42,8 @@ def __init__( desired_capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH, service: Service = None, - keep_alive=DEFAULT_KEEP_ALIVE, + keep_alive=None, + client_config: ClientConfig = ClientConfig(), ) -> None: """Creates a new WebDriver instance of the ChromiumDriver. Starts the service and then creates new WebDriver instance of ChromiumDriver. @@ -74,16 +74,9 @@ def __init__( DeprecationWarning, stacklevel=2, ) - if keep_alive != DEFAULT_KEEP_ALIVE and type(self) == __class__: - warnings.warn( - "keep_alive has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 - ) - else: - keep_alive = True self.vendor_prefix = vendor_prefix - _ignore_proxy = None if not options: options = self.create_options() @@ -91,9 +84,6 @@ def __init__( for key, value in desired_capabilities.items(): options.set_capability(key, value) - if options._ignore_local_proxy: - _ignore_proxy = options._ignore_local_proxy - if not service: raise AttributeError("service cannot be None") @@ -101,7 +91,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/common/options.py b/py/selenium/webdriver/common/options.py index 2d8e4b8b1dd5b..b548a918dd129 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 @@ -30,6 +31,7 @@ def __init__(self) -> None: self._caps = self.default_capabilities self.set_capability("pageLoadStrategy", "normal") self.mobile_options = None + self._ignore_local_proxy = False @property def capabilities(self): @@ -223,12 +225,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): @@ -248,11 +260,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/webdriver.py b/py/selenium/webdriver/edge/webdriver.py index e501d0b4b53fc..01216f1550e01 100644 --- a/py/selenium/webdriver/edge/webdriver.py +++ b/py/selenium/webdriver/edge/webdriver.py @@ -20,6 +20,7 @@ from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.driver_finder import DriverFinder +from ..remote.client_config import ClientConfig from .options import Options from .service import DEFAULT_EXECUTABLE_PATH from .service import Service @@ -44,7 +45,8 @@ def __init__( capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH, service: Service = None, - keep_alive=False, + keep_alive=None, + client_config: ClientConfig = ClientConfig(), verbose=False, # Todo: Why is this now unused? ) -> None: """Creates a new instance of the edge driver. Starts the service and @@ -59,7 +61,7 @@ def __init__( capabilities only, such as "proxy" or "loggingPref". - service_log_path - Deprecated: Where to log information from the driver. - 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. + - keep_alive - Deprecated: Whether to configure EdgeRemoteConnection to use HTTP keep-alive. - verbose - whether to set verbose logging in the service. """ if executable_path != "msedgedriver": @@ -83,6 +85,7 @@ def __init__( service_log_path, service, keep_alive, + client_config, ) def create_options(self) -> Options: diff --git a/py/selenium/webdriver/firefox/remote_connection.py b/py/selenium/webdriver/firefox/remote_connection.py index b99a369cd3bea..29b9c0845ce02 100644 --- a/py/selenium/webdriver/firefox/remote_connection.py +++ b/py/selenium/webdriver/firefox/remote_connection.py @@ -16,14 +16,15 @@ # under the License. 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: str, client_config: ClientConfig = ClientConfig()) -> None: + super().__init__(remote_server_addr, 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 bcfc1fa234f42..84ee7a415b6e9 100644 --- a/py/selenium/webdriver/firefox/webdriver.py +++ b/py/selenium/webdriver/firefox/webdriver.py @@ -27,10 +27,10 @@ from selenium.webdriver.common.driver_finder import DriverFinder from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from ..remote.client_config import ClientConfig from .firefox_binary import FirefoxBinary from .firefox_profile import FirefoxProfile from .options import Options -from .remote_connection import FirefoxRemoteConnection from .service import DEFAULT_EXECUTABLE_PATH from .service import Service @@ -58,7 +58,8 @@ def __init__( service=None, desired_capabilities=None, log_path=DEFAULT_LOG_PATH, - keep_alive=True, # Todo: Why is this now unused? + keep_alive=None, + client_config: ClientConfig = ClientConfig(), ) -> None: """Starts a new local session of Firefox. @@ -106,7 +107,7 @@ def __init__( :param desired_capabilities: Deprecated: alias of capabilities. In future versions of this library, this will replace 'capabilities'. This will make the signature consistent with RemoteWebDriver. - :param keep_alive: Whether to configure remote_connection.RemoteConnection to use + :param keep_alive - Deprecated: Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive. """ @@ -195,7 +196,12 @@ def __init__( self.service.path = DriverFinder.get_path(self.service, options) self.service.start() - 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, + ) self._is_remote = False diff --git a/py/selenium/webdriver/ie/webdriver.py b/py/selenium/webdriver/ie/webdriver.py index c1ae1894bbca0..6c9b94df64974 100644 --- a/py/selenium/webdriver/ie/webdriver.py +++ b/py/selenium/webdriver/ie/webdriver.py @@ -21,6 +21,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 DEFAULT_EXECUTABLE_PATH from .service import Service @@ -30,7 +31,6 @@ DEFAULT_HOST = None DEFAULT_LOG_LEVEL = None DEFAULT_SERVICE_LOG_PATH = None -DEFAULT_KEEP_ALIVE = None class WebDriver(RemoteWebDriver): @@ -49,7 +49,8 @@ def __init__( options: Options = None, service: Service = None, desired_capabilities=None, - keep_alive=DEFAULT_KEEP_ALIVE, + keep_alive=None, + client_config: ClientConfig = ClientConfig(), ) -> None: """Creates a new instance of the Ie driver. @@ -102,12 +103,6 @@ def __init__( DeprecationWarning, stacklevel=2, ) - if keep_alive != DEFAULT_KEEP_ALIVE: - warnings.warn( - "keep_alive has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 - ) - else: - keep_alive = True self.host = host self.port = port @@ -127,7 +122,12 @@ def __init__( self.iedriver.path = DriverFinder.get_path(self.iedriver, options) self.iedriver.start() - super().__init__(command_executor=self.iedriver.service_url, options=options, keep_alive=keep_alive) + super().__init__( + command_executor=self.iedriver.service_url, + options=options, + keep_alive=keep_alive, + client_config=client_config, + ) self._is_remote = False def quit(self) -> None: diff --git a/py/selenium/webdriver/remote/client_config.py b/py/selenium/webdriver/remote/client_config.py new file mode 100644 index 0000000000000..281eb2e6a42cd --- /dev/null +++ b/py/selenium/webdriver/remote/client_config.py @@ -0,0 +1,89 @@ +# 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. + +from selenium.webdriver.common.proxy import Proxy +from selenium.webdriver.common.proxy import ProxyType + + +class ClientConfig: + def __init__( + self, keep_alive: bool = True, timeout=None, proxy=Proxy({"proxyType": ProxyType.SYSTEM}), certificate_path=None + ) -> None: + self._keep_alive = keep_alive + self._timeout = timeout + self._proxy = proxy + self._certificate_path = certificate_path + + @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 timeout(self) -> int: + """:Returns: The amount of time to wait for an http response.""" + return self._timeout + + @timeout.setter + def timeout(self, time: int) -> None: + """Sets the amount of time to wait for an http response. + + :Args: + - value: number of seconds to wait for an http response + """ + self._timeout = time + + @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 + + @property + def certificate_path(self) -> bool: + """:Returns: The path of the .pem encoded certificate + used to verify connection to the driver or server + """ + return self._certificate_path + + @certificate_path.setter + def certificate_path(self, path: str) -> None: + """Set the path to the certificate bundle to verify connection to + command executor. Can also be set to None to disable certificate + validation. + + :Args: + - path - path of a .pem encoded certificate chain. + """ + self._certificate_path = path diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index ef5ce2ec409b3..d04bad1b8976e 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -27,8 +27,10 @@ import urllib3 from selenium import __version__ +from selenium.webdriver.common.proxy import ProxyType from . import utils +from .client_config import ClientConfig from .command import Command from .errorhandler import ErrorCode @@ -38,8 +40,7 @@ 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: """ browser_name = None @@ -116,10 +117,16 @@ 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")) + if self._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 self._proxy.sslProxy + if self._url.startswith("http://"): + return self._proxy.http_proxy def _identify_http_proxy_auth(self): url = self._proxy_url @@ -152,10 +159,17 @@ 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, client_config: ClientConfig = ClientConfig()): self._url = remote_server_addr + if client_config.timeout: + self.set_timeout(client_config.timeout) + if client_config.certificate_path: + self.set_certificate_bundle_path(client_config.certificate_path) + self.keep_alive = client_config.keep_alive + ignore_proxy = client_config.proxy.proxyType == ProxyType.DIRECT + self._proxy = client_config.proxy + # 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: @@ -176,7 +190,7 @@ def __init__(self, remote_server_addr: str, keep_alive: bool = False, ignore_pro break self._proxy_url = self._get_proxy_url() if not ignore_proxy else None - if keep_alive: + if self.keep_alive: self._conn = self._get_connection_manager() self._commands = { diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 276b7356696a6..cdacd3736c1b4 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -42,6 +42,8 @@ from selenium.webdriver.common.html5.application_cache import ApplicationCache from selenium.webdriver.common.options import BaseOptions from selenium.webdriver.common.print_page_options import PrintOptions +from selenium.webdriver.common.proxy import Proxy +from selenium.webdriver.common.proxy import ProxyType from selenium.webdriver.common.timeouts import Timeouts from selenium.webdriver.common.virtual_authenticator import Credential from selenium.webdriver.common.virtual_authenticator import VirtualAuthenticatorOptions @@ -51,6 +53,7 @@ from selenium.webdriver.support.relative_locator import RelativeBy from .bidi_connection import BidiConnection +from .client_config import ClientConfig from .command import Command from .errorhandler import ErrorHandler from .file_detector import FileDetector @@ -130,42 +133,42 @@ def _make_w3c_caps(caps): return {"firstMatch": [{}], "alwaysMatch": always_match} -def get_remote_connection(browser_name, command_executor, keep_alive, ignore_local_proxy=False): +def get_remote_connection(browser_name, command_executor, client_config): if browser_name == "chrome": from selenium.webdriver.chromium.remote_connection import ( - ChromiumRemoteConnection + ChromiumRemoteConnection, + ) + + return ChromiumRemoteConnection( + command_executor, + vendor_prefix="goog", + browser_name=browser_name, + client_config=client_config, ) - return ChromiumRemoteConnection(command_executor, - vendor_prefix="goog", - browser_name=browser_name, - keep_alive=keep_alive, - ignore_proxy=ignore_local_proxy) elif browser_name == "MicrosoftEdge": from selenium.webdriver.chromium.remote_connection import ( - ChromiumRemoteConnection + ChromiumRemoteConnection, + ) + + return ChromiumRemoteConnection( + command_executor, + vendor_prefix="ms", + browser_name=browser_name, + client_config=client_config, ) - return ChromiumRemoteConnection(command_executor, - vendor_prefix="ms", - browser_name=browser_name, - keep_alive=keep_alive, - ignore_proxy=ignore_local_proxy) elif browser_name == "firefox": from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection - return FirefoxRemoteConnection(command_executor, - keep_alive=keep_alive, - ignore_proxy=ignore_local_proxy) + + return FirefoxRemoteConnection(command_executor, client_config=client_config) elif browser_name == "safari": from selenium.webdriver.safari.remote_connection import SafariRemoteConnection - return SafariRemoteConnection(command_executor, - keep_alive=keep_alive, - ignore_proxy=ignore_local_proxy) + + return SafariRemoteConnection(command_executor, client_config=client_config) else: - return RemoteConnection(command_executor, - keep_alive=keep_alive, - ignore_proxy=ignore_local_proxy) + return RemoteConnection(command_executor, client_config=client_config) def create_matches(options: List[BaseOptions]) -> Dict: @@ -232,9 +235,10 @@ def __init__( desired_capabilities=None, browser_profile=None, proxy=None, - keep_alive=True, + keep_alive=None, file_detector=None, options: Union[BaseOptions, List[BaseOptions]] = None, + client_config: ClientConfig = ClientConfig(), ) -> None: """Create a new driver that will issue commands using the wire protocol. @@ -272,22 +276,24 @@ def __init__( DeprecationWarning, stacklevel=2, ) - if not keep_alive: + if keep_alive is not None: warnings.warn( - "keep_alive has been deprecated. We will be using True as the default value as we start removing it.", + "setting keep_alive directly in WebDriver has been deprecated, pass it in with ClientConfig instead", DeprecationWarning, stacklevel=2, ) + client_config.keep_alive = keep_alive + capabilities = {} # If we get a list we can assume that no capabilities # have been passed in if isinstance(options, list): capabilities = create_matches(options) else: - _ignore_local_proxy = False if options: capabilities = options.to_capabilities() - _ignore_local_proxy = options._ignore_local_proxy + if options._ignore_local_proxy: + client_config.proxy = Proxy({"ProxyType": ProxyType.DIRECT}) if desired_capabilities: if not isinstance(desired_capabilities, dict): raise WebDriverException("Desired Capabilities must be a dictionary") @@ -297,8 +303,7 @@ def __init__( self.command_executor = get_remote_connection( capabilities.get("browserName"), 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..34579a7f0653e 100644 --- a/py/selenium/webdriver/safari/remote_connection.py +++ b/py/selenium/webdriver/safari/remote_connection.py @@ -16,14 +16,15 @@ # under the License. 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, client_config: ClientConfig) -> None: + super().__init__(remote_server_addr, 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 40a91e5a615c2..dfdf793d21d75 100644 --- a/py/selenium/webdriver/safari/webdriver.py +++ b/py/selenium/webdriver/safari/webdriver.py @@ -22,8 +22,8 @@ from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from ..remote.client_config import ClientConfig from .options import Options -from .remote_connection import SafariRemoteConnection from .service import DEFAULT_EXECUTABLE_PATH from .service import Service @@ -40,10 +40,11 @@ def __init__( reuse_service=False, desired_capabilities=DEFAULT_SAFARI_CAPS, quiet=False, - keep_alive=True, + keep_alive=None, service_args=None, options: Options = None, service: Service = None, + client_config: ClientConfig = ClientConfig(), ) -> None: """Creates a new Safari driver instance and launches or finds a running safaridriver service. @@ -54,8 +55,7 @@ def __init__( - reuse_service - If True, do not spawn a safaridriver instance; instead, connect to an already-running service that was launched externally. - desired_capabilities: Dictionary object with desired capabilities (Can be used to provide various Safari switches). - quiet - If True, the driver's stdout and stderr is suppressed. - - keep_alive - Whether to configure SafariRemoteConnection to use - HTTP keep-alive. Defaults to True. + - keep_alive - Deprecated: Whether to configure SafariRemoteConnection to use HTTP keep-alive. - service_args : List of args to pass to the safaridriver service - service - Service object for handling the browser driver if you need to pass extra details """ @@ -86,12 +86,6 @@ def __init__( warnings.warn( "quiet has been deprecated, please use the Service class to set it", DeprecationWarning, stacklevel=2 ) - if not keep_alive: - warnings.warn( - "keep_alive has been deprecated, please use the Service class to set it", - DeprecationWarning, - stacklevel=2, - ) if service_args: warnings.warn( @@ -105,7 +99,12 @@ def __init__( if not reuse_service: self.service.start() - 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, + ) self._is_remote = False diff --git a/py/selenium/webdriver/webkitgtk/webdriver.py b/py/selenium/webdriver/webkitgtk/webdriver.py index b364ca5f192be..639298276bbcb 100644 --- a/py/selenium/webdriver/webkitgtk/webdriver.py +++ b/py/selenium/webdriver/webkitgtk/webdriver.py @@ -35,7 +35,7 @@ def __init__( options=None, desired_capabilities=None, service_log_path=None, - keep_alive=False, + keep_alive=None, ): """Creates a new instance of the WebKitGTK driver. diff --git a/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py b/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py index 2c798365d85f5..360f144a8006a 100644 --- a/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py +++ b/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py @@ -21,6 +21,7 @@ import urllib3 from selenium import __version__ +from selenium.webdriver.remote.client_config import ClientConfig from selenium.webdriver.remote.remote_connection import RemoteConnection @@ -49,26 +50,30 @@ def test_get_remote_connection_headers_adds_keep_alive_if_requested(): def test_get_proxy_url_http(mock_proxy_settings): proxy = "http://http_proxy.com:8080" - remote_connection = RemoteConnection("http://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("http://remote", client_config=client_config) proxy_url = remote_connection._get_proxy_url() assert proxy_url == proxy def test_get_proxy_url_https(mock_proxy_settings): proxy = "http://https_proxy.com:8080" - remote_connection = RemoteConnection("https://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("https://remote", client_config=client_config) proxy_url = remote_connection._get_proxy_url() assert proxy_url == proxy def test_get_proxy_url_none(mock_proxy_settings_missing): - remote_connection = RemoteConnection("https://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("https://remote", client_config=client_config) proxy_url = remote_connection._get_proxy_url() assert proxy_url is None def test_get_proxy_url_http_auth(mock_proxy_auth_settings): - remote_connection = RemoteConnection("http://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("http://remote", client_config=client_config) proxy_url = remote_connection._get_proxy_url() raw_proxy_url, basic_auth_string = remote_connection._separate_http_proxy_auth() assert proxy_url == "http://user:password@http_proxy.com:8080" @@ -77,7 +82,8 @@ def test_get_proxy_url_http_auth(mock_proxy_auth_settings): def test_get_proxy_url_https_auth(mock_proxy_auth_settings): - remote_connection = RemoteConnection("https://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("https://remote", client_config=client_config) proxy_url = remote_connection._get_proxy_url() raw_proxy_url, basic_auth_string = remote_connection._separate_http_proxy_auth() assert proxy_url == "https://user:password@https_proxy.com:8080" @@ -86,14 +92,16 @@ def test_get_proxy_url_https_auth(mock_proxy_auth_settings): def test_get_connection_manager_without_proxy(mock_proxy_settings_missing): - remote_connection = RemoteConnection("http://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("http://remote", client_config=client_config) conn = remote_connection._get_connection_manager() assert isinstance(conn, urllib3.PoolManager) def test_get_connection_manager_for_certs_and_timeout(monkeypatch): monkeypatch.setattr(RemoteConnection, "get_timeout", lambda _: 10) # Class state; leaks into subsequent tests. - remote_connection = RemoteConnection("http://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("http://remote", client_config=client_config) conn = remote_connection._get_connection_manager() assert conn.connection_pool_kw["timeout"] == 10 assert conn.connection_pool_kw["cert_reqs"] == "CERT_REQUIRED" @@ -101,19 +109,21 @@ def test_get_connection_manager_for_certs_and_timeout(monkeypatch): def test_default_socket_timeout_is_correct(): - remote_connection = RemoteConnection("http://remote", keep_alive=True) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("http://remote", client_config=client_config) conn = remote_connection._get_connection_manager() assert conn.connection_pool_kw["timeout"] is None def test_get_connection_manager_with_proxy(mock_proxy_settings): - remote_connection = RemoteConnection("http://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("http://remote", client_config=client_config) conn = remote_connection._get_connection_manager() assert isinstance(conn, urllib3.ProxyManager) assert conn.proxy.scheme == "http" assert conn.proxy.host == "http_proxy.com" assert conn.proxy.port == 8080 - remote_connection_https = RemoteConnection("https://remote", keep_alive=False) + remote_connection_https = RemoteConnection("https://remote", client_config=client_config) conn = remote_connection_https._get_connection_manager() assert isinstance(conn, urllib3.ProxyManager) assert conn.proxy.scheme == "http" @@ -123,14 +133,15 @@ def test_get_connection_manager_with_proxy(mock_proxy_settings): def test_get_connection_manager_with_auth_proxy(mock_proxy_auth_settings): proxy_auth_header = urllib3.make_headers(proxy_basic_auth="user:password") - remote_connection = RemoteConnection("http://remote", keep_alive=False) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("http://remote", client_config=client_config) conn = remote_connection._get_connection_manager() assert isinstance(conn, urllib3.ProxyManager) assert conn.proxy.scheme == "http" assert conn.proxy.host == "http_proxy.com" assert conn.proxy.port == 8080 assert conn.proxy_headers == proxy_auth_header - remote_connection_https = RemoteConnection("https://remote", keep_alive=False) + remote_connection_https = RemoteConnection("https://remote", client_config=client_config) conn = remote_connection_https._get_connection_manager() assert isinstance(conn, urllib3.ProxyManager) assert conn.proxy.scheme == "https" @@ -164,7 +175,8 @@ def test_get_connection_manager_when_no_proxy_set(mock_no_proxy_settings, url): def test_ignore_proxy_env_vars(mock_proxy_settings): - remote_connection = RemoteConnection("http://remote", ignore_proxy=True) + client_config = ClientConfig(keep_alive=False) + remote_connection = RemoteConnection("http://remote", client_config=client_config) conn = remote_connection._get_connection_manager() assert isinstance(conn, urllib3.PoolManager)