Skip to content

Commit

Permalink
feat: reuse toolium driver and pageobjects for playwright tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rgonalo committed May 24, 2024
1 parent b114f84 commit 26e9e9f
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 80 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ screeninfo~=0.8
lxml~=5.1
Faker~=18.3
phonenumbers~=8.13
universalasync~=0.3
24 changes: 6 additions & 18 deletions toolium/behave/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,21 +291,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)
22 changes: 21 additions & 1 deletion toolium/driver_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import os

import screeninfo
from behave.api.async_step import use_or_create_async_context
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 +56,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 +207,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 +247,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
12 changes: 9 additions & 3 deletions toolium/pageelements/button_page_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"""

from selenium.common.exceptions import StaleElementReferenceException
from universalasync import async_to_sync_wraps

from toolium.pageelements.page_element import PageElement


Expand All @@ -33,14 +35,18 @@ def text(self):
# Retry if element has changed
return self.web_element.text

def click(self):
@async_to_sync_wraps
async def click(self):
"""Click the element
:returns: page element instance
"""
if self.is_playwright:
await (await self.web_element).click()
return self
try:
self.wait_until_clickable().web_element.click()
(await self.wait_until_clickable().web_element).click()
except StaleElementReferenceException:
# Retry if element has changed
self.web_element.click()
(await self.web_element).click()
return self
7 changes: 7 additions & 0 deletions toolium/pageelements/input_text_page_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ def text(self, value):
else:
self.web_element.send_keys(value)

async def set_async_text(self, value):
"""Set value on the element
:param value: value to be set
"""
await (await self.web_element).fill(value)

def clear(self):
"""Clear the element value
Expand Down
39 changes: 31 additions & 8 deletions toolium/pageelements/page_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.common.by import By
from universalasync import async_to_sync_wraps

from toolium.driver_wrapper import DriverWrappersPool
from toolium.pageobjects.common_object import CommonObject
Expand Down Expand Up @@ -74,15 +75,16 @@ def reset_object(self, driver_wrapper=None):
self.driver_wrapper = driver_wrapper
self._web_element = None

@async_to_sync_wraps
@property
def web_element(self):
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:
self._find_web_element()
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"
Expand All @@ -91,7 +93,8 @@ def web_element(self):
raise exception
return self._web_element

def _find_web_element(self):
@async_to_sync_wraps
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'):
# check context for mobile webviews
Expand All @@ -113,9 +116,12 @@ def _find_web_element(self):
else:
# Element will be searched from parent element or from driver
base = self.utils.get_web_element(self.parent) if self.parent else self.driver
# Find elements and get the correct index or find a single element
self._web_element = base.find_elements(*self.locator)[self.order] if self.order \
else base.find_element(*self.locator)
if self.is_playwright:
self._web_element = self.driver.locator(self.playwright_locator)
else:
# Find elements and get the correct index or find a single element
self._web_element = base.find_elements(*self.locator)[self.order] if self.order \
else base.find_element(*self.locator)

def parent_locator_str(self):
"""Return string with locator tuple for parent element
Expand All @@ -130,6 +136,22 @@ def parent_locator_str(self):
parent_locator = '[WebElement without locator]'
return parent_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

def scroll_element_into_view(self):
"""Scroll element into view
Expand All @@ -140,15 +162,16 @@ def scroll_element_into_view(self):
self.driver.execute_script('window.scrollTo({0}, {1})'.format(x, y))
return self

def is_present(self):
@async_to_sync_wraps
async def is_present(self):
"""Find element and return True if it is present
:returns: True if element is located
"""
try:
# Use _find_web_element() instead of web_element to avoid logging error message
self._web_element = None
self._find_web_element()
await self._find_web_element()
return True
except NoSuchElementException:
return False
Expand Down
7 changes: 7 additions & 0 deletions toolium/pageelements/text_page_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ def text(self):
:returns: the text of the element
"""
return self.web_element.text

async def async_text(self):
"""Get the text of the element
:returns: the text of the element
"""
return await (await self.web_element).text_content()
8 changes: 8 additions & 0 deletions toolium/pageobjects/common_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ def utils(self):
"""
return self.driver_wrapper.utils

@property
def is_playwright(self):
"""Check if the driver is a playwright driver
:returns: True if the driver is a playwright driver
"""
return self.config.get_optional('Driver', 'web_library') == 'playwright'

def _switch_to_new_context(self, context):
""" Change to a new context if its different than the current one"""
if self.driver.context != context:
Expand Down
32 changes: 0 additions & 32 deletions toolium/pageobjects/playwright_page_object.py

This file was deleted.

Loading

0 comments on commit 26e9e9f

Please sign in to comment.