Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support different browsers at playwright starts #398

Merged
merged 12 commits into from
Jul 15, 2024
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ v3.1.5
*Release date: In development*

- Fix `export_poeditor_project` method allowing empty export response
- Add `key=value` expressions for selecting elements in the context storage
- Upgrade Faker version to 25.9.*
- Fix result for action before the feature with error and background to fail scenarios

v3.1.4
------
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ Appium-Python-Client~=2.3 # mobile tests
Pillow~=10.1 # visual testing
screeninfo~=0.8
lxml~=5.1
Faker~=18.3
Faker~=25.9
phonenumbers~=8.13
19 changes: 13 additions & 6 deletions toolium/behave/env_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def execute_after_scenario_steps(self, context):
self.scenario_error = False
self.before_error_message = None
context.scenario.reset()
self.fail_first_step_precondition_exception(context.scenario)
self.fail_first_step_precondition_exception(context.scenario, error_message)
raise Exception(f'Before scenario steps have failed: {error_message}')

def execute_after_feature_steps(self, context):
Expand All @@ -298,18 +298,25 @@ def execute_after_feature_steps(self, context):
context.feature.reset()
for scenario in context.feature.walk_scenarios():
if scenario.should_run(context.config):
self.fail_first_step_precondition_exception(scenario)
self.fail_first_step_precondition_exception(scenario, error_message)
if len(scenario.background_steps) > 0:
context.logger.warn('Background from scenario status udpated to fail')
raise Exception(f'Before feature steps have failed: {error_message}')

def fail_first_step_precondition_exception(self, scenario):
def fail_first_step_precondition_exception(self, scenario, error_message):
"""
Fail first step in the given Scenario and add exception message for the output.
This is needed because xUnit exporter in Behave fails if there are not failed steps.
:param scenario: Behave's Scenario
:param error_message: Exception message
"""
# Behave is an optional dependency in toolium, so it is imported here
from behave.model_core import Status
if len(scenario.steps) > 0:
# Behave is an optional dependency in toolium, so it is imported here
from behave.model_core import Status
scenario.steps[0].status = Status.failed
scenario.steps[0].exception = Exception('Preconditions failed')
scenario.steps[0].error_message = str(self.before_error_message)
scenario.steps[0].error_message = str(error_message)
if len(scenario.background_steps) > 0:
scenario.background_steps[0].status = Status.failed
scenario.background_steps[0].exception = Exception('Preconditions failed')
scenario.background_steps[0].error_message = str(error_message)
266 changes: 264 additions & 2 deletions toolium/config_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ def get_error_message_from_traceback(traceback):


class ConfigDriver(object):
def __init__(self, config, utils=None):
def __init__(self, config, utils=None, playwright=None):
self.logger = logging.getLogger(__name__)
self.config = config
self.utils = utils
self.playwright = playwright

def create_driver(self):
"""Create a selenium driver using specified config properties
Expand All @@ -92,6 +93,36 @@ def create_driver(self):

return driver

def create_playwright_browser(self):
"""
Create a playwright browser using specified config properties

:returns: a new playwright browser o persistent browser context
"""
driver_type = self.config.get('Driver', 'type')
try:
self.logger.info("Creating playwright driver (type = %s)", driver_type)
return self._create_playwright_browser()
except Exception as exc:
error_message = get_error_message_from_exception(exc)
self.logger.error("%s driver can not be launched: %s", driver_type.capitalize(), error_message)
raise

def create_playwright_persistent_browser_context(self):
"""
Create a playwright persistent browser context using specified config properties

:returns: a new playwright persistent browser context
"""
driver_type = self.config.get('Driver', 'type')
try:
self.logger.info("Creating playwright persistent context (type = %s)", driver_type)
return self._create_playwright_persistent_browser_context()
except Exception as exc:
error_message = get_error_message_from_exception(exc)
self.logger.error("%s driver can not be launched: %s", driver_type.capitalize(), error_message)
raise

def _create_remote_driver(self):
"""Create a driver in a remote server
View valid capabilities in https://www.selenium.dev/documentation/webdriver/drivers/options/
Expand Down Expand Up @@ -164,6 +195,76 @@ def _create_local_driver(self):

return driver

