From 99b418a7c5a16c4c487fba8d88e2c4379241dc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Gonz=C3=A1lez=20Alonso?= <rgonalo@gmail.com> Date: Wed, 3 Nov 2021 10:38:42 +0100 Subject: [PATCH] feat: add toolium system properties (#247) * feat: add toolium system properties * underscore is allowed in options --- CHANGELOG.rst | 25 ++- docs/driver_configuration.rst | 58 ++++++- docs/remote_configuration.rst | 4 +- docs/tests_result_analysis.rst | 18 ++- toolium/behave/environment.py | 22 +-- toolium/config_parser.py | 32 +++- toolium/driver_wrapper.py | 14 +- toolium/driver_wrappers_pool.py | 22 ++- toolium/selenoid.py | 1 + toolium/test/behave/test_environment.py | 10 +- toolium/test/test_config_parser.py | 142 ++++++++++++++++-- toolium/test/test_driver_wrapper.py | 45 ++++-- toolium/test/test_driver_wrapper_logger.py | 36 +++-- .../test/test_driver_wrapper_properties.py | 64 +++++--- toolium/test/test_driver_wrappers_pool.py | 12 +- 15 files changed, 400 insertions(+), 105 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 97451353..4106554e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,8 +7,27 @@ v2.2.0 *Release date: In development* - Add JSON object/list conversion to Python dict/list in the type inference logic of the *replace_param* function -- Add *finalize_properties_configuration* method in *DriverWrapper* class to allow the modification of config properties upon initialization - programmatically before driver creation +- Add *finalize_properties_configuration* method in *DriverWrapper* class to allow the modification of config properties + upon initialization programmatically before driver creation +- Properties values configured by properties files can be overridden with system properties named + *TOOLIUM_[SECTION]_[OPTION]*, moreover these system properties can be used to add new properties that do not exist in + properties files +- Configuration system properties have been renamed. The old property names are deprecated but they can still be used. + + | Deprecated property name -> New property name + | Config_environment -> TOOLIUM_CONFIG_ENVIRONMENT + | Output_directory -> TOOLIUM_OUTPUT_DIRECTORY + | Output_log_filename -> TOOLIUM_OUTPUT_LOG_FILENAME + | Config_directory -> TOOLIUM_CONFIG_DIRECTORY + | Config_log_filename -> TOOLIUM_CONFIG_LOG_FILENAME + | Config_prop_filenames -> TOOLIUM_CONFIG_PROPERTIES_FILENAMES + | Visual_baseline_directory -> TOOLIUM_VISUAL_BASELINE_DIRECTORY + +- Behave user property 'Config_environment' is deprecated, use 'TOOLIUM_CONFIG_ENVIRONMENT' instead: + +.. code:: console + + $ behave -D TOOLIUM_CONFIG_ENVIRONMENT=android v2.1.1 ------ @@ -545,7 +564,7 @@ v0.10.0 New config section [ChromeMobileEmulation] with mobile emulation options, e.g. 'deviceName = Google Nexus 5' -- Configuration system properties has been renamed +- Configuration system properties have been renamed | Old properties: Files_output_path, Files_log_filename, Files_properties, Files_logging | New properties: Output_directory, Output_log_filename, Config_directory, Config_prop_filenames, Config_log_filename diff --git a/docs/driver_configuration.rst b/docs/driver_configuration.rst index b5aa7853..3c65ceb0 100644 --- a/docs/driver_configuration.rst +++ b/docs/driver_configuration.rst @@ -20,27 +20,75 @@ If driver is not needed, typically in API tests, disable it using an empty strin By default, Toolium configuration is loaded from :code:`conf/properties.cfg` and :code:`conf/local-properties.cfg` files. If different properties files are used for different environments, they can be selected using a system property named -:code:`Config_environment`. For example, if :code:`Config_environment` value is :code:`android`, Toolium configuration -will be loaded from :code:`conf/properties.cfg`, :code:`conf/android-properties.cfg` and +:code:`TOOLIUM_CONFIG_ENVIRONMENT`. For example, if :code:`TOOLIUM_CONFIG_ENVIRONMENT` value is :code:`android`, +Toolium configuration will be loaded from :code:`conf/properties.cfg`, :code:`conf/android-properties.cfg` and :code:`local-android-properties.cfg` files: Nose: .. code:: console - $ Config_environment=android nosetests web/tests/test_web.py + $ TOOLIUM_CONFIG_ENVIRONMENT=android nosetests web/tests/test_web.py Pytest: .. code:: console - $ Config_environment=android pytest web_pytest/tests/test_web_pytest.py + $ TOOLIUM_CONFIG_ENVIRONMENT=android pytest web_pytest/tests/test_web_pytest.py Behave: .. code:: console - $ behave -D Config_environment=android + $ behave -D TOOLIUM_CONFIG_ENVIRONMENT=android + +Modify configuration by system properties +----------------------------------------- + +Properties values configured by properties files can be overridden with system properties. To modify a particular option +within a section, a new system variable should be defined. The property name must be `TOOLIUM_[SECTION]_[OPTION]` and +its value must be `[Section]_[option]=value`, as can be seen in the following example: + +.. code:: console + + $ TOOLIUM_DRIVER_TYPE=Driver_type=chrome + +This system property means the same as having the following section in the configuration file:: + + [Driver] + type: chrome + +Underscore is allowed in options, but not in sections, for instance: + +.. code:: console + + $ TOOLIUM_SERVER_VIDEO_ENABLED=Server_video_enabled=true + +This is the same as having the following section in the configuration file:: + + [Server] + video_enabled: true + +To be cross-platform, section and option must be configured both in the property name and in the first token of the +value because they are case sensitive and, in Windows, system properties names are case insensitive. + +Modify configuration programmatically +------------------------------------- + +Properties values can also be modified programmatically before Toolium uses them to start the driver. There is a method +named `finalize_properties_configuration`, called after reading configuration files and before starting the driver, that +can be monkey patched to modify properties values, for instance: + +.. code:: python + + from toolium.driver_wrapper import DriverWrapper + + def finalize_properties_configuration(self): + if self.config.getboolean_optional('Server', 'enabled'): + self.config.set('Capabilities', 'enableVideo', 'true'): + + DriverWrapper.finalize_properties_configuration = finalize_properties_configuration + - :ref:`Browser Configuration <browser_configuration>` - :ref:`Mobile Configuration <mobile_configuration>` diff --git a/docs/remote_configuration.rst b/docs/remote_configuration.rst index c8e1c3ea..ffb737db 100644 --- a/docs/remote_configuration.rst +++ b/docs/remote_configuration.rst @@ -52,8 +52,8 @@ video_enabled | `GGR with Selenoid <https://github.com/aerokube/ggr>`_ as remote server, that allow recording videos of test | executions. | -| *true*: remote video recording is enabled, a video of the test execution will be recorded and saved locally -| *false*: remote video recording is disabled +| *true*: remote video download is enabled even if the test passes, the test execution video will be saved locally +| *false*: remote video download is disabled in passed tests, but it is always enabled if the test fails logs_enabled ~~~~~~~~~~~~ diff --git a/docs/tests_result_analysis.rst b/docs/tests_result_analysis.rst index 3e278c92..da6cec00 100644 --- a/docs/tests_result_analysis.rst +++ b/docs/tests_result_analysis.rst @@ -41,7 +41,12 @@ Videos When a test fails, Toolium downloads a video of the test execution into the folder *output/videos/DATE_DRIVER_TYPE*. This feature only works when the test has been executed in a `Selenium Grid Extras <https://github.com/groupon/Selenium-Grid-Extras>`_ or `GGR <https://github.com/aerokube/ggr>`_ -grid node. Both grids allow recording videos of test executions. +grid node and the video recording has been enabled in grid. Both grids allow recording videos of test executions. + +For example, to enable video recording in GGR, the following capability must be configured :: + + [Capabilities] + enableVideo: true In order to download the execution video even if the test passes, configure the property *video_enabled* in *[Server]* section in properties.cfg file :: @@ -49,12 +54,6 @@ section in properties.cfg file :: [Server] video_enabled: true -video_enabled -~~~~~~~~~~~~~ -| *true*: remote video recording is enabled, a video of the test execution will be recorded and saved locally -| *false*: remote video recording is disabled - - Webdriver logs -------------- @@ -74,6 +73,11 @@ Notice that if using Chrome, log types can be enabled in *capabilities* section [Capabilities] goog___loggingPrefs: {'browser':'ALL', 'driver': 'ALL', 'performance': 'ALL'} +Also take into account that, to enable logs in GGR, the following capability must be configured :: + + [Capabilities] + enableLog: true + In order to download webdriver logs even if the test passes, configure the property `logs_enabled <https://toolium.readthedocs.io/en/latest/remote_configuration.html#logs-enabled>`_ in *[Server]* section in properties.cfg file :: diff --git a/toolium/behave/environment.py b/toolium/behave/environment.py index 032bc260..d2c8c966 100644 --- a/toolium/behave/environment.py +++ b/toolium/behave/environment.py @@ -42,12 +42,14 @@ def before_all(context): # Use pytest asserts if behave_pytest is installed install_pytest_asserts() - # Get 'Config_environment' property from user input (e.g. -D Config_environment=ios) - env = context.config.userdata.get('Config_environment') - # Deprecated: Get 'env' property from user input (e.g. -D env=ios) + # Get 'TOOLIUM_CONFIG_ENVIRONMENT' property from user input (e.g. -D TOOLIUM_CONFIG_ENVIRONMENT=ios) + env = context.config.userdata.get('TOOLIUM_CONFIG_ENVIRONMENT') + # Deprecated: use TOOLIUM_CONFIG_ENVIRONMENT property + env = env if env else context.config.userdata.get('Config_environment') + # Deprecated: use TOOLIUM_CONFIG_ENVIRONMENT property env = env if env else context.config.userdata.get('env') if env: - os.environ['Config_environment'] = env + os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] = env if not hasattr(context, 'config_files'): context.config_files = ConfigFiles() @@ -91,14 +93,14 @@ def before_scenario(context, scenario): """ # Configure reset properties from behave tags if 'no_reset_app' in scenario.tags: - os.environ["AppiumCapabilities_noReset"] = 'true' - os.environ["AppiumCapabilities_fullReset"] = 'false' + os.environ['TOOLIUM_APPIUMCAPABILITIES_NORESET'] = 'AppiumCapabilities_noReset=true' + os.environ['TOOLIUM_APPIUMCAPABILITIES_FULLRESET'] = 'AppiumCapabilities_fullReset=false' elif 'reset_app' in scenario.tags: - os.environ["AppiumCapabilities_noReset"] = 'false' - os.environ["AppiumCapabilities_fullReset"] = 'false' + os.environ['TOOLIUM_APPIUMCAPABILITIES_NORESET'] = 'AppiumCapabilities_noReset=false' + os.environ['TOOLIUM_APPIUMCAPABILITIES_FULLRESET'] = 'AppiumCapabilities_fullReset=false' elif 'full_reset_app' in scenario.tags: - os.environ["AppiumCapabilities_noReset"] = 'false' - os.environ["AppiumCapabilities_fullReset"] = 'true' + os.environ['TOOLIUM_APPIUMCAPABILITIES_NORESET'] = 'AppiumCapabilities_noReset=false' + os.environ['TOOLIUM_APPIUMCAPABILITIES_FULLRESET'] = 'AppiumCapabilities_fullReset=true' # Force to reset driver before each scenario if it has @reset_driver tag if 'reset_driver' in scenario.tags: diff --git a/toolium/config_parser.py b/toolium/config_parser.py index b7a2aff4..748420e3 100644 --- a/toolium/config_parser.py +++ b/toolium/config_parser.py @@ -17,13 +17,16 @@ """ import logging +import re from configparser import ConfigParser, NoSectionError, NoOptionError from io import StringIO +logger = logging.getLogger(__name__) + class ExtendedConfigParser(ConfigParser): def optionxform(self, optionstr): - """Override default optionxform in ConfigParser""" + """Override default optionxform in ConfigParser to allow case sensitive options""" return optionstr def get_optional(self, section, option, default=None): @@ -95,6 +98,33 @@ def _update_property_from_dict(self, section, option, new_properties): except KeyError: pass + def update_toolium_system_properties(self, new_properties): + """ Update config properties values or add new values if property does not exist + Property name must be 'TOOLIUM_[SECTION]_[OPTION]' and property value must be '[Section]_[option]=value' + i.e. TOOLIUM_SERVER_ENABLED='Server_enabled=true' + + Section and option can not be configured in property name because they must be case sensitive and, in Windows, + system properties are case insensitive + + :param new_properties: dict with new properties values + """ + for property_name, property_value in new_properties.items(): + name_groups = re.match('^TOOLIUM_([^_]+)_(.+)$', property_name) + value_groups = re.match('^([^_=]+)_([^=]+)=(.*)$', property_value) + if (name_groups and value_groups + and name_groups.group(1).upper() == value_groups.group(1).upper() + and name_groups.group(2).upper() == value_groups.group(2).upper()): + section = value_groups.group(1) + option = value_groups.group(2) + value = value_groups.group(3) + if not self.has_section(section): + self.add_section(section) + self.set(section, option, value) + elif property_name.startswith('TOOLIUM'): + logger.warning('A toolium system property is configured but its name does not math with section' + ' and option in value (use TOOLIUM_[SECTION]_[OPTION]=[Section]_[option]=value):' + ' %s=%s' % (property_name, property_value)) + def translate_config_variables(self, str_with_variables): """ Translate config variables included in string with format {Section_option} diff --git a/toolium/driver_wrapper.py b/toolium/driver_wrapper.py index f728fdc3..70d38cd7 100644 --- a/toolium/driver_wrapper.py +++ b/toolium/driver_wrapper.py @@ -85,14 +85,16 @@ def configure_logger(self, tc_config_log_filename=None, tc_output_log_filename=N :param tc_output_log_filename: test case specific output logger file """ # Get config logger filename - config_log_filename = DriverWrappersPool.get_configured_value('Config_log_filename', tc_config_log_filename, + config_log_filename = DriverWrappersPool.get_configured_value('TOOLIUM_CONFIG_LOG_FILENAME', + 'Config_log_filename', tc_config_log_filename, 'logging.conf') config_log_filename = os.path.join(DriverWrappersPool.config_directory, config_log_filename) # Configure logger only if logging filename has changed if self.config_log_filename != config_log_filename: # Get output logger filename - output_log_filename = DriverWrappersPool.get_configured_value('Output_log_filename', tc_output_log_filename, + output_log_filename = DriverWrappersPool.get_configured_value('TOOLIUM_OUTPUT_LOG_FILENAME', + 'Output_log_filename', tc_output_log_filename, 'toolium.log') output_log_filename = os.path.join(DriverWrappersPool.output_directory, output_log_filename) output_log_filename = output_log_filename.replace('\\', '\\\\') @@ -111,7 +113,8 @@ def configure_properties(self, tc_config_prop_filenames=None, behave_properties= :param tc_config_prop_filenames: test case specific properties filenames :param behave_properties: dict with behave user data properties """ - prop_filenames = DriverWrappersPool.get_configured_value('Config_prop_filenames', tc_config_prop_filenames, + prop_filenames = DriverWrappersPool.get_configured_value('TOOLIUM_CONFIG_PROPERTIES_FILENAMES', + 'Config_prop_filenames', tc_config_prop_filenames, 'properties.cfg;local-properties.cfg') prop_filenames = [os.path.join(DriverWrappersPool.config_directory, filename) for filename in prop_filenames.split(';')] @@ -123,9 +126,12 @@ def configure_properties(self, tc_config_prop_filenames=None, behave_properties= self.config = ExtendedConfigParser.get_config_from_file(prop_filenames) self.config_properties_filenames = prop_filenames - # Override properties with system properties + # Override properties with system properties [Deprecated: use toolium system properties] self.config.update_properties(os.environ) + # Override properties with toolium system properties + self.config.update_toolium_system_properties(os.environ) + # Override properties with behave userdata properties if behave_properties: self.config.update_properties(behave_properties) diff --git a/toolium/driver_wrappers_pool.py b/toolium/driver_wrappers_pool.py index 3a9be6cf..e44ef9a0 100644 --- a/toolium/driver_wrappers_pool.py +++ b/toolium/driver_wrappers_pool.py @@ -254,10 +254,11 @@ def save_all_webdriver_or_ggr_logs(cls, test_name, test_passed, ggr=False): driver_index += 1 @staticmethod - def get_configured_value(system_property_name, specific_value, default_value): + def get_configured_value(system_property_name, deprecated_system_property_name, specific_value, default_value): """Get configured value from system properties, method parameters or default value :param system_property_name: system property name + :param deprecated_system_property_name: deprecated system property name :param specific_value: test case specific value :param default_value: default value :returns: configured value @@ -265,7 +266,10 @@ def get_configured_value(system_property_name, specific_value, default_value): try: return os.environ[system_property_name] except KeyError: - return specific_value if specific_value else default_value + try: + return os.environ[deprecated_system_property_name] + except KeyError: + return specific_value if specific_value else default_value @classmethod def configure_common_directories(cls, tc_config_files): @@ -275,14 +279,15 @@ def configure_common_directories(cls, tc_config_files): """ if cls.config_directory is None: # Get config directory from properties - config_directory = cls.get_configured_value('Config_directory', tc_config_files.config_directory, 'conf') - prop_filenames = cls.get_configured_value('Config_prop_filenames', + config_directory = cls.get_configured_value('TOOLIUM_CONFIG_DIRECTORY', 'Config_directory', + tc_config_files.config_directory, 'conf') + prop_filenames = cls.get_configured_value('TOOLIUM_CONFIG_PROPERTIES_FILENAMES', 'Config_prop_filenames', tc_config_files.config_properties_filenames, 'properties.cfg') cls.config_directory = cls._find_parent_directory(config_directory, prop_filenames.split(';')[0]) # Get output directory from properties and create it - cls.output_directory = cls.get_configured_value('Output_directory', tc_config_files.output_directory, - 'output') + cls.output_directory = cls.get_configured_value('TOOLIUM_OUTPUT_DIRECTORY', 'Output_directory', + tc_config_files.output_directory, 'output') if not os.path.isabs(cls.output_directory): # If output directory is relative, we use the same path as config directory cls.output_directory = os.path.join(os.path.dirname(cls.config_directory), cls.output_directory) @@ -290,7 +295,8 @@ def configure_common_directories(cls, tc_config_files): # Get visual baseline directory from properties default_baseline = os.path.join(cls.output_directory, 'visualtests', 'baseline') - cls.visual_baseline_directory = cls.get_configured_value('Visual_baseline_directory', + cls.visual_baseline_directory = cls.get_configured_value('TOOLIUM_VISUAL_BASELINE_DIRECTORY', + 'Visual_baseline_directory', tc_config_files.visual_baseline_directory, default_baseline) if not os.path.isabs(cls.visual_baseline_directory): @@ -361,7 +367,7 @@ def initialize_config_files(tc_config_files=None): tc_config_files = ConfigFiles() # Update properties and log file names if an environment is configured - env = DriverWrappersPool.get_configured_value('Config_environment', None, None) + env = DriverWrappersPool.get_configured_value('TOOLIUM_CONFIG_ENVIRONMENT', 'Config_environment', None, None) if env: # Update config properties filenames prop_filenames = tc_config_files.config_properties_filenames diff --git a/toolium/selenoid.py b/toolium/selenoid.py index 880c4d70..c791734e 100644 --- a/toolium/selenoid.py +++ b/toolium/selenoid.py @@ -38,6 +38,7 @@ class Selenoid(object): # capabilities to selenoid enableVideo: true enableVNC: true + enableLog: true [Server] enabled: true --> MANDATORY diff --git a/toolium/test/behave/test_environment.py b/toolium/test/behave/test_environment.py index 8dc0344f..085739b4 100644 --- a/toolium/test/behave/test_environment.py +++ b/toolium/test/behave/test_environment.py @@ -66,8 +66,9 @@ def test_before_all(create_and_configure_wrapper): properties = ( - 'env', + 'TOOLIUM_CONFIG_ENVIRONMENT', 'Config_environment', + 'env' ) @@ -81,13 +82,14 @@ def test_before_all_config_environment(create_and_configure_wrapper, property_na before_all(context) - # Check that configuration folder is the same as environment folder and property 'Config_environment' is configured + # Check that configuration folder is the same as environment folder and property 'TOOLIUM_CONFIG_ENVIRONMENT' is + # configured expected_config_directory = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'conf') assert context.config_files.config_directory == expected_config_directory assert context.config_files.config_properties_filenames == 'properties.cfg;os-properties.cfg;local-os-properties.cfg' assert context.config_files.config_log_filename is None - assert os.environ['Config_environment'] == 'os' - del os.environ["Config_environment"] + assert os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] == 'os' + del os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] @mock.patch('toolium.behave.environment.start_driver') diff --git a/toolium/test/test_config_parser.py b/toolium/test/test_config_parser.py index 483e26a3..49eb91e2 100644 --- a/toolium/test/test_config_parser.py +++ b/toolium/test/test_config_parser.py @@ -16,11 +16,46 @@ limitations under the License. """ +import mock import os import pytest from toolium.config_parser import ExtendedConfigParser +environment_properties = [] + + +@pytest.fixture +def config(): + root_path = os.path.dirname(os.path.realpath(__file__)) + conf_properties_file = os.path.join(root_path, 'conf', 'properties.cfg') + config = ExtendedConfigParser() + config.read(conf_properties_file) + + yield config + + # Remove used environment properties after test + global environment_properties + for env_property in environment_properties: + try: + del os.environ[env_property] + except KeyError: + pass + environment_properties = [] + + +@pytest.fixture +def logger(): + # Configure logger mock + logger = mock.MagicMock() + logger_patch = mock.patch('toolium.config_parser.logger', logger) + logger_patch.start() + + yield logger + + logger_patch.stop() + + optional_values = ( ('No section', 'No option', None, None), ('No section', 'No option', 'chrome', 'chrome'), @@ -40,15 +75,6 @@ ) -@pytest.fixture -def config(): - root_path = os.path.dirname(os.path.realpath(__file__)) - conf_properties_file = os.path.join(root_path, 'conf', 'properties.cfg') - config = ExtendedConfigParser() - config.read(conf_properties_file) - return config - - @pytest.mark.parametrize("section, option, default, response", optional_values) def test_get_optional(section, option, default, response, config): if default: @@ -172,7 +198,7 @@ def test_deepcopy_and_modify_option_with_colon(config): assert new_value == new_config.get(section, option) -def test_update_properties_environ(config): +def test_update_properties_environ_deprecated(config): section = 'AppiumCapabilities' option = 'platformName' orig_value = 'Android' @@ -189,6 +215,102 @@ def test_update_properties_environ(config): assert new_value == config.get(section, option) +toolium_system_properties = ( + # Update value + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', 'iOS'), + # Underscore in value + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', 'a_b'), + # Equal symbol in value + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', 'a=b'), + # Empty value + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', ''), + # Underscore in option + ('TOOLIUM_SERVER_VIDEO_ENABLED', 'Server', 'video_enabled', 'false', 'true'), + # New section + ('TOOLIUM_CUSTOMCAPABILITIES_CUSTOMCAPABILITY', 'CustomCapabilities', 'customCapability', None, 'prueba'), + # New option + ('TOOLIUM_CAPABILITIES_CUSTOMCAPABILITY', 'Capabilities', 'customCapability', None, 'prueba'), + # Lowercase section in name + ('TOOLIUM_APPIUMCapabilities_PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', 'iOS'), + # Lowercase option in name + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMName', 'AppiumCapabilities', 'platformName', 'Android', 'iOS'), +) + + +@pytest.mark.parametrize("property_name, section, option, value, new_value", toolium_system_properties) +def test_update_toolium_system_properties(config, property_name, section, option, value, new_value): + # Check previous value + if value is None: + assert not config.has_option(section, option) + else: + assert value == config.get(section, option) + + # Change system property and update config + environment_properties.append(property_name) + os.environ[property_name] = '{}_{}={}'.format(section, option, new_value) + config.update_toolium_system_properties(os.environ) + + # Check the new config value + assert new_value == config.get(section, option) + + +toolium_system_properties_wrong_format = ( + # No section and option in name + ('TOOLIUM', 'AppiumCapabilities', 'platformName', 'Android', 'AppiumCapabilities_platformName=iOS'), + # No option in name + ('TOOLIUM_APPIUMCAPABILITIES', 'AppiumCapabilities', 'platformName', 'Android', + 'AppiumCapabilities_platformName=iOS'), + # Option in name different from option in value + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORM', 'AppiumCapabilities', 'platformName', 'Android', + 'AppiumCapabilities_platformName=iOS'), + # Additional param in name + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMNAME_WRONG', 'AppiumCapabilities', 'platformName', 'Android', + 'AppiumCapabilities_platformName=iOS'), + # No option in value + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', + 'AppiumCapabilities=iOS'), + # Additional param in value + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', + 'AppiumCapabilities_platformName_wrong=iOS'), + # No equal in value + ('TOOLIUM_APPIUMCAPABILITIES_PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', + 'AppiumCapabilities_platformName iOS'), + # Empty section + ('TOOLIUM__PLATFORMNAME', 'AppiumCapabilities', 'platformName', 'Android', '_platformName=iOS'), + # Empty option + ('TOOLIUM_APPIUMCAPABILITIES_', 'AppiumCapabilities', 'platformName', 'Android', 'AppiumCapabilities_=iOS'), + # No toolium system property + ('WRONGNAME', 'AppiumCapabilities', 'platformName', 'Android', 'platformName_Android=iOS'), +) + + +@pytest.mark.parametrize("property_name, section, option, value, property_value", + toolium_system_properties_wrong_format) +def test_update_toolium_system_properties_wrong_format(config, logger, property_name, section, option, value, + property_value): + # Check previous value + if value is None: + assert not config.has_option(section, option) + else: + assert value == config.get(section, option) + + # Change system property and update config + environment_properties.append(property_name) + os.environ[property_name] = property_value + config.update_toolium_system_properties(os.environ) + + # Check that config value has not changed + assert value == config.get(section, option) + + # Check logging error message + if property_name.startswith('TOOLIUM'): + logger.warning.assert_called_once_with('A toolium system property is configured but its name does not math with' + ' section and option in value (use TOOLIUM_[SECTION]_[OPTION]=[Section]_' + '[option]=value): %s=%s' % (property_name, property_value)) + else: + logger.warning.assert_not_called() + + def test_update_properties_behave(config): section = 'AppiumCapabilities' option = 'platformName' diff --git a/toolium/test/test_driver_wrapper.py b/toolium/test/test_driver_wrapper.py index 4e5c66d5..81bf725d 100644 --- a/toolium/test/test_driver_wrapper.py +++ b/toolium/test/test_driver_wrapper.py @@ -38,8 +38,7 @@ # (driver_type, appium_app, appium_browser_name, is_web, is_android_web, is_ios_web) web_tests = ( - ('android-4.1.2-on-android', 'C:/Demo.apk', None, False, False, - False), + ('android-4.1.2-on-android', 'C:/Demo.apk', None, False, False, False), ('android', 'C:/Demo.apk', None, False, False, False), ('android', 'C:/Demo.apk', '', False, False, False), ('android', None, 'chrome', True, True, False), @@ -67,6 +66,8 @@ ('iphone', False), ) +environment_properties = [] + @pytest.fixture def driver_wrapper(): @@ -87,15 +88,14 @@ def driver_wrapper(): yield new_driver_wrapper - # Remove environment properties after test - try: - del os.environ["Config_prop_filenames"] - except KeyError: - pass - try: - del os.environ["Config_environment"] - except KeyError: - pass + # Remove used environment properties after test + global environment_properties + for env_property in environment_properties: + try: + del os.environ[env_property] + except KeyError: + pass + environment_properties = [] def test_multiple(driver_wrapper): @@ -136,7 +136,8 @@ def test_configure_change_configuration_file(driver_wrapper): # Change properties file and try to configure again root_path = os.path.dirname(os.path.realpath(__file__)) - os.environ["Config_prop_filenames"] = os.path.join(root_path, 'conf', 'android-properties.cfg') + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = os.path.join(root_path, 'conf', 'android-properties.cfg') driver_wrapper.configure(ConfigFiles()) # Check that configuration has been initialized @@ -148,7 +149,22 @@ def test_configure_environment(driver_wrapper): assert driver_wrapper.config.get('Driver', 'type') == 'firefox' # Change environment and try to configure again - os.environ["Config_environment"] = 'android' + environment_properties.append('TOOLIUM_CONFIG_ENVIRONMENT') + os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] = 'android' + config_files = DriverWrappersPool.initialize_config_files(ConfigFiles()) + driver_wrapper.configure(config_files) + + # Check that configuration has been initialized + assert driver_wrapper.config.get('Driver', 'type') == 'android' + + +def test_configure_environment_deprecated_property(driver_wrapper): + # Check previous values + assert driver_wrapper.config.get('Driver', 'type') == 'firefox' + + # Change environment and try to configure again + environment_properties.append('Config_environment') + os.environ['Config_environment'] = 'android' config_files = DriverWrappersPool.initialize_config_files(ConfigFiles()) driver_wrapper.configure(config_files) @@ -200,7 +216,8 @@ def test_connect_api_from_file(driver_wrapper): # Change driver type to api and configure again root_path = os.path.dirname(os.path.realpath(__file__)) - os.environ["Config_prop_filenames"] = os.path.join(root_path, 'conf', 'api-properties.cfg') + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = os.path.join(root_path, 'conf', 'api-properties.cfg') driver_wrapper.configure(ConfigFiles()) # Connect and check that the returned driver is None diff --git a/toolium/test/test_driver_wrapper_logger.py b/toolium/test/test_driver_wrapper_logger.py index b1c257fe..347515e0 100644 --- a/toolium/test/test_driver_wrapper_logger.py +++ b/toolium/test/test_driver_wrapper_logger.py @@ -24,6 +24,8 @@ from toolium.config_files import ConfigFiles from toolium.driver_wrappers_pool import DriverWrappersPool +environment_properties = [] + @pytest.fixture def driver_wrapper(): @@ -39,15 +41,26 @@ def driver_wrapper(): yield new_driver_wrapper - # Remove environment properties after test - try: - del os.environ["Config_log_filename"] - except KeyError: - pass + # Remove used environment properties after test + global environment_properties + for env_property in environment_properties: + try: + del os.environ[env_property] + except KeyError: + pass + environment_properties = [] + + +def test_configure_logger_deprecated(driver_wrapper): + environment_properties.append('Config_log_filename') + os.environ['Config_log_filename'] = 'logging.conf' + driver_wrapper.configure_logger() + assert logging.getLevelName(driver_wrapper.logger.getEffectiveLevel()) == 'DEBUG' def test_configure_logger(driver_wrapper): - os.environ["Config_log_filename"] = 'logging.conf' + environment_properties.append('TOOLIUM_CONFIG_LOG_FILENAME') + os.environ['TOOLIUM_CONFIG_LOG_FILENAME'] = 'logging.conf' driver_wrapper.configure_logger() assert logging.getLevelName(driver_wrapper.logger.getEffectiveLevel()) == 'DEBUG' @@ -58,14 +71,16 @@ def test_configure_logger_file_not_configured(driver_wrapper): def test_configure_logger_file_not_found(driver_wrapper): - os.environ["Config_log_filename"] = 'notfound-logging.conf' + environment_properties.append('TOOLIUM_CONFIG_LOG_FILENAME') + os.environ['TOOLIUM_CONFIG_LOG_FILENAME'] = 'notfound-logging.conf' driver_wrapper.configure_logger() driver_wrapper.logger.info('No error, default logging configuration') def test_configure_logger_no_changes(driver_wrapper): # Configure logger - os.environ["Config_log_filename"] = 'logging.conf' + environment_properties.append('TOOLIUM_CONFIG_LOG_FILENAME') + os.environ['TOOLIUM_CONFIG_LOG_FILENAME'] = 'logging.conf' driver_wrapper.configure_logger() logger = logging.getLogger('selenium.webdriver.remote.remote_connection') @@ -83,7 +98,8 @@ def test_configure_logger_no_changes(driver_wrapper): def test_configure_logger_change_configuration_file(driver_wrapper): # Configure logger - os.environ["Config_log_filename"] = 'logging.conf' + environment_properties.append('TOOLIUM_CONFIG_LOG_FILENAME') + os.environ['TOOLIUM_CONFIG_LOG_FILENAME'] = 'logging.conf' logger = logging.getLogger('selenium.webdriver.remote.remote_connection') # Modify property @@ -92,7 +108,7 @@ def test_configure_logger_change_configuration_file(driver_wrapper): assert logging.getLevelName(logger.level) == new_level # Change file and try to configure again - os.environ["Config_log_filename"] = 'warn-logging.conf' + os.environ['TOOLIUM_CONFIG_LOG_FILENAME'] = 'warn-logging.conf' driver_wrapper.configure_logger() # Check that configuration has been initialized diff --git a/toolium/test/test_driver_wrapper_properties.py b/toolium/test/test_driver_wrapper_properties.py index 1b5311f3..5b4f6d33 100644 --- a/toolium/test/test_driver_wrapper_properties.py +++ b/toolium/test/test_driver_wrapper_properties.py @@ -24,6 +24,8 @@ from toolium.driver_wrapper import DriverWrapper from toolium.driver_wrappers_pool import DriverWrappersPool +environment_properties = [] + @pytest.fixture def driver_wrapper(): @@ -45,19 +47,28 @@ def driver_wrapper(): yield new_driver_wrapper - # Remove environment properties after test - try: - del os.environ["Config_prop_filenames"] - except KeyError: - pass - try: - del os.environ["Driver_type"] - except KeyError: - pass + # Remove used environment properties after test + global environment_properties + for env_property in environment_properties: + try: + del os.environ[env_property] + except KeyError: + pass + environment_properties = [] + + +def test_configure_properties_deprecated(driver_wrapper): + environment_properties.append('Config_prop_filenames') + os.environ['Config_prop_filenames'] = 'properties.cfg' + driver_wrapper.configure_properties() + assert driver_wrapper.config.get('Driver', 'type') == 'firefox' # get last value + assert driver_wrapper.config.get_optional('Driver', 'implicitly_wait') == '5' # only in properties + assert driver_wrapper.config.get_optional('AppiumCapabilities', 'app') is None # only in android def test_configure_properties(driver_wrapper): - os.environ["Config_prop_filenames"] = 'properties.cfg' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'properties.cfg' driver_wrapper.configure_properties() assert driver_wrapper.config.get('Driver', 'type') == 'firefox' # get last value assert driver_wrapper.config.get_optional('Driver', 'implicitly_wait') == '5' # only in properties @@ -65,7 +76,8 @@ def test_configure_properties(driver_wrapper): def test_configure_properties_android(driver_wrapper): - os.environ["Config_prop_filenames"] = 'android-properties.cfg' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'android-properties.cfg' driver_wrapper.configure_properties() assert driver_wrapper.config.get('Driver', 'type') == 'android' # get last value assert driver_wrapper.config.get_optional('Driver', 'implicitly_wait') is None # only in properties @@ -74,7 +86,8 @@ def test_configure_properties_android(driver_wrapper): def test_configure_properties_two_files(driver_wrapper): - os.environ["Config_prop_filenames"] = 'properties.cfg;android-properties.cfg' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'properties.cfg;android-properties.cfg' driver_wrapper.configure_properties() assert driver_wrapper.config.get('Driver', 'type') == 'android' # get last value assert driver_wrapper.config.get_optional('Driver', 'implicitly_wait') == '5' # only in properties @@ -83,7 +96,8 @@ def test_configure_properties_two_files(driver_wrapper): def test_configure_properties_two_files_android_first(driver_wrapper): - os.environ["Config_prop_filenames"] = 'android-properties.cfg;properties.cfg' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'android-properties.cfg;properties.cfg' driver_wrapper.configure_properties() assert driver_wrapper.config.get('Driver', 'type') == 'firefox' # get last value assert driver_wrapper.config.get_optional('Driver', 'implicitly_wait') == '5' # only in properties @@ -92,8 +106,10 @@ def test_configure_properties_two_files_android_first(driver_wrapper): def test_configure_properties_system_property(driver_wrapper): - os.environ["Config_prop_filenames"] = 'properties.cfg' - os.environ["Driver_type"] = 'opera' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + environment_properties.append('TOOLIUM_DRIVER_TYPE') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'properties.cfg' + os.environ['TOOLIUM_DRIVER_TYPE'] = 'Driver_type=opera' driver_wrapper.configure_properties() assert driver_wrapper.config.get('Driver', 'type') == 'opera' @@ -104,7 +120,8 @@ def test_configure_properties_file_default_file(driver_wrapper): def test_configure_properties_file_not_found(driver_wrapper): - os.environ["Config_prop_filenames"] = 'notfound-properties.cfg' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'notfound-properties.cfg' with pytest.raises(Exception) as excinfo: driver_wrapper.configure_properties() assert 'Properties config file not found' in str(excinfo.value) @@ -112,7 +129,8 @@ def test_configure_properties_file_not_found(driver_wrapper): def test_configure_properties_no_changes(driver_wrapper): # Configure properties - os.environ["Config_prop_filenames"] = 'properties.cfg' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'properties.cfg' driver_wrapper.configure_properties() assert driver_wrapper.config.get('Driver', 'type') == 'firefox' @@ -130,7 +148,8 @@ def test_configure_properties_no_changes(driver_wrapper): def test_configure_properties_change_configuration_file(driver_wrapper): # Configure properties - os.environ["Config_prop_filenames"] = 'properties.cfg' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'properties.cfg' driver_wrapper.configure_properties() assert driver_wrapper.config.get('Driver', 'type') == 'firefox' @@ -140,7 +159,8 @@ def test_configure_properties_change_configuration_file(driver_wrapper): assert driver_wrapper.config.get('Driver', 'type') == new_driver_type # Change file and try to configure again - os.environ["Config_prop_filenames"] = 'android-properties.cfg' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'android-properties.cfg' driver_wrapper.configure_properties() # Check that configuration has been initialized @@ -148,8 +168,10 @@ def test_configure_properties_change_configuration_file(driver_wrapper): def test_configure_properties_finalize_properties(driver_wrapper): - os.environ["Config_prop_filenames"] = 'properties.cfg' - os.environ["Driver_type"] = 'opera' + environment_properties.append('TOOLIUM_CONFIG_PROPERTIES_FILENAMES') + environment_properties.append('TOOLIUM_DRIVER_TYPE') + os.environ['TOOLIUM_CONFIG_PROPERTIES_FILENAMES'] = 'properties.cfg' + os.environ['TOOLIUM_DRIVER_TYPE'] = 'Driver_type=opera' # Modify properties after reading properties file and system properties def finalize_properties_configuration(self): diff --git a/toolium/test/test_driver_wrappers_pool.py b/toolium/test/test_driver_wrappers_pool.py index 153090eb..70656e36 100644 --- a/toolium/test/test_driver_wrappers_pool.py +++ b/toolium/test/test_driver_wrappers_pool.py @@ -159,11 +159,11 @@ def test_initialize_config_files_new(): def test_initialize_config_files_new_environment(): config_files = None - os.environ["Config_environment"] = 'android' + os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] = 'android' # Initialize config files config_files = DriverWrappersPool.initialize_config_files(config_files) - del os.environ["Config_environment"] + del os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] # Check expected config files expected_properties_filenames = 'properties.cfg;android-properties.cfg;local-android-properties.cfg' @@ -188,11 +188,11 @@ def test_initialize_config_files_configured_environment(): config_files = ConfigFiles() config_files.set_config_properties_filenames('test.conf', 'local-test.conf') config_files.set_output_log_filename('test.log') - os.environ["Config_environment"] = 'android' + os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] = 'android' # Initialize config files config_files = DriverWrappersPool.initialize_config_files(config_files) - del os.environ["Config_environment"] + del os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] # Check expected config files expected_properties_filenames = 'test.conf;local-test.conf;android-test.conf;local-android-test.conf' @@ -204,11 +204,11 @@ def test_initialize_config_files_configured_environment_with_points(): config_files = ConfigFiles() config_files.set_config_properties_filenames('test.new.conf', 'local-test.new.conf') config_files.set_output_log_filename('test.new.log') - os.environ["Config_environment"] = 'ios' + os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] = 'ios' # Initialize config files config_files = DriverWrappersPool.initialize_config_files(config_files) - del os.environ["Config_environment"] + del os.environ['TOOLIUM_CONFIG_ENVIRONMENT'] # Check expected config files expected_properties_filenames = 'test.new.conf;local-test.new.conf;ios-test.new.conf;local-ios-test.new.conf'