From e5dc5cd551d7589abc587dc31704452840058b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Gonz=C3=A1lez=20Alonso?= Date: Thu, 16 May 2024 18:22:36 +0200 Subject: [PATCH] feat: reuse toolium driver and pageobjects for playwright tests --- 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()