def _create_playwright_browser(self):
"""Create a browser in local machine using Playwright

:returns: a new browser Playwright
"""
driver_name = self.utils.get_driver_name()
if driver_name in ('android', 'ios', 'iphone'):
raise Exception('Playwright does not support mobile devices')
else:
if driver_name in ['chrome', 'chromium']:
browser = self._setup_playwright_chrome()
elif driver_name == 'firefox':
browser = self._setup_playwright_firefox()
elif driver_name in ['safari', 'webkit']:
browser = self._setup_playwright_webkit()
else:
raise Exception(f'Playwright does not support {driver_name} driver')
return browser

def _create_playwright_persistent_browser_context(self):
"""Create a browser in local machine using Playwright

:returns: a new persistent browser context Playwright
"""
driver_name = self.utils.get_driver_name()
if driver_name in ('android', 'ios', 'iphone'):
raise Exception('Playwright does not support mobile devices')
else:
if driver_name in ['chrome', 'chromium']:
browser_context = self._setup_playwright_persistent_chrome()
elif driver_name == 'firefox':
browser_context = self._setup_playwright_persistent_firefox()
elif driver_name in ['safari', 'webkit']:
browser_context = self._setup_playwright_persistent_webkit()
else:
raise Exception(f'Playwright does not support {driver_name} driver')
return browser_context

def get_playwright_context_options(self):
"""Get Playwright context options from properties file

:returns: Playwright context options
"""
context_options = {}
try:
for key, value in dict(self.config.items('PlaywrightContextOptions')).items():
self.logger.debug("Added Playwright context option: %s = %s", key, value)
context_options[key] = self._convert_property_type(value)
except NoSectionError:
pass
window_width = self.config.get_optional('Driver', 'window_width')
window_height = self.config.get_optional('Driver', 'window_height')
if window_width and window_height:
context_options['viewport'] = {'width': int(window_width), 'height': int(window_height)}
return context_options

def get_playwright_page_options(self):
"""Get Playwright page options from properties file

:returns: Playwright page options
"""
page_options = {}
try:
for key, value in dict(self.config.items('PlaywrightPageOptions')).items():
self.logger.debug("Added Playwright page option: %s = %s", key, value)
page_options[key] = self._convert_property_type(value)
except NoSectionError:
pass
return page_options

def _get_capabilities_from_driver_type(self):
"""Extract browserVersion and platformName from driver type and add them to capabilities

Expand Down Expand Up @@ -295,6 +396,69 @@ def _add_firefox_extensions(self, driver):
except NoSectionError:
pass

def _setup_playwright_firefox(self):
"""Setup Playwright Firefox browser

:returns: a new Playwright Firefox browser
"""
headless_mode = self.config.getboolean_optional('Driver', 'headless')
arguments = []
preferences = {}
self._add_playwright_firefox_arguments(arguments)
# Note: Playwright does not support Firefox extensions
self._add_playwright_firefox_preferences(preferences)
browser_options = self._get_playwright_browser_options()
browser_options = self._update_dict(browser_options, {'args': arguments})
browser_options = self._update_dict(browser_options, {'firefox_user_prefs': preferences})
return self.playwright.firefox.launch(
headless=headless_mode,
**browser_options
)

def _setup_playwright_persistent_firefox(self):
"""Setup Playwright Firefox persistent browser context

:returns: a new Playwright Firefox persistent browser context
"""
headless_mode = self.config.getboolean_optional('Driver', 'headless')
arguments = []
preferences = {}
self._add_playwright_firefox_arguments(arguments)
# Note: Playwright does not support Firefox extensions
self._add_playwright_firefox_preferences(preferences)
context_options = self.get_playwright_context_options()
context_options = self._update_dict(context_options, {'args': arguments})
context_options = self._update_dict(context_options, {'firefox_user_prefs': preferences})
return self.playwright.firefox.launch_persistent_context(
headless=headless_mode,
**context_options
)

def _add_playwright_firefox_arguments(self, arguments):
"""Add Firefox arguments from properties file prepared for Playwright

:param arguments: Firefox arguments object
"""
try:
for pref, pref_value in dict(self.config.items('FirefoxArguments')).items():
pref_value = '={}'.format(pref_value) if pref_value else ''
self.logger.debug("Added Firefox argument: %s%s", pref, pref_value)
arguments.append('--{}{}'.format(pref, self._convert_property_type(pref_value)))
except NoSectionError:
pass

def _add_playwright_firefox_preferences(self, preferences):
"""Add Firefox preferences from properties file prepared for Playwright

