From b114f8415f6c8718ca26eee40d2f4c77142e0ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Gonz=C3=A1lez=20Alonso?= Date: Thu, 16 May 2024 18:08:55 +0200 Subject: [PATCH 1/5] feat: add initial integration of playwright (#387) * feat: add initial integration of playwright * avoid error in after_scenario --- VERSION | 2 +- requirements.txt | 1 + toolium/behave/environment.py | 31 ++++++++++++++++-- toolium/pageobjects/playwright_page_object.py | 32 +++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 toolium/pageobjects/playwright_page_object.py diff --git a/VERSION b/VERSION index e94017a5..745e2a57 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.5.dev0 +3.2.0.dev0 diff --git a/requirements.txt b/requirements.txt index 281ce90d..32034f00 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ requests~=2.27 # api tests selenium~=4.0 # web tests +playwright~=1.43 # web tests Appium-Python-Client~=2.3 # mobile tests Pillow~=10.1 # visual testing screeninfo~=0.8 diff --git a/toolium/behave/environment.py b/toolium/behave/environment.py index b6f66f09..8236aa3f 100644 --- a/toolium/behave/environment.py +++ b/toolium/behave/environment.py @@ -20,6 +20,9 @@ import os import re +from behave.api.async_step import use_or_create_async_context +from playwright.async_api import async_playwright + from toolium.utils import dataset from toolium.config_files import ConfigFiles from toolium.driver_wrapper import DriverWrappersPool @@ -225,6 +228,12 @@ def after_scenario(context, scenario): DriverWrappersPool.close_drivers(scope='function', test_name=scenario.name, test_passed=scenario.status in ['passed', 'skipped'], context=context) + # Stop playwright + if context.toolium_config.get_optional('Driver', 'web_library') == 'playwright' and hasattr(context, 'playwright'): + # TODO: reuse driver like in close_drivers + loop = context.async_context.loop + loop.run_until_complete(context.playwright.stop()) + # Save test status to be updated later if jira_test_status: add_jira_status(get_jira_key_from_scenario(scenario), jira_test_status, jira_test_comment) @@ -281,6 +290,22 @@ def start_driver(context, no_driver): :param context: behave context :param no_driver: True if this is an api test and driver should not be started """ - create_and_configure_wrapper(context) - if not no_driver: - connect_wrapper(context) + if context.toolium_config.get_optional('Driver', 'web_library') == 'playwright': + start_playwright(context) + else: + create_and_configure_wrapper(context) + if not no_driver: + connect_wrapper(context) + + +def start_playwright(context): + """Start playwright with configured values + + :param context: behave context + """ + use_or_create_async_context(context) + loop = context.async_context.loop + context.playwright = loop.run_until_complete(async_playwright().start()) + # TODO: select browser from config + context.browser = loop.run_until_complete(context.playwright.chromium.launch(headless=False)) + context.page = loop.run_until_complete(context.browser.new_page()) diff --git a/toolium/pageobjects/playwright_page_object.py b/toolium/pageobjects/playwright_page_object.py new file mode 100644 index 00000000..d4e33ea6 --- /dev/null +++ b/toolium/pageobjects/playwright_page_object.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +""" +Copyright 2024 Telefónica Innovación Digital, S.L. +This file is part of Toolium. + +Licensed 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 playwright.async_api import Page +from toolium.pageobjects.page_object import PageObject + + +class PlaywrightPageObject(PageObject): + """Class to represent a playwright web page""" + + def __init__(self, page: Page): + """Initialize page object properties + + :param page: playwright page instance + """ + self.page = page + super(PlaywrightPageObject, self).__init__() From cdc8ca5c32c04f073cc4144753d521254672eacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Gonz=C3=A1lez=20Alonso?= Date: Fri, 31 May 2024 09:39:54 +0200 Subject: [PATCH 2/5] feat: reuse toolium driver and pageobjects for playwright tests (#389) --- toolium/behave/environment.py | 25 ++----- toolium/driver_wrapper.py | 21 +++++- toolium/pageelements/playwright/__init__.py | 23 +++++++ .../playwright/button_page_element.py | 36 ++++++++++ .../playwright/input_text_page_element.py | 69 +++++++++++++++++++ .../pageelements/playwright/page_element.py | 65 +++++++++++++++++ .../playwright/text_page_element.py} | 16 ++--- 7 files changed, 225 insertions(+), 30 deletions(-) create mode 100644 toolium/pageelements/playwright/__init__.py create mode 100644 toolium/pageelements/playwright/button_page_element.py create mode 100644 toolium/pageelements/playwright/input_text_page_element.py create mode 100644 toolium/pageelements/playwright/page_element.py rename toolium/{pageobjects/playwright_page_object.py => pageelements/playwright/text_page_element.py} (62%) diff --git a/toolium/behave/environment.py b/toolium/behave/environment.py index 8236aa3f..8a35028f 100644 --- a/toolium/behave/environment.py +++ b/toolium/behave/environment.py @@ -21,7 +21,6 @@ import re from behave.api.async_step import use_or_create_async_context -from playwright.async_api import async_playwright from toolium.utils import dataset from toolium.config_files import ConfigFiles @@ -291,21 +290,9 @@ def start_driver(context, no_driver): :param no_driver: True if this is an api test and driver should not be started """ if context.toolium_config.get_optional('Driver', 'web_library') == 'playwright': - start_playwright(context) - else: - create_and_configure_wrapper(context) - if not no_driver: - connect_wrapper(context) - - -def start_playwright(context): - """Start playwright with configured values - - :param context: behave context - """ - use_or_create_async_context(context) - loop = context.async_context.loop - context.playwright = loop.run_until_complete(async_playwright().start()) - # TODO: select browser from config - context.browser = loop.run_until_complete(context.playwright.chromium.launch(headless=False)) - context.page = loop.run_until_complete(context.browser.new_page()) + # Activate behave async context to execute playwright + use_or_create_async_context(context) + context.driver_wrapper.async_loop = context.async_context.loop + create_and_configure_wrapper(context) + if not no_driver: + connect_wrapper(context) diff --git a/toolium/driver_wrapper.py b/toolium/driver_wrapper.py index ffa9a940..560b592a 100644 --- a/toolium/driver_wrapper.py +++ b/toolium/driver_wrapper.py @@ -20,6 +20,7 @@ import os import screeninfo +from playwright.async_api import async_playwright from toolium.config_driver import ConfigDriver from toolium.config_parser import ExtendedConfigParser @@ -54,6 +55,7 @@ class DriverWrapper(object): remote_node = None #: remote grid node remote_node_video_enabled = False #: True if the remote grid node has the video recorder enabled logger = None #: logger instance + async_loop = None #: async loop for playwright tests # Configuration and output files config_properties_filenames = None #: configuration filenames separated by commas @@ -204,11 +206,16 @@ def configure(self, tc_config_files, is_selenium_test=True, behave_properties=No def connect(self): """Set up the selenium driver and connect to the server - :returns: selenium driver + :returns: selenium or playwright driver """ if not self.config.get('Driver', 'type') or self.config.get('Driver', 'type') in ['api', 'no_driver']: return None + if self.async_loop: + # Connect playwright driver + self.driver = self.connect_playwright(self.async_loop) + return self.driver + self.driver = ConfigDriver(self.config, self.utils).create_driver() # Save session id and remote node to download video after the test execution @@ -239,6 +246,18 @@ def connect(self): return self.driver + def connect_playwright(self, async_loop): + """Set up the playwright page + + :returns: playwright page + """ + # TODO: should playwright and browser be saved in driver_wrapper? + playwright = async_loop.run_until_complete(async_playwright().start()) + # TODO: select browser from config + browser = async_loop.run_until_complete(playwright.chromium.launch(headless=False)) + page = async_loop.run_until_complete(browser.new_page()) + return page + def resize_window(self): """Resize and move browser window""" if self.is_maximizable(): diff --git a/toolium/pageelements/playwright/__init__.py b/toolium/pageelements/playwright/__init__.py new file mode 100644 index 00000000..e323b03c --- /dev/null +++ b/toolium/pageelements/playwright/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" +Copyright 2024 Telefónica Innovación Digital, S.L. +This file is part of Toolium. + +Licensed 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 toolium.pageelements.playwright.button_page_element import Button +from toolium.pageelements.playwright.input_text_page_element import InputText +from toolium.pageelements.playwright.text_page_element import Text + +__all__ = ['Text', 'InputText', 'Button'] diff --git a/toolium/pageelements/playwright/button_page_element.py b/toolium/pageelements/playwright/button_page_element.py new file mode 100644 index 00000000..e73f3161 --- /dev/null +++ b/toolium/pageelements/playwright/button_page_element.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +Copyright 2024 Telefónica Innovación Digital, S.L. +This file is part of Toolium. + +Licensed 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 toolium.pageelements.playwright.page_element import PageElement + + +class Button(PageElement): + async def get_text(self): + """Get the element text value + + :returns: element text value + """ + return await (await self.web_element).get_text() + + async def click(self): + """Click the element + + :returns: page element instance + """ + await (await self.web_element).click() + return self diff --git a/toolium/pageelements/playwright/input_text_page_element.py b/toolium/pageelements/playwright/input_text_page_element.py new file mode 100644 index 00000000..7f7f540c --- /dev/null +++ b/toolium/pageelements/playwright/input_text_page_element.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" +Copyright 2024 Telefónica Innovación Digital, S.L. +This file is part of Toolium. + +Licensed 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 toolium.pageelements.playwright.page_element import PageElement + + +class InputText(PageElement): + # TODO: convert to async get_text + @property + def text(self): + """Get the element text value + + :returns: element text value + """ + if self.driver_wrapper.is_web_test() or self.webview: + return self.web_element.get_attribute("value") + elif self.driver_wrapper.is_ios_test(): + return self.web_element.get_attribute("label") + elif self.driver_wrapper.is_android_test(): + return self.web_element.get_attribute("text") + + async def fill(self, value): + """Set value on the element + + :param value: value to be set + """ + await (await self.web_element).fill(value) + + # TODO: convert to async method + def clear(self): + """Clear the element value + + :returns: page element instance + """ + self.web_element.clear() + return self + + async def click(self): + """Click the element + + :returns: page element instance + """ + await (await self.web_element).click() + return self + + # TODO: convert to async method + def set_focus(self): + """ + Set the focus over the element and click on the InputField + + :returns: page element instance + """ + self.utils.focus_element(self.web_element, click=True) + return self diff --git a/toolium/pageelements/playwright/page_element.py b/toolium/pageelements/playwright/page_element.py new file mode 100644 index 00000000..c7e653b2 --- /dev/null +++ b/toolium/pageelements/playwright/page_element.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +""" +Copyright 2024 Telefónica Innovación Digital, S.L. +This file is part of Toolium. + +Licensed 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.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By + +from toolium.pageelements import PageElement as BasePageElement + + +class PageElement(BasePageElement): + @property + async def web_element(self): + """Find WebElement using element locator + + :returns: web element object + :rtype: selenium.webdriver.remote.webelement.WebElement or appium.webdriver.webelement.WebElement + """ + try: + await self._find_web_element() + except NoSuchElementException as exception: + parent_msg = f" and parent locator {self.parent_locator_str()}" if self.parent else '' + msg = "Page element of type '%s' with locator %s%s not found" + self.logger.error(msg, type(self).__name__, self.locator, parent_msg) + exception.msg += "\n {}".format(msg % (type(self).__name__, self.locator, parent_msg)) + raise exception + return self._web_element + + async def _find_web_element(self): + """Find WebElement using element locator and save it in _web_element attribute""" + if not self._web_element or not self.driver_wrapper.config.getboolean_optional('Driver', 'save_web_element'): + # Element will be searched from parent element or from driver + # TODO: search from parent element + # base = self.utils.get_web_element(self.parent) if self.parent else self.driver + self._web_element = self.driver.locator(self.playwright_locator) + + @property + def playwright_locator(self): + """Return playwright locator converted from toolium/selenium locator + + :returns: playwright locator + """ + # TODO: Implement playwright locator conversion + if self.locator[0] == By.ID: + prefix = '#' + elif self.locator[0] == By.XPATH: + prefix = 'xpath=' + else: + raise ValueError(f'Locator type not supported to be converted to playwright: {self.locator[0]}') + playwright_locator = f'{prefix}{self.locator[1]}' + return playwright_locator diff --git a/toolium/pageobjects/playwright_page_object.py b/toolium/pageelements/playwright/text_page_element.py similarity index 62% rename from toolium/pageobjects/playwright_page_object.py rename to toolium/pageelements/playwright/text_page_element.py index d4e33ea6..e1fea263 100644 --- a/toolium/pageobjects/playwright_page_object.py +++ b/toolium/pageelements/playwright/text_page_element.py @@ -16,17 +16,13 @@ limitations under the License. """ -from playwright.async_api import Page -from toolium.pageobjects.page_object import PageObject +from toolium.pageelements.playwright.page_element import PageElement -class PlaywrightPageObject(PageObject): - """Class to represent a playwright web page""" +class Text(PageElement): + async def get_text(self): + """Get the text of the element - def __init__(self, page: Page): - """Initialize page object properties - - :param page: playwright page instance + :returns: the text of the element """ - self.page = page - super(PlaywrightPageObject, self).__init__() + return await (await self.web_element).text_content() From d1a4090cf8f5ea8c6efeaf267414eab2bf230b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Garc=C3=ADa=20Fern=C3=A1ndez?= Date: Thu, 6 Jun 2024 11:24:14 +0200 Subject: [PATCH 3/5] chore: include playwright module on setup (#391) * chore: include playwright module on setup --- setup.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 94941cc1..f96063d0 100644 --- a/setup.py +++ b/setup.py @@ -43,8 +43,21 @@ def get_long_description(): setup( name='toolium', version=read_file('VERSION').strip(), - packages=['toolium', 'toolium.pageobjects', 'toolium.pageelements', 'toolium.behave', 'toolium.utils'], - package_data={'': ['resources/VisualTestsTemplate.html', 'resources/VisualTests.js', 'resources/VisualTests.css']}, + packages=[ + 'toolium', + 'toolium.pageobjects', + 'toolium.pageelements', + 'toolium.pageelements.playwright', + 'toolium.behave', + 'toolium.utils', + ], + package_data={ + '': [ + 'resources/VisualTestsTemplate.html', + 'resources/VisualTests.js', + 'resources/VisualTests.css', + ] + }, install_requires=read_file('requirements.txt').splitlines(), setup_requires=['pytest-runner'], tests_require=read_file('requirements_dev.txt').splitlines(), From d9b3435d074f1d0a8c312be189354eb35ab98489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Garc=C3=ADa=20Fern=C3=A1ndez?= Date: Thu, 6 Jun 2024 12:06:19 +0200 Subject: [PATCH 4/5] feat: playwright enable headless config (#392) --- toolium/driver_wrapper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toolium/driver_wrapper.py b/toolium/driver_wrapper.py index 560b592a..b3ec879b 100644 --- a/toolium/driver_wrapper.py +++ b/toolium/driver_wrapper.py @@ -254,7 +254,8 @@ def connect_playwright(self, async_loop): # TODO: should playwright and browser be saved in driver_wrapper? playwright = async_loop.run_until_complete(async_playwright().start()) # TODO: select browser from config - browser = async_loop.run_until_complete(playwright.chromium.launch(headless=False)) + headless_mode = self.config.getboolean_optional('Driver', 'headless') + browser = async_loop.run_until_complete(playwright.chromium.launch(headless=headless_mode)) page = async_loop.run_until_complete(browser.new_page()) return page From 860493547bc16463d34018242f56daa97b0f4a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Gonz=C3=A1lez=20Alonso?= Date: Tue, 11 Jun 2024 15:22:34 +0200 Subject: [PATCH 5/5] feat: stop playwright when driver is closed (#393) --- toolium/behave/environment.py | 16 ++++------ toolium/driver_wrapper.py | 53 +++++++++++++++++++++++++-------- toolium/driver_wrappers_pool.py | 2 +- 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/toolium/behave/environment.py b/toolium/behave/environment.py index 8a35028f..11509469 100644 --- a/toolium/behave/environment.py +++ b/toolium/behave/environment.py @@ -156,6 +156,12 @@ def create_and_configure_wrapper(context): # Configure wrapper context.driver_wrapper.configure(context.config_files, behave_properties=behave_properties) + # Activate behave async context to execute playwright + if (context.driver_wrapper.config.get_optional('Driver', 'web_library') == 'playwright' + and context.driver_wrapper.async_loop is None): + use_or_create_async_context(context) + context.driver_wrapper.async_loop = context.async_context.loop + # Copy config object context.toolium_config = context.driver_wrapper.config @@ -227,12 +233,6 @@ def after_scenario(context, scenario): DriverWrappersPool.close_drivers(scope='function', test_name=scenario.name, test_passed=scenario.status in ['passed', 'skipped'], context=context) - # Stop playwright - if context.toolium_config.get_optional('Driver', 'web_library') == 'playwright' and hasattr(context, 'playwright'): - # TODO: reuse driver like in close_drivers - loop = context.async_context.loop - loop.run_until_complete(context.playwright.stop()) - # Save test status to be updated later if jira_test_status: add_jira_status(get_jira_key_from_scenario(scenario), jira_test_status, jira_test_comment) @@ -289,10 +289,6 @@ def start_driver(context, no_driver): :param context: behave context :param no_driver: True if this is an api test and driver should not be started """ - if context.toolium_config.get_optional('Driver', 'web_library') == 'playwright': - # Activate behave async context to execute playwright - use_or_create_async_context(context) - context.driver_wrapper.async_loop = context.async_context.loop create_and_configure_wrapper(context) if not no_driver: connect_wrapper(context) diff --git a/toolium/driver_wrapper.py b/toolium/driver_wrapper.py index b3ec879b..0d9e2470 100644 --- a/toolium/driver_wrapper.py +++ b/toolium/driver_wrapper.py @@ -56,6 +56,8 @@ class DriverWrapper(object): remote_node_video_enabled = False #: True if the remote grid node has the video recorder enabled logger = None #: logger instance async_loop = None #: async loop for playwright tests + playwright = None #: playwright instance + playwright_browser = None #: playwright browser instance # Configuration and output files config_properties_filenames = None #: configuration filenames separated by commas @@ -70,6 +72,9 @@ def __init__(self): default_wrapper = DriverWrappersPool.get_default_wrapper() self.config = default_wrapper.config.deepcopy() self.logger = default_wrapper.logger + self.async_loop = default_wrapper.async_loop + self.playwright = default_wrapper.playwright + self.playwright_browser = default_wrapper.playwright_browser self.config_properties_filenames = default_wrapper.config_properties_filenames self.config_log_filename = default_wrapper.config_log_filename self.output_log_filename = default_wrapper.output_log_filename @@ -204,7 +209,7 @@ def configure(self, tc_config_files, is_selenium_test=True, behave_properties=No self.configure_visual_baseline() def connect(self): - """Set up the selenium driver and connect to the server + """Set up the driver and connect to the server :returns: selenium or playwright driver """ @@ -212,10 +217,17 @@ def connect(self): return None if self.async_loop: - # Connect playwright driver - self.driver = self.connect_playwright(self.async_loop) - return self.driver + self.connect_playwright() + else: + self.connect_selenium() + return self.driver + + def connect_selenium(self): + """Set up selenium driver + + :returns: selenium driver + """ self.driver = ConfigDriver(self.config, self.utils).create_driver() # Save session id and remote node to download video after the test execution @@ -244,20 +256,37 @@ def connect(self): # Set implicitly wait timeout self.utils.set_implicitly_wait() - return self.driver - - def connect_playwright(self, async_loop): + def connect_playwright(self): """Set up the playwright page + It is a sync method because it is called from sync behave initialization method :returns: playwright page """ - # TODO: should playwright and browser be saved in driver_wrapper? - playwright = async_loop.run_until_complete(async_playwright().start()) + async_loop = self.async_loop + self.playwright = async_loop.run_until_complete(async_playwright().start()) # TODO: select browser from config headless_mode = self.config.getboolean_optional('Driver', 'headless') - browser = async_loop.run_until_complete(playwright.chromium.launch(headless=headless_mode)) - page = async_loop.run_until_complete(browser.new_page()) - return page + self.playwright_browser = async_loop.run_until_complete(self.playwright.chromium.launch(headless=headless_mode)) + self.driver = async_loop.run_until_complete(self.playwright_browser.new_page()) + + async def connect_playwright_new_page(self): + """Set up and additional playwright driver creating a new context and page in current browser instance + It is an async method to be called from async steps or page objects + + :returns: playwright driver + """ + context = await self.playwright_browser.new_context() + self.driver = await context.new_page() + return self.driver + + def stop(self): + """Stop selenium or playwright driver""" + if self.async_loop: + # Stop playwright driver + self.async_loop.run_until_complete(self.driver.close()) + else: + # Stop selenium driver + self.driver.quit() def resize_window(self): """Resize and move browser window""" diff --git a/toolium/driver_wrappers_pool.py b/toolium/driver_wrappers_pool.py index 7f2298b6..4aea05d7 100644 --- a/toolium/driver_wrappers_pool.py +++ b/toolium/driver_wrappers_pool.py @@ -167,7 +167,7 @@ def stop_drivers(cls, maintain_default=False): if not driver_wrapper.driver: continue try: - driver_wrapper.driver.quit() + driver_wrapper.stop() except Exception as e: driver_wrapper.logger.warning( "Capture exceptions to avoid errors in teardown method due to session timeouts: \n %s" % e)