From 617f16f3a0142cba3740705ebc67ca7055a47498 Mon Sep 17 00:00:00 2001 From: Brandon Walderman Date: Tue, 10 Mar 2020 04:12:51 -0700 Subject: [PATCH] [py] Use a capability to switch engines for Edge (#8096) * Use ms:edgeChromium capability in python bindings. * Update edge_options_tests. * Fix chromium command prefixes. * Import warnings for edge webdriver.py. Co-authored-by: David Burns --- py/BUILD.bazel | 46 ++++++++++++++ py/conftest.py | 9 +-- py/selenium/webdriver/chrome/webdriver.py | 7 ++- .../webdriver/chromium/remote_connection.py | 18 +++--- py/selenium/webdriver/chromium/webdriver.py | 11 ++-- py/selenium/webdriver/edge/options.py | 52 +++++++--------- py/selenium/webdriver/edge/service.py | 13 ++-- py/selenium/webdriver/edge/webdriver.py | 60 ++++++++++--------- .../webdriver/edge/edge_options_tests.py | 21 +++++-- 9 files changed, 144 insertions(+), 93 deletions(-) diff --git a/py/BUILD.bazel b/py/BUILD.bazel index 48e995fb2aa8b..554e9b44ecefe 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -160,6 +160,52 @@ py_test_suite( ], ) +py_test_suite( + name = "test-edge", + size = "large", + srcs = glob([ + "test/selenium/webdriver/edge/**/*.py", + "test/selenium/webdriver/common/**/*.py", + "test/selenium/webdriver/support/**/*.py", + ]), + args = [ + "--instafail", + "--driver=Edge", + ], + tags = [ + "no-sandbox", + ], + deps = [ + ":init-tree", + ":selenium", + ":webserver", + "//third_party/py:pytest", + ], +) + +py_test_suite( + name = "test-edge-chromium", + size = "large", + srcs = glob([ + "test/selenium/webdriver/edge/**/*.py", + "test/selenium/webdriver/common/**/*.py", + "test/selenium/webdriver/support/**/*.py", + ]), + args = [ + "--instafail", + "--driver=ChromiumEdge", + ], + tags = [ + "no-sandbox", + ], + deps = [ + ":init-tree", + ":selenium", + ":webserver", + "//third_party/py:pytest", + ], +) + py_test_suite( name = "test-firefox", size = "large", diff --git a/py/conftest.py b/py/conftest.py index 50903104d097d..1c11ddb4f1cdc 100644 --- a/py/conftest.py +++ b/py/conftest.py @@ -120,7 +120,6 @@ def fin(): options = get_options(driver_class, request.config) if driver_class == 'ChromiumEdge': options = get_options(driver_class, request.config) - kwargs.update({'is_legacy': False}) if driver_path is not None: kwargs['executable_path'] = driver_path if options is not None: @@ -136,10 +135,12 @@ def get_options(driver_class, config): browser_args = config.option.args options = None + if driver_class == 'ChromiumEdge': + options = getattr(webdriver, 'EdgeOptions')() + options.use_chromium = True + if browser_path or browser_args: - if driver_class == 'ChromiumEdge': - options = getattr(webdriver, 'EdgeOptions')(False) - else: + if not options: options = getattr(webdriver, '{}Options'.format(driver_class))() if driver_class == 'WebKitGTK': options.overlay_scrollbars_enabled = False diff --git a/py/selenium/webdriver/chrome/webdriver.py b/py/selenium/webdriver/chrome/webdriver.py index 1529142ed90c3..2b9b04ffa3325 100644 --- a/py/selenium/webdriver/chrome/webdriver.py +++ b/py/selenium/webdriver/chrome/webdriver.py @@ -18,6 +18,7 @@ from selenium.webdriver.chromium.webdriver import ChromiumDriver from .options import Options from .service import Service +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities DEFAULT_PORT = 0 @@ -49,6 +50,9 @@ def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT, - service_log_path - Deprecated: Where to log information from the driver. - keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive. """ + if executable_path != 'chromedriver': + warnings.warn('executable_path has been deprecated, please pass in a Service object', + DeprecationWarning, stacklevel=2) if chrome_options: warnings.warn('use options instead of chrome_options', DeprecationWarning, stacklevel=2) @@ -57,7 +61,8 @@ def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT, if service is None: service = Service(executable_path, port, service_args, service_log_path) - super(WebDriver, self).__init__(executable_path, port, options, + super(WebDriver, self).__init__(DesiredCapabilities.CHROME['browserName'], "goog", + port, options, service_args, desired_capabilities, service_log_path, service, keep_alive) diff --git a/py/selenium/webdriver/chromium/remote_connection.py b/py/selenium/webdriver/chromium/remote_connection.py index 61f07011306c8..e71ae91718b35 100644 --- a/py/selenium/webdriver/chromium/remote_connection.py +++ b/py/selenium/webdriver/chromium/remote_connection.py @@ -20,17 +20,15 @@ class ChromiumRemoteConnection(RemoteConnection): - - browser_name = DesiredCapabilities.CHROME['browserName'] - - def __init__(self, remote_server_addr, keep_alive=True): + def __init__(self, remote_server_addr, vendor_prefix, browser_name, keep_alive=True): RemoteConnection.__init__(self, remote_server_addr, keep_alive) + self.browser_name = browser_name self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app') self._commands["setNetworkConditions"] = ('POST', '/session/$sessionId/chromium/network_conditions') self._commands["getNetworkConditions"] = ('GET', '/session/$sessionId/chromium/network_conditions') - self._commands['executeCdpCommand'] = ('POST', '/session/$sessionId/goog/cdp/execute') - self._commands['getSinks'] = ('GET', '/session/$sessionId/goog/cast/get_sinks') - self._commands['getIssueMessage'] = ('GET', '/session/$sessionId/goog/cast/get_issue_message') - self._commands['setSinkToUse'] = ('POST', '/session/$sessionId/goog/cast/set_sink_to_use') - self._commands['startTabMirroring'] = ('POST', '/session/$sessionId/goog/cast/start_tab_mirroring') - self._commands['stopCasting'] = ('POST', '/session/$sessionId/goog/cast/stop_casting') + self._commands['executeCdpCommand'] = ('POST', '/session/$sessionId/{}/cdp/execute'.format(vendor_prefix)) + self._commands['getSinks'] = ('GET', '/session/$sessionId/{}/cast/get_sinks'.format(vendor_prefix)) + self._commands['getIssueMessage'] = ('GET', '/session/$sessionId/{}/cast/get_issue_message'.format(vendor_prefix)) + self._commands['setSinkToUse'] = ('POST', '/session/$sessionId/{}/cast/set_sink_to_use'.format(vendor_prefix)) + self._commands['startTabMirroring'] = ('POST', '/session/$sessionId/{}/cast/start_tab_mirroring'.format(vendor_prefix)) + self._commands['stopCasting'] = ('POST', '/session/$sessionId/{}/cast/stop_casting'.format(vendor_prefix)) diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py index 9caec27c6c518..a27c6966e3991 100644 --- a/py/selenium/webdriver/chromium/webdriver.py +++ b/py/selenium/webdriver/chromium/webdriver.py @@ -28,8 +28,8 @@ class ChromiumDriver(RemoteWebDriver): Controls the WebDriver instance of ChromiumDriver and allows you to drive the browser. """ - def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT, - options=None, service_args=None, + def __init__(self, browser_name, vendor_prefix, + port=DEFAULT_PORT, options=None, service_args=None, desired_capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH, service=None, keep_alive=True): """ @@ -37,7 +37,8 @@ def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT, Starts the service and then creates new WebDriver instance of ChromiumDriver. :Args: - - executable_path - Deprecated: path to the executable. If the default is used it assumes the executable is in the $PATH + browser_name - Browser name used when matching capabilities. + vendor_prefix - Company prefix to apply to vendor-specific WebDriver extension commands. - port - Deprecated: port you would like the service to run, if left as 0, a free port will be found. - options - this takes an instance of ChromiumOptions - service_args - Deprecated: List of args to pass to the driver service @@ -46,9 +47,6 @@ def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT, - service_log_path - Deprecated: Where to log information from the driver. - keep_alive - Whether to configure ChromiumRemoteConnection to use HTTP keep-alive. """ - if executable_path != 'chromedriver': - warnings.warn('executable_path has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) if desired_capabilities is not None: warnings.warn('desired_capabilities has been deprecated, please pass in a Service object', DeprecationWarning, stacklevel=2) @@ -81,6 +79,7 @@ def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT, self, command_executor=ChromiumRemoteConnection( remote_server_addr=self.service.service_url, + browser_name=browser_name, vendor_prefix=vendor_prefix, keep_alive=keep_alive), desired_capabilities=desired_capabilities) except Exception: diff --git a/py/selenium/webdriver/edge/options.py b/py/selenium/webdriver/edge/options.py index 55890da7f7e95..f5d414407ecf9 100644 --- a/py/selenium/webdriver/edge/options.py +++ b/py/selenium/webdriver/edge/options.py @@ -22,52 +22,42 @@ class Options(ChromiumOptions): KEY = "ms:edgeOptions" - def __init__(self, is_legacy=True): + def __init__(self): super(Options, self).__init__() - self._is_legacy = is_legacy - self._custom_browser_name = None - - if is_legacy: - self._page_load_strategy = "normal" + self._use_chromium = False + self._use_webview = False @property - def custom_browser_name(self): - return self._custom_browser_name + def use_chromium(self): + return self._use_chromium - @custom_browser_name.setter - def custom_browser_name(self, value): - self._custom_browser_name = value + @use_chromium.setter + def use_chromium(self, value): + self._use_chromium = bool(value) @property - def page_load_strategy(self): - if not self._is_legacy: - raise AttributeError("Page Load Strategy only exists in Legacy Mode") - - return self._page_load_strategy - - @page_load_strategy.setter - def page_load_strategy(self, value): - if not self._is_legacy: - raise AttributeError("Page Load Strategy only exists in Legacy Mode") + def use_webview(self): + return self._use_webview - if value not in ['normal', 'eager', 'none']: - raise ValueError("Page Load Strategy should be 'normal', 'eager' or 'none'.") - self._page_load_strategy = value + @use_webview.setter + def use_webview(self, value): + self._use_webview = bool(value) def to_capabilities(self): """ Creates a capabilities with all the options that have been set and :Returns: A dictionary with everything """ - if not self._is_legacy: - return_caps = super(Options, self).to_capabilities() - if self._custom_browser_name: - return_caps['browserName'] = self._custom_browser_name - return return_caps - caps = self._caps - caps['pageLoadStrategy'] = self._page_load_strategy + if self._use_chromium: + caps = super(Options, self).to_capabilities() + if self._use_webview: + caps['browserName'] = 'WebView2' + else: + caps['platform'] = 'windows' + + caps['ms:edgeChromium'] = self._use_chromium return caps @property diff --git a/py/selenium/webdriver/edge/service.py b/py/selenium/webdriver/edge/service.py index b1b8501f3c35a..118f906f2e7bb 100644 --- a/py/selenium/webdriver/edge/service.py +++ b/py/selenium/webdriver/edge/service.py @@ -21,7 +21,7 @@ class Service(service.ChromiumService): def __init__(self, executable_path, port=0, verbose=False, log_path=None, - is_legacy=True, service_args=None, env=None): + service_args=None, env=None): """ Creates a new instance of the EdgeDriver service. EdgeDriver provides an interface for Microsoft WebDriver to use @@ -32,19 +32,16 @@ def __init__(self, executable_path, port=0, verbose=False, log_path=None, - port : Run the remote service on a specified port. Defaults to 0, which binds to a random open port of the system's choosing. - verbose : Whether to make the webdriver more verbose (passes the --verbose option to the binary). - Defaults to False. Should be only used for legacy mode. + Defaults to False. - log_path : Optional path for the webdriver binary to log to. Defaults to None which disables logging. - - is_legacy : Whether to use MicrosoftWebDriver.exe (legacy) or MSEdgeDriver.exe (chromium-based). Defaults to True. - service_args : List of args to pass to the WebDriver service. """ self.service_args = service_args or [] - if is_legacy: - if verbose: - self.service_args.append("--verbose") + if verbose: + self.service_args.append("--verbose") - service.ChromiumService.__init__( - self, + super(Service, self).__init__( executable_path, port, service_args, diff --git a/py/selenium/webdriver/edge/webdriver.py b/py/selenium/webdriver/edge/webdriver.py index 36cd1afda2118..f00297a9ec6b1 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. -from selenium.webdriver.common.desired_capabilities import DesiredCapabilities -from selenium.webdriver.edge.service import Service +import warnings from selenium.webdriver.chromium.webdriver import ChromiumDriver +from .options import Options +from .service import Service +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities DEFAULT_PORT = 0 @@ -24,42 +26,46 @@ class WebDriver(ChromiumDriver): + """ + Controls the Microsoft Edge driver and allows you to drive the browser. + You will need to download either the MicrosoftWebDriver (Legacy) + or MSEdgeDriver (Chromium) executable from + https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ + """ - def __init__(self, executable_path='MicrosoftWebDriver.exe', - capabilities=None, port=DEFAULT_PORT, verbose=False, - service_log_path=None, log_path=DEFAULT_SERVICE_LOG_PATH, - service=None, options=None, keep_alive=False, is_legacy=True, - service_args=None): + def __init__(self, executable_path='MicrosoftWebDriver.exe', port=DEFAULT_PORT, + options=None, service_args=None, + capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH, + service=None, keep_alive=False, verbose=False): """ Creates a new instance of the edge driver. Starts the service and then creates new instance of edge driver. :Args: - executable_path - Deprecated: path to the executable. If the default is used it assumes the executable is in the $PATH - - capabilities - Dictionary object with non-browser specific capabilities only, such as "proxy" or "loggingPref". - Only available in Legacy mode - port - Deprecated: port you would like the service to run, if left as 0, a free port will be found. - - verbose - whether to set verbose logging in the service. Only available in Legacy Mode + - options - this takes an instance of EdgeOptions + - service_args - Deprecated: List of args to pass to the driver service + - capabilities - Deprecated: Dictionary object with non-browser specific + capabilities only, such as "proxy" or "loggingPref". - service_log_path - Deprecated: Where to log information from the driver. - keep_alive - Whether to configure EdgeRemoteConnection to use HTTP keep-alive. - - service_args - Deprecated: List of args to pass to the driver service - - is_legacy: Whether to use MicrosoftWebDriver.exe (legacy) or MSEdgeDriver.exe (chromium-based). Defaults to True. + - verbose - whether to set verbose logging in the service. """ - if not is_legacy: + if executable_path != 'MicrosoftWebDriver.exe': + warnings.warn('executable_path has been deprecated, please pass in a Service object', + DeprecationWarning, stacklevel=2) + + if options is not None and options.use_chromium: executable_path = "msedgedriver" - service = service or Service(executable_path, - port=port, - verbose=verbose, - log_path=service_log_path, - is_legacy=is_legacy) + if service is None: + service = Service(executable_path, port, service_args, service_log_path) + + super(WebDriver, self).__init__(DesiredCapabilities.EDGE['browserName'], "ms", + port, options, + service_args, capabilities, + service_log_path, service, keep_alive) - super(WebDriver, self).__init__( - executable_path, - port, - options, - service_args, - DesiredCapabilities.EDGE, - service_log_path, - service, - keep_alive) + def create_options(self): + return Options() diff --git a/py/test/unit/selenium/webdriver/edge/edge_options_tests.py b/py/test/unit/selenium/webdriver/edge/edge_options_tests.py index 38f121b6ceff3..7aa8d70d36c63 100644 --- a/py/test/unit/selenium/webdriver/edge/edge_options_tests.py +++ b/py/test/unit/selenium/webdriver/edge/edge_options_tests.py @@ -32,11 +32,12 @@ def test_raises_exception_with_invalid_page_load_strategy(options): def test_set_page_load_strategy(options): options.page_load_strategy = 'normal' - assert options._page_load_strategy == 'normal' + caps = options.to_capabilities() + assert caps['pageLoadStrategy'] == 'normal' def test_get_page_load_strategy(options): - options._page_load_strategy = 'normal' + options._caps['pageLoadStrategy'] = 'normal' assert options.page_load_strategy == 'normal' @@ -58,8 +59,16 @@ def test_is_a_baseoptions(options): assert isinstance(options, BaseOptions) -def test_custom_browser_name(): - options = Options(is_legacy=False) - options.custom_browser_name = "testbrowsername" +def test_use_chromium(): + options = Options() + options.use_chromium = True + caps = options.to_capabilities() + assert caps['ms:edgeChromium'] == True + + +def test_use_webview(): + options = Options() + options.use_chromium = True + options.use_webview = True caps = options.to_capabilities() - assert caps['browserName'] == "testbrowsername" + assert caps['browserName'] == "WebView2"