From be359c779b30d56eccdb0bef83f57aaa75b3a3bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ruiz=20Garc=C3=ADa?= Date: Mon, 8 Jul 2024 14:04:43 +0200 Subject: [PATCH] playwright start by browser --- toolium/config_driver.py | 267 +++++++++++++++++++++++++++++++++++++- toolium/driver_wrapper.py | 24 ++-- 2 files changed, 280 insertions(+), 11 deletions(-) diff --git a/toolium/config_driver.py b/toolium/config_driver.py index 9b4bb730..0adea55b 100644 --- a/toolium/config_driver.py +++ b/toolium/config_driver.py @@ -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 @@ -91,6 +92,36 @@ def create_driver(self): raise 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 @@ -163,6 +194,77 @@ def _create_local_driver(self): driver = driver_setup_method() 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 @@ -295,6 +397,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 @@ -361,6 +526,80 @@ def _get_chrome_options(self, capabilities={}): self._update_dict(options.capabilities, 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 @@ -427,7 +666,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: @@ -458,6 +697,30 @@ def _get_safari_options(self, capabilities={}): self._add_capabilities_from_properties(capabilities, '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 diff --git a/toolium/driver_wrapper.py b/toolium/driver_wrapper.py index 0d9e2470..5613289c 100644 --- a/toolium/driver_wrapper.py +++ b/toolium/driver_wrapper.py @@ -58,6 +58,7 @@ class DriverWrapper(object): async_loop = None #: async loop for playwright tests playwright = None #: playwright instance playwright_browser = None #: playwright browser instance + playwright_context = None #: playwright context instance # Configuration and output files config_properties_filenames = None #: configuration filenames separated by commas @@ -75,6 +76,7 @@ def __init__(self): self.async_loop = default_wrapper.async_loop self.playwright = default_wrapper.playwright self.playwright_browser = default_wrapper.playwright_browser + self.playwright_context = default_wrapper.playwright_context 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 @@ -264,26 +266,30 @@ def connect_playwright(self): """ 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') - 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()) + + # In case of using a persistent context this property must be set and a BrowserContext is returned instead of a Browser + user_data_dir = self.config.get_optional('PlaywrightContextOptions', 'user_data_dir', None) + if user_data_dir: + self.playwright_context = async_loop.run_until_complete(ConfigDriver(self.config, self.utils, self.playwright).create_playwright_persistent_browser_context()) + else: + self.playwright_browser = async_loop.run_until_complete(ConfigDriver(self.config, self.utils, self.playwright).create_playwright_browser()) + self.playwright_context = async_loop.run_until_complete(self.playwright_browser.new_context(**ConfigDriver(self.config, self.utils, self.playwright).get_playwright_context_options())) + self.driver = async_loop.run_until_complete(self.playwright_context.new_page(**ConfigDriver(self.config, self.utils, self.playwright).get_playwright_page_options())) async def connect_playwright_new_page(self): - """Set up and additional playwright driver creating a new context and page in current browser instance + """Set up and additional playwright driver creating a new page in current browser and context 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() + """ + self.driver = await self.playwright_context.new_page(**ConfigDriver(self.config, self.utils).get_playwright_page_options()) 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()) + self.async_loop.run_until_complete(self.playwright_context.close()) else: # Stop selenium driver self.driver.quit()