Skip to content

Commit

Permalink
feat: reuse toolium driver and pageobjects for playwright tests (#389)
Browse files Browse the repository at this point in the history
  • Loading branch information
rgonalo committed May 31, 2024
1 parent b114f84 commit cdc8ca5
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 30 deletions.
25 changes: 6 additions & 19 deletions toolium/behave/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
21 changes: 20 additions & 1 deletion toolium/driver_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand Down
23 changes: 23 additions & 0 deletions toolium/pageelements/playwright/__init__.py
Original file line number Diff line number Diff line change
@@ -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']
36 changes: 36 additions & 0 deletions toolium/pageelements/playwright/button_page_element.py
Original file line number Diff line number Diff line change
@@ -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
69 changes: 69 additions & 0 deletions toolium/pageelements/playwright/input_text_page_element.py
Original file line number Diff line number Diff line change
@@ -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
65 changes: 65 additions & 0 deletions toolium/pageelements/playwright/page_element.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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()

0 comments on commit cdc8ca5

Please sign in to comment.