def __init__(self): if not DriverWrappersPool.is_empty(): # Copy config object and other properties from default driver default_wrapper = DriverWrappersPool.get_default_wrapper() self.config = default_wrapper.config.deepcopy() self.logger = default_wrapper.logger 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 self.visual_baseline_directory = default_wrapper.visual_baseline_directory self.baseline_name = default_wrapper.baseline_name # Create utils instance and add wrapper to the pool self.utils = Utils(self) DriverWrappersPool.add_wrapper(self)
def setUp(self): # Reset wrappers pool values DriverWrappersPool._empty_pool() DriverWrapper.config_properties_filenames = None # Create a new wrapper self.driver_wrapper = DriverWrappersPool.get_default_wrapper() self.driver_wrapper.driver = mock.MagicMock() # Configure properties self.root_path = os.path.dirname(os.path.realpath(__file__)) config_files = ConfigFiles() config_files.set_config_directory(os.path.join(self.root_path, 'conf')) config_files.set_config_properties_filenames('properties.cfg') config_files.set_output_directory(os.path.join(self.root_path, 'output')) self.driver_wrapper.configure(tc_config_files=config_files) # Create a new Utils instance self.utils = Utils()
class UtilsTests(unittest.TestCase): def setUp(self): # Reset wrappers pool values DriverWrappersPool._empty_pool() DriverWrapper.config_properties_filenames = None # Create a new wrapper self.driver_wrapper = DriverWrappersPool.get_default_wrapper() self.driver_wrapper.driver = mock.MagicMock() # Configure properties self.root_path = os.path.dirname(os.path.realpath(__file__)) config_files = ConfigFiles() config_files.set_config_directory(os.path.join(self.root_path, 'conf')) config_files.set_config_properties_filenames('properties.cfg') config_files.set_output_directory( os.path.join(self.root_path, 'output')) self.driver_wrapper.configure(tc_config_files=config_files) # Create a new Utils instance self.utils = Utils() @classmethod def tearDownClass(cls): # Reset wrappers pool values DriverWrappersPool._empty_pool() DriverWrapper.config_properties_filenames = None @requests_mock.Mocker() def test_get_remote_node(self, req_mock): # Configure mock self.driver_wrapper.driver.session_id = '5af' url = 'http://{}:{}/grid/api/testsession?session={}'.format( 'localhost', 4444, '5af') grid_response_json = { 'session': 'e2', 'proxyId': 'http://10.20.30.40:5555', 'msg': 'slot found !', 'inactivityTime': 78, 'success': True, 'internalKey': '7a' } req_mock.get(url, json=grid_response_json) # Get remote node and check result assert_equal(self.utils.get_remote_node(), '10.20.30.40') assert_equal(url, req_mock.request_history[0].url) @requests_mock.Mocker() def test_get_remote_node_non_grid(self, req_mock): # Configure mock self.driver_wrapper.driver.session_id = '5af' url = 'http://{}:{}/grid/api/testsession?session={}'.format( 'localhost', 4444, '5af') req_mock.get(url, text='non_json_response') # Get remote node and check result assert_equal(self.utils.get_remote_node(), 'localhost') assert_equal(url, req_mock.request_history[0].url) def test_get_remote_node_local_execution(self): self.driver_wrapper.config.set('Server', 'enabled', 'false') assert_is_none(self.utils.get_remote_node()) @requests_mock.Mocker() def test_get_remote_video_url(self, req_mock): # Configure mock url = 'http://{}:{}/video'.format('10.20.30.40', 3000) video_url = 'http://{}:{}/download_video/f4.mp4'.format( '10.20.30.40', 3000) video_response_json = { 'exit_code': 1, 'out': [], 'error': [ 'Cannot call this endpoint without required parameters: session and action' ], 'available_videos': { '5af': { 'size': 489701, 'session': '5af', 'last_modified': 1460041262558, 'download_url': video_url, 'absolute_path': 'C:\\f4.mp4' } }, 'current_videos': [] } req_mock.get(url, json=video_response_json) # Get remote video url and check result assert_equal(self.utils._get_remote_video_url('10.20.30.40', '5af'), video_url) assert_equal(url, req_mock.request_history[0].url) @requests_mock.Mocker() def test_get_remote_video_url_no_videos(self, req_mock): # Configure mock url = 'http://{}:{}/video'.format('10.20.30.40', 3000) video_response_json = { 'exit_code': 1, 'out': [], 'error': [ 'Cannot call this endpoint without required parameters: session and action' ], 'available_videos': {}, 'current_videos': [] } req_mock.get(url, json=video_response_json) # Get remote video url and check result assert_is_none(self.utils._get_remote_video_url('10.20.30.40', '5af')) assert_equal(url, req_mock.request_history[0].url) @requests_mock.Mocker() def test_is_remote_video_enabled(self, req_mock): # Configure mock url = 'http://{}:{}/config'.format('10.20.30.40', 3000) config_response_json = { 'out': [], 'error': [], 'exit_code': 0, 'filename': ['selenium_grid_extras_config.json'], 'config_runtime': { 'theConfigMap': { 'video_recording_options': { 'width': '1024', 'videos_to_keep': '5', 'frames': '30', 'record_test_videos': 'true' } } } } req_mock.get(url, json=config_response_json) # Get remote video configuration and check result assert_equal(self.utils.is_remote_video_enabled('10.20.30.40'), True) assert_equal(url, req_mock.request_history[0].url) @requests_mock.Mocker() def test_is_remote_video_enabled_disabled(self, req_mock): # Configure mock url = 'http://{}:{}/config'.format('10.20.30.40', 3000) config_response_json = { 'out': [], 'error': [], 'exit_code': 0, 'filename': ['selenium_grid_extras_config.json'], 'config_runtime': { 'theConfigMap': { 'video_recording_options': { 'width': '1024', 'videos_to_keep': '5', 'frames': '30', 'record_test_videos': 'false' } } } } req_mock.get(url, json=config_response_json) # Get remote video configuration and check result assert_equal(self.utils.is_remote_video_enabled('10.20.30.40'), False) assert_equal(url, req_mock.request_history[0].url) @mock.patch('toolium.utils.requests.get') def test_is_remote_video_enabled_non_grid_extras(self, req_get_mock): # Configure mock req_get_mock.side_effect = ConnectionError('exception error') # Get remote video configuration and check result assert_equal(self.utils.is_remote_video_enabled('10.20.30.40'), False) @data(*navigation_bar_tests) @unpack def test_get_safari_navigation_bar_height(self, driver_type, appium_app, appium_browser_name, bar_height): self.driver_wrapper.config.set('Driver', 'type', driver_type) if appium_app: self.driver_wrapper.config.set('AppiumCapabilities', 'app', appium_app) if appium_browser_name: self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', appium_browser_name) assert_equal(self.utils.get_safari_navigation_bar_height(), bar_height) def test_get_window_size_android_native(self): # Configure driver mock window_size = {'width': 375, 'height': 667} self.driver_wrapper.driver.get_window_size.return_value = window_size self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'app', 'C:/Demo.apk') assert_equal(self.utils.get_window_size(), window_size) def test_get_window_size_android_web(self): # Configure driver mock window_size = {'width': 375, 'height': 667} self.driver_wrapper.driver.current_context = 'WEBVIEW' self.driver_wrapper.driver.execute_script.side_effect = [ window_size['width'], window_size['height'] ] self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'chrome') assert_equal(self.utils.get_window_size(), window_size) def test_get_native_coords_android_web(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.current_context = 'WEBVIEW' self.driver_wrapper.driver.execute_script.side_effect = [ web_window_size['width'], web_window_size['height'], native_window_size['width'], native_window_size['height'] ] self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'chrome') web_coords = {'x': 105, 'y': 185} native_coords = {'x': 52.5, 'y': 92.5} assert_equal(self.utils.get_native_coords(web_coords), native_coords) def test_get_native_coords_ios_web(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.get_window_size.side_effect = [ web_window_size, native_window_size ] self.driver_wrapper.config.set('Driver', 'type', 'ios') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'safari') web_coords = {'x': 105, 'y': 185} native_coords = {'x': 52.5, 'y': 156.5} assert_equal(self.utils.get_native_coords(web_coords), native_coords) def test_swipe_android_native(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.get_window_size.side_effect = [ web_window_size, native_window_size ] self.driver_wrapper.driver.current_context = 'NATIVE_APP' self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'app', 'C:/Demo.apk') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) self.utils.swipe(element, 50, 100) self.driver_wrapper.driver.swipe.assert_called_once_with( 400, 60, 450, 160, None) def test_swipe_android_web(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.current_context = 'WEBVIEW' self.driver_wrapper.driver.execute_script.side_effect = [ web_window_size['width'], web_window_size['height'], native_window_size['width'], native_window_size['height'] ] self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'chrome') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) self.utils.swipe(element, 50, 100) self.driver_wrapper.driver.swipe.assert_called_once_with( 200, 30, 250, 130, None) def test_swipe_android_hybrid(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.get_window_size.side_effect = [ web_window_size, native_window_size ] self.driver_wrapper.driver.current_context = 'WEBVIEW' self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'app', 'C:/Demo.apk') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) self.utils.swipe(element, 50, 100) self.driver_wrapper.driver.swipe.assert_called_once_with( 200, 30, 250, 130, None) def test_swipe_ios_web(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.get_window_size.side_effect = [ web_window_size, native_window_size ] self.driver_wrapper.config.set('Driver', 'type', 'ios') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'safari') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) self.utils.swipe(element, 50, 100) self.driver_wrapper.driver.swipe.assert_called_once_with( 200, 94, 50, 100, None) def test_swipe_web(self): # Configure driver mock self.driver_wrapper.config.set('Driver', 'type', 'firefox') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) with assert_raises(Exception) as cm: self.utils.swipe(element, 50, 100) assert_equal(str(cm.exception), 'Swipe method is not implemented in Selenium') def test_get_web_element_from_web_element(self): element = WebElement(None, 1) web_element = self.utils.get_web_element(element) assert_equal(element, web_element) def test_get_web_element_from_page_element(self): element = PageElement(By.ID, 'element_id') element._web_element = 'mock_element' web_element = self.utils.get_web_element(element) assert_equal('mock_element', web_element) def test_get_web_element_from_locator(self): # Configure driver mock self.driver_wrapper.driver.find_element.return_value = 'mock_element' element_locator = (By.ID, 'element_id') # Get element and assert response web_element = self.utils.get_web_element(element_locator) assert_equal('mock_element', web_element) self.driver_wrapper.driver.find_element.assert_called_once_with( *element_locator) def test_get_web_element_from_none(self): web_element = self.utils.get_web_element(None) assert_is_none(web_element) def test_get_web_element_from_unknown(self): web_element = self.utils.get_web_element(dict()) assert_is_none(web_element) def test_wait_until_first_element_is_found_locator(self): # Configure driver mock self.driver_wrapper.driver.find_element.return_value = 'mock_element' element_locator = (By.ID, 'element_id') element = self.utils.wait_until_first_element_is_found( [element_locator]) assert_equal(element_locator, element) self.driver_wrapper.driver.find_element.assert_called_once_with( *element_locator) def test_wait_until_first_element_is_found_page_element(self): page_element = PageElement(By.ID, 'element_id') page_element._web_element = 'mock_element' element = self.utils.wait_until_first_element_is_found([page_element]) assert_equal(page_element, element) def test_wait_until_first_element_is_found_none(self): page_element = PageElement(By.ID, 'element_id') page_element._web_element = 'mock_element' element = self.utils.wait_until_first_element_is_found( [None, page_element]) assert_equal(page_element, element) def test_wait_until_first_element_is_found_timeout(self): # Configure driver mock self.driver_wrapper.driver.find_element.side_effect = NoSuchElementException( 'Unknown') element_locator = (By.ID, 'element_id') start_time = time.time() with assert_raises(TimeoutException) as cm: self.utils.wait_until_first_element_is_found([element_locator], timeout=10) end_time = time.time() assert_in("None of the page elements has been found after 10 seconds", str(cm.exception)) # find_element has been called more than one time self.driver_wrapper.driver.find_element.assert_called_with( *element_locator) assert_greater(end_time - start_time, 10, 'Execution time must be greater than timeout')
class DriverWrapper(object): """Wrapper with the webdriver and the configuration needed to execute tests :type driver: selenium.webdriver.remote.webdriver.WebDriver or appium.webdriver.webdriver.WebDriver :type config: toolium.config_parser.ExtendedConfigParser or configparser.ConfigParser :type utils: toolium.utils.Utils :type app_strings: dict :type session_id: str :type remote_node: str :type remote_node_video_enabled: bool :type logger: logging.Logger :type config_properties_filenames: str :type config_log_filename: str :type output_log_filename: str :type visual_baseline_directory: str :type baseline_name: str """ driver = None #: webdriver instance config = ExtendedConfigParser() #: driver configuration utils = None #: test utils instance app_strings = None #: mobile application strings session_id = None #: remote webdriver session id 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 # Configuration and output files config_properties_filenames = None #: configuration filenames separated by commas config_log_filename = None #: configuration log file output_log_filename = None #: output log file visual_baseline_directory = None #: folder with the baseline images baseline_name = None #: baseline name def __init__(self): if not DriverWrappersPool.is_empty(): # Copy config object and other properties from default driver default_wrapper = DriverWrappersPool.get_default_wrapper() self.config = default_wrapper.config.deepcopy() self.logger = default_wrapper.logger 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 self.visual_baseline_directory = default_wrapper.visual_baseline_directory self.baseline_name = default_wrapper.baseline_name # Create utils instance and add wrapper to the pool self.utils = Utils(self) DriverWrappersPool.add_wrapper(self) def configure_logger(self, tc_config_log_filename=None, tc_output_log_filename=None): """Configure selenium instance logger :param tc_config_log_filename: test case specific logging config file :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, '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, 'toolium.log') output_log_filename = os.path.join(DriverWrappersPool.output_directory, output_log_filename) output_log_filename = output_log_filename.replace('\\', '\\\\') try: logging.config.fileConfig(config_log_filename, {'logfilename': output_log_filename}, False) except Exception as exc: print("[WARN] Error reading logging config file '{}': {}".format(config_log_filename, exc)) self.config_log_filename = config_log_filename self.output_log_filename = output_log_filename self.logger = logging.getLogger(__name__) def configure_properties(self, tc_config_prop_filenames=None, behave_properties=None): """Configure selenium instance 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, 'properties.cfg;local-properties.cfg') prop_filenames = [os.path.join(DriverWrappersPool.config_directory, filename) for filename in prop_filenames.split(';')] prop_filenames = ';'.join(prop_filenames) # Configure config only if properties filename has changed if self.config_properties_filenames != prop_filenames: # Initialize the config object self.config = ExtendedConfigParser.get_config_from_file(prop_filenames) self.config_properties_filenames = prop_filenames # Override properties with system properties self.config.update_properties(os.environ) # Override properties with behave userdata properties if behave_properties: self.config.update_properties(behave_properties) def configure_visual_baseline(self): """Configure baseline directory""" # Get baseline name baseline_name = self.config.get_optional('VisualTests', 'baseline_name', '{Driver_type}') for section in self.config.sections(): for option in self.config.options(section): option_value = self.config.get(section, option) baseline_name = baseline_name.replace('{{{0}_{1}}}'.format(section, option), option_value) # Configure baseline directory if baseline name has changed if self.baseline_name != baseline_name: self.baseline_name = baseline_name self.visual_baseline_directory = os.path.join(DriverWrappersPool.visual_baseline_directory, get_valid_filename(baseline_name)) def update_visual_baseline(self): """Configure baseline directory after driver is created""" # Update baseline with real platformVersion value if '{PlatformVersion}' in self.baseline_name: try: platform_version = self.driver.desired_capabilities['platformVersion'] except KeyError: platform_version = None self.baseline_name = self.baseline_name.replace('{PlatformVersion}', str(platform_version)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.visual_baseline_directory, self.baseline_name) # Update baseline with real version value if '{Version}' in self.baseline_name: try: splitted_version = self.driver.desired_capabilities['version'].split('.') version = '.'.join(splitted_version[:2]) except KeyError: version = None self.baseline_name = self.baseline_name.replace('{Version}', str(version)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.visual_baseline_directory, self.baseline_name) # Update baseline with remote node value if '{RemoteNode}' in self.baseline_name: self.baseline_name = self.baseline_name.replace('{RemoteNode}', str(self.remote_node)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.visual_baseline_directory, self.baseline_name) def configure(self, tc_config_files, is_selenium_test=True, behave_properties=None): """Configure initial selenium instance using logging and properties files for Selenium or Appium tests :param tc_config_files: test case specific config files :param is_selenium_test: true if test is a selenium or appium test case :param behave_properties: dict with behave user data properties """ # Configure config and output directories DriverWrappersPool.configure_common_directories(tc_config_files) # Configure logger self.configure_logger(tc_config_files.config_log_filename, tc_config_files.output_log_filename) # Initialize the config object self.configure_properties(tc_config_files.config_properties_filenames, behave_properties) # Configure visual directories if is_selenium_test: driver_info = self.config.get('Driver', 'type') DriverWrappersPool.configure_visual_directories(driver_info) self.configure_visual_baseline() def connect(self, maximize=True): """Set up the selenium driver and connect to the server :param maximize: True if the driver should be maximized :returns: selenium driver """ if not self.config.get('Driver', 'type') or self.config.get('Driver', 'type') in ['api', 'no_driver']: return None self.driver = ConfigDriver(self.config).create_driver() # Save session id and remote node to download video after the test execution self.session_id = self.driver.session_id self.remote_node = self.utils.get_remote_node() self.remote_node_video_enabled = self.utils.is_remote_video_enabled(self.remote_node) # Save app_strings in mobile tests if self.is_mobile_test() and not self.is_web_test() and self.config.getboolean_optional('Driver', 'appium_app_strings'): self.app_strings = self.driver.app_strings() if self.is_maximizable(): # Bounds and screen bounds_x, bounds_y = self.get_config_window_bounds() self.driver.set_window_position(bounds_x, bounds_y) self.logger.debug('Window bounds: %s x %s', bounds_x, bounds_y) # Maximize browser if maximize: # Set window size or maximize window_width = self.config.get_optional('Driver', 'window_width') window_height = self.config.get_optional('Driver', 'window_height') if window_width and window_height: self.driver.set_window_size(window_width, window_height) else: self.driver.maximize_window() # Log window size window_size = self.utils.get_window_size() self.logger.debug('Window size: %s x %s', window_size['width'], window_size['height']) # Update baseline self.update_visual_baseline() # Discard previous logcat logs self.utils.discard_logcat_logs() # Set implicitly wait timeout self.utils.set_implicitly_wait() return self.driver def get_config_window_bounds(self): """Reads bounds from config and, if monitor is specified, modify the values to match with the specified monitor :return: coords X and Y where set the browser window. """ bounds_x = int(self.config.get_optional('Driver', 'bounds_x') or 0) bounds_y = int(self.config.get_optional('Driver', 'bounds_y') or 0) monitor_index = int(self.config.get_optional('Driver', 'monitor') or -1) if monitor_index > -1: try: monitor = screeninfo.get_monitors()[monitor_index] bounds_x += monitor.x bounds_y += monitor.y except NotImplementedError: self.logger.warn('Current environment doesn\'t support get_monitors') return bounds_x, bounds_y def is_android_test(self): """Check if actual test must be executed in an Android mobile :returns: True if test must be executed in an Android mobile """ driver_name = self.config.get('Driver', 'type').split('-')[0] return driver_name == 'android' def is_ios_test(self): """Check if actual test must be executed in an iOS mobile :returns: True if test must be executed in an iOS mobile """ driver_name = self.config.get('Driver', 'type').split('-')[0] return driver_name in ('ios', 'iphone') def is_mobile_test(self): """Check if actual test must be executed in a mobile :returns: True if test must be executed in a mobile """ return self.is_android_test() or self.is_ios_test() def is_web_test(self): """Check if actual test must be executed in a browser :returns: True if test must be executed in a browser """ appium_browser_name = self.config.get_optional('AppiumCapabilities', 'browserName') return not self.is_mobile_test() or appium_browser_name not in (None, '') def is_android_web_test(self): """Check if actual test must be executed in a browser of an Android mobile :returns: True if test must be executed in a browser of an Android mobile """ return self.is_android_test() and self.is_web_test() def is_ios_web_test(self): """Check if actual test must be executed in a browser of an iOS mobile :returns: True if test must be executed in a browser of an iOS mobile """ return self.is_ios_test() and self.is_web_test() def is_maximizable(self): """Check if the browser is maximizable :returns: True if the browser is maximizable """ return not self.is_mobile_test() def should_reuse_driver(self, scope, test_passed, context=None): """Check if the driver should be reused :param scope: execution scope (function, module, class or session) :param test_passed: True if the test has passed :param context: behave context :returns: True if the driver should be reused """ reuse_driver = self.config.getboolean_optional('Driver', 'reuse_driver') reuse_driver_session = self.config.getboolean_optional('Driver', 'reuse_driver_session') restart_driver_after_failure = (self.config.getboolean_optional('Driver', 'restart_driver_after_failure') or self.config.getboolean_optional('Driver', 'restart_driver_fail')) if context and scope == 'function': reuse_driver = reuse_driver or (hasattr(context, 'reuse_driver_from_tags') and context.reuse_driver_from_tags) return (((reuse_driver and scope == 'function') or (reuse_driver_session and scope != 'session')) and (test_passed or not restart_driver_after_failure))
class DriverWrapper(object): """Wrapper with the webdriver and the configuration needed to execute tests :type driver: selenium.webdriver.remote.webdriver.WebDriver or appium.webdriver.webdriver.WebDriver :type config: toolium.config_parser.ExtendedConfigParser or configparser.ConfigParser :type utils: toolium.utils.Utils :type app_strings: dict :type session_id: str :type remote_node: str :type remote_node_video_enabled: bool :type logger: logging.Logger :type config_properties_filenames: str :type config_log_filename: str :type output_log_filename: str :type visual_baseline_directory: str :type baseline_name: str """ driver = None #: webdriver instance config = ExtendedConfigParser() #: driver configuration utils = None #: test utils instance app_strings = None #: mobile application strings session_id = None #: remote webdriver session id 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 # Configuration and output files config_properties_filenames = None #: configuration filenames separated by commas config_log_filename = None #: configuration log file output_log_filename = None #: output log file visual_baseline_directory = None #: folder with the baseline images baseline_name = None #: baseline name def __init__(self): if not DriverWrappersPool.is_empty(): # Copy config object and other properties from default driver default_wrapper = DriverWrappersPool.get_default_wrapper() self.config = default_wrapper.config.deepcopy() self.logger = default_wrapper.logger 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 self.visual_baseline_directory = default_wrapper.visual_baseline_directory self.baseline_name = default_wrapper.baseline_name # Create utils instance and add wrapper to the pool self.utils = Utils(self) DriverWrappersPool.add_wrapper(self) def configure_logger(self, tc_config_log_filename=None, tc_output_log_filename=None): """Configure selenium instance logger :param tc_config_log_filename: test case specific logging config file :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, '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, 'toolium.log') output_log_filename = os.path.join(DriverWrappersPool.output_directory, output_log_filename) output_log_filename = output_log_filename.replace('\\', '\\\\') try: logging.config.fileConfig(config_log_filename, {'logfilename': output_log_filename}, False) except Exception as exc: print("[WARN] Error reading logging config file '{}': {}".format(config_log_filename, exc)) self.config_log_filename = config_log_filename self.output_log_filename = output_log_filename self.logger = logging.getLogger(__name__) def configure_properties(self, tc_config_prop_filenames=None, behave_properties=None): """Configure selenium instance 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, 'properties.cfg;local-properties.cfg') prop_filenames = [os.path.join(DriverWrappersPool.config_directory, filename) for filename in prop_filenames.split(';')] prop_filenames = ';'.join(prop_filenames) # Configure config only if properties filename has changed if self.config_properties_filenames != prop_filenames: # Initialize the config object self.config = ExtendedConfigParser.get_config_from_file(prop_filenames) self.config_properties_filenames = prop_filenames # Override properties with system properties self.config.update_properties(os.environ) # Override properties with behave userdata properties if behave_properties: self.config.update_properties(behave_properties) def configure_visual_baseline(self): """Configure baseline directory""" # Get baseline name baseline_name = self.config.get_optional('VisualTests', 'baseline_name', '{Driver_type}') for section in self.config.sections(): for option in self.config.options(section): option_value = self.config.get(section, option).replace('-', '_').replace(' ', '_') baseline_name = baseline_name.replace('{{{0}_{1}}}'.format(section, option), option_value) # Configure baseline directory if baseline name has changed if self.baseline_name != baseline_name: self.baseline_name = baseline_name self.visual_baseline_directory = os.path.join(DriverWrappersPool.visual_baseline_directory, baseline_name) def update_visual_baseline(self): """Configure baseline directory after driver is created""" # Update baseline with real platformVersion value if '{PlatformVersion}' in self.baseline_name: try: platform_version = self.driver.desired_capabilities['platformVersion'] except KeyError: platform_version = None self.baseline_name = self.baseline_name.replace('{PlatformVersion}', str(platform_version)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.visual_baseline_directory, self.baseline_name) # Update baseline with real version value if '{Version}' in self.baseline_name: try: splitted_version = self.driver.desired_capabilities['version'].split('.') version = '.'.join(splitted_version[:2]) except KeyError: version = None self.baseline_name = self.baseline_name.replace('{Version}', str(version)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.visual_baseline_directory, self.baseline_name) # Update baseline with remote node value if '{RemoteNode}' in self.baseline_name: self.baseline_name = self.baseline_name.replace('{RemoteNode}', str(self.remote_node)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.visual_baseline_directory, self.baseline_name) def configure(self, is_selenium_test=True, tc_config_files=None, behave_properties=None): """Configure initial selenium instance using logging and properties files for Selenium or Appium tests :param is_selenium_test: true if test is a selenium or appium test case :param tc_config_files: test case specific config files :param behave_properties: dict with behave user data properties """ # Initialize config files tc_config_files = self._initialize_config_files(tc_config_files) # Configure config and output directories DriverWrappersPool.configure_common_directories(tc_config_files) # Configure logger self.configure_logger(tc_config_files.config_log_filename, tc_config_files.output_log_filename) # Initialize the config object self.configure_properties(tc_config_files.config_properties_filenames, behave_properties) # Configure visual directories if is_selenium_test: driver_info = self.config.get('Driver', 'type').replace('-', '_') DriverWrappersPool.configure_visual_directories(driver_info) self.configure_visual_baseline() @staticmethod def _initialize_config_files(tc_config_files=None): """Initialize config files and update config files names with the environment :param tc_config_files: test case specific config files :returns: initialized config files object """ # Initialize config files if tc_config_files is 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) if env: # Update config properties filenames prop_filenames = tc_config_files.config_properties_filenames new_prop_filenames_list = prop_filenames.split(';') if prop_filenames else ['properties.cfg'] base = new_prop_filenames_list[0].split('.')[0] ext = new_prop_filenames_list[0].split('.')[1] new_prop_filenames_list.append('{}-{}.{}'.format(env, base, ext)) new_prop_filenames_list.append('local-{}-{}.{}'.format(env, base, ext)) tc_config_files.set_config_properties_filenames(*new_prop_filenames_list) # Update output log filename output_log_filename = tc_config_files.output_log_filename base = output_log_filename.split('.')[0] if output_log_filename else 'toolium' ext = output_log_filename.split('.')[1] if output_log_filename else 'log' tc_config_files.set_output_log_filename('{}_{}.{}'.format(base, env, ext)) return tc_config_files def connect(self, maximize=True): """Set up the selenium driver and connect to the server :param maximize: True if the driver should be maximized :returns: selenium driver """ if not self.config.get('Driver', 'type'): return None self.driver = ConfigDriver(self.config).create_driver() # Save session id and remote node to download video after the test execution self.session_id = self.driver.session_id self.remote_node = self.utils.get_remote_node() self.remote_node_video_enabled = self.utils.is_remote_video_enabled(self.remote_node) # Save app_strings in mobile tests if self.is_mobile_test() and not self.is_web_test() and self.config.getboolean_optional('Driver', 'appium_app_strings'): self.app_strings = self.driver.app_strings() # Maximize browser if maximize and self.is_maximizable(): # Set window size or maximize window_width = self.config.get_optional('Driver', 'window_width') window_height = self.config.get_optional('Driver', 'window_height') if window_width and window_height: self.driver.set_window_size(window_width, window_height) else: self.driver.maximize_window() # Log window size window_size = self.utils.get_window_size() self.logger.debug('Window size: %s x %s', window_size['width'], window_size['height']) # Update baseline self.update_visual_baseline() # Discard previous logcat logs self.utils.discard_logcat_logs() # Set implicitly wait timeout self.utils.set_implicitly_wait() return self.driver def is_android_test(self): """Check if actual test must be executed in an Android mobile :returns: true if test must be executed in an Android mobile """ driver_name = self.config.get('Driver', 'type').split('-')[0] return driver_name == 'android' def is_ios_test(self): """Check if actual test must be executed in an iOS mobile :returns: true if test must be executed in an iOS mobile """ driver_name = self.config.get('Driver', 'type').split('-')[0] return driver_name in ('ios', 'iphone') def is_mobile_test(self): """Check if actual test must be executed in a mobile :returns: true if test must be executed in a mobile """ return self.is_android_test() or self.is_ios_test() def is_web_test(self): """Check if actual test must be executed in a browser :returns: true if test must be executed in a browser """ appium_browser_name = self.config.get_optional('AppiumCapabilities', 'browserName') return not self.is_mobile_test() or appium_browser_name not in (None, '') def is_android_web_test(self): """Check if actual test must be executed in a browser of an Android mobile :returns: true if test must be executed in a browser of an Android mobile """ return self.is_android_test() and self.is_web_test() def is_ios_web_test(self): """Check if actual test must be executed in a browser of an iOS mobile :returns: true if test must be executed in a browser of an iOS mobile """ return self.is_ios_test() and self.is_web_test() def is_maximizable(self): """Check if the browser is maximizable :returns: true if the browser is maximizable """ return not self.is_mobile_test()
class UtilsTests(unittest.TestCase): def setUp(self): # Reset wrappers pool values DriverWrappersPool._empty_pool() DriverWrapper.config_properties_filenames = None # Create a new wrapper self.driver_wrapper = DriverWrappersPool.get_default_wrapper() self.driver_wrapper.driver = mock.MagicMock() # Configure properties self.root_path = os.path.dirname(os.path.realpath(__file__)) config_files = ConfigFiles() config_files.set_config_directory(os.path.join(self.root_path, 'conf')) config_files.set_config_properties_filenames('properties.cfg') config_files.set_output_directory(os.path.join(self.root_path, 'output')) self.driver_wrapper.configure(tc_config_files=config_files) # Create a new Utils instance self.utils = Utils() @classmethod def tearDownClass(cls): # Reset wrappers pool values DriverWrappersPool._empty_pool() DriverWrapper.config_properties_filenames = None @requests_mock.Mocker() def test_get_remote_node(self, req_mock): # Configure mock self.driver_wrapper.driver.session_id = '5af' url = 'http://{}:{}/grid/api/testsession?session={}'.format('localhost', 4444, '5af') grid_response_json = {'session': 'e2', 'proxyId': 'http://10.20.30.40:5555', 'msg': 'slot found !', 'inactivityTime': 78, 'success': True, 'internalKey': '7a'} req_mock.get(url, json=grid_response_json) # Get remote node and check result assert_equal(self.utils.get_remote_node(), '10.20.30.40') assert_equal(url, req_mock.request_history[0].url) @requests_mock.Mocker() def test_get_remote_node_non_grid(self, req_mock): # Configure mock self.driver_wrapper.driver.session_id = '5af' url = 'http://{}:{}/grid/api/testsession?session={}'.format('localhost', 4444, '5af') req_mock.get(url, text='non_json_response') # Get remote node and check result assert_equal(self.utils.get_remote_node(), 'localhost') assert_equal(url, req_mock.request_history[0].url) def test_get_remote_node_local_execution(self): self.driver_wrapper.config.set('Server', 'enabled', 'false') assert_is_none(self.utils.get_remote_node()) @requests_mock.Mocker() def test_get_remote_video_url(self, req_mock): # Configure mock url = 'http://{}:{}/video'.format('10.20.30.40', 3000) video_url = 'http://{}:{}/download_video/f4.mp4'.format('10.20.30.40', 3000) video_response_json = {'exit_code': 1, 'out': [], 'error': ['Cannot call this endpoint without required parameters: session and action'], 'available_videos': {'5af': {'size': 489701, 'session': '5af', 'last_modified': 1460041262558, 'download_url': video_url, 'absolute_path': 'C:\\f4.mp4'}}, 'current_videos': []} req_mock.get(url, json=video_response_json) # Get remote video url and check result assert_equal(self.utils._get_remote_video_url('10.20.30.40', '5af'), video_url) assert_equal(url, req_mock.request_history[0].url) @requests_mock.Mocker() def test_get_remote_video_url_no_videos(self, req_mock): # Configure mock url = 'http://{}:{}/video'.format('10.20.30.40', 3000) video_response_json = {'exit_code': 1, 'out': [], 'error': ['Cannot call this endpoint without required parameters: session and action'], 'available_videos': {}, 'current_videos': []} req_mock.get(url, json=video_response_json) # Get remote video url and check result assert_is_none(self.utils._get_remote_video_url('10.20.30.40', '5af')) assert_equal(url, req_mock.request_history[0].url) @requests_mock.Mocker() def test_is_remote_video_enabled(self, req_mock): # Configure mock url = 'http://{}:{}/config'.format('10.20.30.40', 3000) config_response_json = {'out': [], 'error': [], 'exit_code': 0, 'filename': ['selenium_grid_extras_config.json'], 'config_runtime': {'theConfigMap': { 'video_recording_options': {'width': '1024', 'videos_to_keep': '5', 'frames': '30', 'record_test_videos': 'true'}}}} req_mock.get(url, json=config_response_json) # Get remote video configuration and check result assert_equal(self.utils.is_remote_video_enabled('10.20.30.40'), True) assert_equal(url, req_mock.request_history[0].url) @requests_mock.Mocker() def test_is_remote_video_enabled_disabled(self, req_mock): # Configure mock url = 'http://{}:{}/config'.format('10.20.30.40', 3000) config_response_json = {'out': [], 'error': [], 'exit_code': 0, 'filename': ['selenium_grid_extras_config.json'], 'config_runtime': {'theConfigMap': { 'video_recording_options': {'width': '1024', 'videos_to_keep': '5', 'frames': '30', 'record_test_videos': 'false'}}}} req_mock.get(url, json=config_response_json) # Get remote video configuration and check result assert_equal(self.utils.is_remote_video_enabled('10.20.30.40'), False) assert_equal(url, req_mock.request_history[0].url) @mock.patch('toolium.utils.requests.get') def test_is_remote_video_enabled_non_grid_extras(self, req_get_mock): # Configure mock req_get_mock.side_effect = ConnectionError('exception error') # Get remote video configuration and check result assert_equal(self.utils.is_remote_video_enabled('10.20.30.40'), False) @data(*navigation_bar_tests) @unpack def test_get_safari_navigation_bar_height(self, driver_type, appium_app, appium_browser_name, bar_height): self.driver_wrapper.config.set('Driver', 'type', driver_type) if appium_app: self.driver_wrapper.config.set('AppiumCapabilities', 'app', appium_app) if appium_browser_name: self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', appium_browser_name) assert_equal(self.utils.get_safari_navigation_bar_height(), bar_height) def test_get_window_size_android_native(self): # Configure driver mock window_size = {'width': 375, 'height': 667} self.driver_wrapper.driver.get_window_size.return_value = window_size self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'app', 'C:/Demo.apk') assert_equal(self.utils.get_window_size(), window_size) def test_get_window_size_android_web(self): # Configure driver mock window_size = {'width': 375, 'height': 667} self.driver_wrapper.driver.current_context = 'WEBVIEW' self.driver_wrapper.driver.execute_script.side_effect = [window_size['width'], window_size['height']] self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'chrome') assert_equal(self.utils.get_window_size(), window_size) def test_get_native_coords_android_web(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.current_context = 'WEBVIEW' self.driver_wrapper.driver.execute_script.side_effect = [web_window_size['width'], web_window_size['height'], native_window_size['width'], native_window_size['height']] self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'chrome') web_coords = {'x': 105, 'y': 185} native_coords = {'x': 52.5, 'y': 92.5} assert_equal(self.utils.get_native_coords(web_coords), native_coords) def test_get_native_coords_ios_web(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.get_window_size.side_effect = [web_window_size, native_window_size] self.driver_wrapper.config.set('Driver', 'type', 'ios') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'safari') web_coords = {'x': 105, 'y': 185} native_coords = {'x': 52.5, 'y': 156.5} assert_equal(self.utils.get_native_coords(web_coords), native_coords) def test_swipe_android_native(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.get_window_size.side_effect = [web_window_size, native_window_size] self.driver_wrapper.driver.current_context = 'NATIVE_APP' self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'app', 'C:/Demo.apk') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) self.utils.swipe(element, 50, 100) self.driver_wrapper.driver.swipe.assert_called_once_with(400, 60, 450, 160, None) def test_swipe_android_web(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.current_context = 'WEBVIEW' self.driver_wrapper.driver.execute_script.side_effect = [web_window_size['width'], web_window_size['height'], native_window_size['width'], native_window_size['height']] self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'chrome') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) self.utils.swipe(element, 50, 100) self.driver_wrapper.driver.swipe.assert_called_once_with(200, 30, 250, 130, None) def test_swipe_android_hybrid(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.get_window_size.side_effect = [web_window_size, native_window_size] self.driver_wrapper.driver.current_context = 'WEBVIEW' self.driver_wrapper.config.set('Driver', 'type', 'android') self.driver_wrapper.config.set('AppiumCapabilities', 'app', 'C:/Demo.apk') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) self.utils.swipe(element, 50, 100) self.driver_wrapper.driver.swipe.assert_called_once_with(200, 30, 250, 130, None) def test_swipe_ios_web(self): # Configure driver mock web_window_size = {'width': 500, 'height': 667} native_window_size = {'width': 250, 'height': 450} self.driver_wrapper.driver.get_window_size.side_effect = [web_window_size, native_window_size] self.driver_wrapper.config.set('Driver', 'type', 'ios') self.driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'safari') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) self.utils.swipe(element, 50, 100) self.driver_wrapper.driver.swipe.assert_called_once_with(200, 94, 50, 100, None) def test_swipe_web(self): # Configure driver mock self.driver_wrapper.config.set('Driver', 'type', 'firefox') # Create element mock element = get_mock_element(x=250, y=40, height=40, width=300) with assert_raises(Exception) as cm: self.utils.swipe(element, 50, 100) assert_equal(str(cm.exception), 'Swipe method is not implemented in Selenium') def test_get_web_element_from_web_element(self): element = WebElement(None, 1) web_element = self.utils.get_web_element(element) assert_equal(element, web_element) def test_get_web_element_from_page_element(self): element = PageElement(By.ID, 'element_id') element._web_element = 'mock_element' web_element = self.utils.get_web_element(element) assert_equal('mock_element', web_element) def test_get_web_element_from_locator(self): # Configure driver mock self.driver_wrapper.driver.find_element.return_value = 'mock_element' element_locator = (By.ID, 'element_id') # Get element and assert response web_element = self.utils.get_web_element(element_locator) assert_equal('mock_element', web_element) self.driver_wrapper.driver.find_element.assert_called_once_with(*element_locator) def test_get_web_element_from_none(self): web_element = self.utils.get_web_element(None) assert_is_none(web_element) def test_get_web_element_from_unknown(self): web_element = self.utils.get_web_element(dict()) assert_is_none(web_element) def test_wait_until_first_element_is_found_locator(self): # Configure driver mock self.driver_wrapper.driver.find_element.return_value = 'mock_element' element_locator = (By.ID, 'element_id') element = self.utils.wait_until_first_element_is_found([element_locator]) assert_equal(element_locator, element) self.driver_wrapper.driver.find_element.assert_called_once_with(*element_locator) def test_wait_until_first_element_is_found_page_element(self): page_element = PageElement(By.ID, 'element_id') page_element._web_element = 'mock_element' element = self.utils.wait_until_first_element_is_found([page_element]) assert_equal(page_element, element) def test_wait_until_first_element_is_found_none(self): page_element = PageElement(By.ID, 'element_id') page_element._web_element = 'mock_element' element = self.utils.wait_until_first_element_is_found([None, page_element]) assert_equal(page_element, element) def test_wait_until_first_element_is_found_timeout(self): # Configure driver mock self.driver_wrapper.driver.find_element.side_effect = NoSuchElementException('Unknown') element_locator = (By.ID, 'element_id') start_time = time.time() with assert_raises(TimeoutException) as cm: self.utils.wait_until_first_element_is_found([element_locator], timeout=10) end_time = time.time() assert_in("None of the page elements has been found after 10 seconds", str(cm.exception)) # find_element has been called more than one time self.driver_wrapper.driver.find_element.assert_called_with(*element_locator) assert_greater(end_time - start_time, 10, 'Execution time must be greater than timeout')
class DriverWrapper(object): """Wrapper with the webdriver and the configuration needed to execute tests :type driver: selenium.webdriver.remote.webdriver.WebDriver or appium.webdriver.webdriver.WebDriver :type config: toolium.config_parser.ExtendedConfigParser or configparser.ConfigParser :type utils: toolium.utils.Utils :type app_strings: dict :type session_id: str :type remote_node: str :type remote_node_video_enabled: bool :type logger: logging.Logger :type config_properties_filenames: str :type config_log_filename: str :type output_log_filename: str :type visual_baseline_directory: str :type baseline_name: str """ driver = None #: webdriver instance config = ExtendedConfigParser() #: driver configuration utils = None #: test utils instance app_strings = None #: mobile application strings session_id = None #: remote webdriver session id 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 # Configuration and output files config_properties_filenames = None #: configuration filenames separated by commas config_log_filename = None #: configuration log file output_log_filename = None #: output log file visual_baseline_directory = None #: folder with the baseline images baseline_name = None #: baseline name def __init__(self): if not DriverWrappersPool.is_empty(): # Copy config object and other properties from default driver default_wrapper = DriverWrappersPool.get_default_wrapper() self.config = default_wrapper.config.deepcopy() self.logger = default_wrapper.logger 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 self.visual_baseline_directory = default_wrapper.visual_baseline_directory self.baseline_name = default_wrapper.baseline_name # Create utils instance and add wrapper to the pool self.utils = Utils(self) DriverWrappersPool.add_wrapper(self) def configure_logger(self, tc_config_log_filename=None, tc_output_log_filename=None): """Configure selenium instance logger :param tc_config_log_filename: test case specific logging config file :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, '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, 'toolium.log') output_log_filename = os.path.join(DriverWrappersPool.output_directory, output_log_filename) output_log_filename = output_log_filename.replace('\\', '\\\\') try: logging.config.fileConfig(config_log_filename, {'logfilename': output_log_filename}, False) except Exception as exc: print("[WARN] Error reading logging config file '{}': {}".format(config_log_filename, exc)) self.config_log_filename = config_log_filename self.output_log_filename = output_log_filename self.logger = logging.getLogger(__name__) def configure_properties(self, tc_config_prop_filenames=None): """Configure selenium instance properties :param tc_config_prop_filenames: test case specific properties filenames """ prop_filenames = DriverWrappersPool.get_configured_value('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(';')] prop_filenames = ';'.join(prop_filenames) # Configure config only if properties filename has changed if self.config_properties_filenames != prop_filenames: # Initialize the config object self.config = ExtendedConfigParser.get_config_from_file(prop_filenames) self.config_properties_filenames = prop_filenames # Override properties with system properties self.config.update_from_system_properties() def configure_visual_baseline(self): """Configure baseline directory""" # Get baseline name baseline_name = self.config.get_optional('VisualTests', 'baseline_name', '{Driver_type}') for section in self.config.sections(): for option in self.config.options(section): option_value = self.config.get(section, option).replace('-', '_').replace(' ', '_') baseline_name = baseline_name.replace('{{{0}_{1}}}'.format(section, option), option_value) # Configure baseline directory if baseline name has changed if self.baseline_name != baseline_name: self.baseline_name = baseline_name self.visual_baseline_directory = os.path.join(DriverWrappersPool.output_directory, 'visualtests', 'baseline', baseline_name) def update_visual_baseline(self): """Configure baseline directory after driver is created""" # Update baseline with real platformVersion value if '{PlatformVersion}' in self.baseline_name: try: platform_version = self.driver.desired_capabilities['platformVersion'] except KeyError: platform_version = None self.baseline_name = self.baseline_name.replace('{PlatformVersion}', str(platform_version)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.output_directory, 'visualtests', 'baseline', self.baseline_name) # Update baseline with real version value if '{Version}' in self.baseline_name: try: splitted_version = self.driver.desired_capabilities['version'].split('.') version = '.'.join(splitted_version[:2]) except KeyError: version = None self.baseline_name = self.baseline_name.replace('{Version}', str(version)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.output_directory, 'visualtests', 'baseline', self.baseline_name) # Update baseline with remote node value if '{RemoteNode}' in self.baseline_name: self.baseline_name = self.baseline_name.replace('{RemoteNode}', str(self.remote_node)) self.visual_baseline_directory = os.path.join(DriverWrappersPool.output_directory, 'visualtests', 'baseline', self.baseline_name) def configure(self, is_selenium_test=True, tc_config_files=ConfigFiles()): """Configure initial selenium instance using logging and properties files for Selenium or Appium tests :param is_selenium_test: true if test is a selenium or appium test case :param tc_config_files: test case specific config files """ # Configure config and output directories DriverWrappersPool.configure_common_directories(tc_config_files) # Configure logger self.configure_logger(tc_config_files.config_log_filename, tc_config_files.output_log_filename) # Initialize the config object self.configure_properties(tc_config_files.config_properties_filenames) # Configure visual directories if is_selenium_test: driver_info = self.config.get('Driver', 'type').replace('-', '_') DriverWrappersPool.configure_visual_directories(driver_info) self.configure_visual_baseline() def connect(self, maximize=True): """Set up the selenium driver and connect to the server :param maximize: True if the driver should be maximized :returns: selenium driver """ if not self.config.get('Driver', 'type'): return None self.driver = ConfigDriver(self.config).create_driver() # Save session id and remote node to download video after the test execution self.session_id = self.driver.session_id self.remote_node = self.utils.get_remote_node() self.remote_node_video_enabled = self.utils.is_remote_video_enabled(self.remote_node) # Save app_strings in mobile tests if self.is_mobile_test() and not self.is_web_test() and self.config.getboolean_optional('Driver', 'appium_app_strings'): self.app_strings = self.driver.app_strings() # Maximize browser if maximize and self.is_maximizable(): # Set window size or maximize window_width = self.config.get_optional('Driver', 'window_width') window_height = self.config.get_optional('Driver', 'window_height') if window_width and window_height: self.driver.set_window_size(window_width, window_height) else: self.driver.maximize_window() # Log window size window_size = self.utils.get_window_size() self.logger.debug('Window size: {} x {}'.format(window_size['width'], window_size['height'])) # Update baseline self.update_visual_baseline() # Discard previous logcat logs self.utils.discard_logcat_logs() return self.driver def is_android_test(self): """Check if actual test must be executed in an Android mobile :returns: true if test must be executed in an Android mobile """ driver_name = self.config.get('Driver', 'type').split('-')[0] return driver_name == 'android' def is_ios_test(self): """Check if actual test must be executed in an iOS mobile :returns: true if test must be executed in an iOS mobile """ driver_name = self.config.get('Driver', 'type').split('-')[0] return driver_name in ('ios', 'iphone') def is_mobile_test(self): """Check if actual test must be executed in a mobile :returns: true if test must be executed in a mobile """ return self.is_android_test() or self.is_ios_test() def is_web_test(self): """Check if actual test must be executed in a browser :returns: true if test must be executed in a browser """ appium_browser_name = self.config.get_optional('AppiumCapabilities', 'browserName') return not self.is_mobile_test() or appium_browser_name not in (None, '') def is_android_web_test(self): """Check if actual test must be executed in a browser of an Android mobile :returns: true if test must be executed in a browser of an Android mobile """ return self.is_android_test() and self.is_web_test() def is_ios_web_test(self): """Check if actual test must be executed in a browser of an iOS mobile :returns: true if test must be executed in a browser of an iOS mobile """ return self.is_ios_test() and self.is_web_test() def is_maximizable(self): """Check if the browser is maximizable :returns: true if the browser is maximizable """ return not self.is_mobile_test()
def utils(): # Create a new Utils instance return Utils()
class DriverWrapper(object): """Wrapper with the webdriver and the configuration needed to execute tests :type driver: selenium.webdriver.remote.webdriver.WebDriver or appium.webdriver.webdriver.WebDriver :type config: toolium.config_parser.ExtendedConfigParser or configparser.ConfigParser :type utils: toolium.utils.Utils :type app_strings: dict :type session_id: str :type remote_node: str :type remote_node_video_enabled: bool :type logger: logging.Logger :type config_properties_filenames: str :type config_log_filename: str :type output_log_filename: str :type visual_baseline_directory: str :type baseline_name: str """ driver = None #: webdriver instance config = ExtendedConfigParser() #: driver configuration utils = None #: test utils instance app_strings = None #: mobile application strings session_id = None #: remote webdriver session id 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 # Configuration and output files config_properties_filenames = None #: configuration filenames separated by commas config_log_filename = None #: configuration log file output_log_filename = None #: output log file visual_baseline_directory = None #: folder with the baseline images baseline_name = None #: baseline name def __init__(self): if not DriverWrappersPool.is_empty(): # Copy config object and other properties from default driver default_wrapper = DriverWrappersPool.get_default_wrapper() self.config = default_wrapper.config.deepcopy() self.logger = default_wrapper.logger 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 self.visual_baseline_directory = default_wrapper.visual_baseline_directory self.baseline_name = default_wrapper.baseline_name # Create utils instance and add wrapper to the pool self.utils = Utils(self) DriverWrappersPool.add_wrapper(self) def configure_logger(self, tc_config_log_filename=None, tc_output_log_filename=None): """Configure selenium instance logger :param tc_config_log_filename: test case specific logging config file :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, '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, 'toolium.log') output_log_filename = os.path.join( DriverWrappersPool.output_directory, output_log_filename) output_log_filename = output_log_filename.replace('\\', '\\\\') try: logging.config.fileConfig(config_log_filename, {'logfilename': output_log_filename}, False) except Exception as exc: print( "[WARN] Error reading logging config file '{}': {}".format( config_log_filename, exc)) self.config_log_filename = config_log_filename self.output_log_filename = output_log_filename self.logger = logging.getLogger(__name__) def configure_properties(self, tc_config_prop_filenames=None, behave_properties=None): """Configure selenium instance 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, 'properties.cfg;local-properties.cfg') prop_filenames = [ os.path.join(DriverWrappersPool.config_directory, filename) for filename in prop_filenames.split(';') ] prop_filenames = ';'.join(prop_filenames) # Configure config only if properties filename has changed if self.config_properties_filenames != prop_filenames: # Initialize the config object self.config = ExtendedConfigParser.get_config_from_file( prop_filenames) self.config_properties_filenames = prop_filenames # Override properties with system properties self.config.update_properties(os.environ) # Override properties with behave userdata properties if behave_properties: self.config.update_properties(behave_properties) def configure_visual_baseline(self): """Configure baseline directory""" # Get baseline name baseline_name = self.config.get_optional('VisualTests', 'baseline_name', '{Driver_type}') for section in self.config.sections(): for option in self.config.options(section): option_value = self.config.get(section, option) baseline_name = baseline_name.replace( '{{{0}_{1}}}'.format(section, option), option_value) # Configure baseline directory if baseline name has changed if self.baseline_name != baseline_name: self.baseline_name = baseline_name self.visual_baseline_directory = os.path.join( DriverWrappersPool.visual_baseline_directory, get_valid_filename(baseline_name)) def update_visual_baseline(self): """Configure baseline directory after driver is created""" # Update baseline with real platformVersion value if '{PlatformVersion}' in self.baseline_name: try: platform_version = self.driver.desired_capabilities[ 'platformVersion'] except KeyError: platform_version = None self.baseline_name = self.baseline_name.replace( '{PlatformVersion}', str(platform_version)) self.visual_baseline_directory = os.path.join( DriverWrappersPool.visual_baseline_directory, self.baseline_name) # Update baseline with real version value if '{Version}' in self.baseline_name: try: splitted_version = self.driver.desired_capabilities[ 'version'].split('.') version = '.'.join(splitted_version[:2]) except KeyError: version = None self.baseline_name = self.baseline_name.replace( '{Version}', str(version)) self.visual_baseline_directory = os.path.join( DriverWrappersPool.visual_baseline_directory, self.baseline_name) # Update baseline with remote node value if '{RemoteNode}' in self.baseline_name: self.baseline_name = self.baseline_name.replace( '{RemoteNode}', str(self.remote_node)) self.visual_baseline_directory = os.path.join( DriverWrappersPool.visual_baseline_directory, self.baseline_name) def configure(self, tc_config_files, is_selenium_test=True, behave_properties=None): """Configure initial selenium instance using logging and properties files for Selenium or Appium tests :param tc_config_files: test case specific config files :param is_selenium_test: true if test is a selenium or appium test case :param behave_properties: dict with behave user data properties """ # Configure config and output directories DriverWrappersPool.configure_common_directories(tc_config_files) # Configure logger self.configure_logger(tc_config_files.config_log_filename, tc_config_files.output_log_filename) # Initialize the config object self.configure_properties(tc_config_files.config_properties_filenames, behave_properties) # Configure visual directories if is_selenium_test: driver_info = self.config.get('Driver', 'type') DriverWrappersPool.configure_visual_directories(driver_info) self.configure_visual_baseline() def connect(self, maximize=True): """Set up the selenium driver and connect to the server :param maximize: True if the driver should be maximized :returns: selenium driver """ if not self.config.get('Driver', 'type') or self.config.get( 'Driver', 'type') in ['api', 'no_driver']: return None self.driver = ConfigDriver(self.config).create_driver() # Save session id and remote node to download video after the test execution self.session_id = self.driver.session_id self.remote_node = self.utils.get_remote_node() self.remote_node_video_enabled = self.utils.is_remote_video_enabled( self.remote_node) # Save app_strings in mobile tests if self.is_mobile_test( ) and not self.is_web_test() and self.config.getboolean_optional( 'Driver', 'appium_app_strings'): self.app_strings = self.driver.app_strings() if self.is_maximizable(): # Bounds and screen bounds_x, bounds_y = self.get_config_window_bounds() self.driver.set_window_position(bounds_x, bounds_y) self.logger.debug('Window bounds: %s x %s', bounds_x, bounds_y) # Maximize browser if maximize: # Set window size or maximize window_width = self.config.get_optional( 'Driver', 'window_width') window_height = self.config.get_optional( 'Driver', 'window_height') if window_width and window_height: self.driver.set_window_size(window_width, window_height) else: self.driver.maximize_window() # Log window size window_size = self.utils.get_window_size() self.logger.debug('Window size: %s x %s', window_size['width'], window_size['height']) # Update baseline self.update_visual_baseline() # Discard previous logcat logs self.utils.discard_logcat_logs() # Set implicitly wait timeout self.utils.set_implicitly_wait() return self.driver def get_config_window_bounds(self): """Reads bounds from config and, if monitor is specified, modify the values to match with the specified monitor :return: coords X and Y where set the browser window. """ bounds_x = int(self.config.get_optional('Driver', 'bounds_x') or 0) bounds_y = int(self.config.get_optional('Driver', 'bounds_y') or 0) monitor_index = int( self.config.get_optional('Driver', 'monitor') or -1) if monitor_index > -1: try: monitor = screeninfo.get_monitors()[monitor_index] bounds_x += monitor.x bounds_y += monitor.y except NotImplementedError: self.logger.warn( 'Current environment doesn\'t support get_monitors') return bounds_x, bounds_y def is_android_test(self): """Check if actual test must be executed in an Android mobile :returns: True if test must be executed in an Android mobile """ driver_name = self.config.get('Driver', 'type').split('-')[0] return driver_name == 'android' def is_ios_test(self): """Check if actual test must be executed in an iOS mobile :returns: True if test must be executed in an iOS mobile """ driver_name = self.config.get('Driver', 'type').split('-')[0] return driver_name in ('ios', 'iphone') def is_mobile_test(self): """Check if actual test must be executed in a mobile :returns: True if test must be executed in a mobile """ return self.is_android_test() or self.is_ios_test() def is_web_test(self): """Check if actual test must be executed in a browser :returns: True if test must be executed in a browser """ appium_browser_name = self.config.get_optional('AppiumCapabilities', 'browserName') return not self.is_mobile_test() or appium_browser_name not in (None, '') def is_android_web_test(self): """Check if actual test must be executed in a browser of an Android mobile :returns: True if test must be executed in a browser of an Android mobile """ return self.is_android_test() and self.is_web_test() def is_ios_web_test(self): """Check if actual test must be executed in a browser of an iOS mobile :returns: True if test must be executed in a browser of an iOS mobile """ return self.is_ios_test() and self.is_web_test() def is_maximizable(self): """Check if the browser is maximizable :returns: True if the browser is maximizable """ return not self.is_mobile_test() def should_reuse_driver(self, scope, test_passed, context=None): """Check if the driver should be reused :param scope: execution scope (function, module, class or session) :param test_passed: True if the test has passed :param context: behave context :returns: True if the driver should be reused """ reuse_driver = self.config.getboolean_optional('Driver', 'reuse_driver') reuse_driver_session = self.config.getboolean_optional( 'Driver', 'reuse_driver_session') restart_driver_after_failure = (self.config.getboolean_optional( 'Driver', 'restart_driver_after_failure') or self.config.getboolean_optional( 'Driver', 'restart_driver_fail')) if context and scope == 'function': reuse_driver = reuse_driver or (hasattr(context, 'reuse_driver_from_tags') and context.reuse_driver_from_tags) return (((reuse_driver and scope == 'function') or (reuse_driver_session and scope != 'session')) and (test_passed or not restart_driver_after_failure))