:param preferences: Firefox preferences object
"""
try:
for pref, pref_value in dict(self.config.items('FirefoxPreferences')).items():
self.logger.debug("Added Firefox preference: %s = %s", pref, pref_value)
preferences[pref] = self._convert_property_type(pref_value)
except NoSectionError:
pass

@staticmethod
def _convert_property_type(value):
"""Converts the string value in a boolean, integer or string
Expand Down Expand Up @@ -362,6 +526,80 @@ def _get_chrome_options(self, capabilities={}):

return options

def _get_playwright_browser_options(self):
"""
Get Playwright browser options from properties file

:returns: Playwright browser options
"""
browser_options = {}
try:
for key, value in dict(self.config.items('PlaywrightBrowserOptions')).items():
self.logger.debug("Added Playwright Browser option: %s = %s", key, value)
browser_options[key] = self._convert_property_type(value)
except NoSectionError:
pass
return browser_options

def _setup_playwright_chrome(self):
"""
Setup Playwright Chrome browser

:returns: a new Playwright Chrome browser
"""
headless_mode = self.config.getboolean_optional('Driver', 'headless')
arguments = []
self._add_playwright_chrome_arguments(arguments)
self._add_playwright_chrome_extensions(arguments)
browser_options = self._get_playwright_browser_options()
browser_options = self._update_dict(browser_options, {'args': arguments})
return self.playwright.chromium.launch(
headless=headless_mode,
**browser_options
)

def _setup_playwright_persistent_chrome(self):
"""
Setup Playwright Chrome persistent browser context

:returns: a new Playwright Chrome persistent browser context
"""
headless_mode = self.config.getboolean_optional('Driver', 'headless')
arguments = []
self._add_playwright_chrome_arguments(arguments)
self._add_playwright_chrome_extensions(arguments)
context_options = self.get_playwright_context_options()
context_options = self._update_dict(context_options, {'args': arguments})
return self.playwright.chromium.launch_persistent_context(
headless=headless_mode,
**context_options
)

def _add_playwright_chrome_arguments(self, arguments):
"""Add Chrome arguments from properties file prepared for Playwright

:param arguments: Chrome arguments object
"""
try:
for pref, pref_value in dict(self.config.items('ChromeArguments')).items():
pref_value = '={}'.format(pref_value) if pref_value else ''
self.logger.debug("Added Chrome argument: %s%s", pref, pref_value)
arguments.append('--{}{}'.format(pref, self._convert_property_type(pref_value)))
except NoSectionError:
pass

def _add_playwright_chrome_extensions(self, arguments):
"""Add Chrome extensions from properties file

:param arguments: Chrome options object
"""
try:
for pref, pref_value in dict(self.config.items('ChromeExtensions')).items():
self.logger.debug("Added Chrome extension: %s = %s", pref, pref_value)
arguments.append('--load-extension={}'.format(pref_value))
except NoSectionError:
pass

def _add_chrome_options(self, options, option_name):
"""Add Chrome options from properties file

Expand Down Expand Up @@ -427,7 +665,7 @@ def _update_dict(self, initial, update, initial_key=None):
:param initial: initial dict to be updated
:param update: new dict
:param initial_key: update only one key in initial dicts
:return: merged dict
:returns: merged dict
"""
for key, value in update.items():
if initial_key is None or key == initial_key:
Expand Down Expand Up @@ -459,6 +697,30 @@ def _get_safari_options(self, capabilities={}):
self._update_dict(options.capabilities, capabilities)
return options

def _setup_playwright_webkit(self):
"""Setup Playwright Webkit browser

:returns: a new Playwright Webkit browser
"""
headless_mode = self.config.getboolean_optional('Driver', 'headless')
browser_options = self._get_playwright_browser_options()
return self.playwright.webkit.launch(
headless=headless_mode,
**browser_options
)

def _setup_playwright_persistent_webkit(self):
"""Setup Playwright Webkit persistent browser context

:returns: a new Playwright Webkit persistent browser context
"""
headless_mode = self.config.getboolean_optional('Driver', 'headless')
context_options = self.get_playwright_context_options()
return self.playwright.webkit.launch_persistent_context(
headless=headless_mode,
**context_options
)

def _setup_explorer(self):
"""Setup Internet Explorer webdriver

Expand Down
Loading
Loading