def hide_scrollbars(self): """ Hides the scrollbars of the current element. :return: The previous value of the overflow property (could be None). """ logger.debug('EyesWebElement.HideScrollbars()') return self.set_overflow('hidden')
def stop_session(self, running_session, is_aborted, save): """ Stops a running session in the Eyes server. Args: running_session (RunningSession): The session to stop. is_aborted (boolean): Whether the server should mark this session as aborted. save (boolean): Whether the session should be automatically saved if it is not aborted. Returns: TestResults: Test results of the stopped session. Raises: see :mod:'requests' """ logger.debug('Stop session called..') session_uri = "%s/%s" % (self._endpoint_uri, running_session['session_id']) params = { 'aborted': is_aborted, 'updateBaseline': save, 'apiKey': self.api_key } response = AgentConnector._send_long_request( "stop_session", requests.delete, session_uri, params=params, verify=False, headers=AgentConnector._DEFAULT_HEADERS, timeout=AgentConnector._TIMEOUT) pr = _parse_response_with_json_data(response) return TestResults(pr['steps'], pr['matches'], pr['mismatches'], pr['missing'], pr['exactMatches'], pr['strictMatches'], pr['contentMatches'], pr['layoutMatches'], pr['noneMatches'])
def hide_scrollbars(self): """ Hides the scrollbars of the current element. :return: The previous value of the overflow property (could be None). """ logger.debug('EyesWebElement.HideScrollbars()') return self.set_overflow('hidden')
def __init__(self, driver, eyes): self.driver = driver self._eyes = eyes # List of frames the user switched to, and the current offset, so we can properly # calculate elements' coordinates self._frames = [] driver_takes_screenshot = driver.capabilities.get('takesScreenshot', False) if driver_takes_screenshot: self._screenshot_taker = None else: logger.debug('Driver can\'t take screenshots, using our own screenshot taker.') # noinspection PyProtectedMember self._screenshot_taker = _ScreenshotTaker(driver.command_executor._url, driver.session_id) # Creating the rest of the driver interface by simply forwarding it to the underlying # driver. general_utils.create_proxy_interface(self, driver, self._READONLY_PROPERTIES + self._SETTABLE_PROPERTIES) for attr in self._READONLY_PROPERTIES: if not hasattr(self.__class__, attr): setattr(self.__class__, attr, general_utils.create_proxy_property(attr, 'driver')) for attr in self._SETTABLE_PROPERTIES: if not hasattr(self.__class__, attr): setattr(self.__class__, attr, general_utils.create_proxy_property(attr, 'driver', True))
def check_region_in_frame_by_selector(self, frame_reference, by, value, tag=None, match_timeout=-1): """ Checks a region within a frame, and returns to the current frame. Args: :param frame_reference: (int/str/WebElement) A reference to the frame in which the region should be checked. :param by: (By) The way by which an element to be validated should be found (e.g., By.ID). :param value: (str) The value identifying the element using the "by" type. :param tag: (str) Description of the visual validation checkpoint. :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds). Returns: None """ if self.is_disabled: logger.info('check_region_in_frame_by_selector(): ignored (disabled)') return logger.info("check_region_in_frame_by_selector('%s')" % tag) # We need to temporarily save the hide_scrollbars value, since we'll change it to make sure that hide_scrollbars # will NOT be called twice (once outside the frame and once inside the frame). original_hide_scrollbars_value = self.hide_scrollbars if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self.hide_scrollbars = False # Switching to the relevant frame self._driver.switch_to.frame(frame_reference) logger.debug("calling 'check_region_by_selector'...") self.check_region_by_selector(by, value, tag, match_timeout) # Switching back to our original frame self._driver.switch_to.parent_frame() if original_hide_scrollbars_value: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self.hide_scrollbars = original_hide_scrollbars_value
def check_region_in_frame_by_selector(self, frame_reference, by, value, tag=None, match_timeout=-1): """ Checks a region within a frame, and returns to the current frame. :param frame_reference: (int/str/WebElement) A reference to the frame in which the region should be checked. :param by: (By) The way by which an element to be validated should be found (e.g., By.ID). :param value: (str) The value identifying the element using the "by" type. :param tag: (str) Description of the visual validation checkpoint. :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds). :return: None """ if self.is_disabled: logger.info('check_region_in_frame_by_selector(): ignored (disabled)') return logger.info("check_region_in_frame_by_selector('%s')" % tag) # We need to temporarily save the hide_scrollbars value, since we'll change it to make sure that hide_scrollbars # will NOT be called twice (once outside the frame and once inside the frame). original_hide_scrollbars_value = self.hide_scrollbars if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self.hide_scrollbars = False # Switching to the relevant frame self._driver.switch_to.frame(frame_reference) logger.debug("calling 'check_region_by_selector'...") self.check_region_by_selector(by, value, tag, match_timeout) # Switching back to our original frame self._driver.switch_to.parent_frame() if original_hide_scrollbars_value: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self.hide_scrollbars = original_hide_scrollbars_value
def set_overflow(self, overflow, stabilization_time=None): """ Sets the overflow of the current element. :param overflow: The overflow value to set. If the given value is None, then overflow will be set to undefined. :param stabilization_time: The time to wait for the page to stabilize after overflow is set. If the value is None, then no waiting will take place. (Milliseconds) :return: The previous overflow value. """ logger.debug("Setting overflow: %s" % overflow) if overflow is None: script = "var elem = arguments[0]; var origOverflow = elem.style.overflow; " \ "elem.style.overflow = undefined; " \ "return origOverflow;" else: script = "var elem = arguments[0]; var origOverflow = elem.style.overflow; " \ "elem.style.overflow = \"{0}\"; " \ "return origOverflow;".format(overflow) # noinspection PyUnresolvedReferences original_overflow = self._driver.execute_script(script, self.element) logger.debug("Original overflow: %s" % original_overflow) if stabilization_time is not None: time.sleep(stabilization_time / 1000) return original_overflow
def set_position(self, point): """ Commands the browser to scroll to a given position using javascript. """ scroll_command = "window.scrollTo({0}, {1})".format(point.x, point.y) logger.debug(scroll_command) self._execute_script(scroll_command)
def hide_scrollbars(self): """ Hides the scrollbars of the current context's document element. :return: The previous value of the overflow property (could be None). """ logger.debug('HideScrollbars() called. Waiting for page load...') self.wait_for_page_load() logger.debug('About to hide scrollbars') return self.set_overflow('hidden')
def _start_session(self): logger.debug("_start_session()") self._assign_viewport_size() if not self.batch: self.batch = BatchInfo() self._create_start_info() # Actually start the session. self._running_session = self._agent_connector.start_session(self._start_info) self._should_match_once_on_timeout = self._running_session['is_new_session']
def hide_scrollbars(self): """ Hides the scrollbars of the current context's document element. :return: The previous value of the overflow property (could be None). """ logger.debug('HideScrollbars() called. Waiting for page load...') self.wait_for_page_load() logger.debug('About to hide scrollbars') return self.set_overflow('hidden')
def _start_session(self): logger.debug("_start_session()") self._assign_viewport_size() if not self.batch: self.batch = BatchInfo() self._create_start_info() # Actually start the session. self._running_session = self._agent_connector.start_session(self._start_info) self._should_match_once_on_timeout = self._running_session['is_new_session']
def _prepare_to_check(self): logger.debug("_prepare_to_check()") if not self.is_open(): raise EyesError('Eyes not open!') if not self._running_session: self._start_session() self._match_window_task = MatchWindowTask(self, self._agent_connector, self._running_session, self._driver, self.match_timeout)
def set_position(self, point): """ Commands the browser to scroll to a given position using javascript. """ translate_command = "translate(-{}px, -{}px)".format(point.x, point.y) logger.debug(translate_command) transform_list = dict( (key, translate_command) for key in self._JS_TRANSFORM_KEYS) self._set_transform(transform_list) self.current_position = point.clone()
def _prepare_to_check(self): logger.debug("_prepare_to_check()") if not self.is_open(): raise EyesError('Eyes not open!') if not self._running_session: self._start_session() self._match_window_task = MatchWindowTask(self, self._agent_connector, self._running_session, self._driver, self.match_timeout)
def _send_long_request(name, method, *args, **kwargs): delay = 2 # Seconds headers = kwargs['headers'].copy() headers['Eyes-Expect'] = '202-accepted' while True: # Sending the current time of the request (in RFC 1123 format) headers['Eyes-Date'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime()) kwargs['headers'] = headers response = method(*args, **kwargs) if response.status_code != 202: return response logger.debug("{0}: Still running... Retrying in {1}s".format(name, delay)) time.sleep(delay) delay = min(10, delay * 1.5)
def add_mouse_trigger_by_element(self, action, element): """ Adds a mouse trigger. Args: action: (string) Mouse action (click, double click etc.) element: (WebElement) The element on which the action was performed. """ if self.is_disabled: logger.debug("add_mouse_trigger: Ignoring %s (disabled)" % action) return # Triggers are activated on the last checked window. if self._last_screenshot is None: logger.debug("add_mouse_trigger: Ignoring %s (no screenshot)" % action) return if not EyesFrame.is_same_frame_chain(self._driver.get_frame_chain(), self._last_screenshot.get_frame_chain()): logger.debug("add_mouse_trigger: Ignoring %s (different frame)" % action) return control = self._last_screenshot.get_intersected_region_by_element(element) # Making sure the trigger is within the last screenshot bounds if control.is_empty(): logger.debug("add_mouse_trigger: Ignoring %s (out of bounds)" % action) return cursor = control.middle_offset trigger = MouseTrigger(action, control, cursor) self._user_inputs.append(trigger) logger.info("add_mouse_trigger: Added %s" % trigger)
def wait_for_page_load(self, timeout=3, throw_on_timeout=False): """ Waits for the current document to be "loaded". :param timeout: The maximum time to wait, in seconds. :param throw_on_timeout: Whether to throw an exception when timeout is reached. """ # noinspection PyBroadException try: WebDriverWait(self.driver, timeout)\ .until(lambda driver: driver.execute_script('return document.readyState') == 'complete') except: logger.debug('Page load timeout reached!') if throw_on_timeout: raise
def add_text_trigger_by_element(self, element, text): """ Adds a text trigger. Args: element: (WebElement) The element to which the text was sent. text: (str) The trigger's text. """ if self.is_disabled: logger.debug("add_text_trigger: Ignoring '%s' (disabled)" % text) return # Triggers are activated on the last checked window. if self._last_screenshot is None: logger.debug("add_text_trigger: Ignoring '%s' (no screenshot)" % text) return if not EyesFrame.is_same_frame_chain(self._driver.get_frame_chain(), self._last_screenshot.get_frame_chain()): logger.debug("add_text_trigger: Ignoring %s (different frame)" % text) return control = self._last_screenshot.get_intersected_region_by_element(element) # Making sure the trigger is within the last screenshot bounds if control.is_empty(): logger.debug("add_text_trigger: Ignoring %s (out of bounds)" % text) return trigger = TextTrigger(control, text) self._user_inputs.append(trigger) logger.info("add_text_trigger: Added %s" % trigger)
def add_text_trigger_by_element(self, element, text): """ Adds a text trigger. :param element: The element to which the text was sent. :param text: The trigger's text. """ if self.is_disabled: logger.debug("add_text_trigger: Ignoring '%s' (disabled)" % text) return # Triggers are activated on the last checked window. if self._last_screenshot is None: logger.debug("add_text_trigger: Ignoring '%s' (no screenshot)" % text) return if not EyesFrame.is_same_frame_chain(self._driver.get_frame_chain(), self._last_screenshot.get_frame_chain()): logger.debug("add_text_trigger: Ignoring %s (different frame)" % text) return control = self._last_screenshot.get_intersected_region_by_element(element) # Making sure the trigger is within the last screenshot bounds if control.is_empty(): logger.debug("add_text_trigger: Ignoring %s (out of bounds)" % text) return trigger = TextTrigger(control, text) self._user_inputs.append(trigger) logger.info("add_text_trigger: Added %s" % trigger)
def add_mouse_trigger_by_element(self, action, element): """ Adds a mouse trigger. :param action: Mouse action (click, double click etc.) :param element: The element on which the action was performed. """ if self.is_disabled: logger.debug("add_mouse_trigger: Ignoring %s (disabled)" % action) return # Triggers are activated on the last checked window. if self._last_screenshot is None: logger.debug("add_mouse_trigger: Ignoring %s (no screenshot)" % action) return if not EyesFrame.is_same_frame_chain(self._driver.get_frame_chain(), self._last_screenshot.get_frame_chain()): logger.debug("add_mouse_trigger: Ignoring %s (different frame)" % action) return control = self._last_screenshot.get_intersected_region_by_element(element) # Making sure the trigger is within the last screenshot bounds if control.is_empty(): logger.debug("add_mouse_trigger: Ignoring %s (out of bounds)" % action) return cursor = control.middle_offset trigger = MouseTrigger(action, control, cursor) self._user_inputs.append(trigger) logger.info("add_mouse_trigger: Added %s" % trigger)
def quadrant_rotate(self, num_quadrants): """ Rotates the image by 90 degrees clockwise or counter-clockwise. :param num_quadrants: The number of rotations to perform. """ # Divide the continuous sequence of bytes in each row into pixel groups (since values within a single pixel # should maintain order). logger.debug('Dividing into chunks...') pixels = list( map( lambda bytes_row: general_utils.divide_to_chunks( bytes_row, self.pixel_size), self.pixel_bytes)) logger.debug('Done! Rotating pixels...') rotated_pixels = quadrant_rotate(pixels, num_quadrants) logger.debug('Done! flattening chunks back to bytes...') # Unite the pixel groups back to continuous pixel bytes for each row. rotated_pixel_bytes = list( map(lambda pixels_row: general_utils.join_chunks(pixels_row), rotated_pixels)) logger.debug('Done!') self.pixel_bytes = rotated_pixel_bytes self._update_size( len(rotated_pixel_bytes[0]) / self.pixel_size, len(rotated_pixel_bytes))
def wait_for_page_load(self, timeout=3, throw_on_timeout=False): """ Waits for the current document to be "loaded". :param timeout: The maximum time to wait, in seconds. :param throw_on_timeout: Whether to throw an exception when timeout is reached. """ # noinspection PyBroadException try: WebDriverWait(self.driver, timeout)\ .until(lambda driver: driver.execute_script('return document.readyState') == 'complete') except: logger.debug('Page load timeout reached!') if throw_on_timeout: raise
def _run(self, prepare_action, run_once_after_wait=False, retry_timeout=-1): if 0 < retry_timeout < MatchWindowTask.MINIMUM_MATCH_TIMEOUT: raise ValueError( "Match timeout must be at least 60ms, got {} instead.".format( retry_timeout)) if retry_timeout < 0: retry_timeout = self._default_retry_timeout else: retry_timeout /= 1000.0 logger.debug("Match timeout set to: {0} seconds".format(retry_timeout)) start = time.time() if run_once_after_wait or retry_timeout == 0: logger.debug("Matching once...") # If the load time is 0, the sleep would immediately return anyway. time.sleep(retry_timeout) data = prepare_action() as_expected = self._agent_connector.match_window( self._running_session, data) result = { "as_expected": as_expected, "screenshot": self._screenshot } else: result = self._run_with_intervals(prepare_action, retry_timeout) logger.debug("Match result: {0}".format(result["as_expected"])) elapsed_time = time.time() - start logger.debug( "_run(): Completed in {0:.1f} seconds".format(elapsed_time)) return result
def close(self, raise_ex=True): """ Ends the test. Args: (boolean) raise_ex: If true, an exception will be raised for failed/new tests. Returns: The test results. """ if self.is_disabled: logger.debug('close(): ignored (disabled)') return try: self._is_open = False logger.debug('close()') self._reset_last_screenshot() # If there's no running session, we simply return the default test results. if not self._running_session: logger.debug('close(): Server session was not started') logger.info('close(): --- Empty test ended.') return TestResults() results_url = self._running_session['session_url'] is_new_session = self._running_session['is_new_session'] should_save = (is_new_session and self.save_new_tests) or \ ((not is_new_session) and self.save_failed_tests) logger.debug("close(): automatically save session? %s" % should_save) logger.info('close(): Closing session...') results = self._agent_connector.stop_session(self._running_session, False, should_save) results.is_new = is_new_session results.url = results_url logger.info("close(): %s" % results) self._running_session = None if not is_new_session and (0 < results.mismatches or 0 < results.missing): # Test failed logger.info("--- Failed test ended. See details at " + results_url) if raise_ex: message = "'%s' of '%s'. See details at %s" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName'], results_url) raise TestFailedError(message, results) return results if is_new_session: # New test instructions = "Please approve the new baseline at %s" % results_url logger.info("--- New test ended. %s" % instructions) if raise_ex and self.fail_on_new_test: message = "'%s' of '%s'. %s" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName'], instructions) raise NewTestError(message, results) return results # Test passed logger.info('--- Test passed.') return results finally: logger.close()
def close(self, raise_ex=True): """ Ends the test. Args: (boolean) raise_ex: If true, an exception will be raised for failed/new tests. Returns: The test results. """ if self.is_disabled: logger.debug('close(): ignored (disabled)') return try: self._is_open = False logger.debug('close()') self._reset_last_screenshot() # If there's no running session, we simply return the default test results. if not self._running_session: logger.debug('close(): Server session was not started') logger.info('close(): --- Empty test ended.') return TestResults() results_url = self._running_session['session_url'] is_new_session = self._running_session['is_new_session'] should_save = (is_new_session and self.save_new_tests) or \ ((not is_new_session) and self.save_failed_tests) logger.debug("close(): automatically save session? %s" % should_save) logger.info('close(): Closing session...') results = self._agent_connector.stop_session(self._running_session, False, should_save) results.is_new = is_new_session results.url = results_url logger.info("close(): %s" % results) self._running_session = None if not is_new_session and (0 < results.mismatches or 0 < results.missing): # Test failed logger.info("--- Failed test ended. See details at " + results_url) if raise_ex: message = "'%s' of '%s'. See details at %s" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName'], results_url) raise TestFailedError(message, results) return results if is_new_session: # New test instructions = "Please approve the new baseline at %s" % results_url logger.info("--- New test ended. %s" % instructions) if raise_ex and self.fail_on_new_test: message = "'%s' of '%s'. %s" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName'], instructions) raise NewTestError(message, results) return results # Test passed logger.info('--- Test passed.') return results finally: logger.close()
def _send_long_request(name, method, *args, **kwargs): delay = 2 # Seconds headers = kwargs['headers'].copy() headers['Eyes-Expect'] = '202-accepted' while True: # Sending the current time of the request (in RFC 1123 format) headers['Eyes-Date'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime()) kwargs['headers'] = headers response = method(*args, **kwargs) if response.status_code != 202: return response logger.debug("{0}: Still running... Retrying in {1}s".format( name, delay)) time.sleep(delay) delay = min(10, delay * 1.5)
def check_region_by_selector(self, by, value, tag=None, match_timeout=-1): """ Takes a snapshot of the region of the element found by calling find_element(by, value) and matches it with the expected output. :param by: (By) The way by which an element to be validated should be found (e.g., By.ID). :param value: (str) The value identifying the element using the "by" type. :param tag: (str) Description of the visual validation checkpoint. :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds). :return: None """ if self.is_disabled: logger.info('check_region_by_selector(): ignored (disabled)') return logger.debug("calling 'check_region_by_element'...") self.check_region_by_element(self._driver.find_element(by, value), tag, match_timeout)
def open(self, driver, app_name, test_name, viewport_size=None): """ Starts a test. Args: params (dictionary): app_name (str): The name of the application under test. test_name (str): The test name. (Optional) viewport_size ({width, height}): The client's viewport size (i.e., the visible part of the document's body) or None to allow any viewport size. Returns: An updated web driver Raises: EyesError """ logger.open_() if self.is_disabled: logger.debug('open(): ignored (disabled)') return driver if self.api_key is None: raise EyesError("API key not set! Log in to https://applitools.com to obtain your" " API Key and use 'api_key' to set it.") if isinstance(driver, RemoteWebDriver): self._driver = EyesWebDriver(driver, self) elif isinstance(driver, EyesWebDriver): # If the driver is an EyesWebDriver (as might be the case when tests are ran # consecutively using the same driver object) self._driver = driver else: raise EyesError("driver is not a RemoteWebDriver ({0})".format(driver.__class__)) logger.info("open(%s, %s, %s, %s)" % (app_name, test_name, viewport_size, self.failure_reports)) if self.is_open(): self.abort_if_not_closed() raise EyesError('a test is already running') self._app_name = app_name self._test_name = test_name self._viewport_size = viewport_size self._is_open = True return self._driver
def check_region_by_selector(self, by, value, tag=None, match_timeout=-1): """ Takes a snapshot of the region of the element found by calling find_element(by, value) and matches it with the expected output. Args: :param by: (By) The way by which an element to be validated should be found (e.g., By.ID). :param value: (str) The value identifying the element using the "by" type. :param tag: (str) Description of the visual validation checkpoint. :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds). Returns: None """ if self.is_disabled: logger.info('check_region_by_selector(): ignored (disabled)') return logger.debug("calling 'check_region_by_element'...") self.check_region_by_element(self._driver.find_element(by, value), tag, match_timeout)
def check_region_by_selector(self, by, value, tag=None, specific_match_timeout=-1): """ Takes a snapshot of the region of the element found by calling find_element(by, value) and matches it with the expected output. Args: (By) by: The way by which an element to be validated should be found (e.g., By.ID). (str) selector: The value identifying the element using the "by" type. (str) tag: (Optional) Description of the visual validation checkpoint. (float) specific_match_timeout: (Optional) Timeout for the visual validation checkpoint (given in order to let the current page time to stabilize). Returns: None """ if self.is_disabled: logger.info('check_region_by_selector(): ignored (disabled)') return logger.debug("calling 'check_region_by_element'...") self.check_region_by_element(self._driver.find_element(by, value), tag, specific_match_timeout)
def start_session(self, session_start_info): """ Starts a new running session in the agent. Based on the given parameters, this running session will either be linked to an existing session, or to a completely new session. Args: session_start_info (dictionary): The start parameters for the session. Returns: (dictionary): Represents the current running session. Raises: see :mod:'requests' """ data = '{"startInfo": %s}' % (general_utils.to_json(session_start_info)) logger.debug("Starting session: %s " % data) response = requests.post(self._endpoint_uri, data=data, verify=False, params=dict(apiKey=self.api_key), headers=AgentConnector._DEFAULT_HEADERS, timeout=AgentConnector._TIMEOUT) parsed_response = _parse_response_with_json_data(response) return dict(session_id=parsed_response['id'], session_url=parsed_response['url'], is_new_session=(response.status_code == requests.codes.created))
def open(self, driver, app_name, test_name, viewport_size=None): """ Starts a test. :param driver: The webdriver to use. :param app_name: The name of the application under test. :param test_name: The test name. :param viewport_size: The client's viewport size (i.e., the visible part of the document's body) or None to allow any viewport size. :return: An updated web driver :raise EyesError: If the session was already open. """ logger.open_() if self.is_disabled: logger.debug('open(): ignored (disabled)') return driver if self.api_key is None: raise EyesError("API key not set! Log in to https://applitools.com to obtain your" " API Key and use 'api_key' to set it.") if isinstance(driver, EyesWebDriver): # If the driver is an EyesWebDriver (as might be the case when tests are ran # consecutively using the same driver object) self._driver = driver else: if not isinstance(driver, RemoteWebDriver): logger.info("WARNING: driver is not a RemoteWebDriver (class: {0})".format(driver.__class__)) self._driver = EyesWebDriver(driver, self, self._stitch_mode) logger.info("open(%s, %s, %s, %s)" % (app_name, test_name, viewport_size, self.failure_reports)) if self.is_open(): self.abort_if_not_closed() raise EyesError('a test is already running') self._app_name = app_name self._test_name = test_name self._viewport_size = viewport_size self._is_open = True return self._driver
def abort_if_not_closed(self): """ If a test is running, aborts it. Otherwise, does nothing. """ if self.is_disabled: logger.debug('abort_if_not_closed(): ignored (disabled)') return try: self._reset_last_screenshot() if self._running_session: logger.debug('abort_if_not_closed(): Aborting session...') try: self._agent_connector.stop_session(self._running_session, True, False) logger.info('--- Test aborted.') except EyesError as e: logger.info("Failed to abort server session: %s " % e) pass finally: self._running_session = None finally: logger.close()
def abort_if_not_closed(self): """ If a test is running, aborts it. Otherwise, does nothing. """ if self.is_disabled: logger.debug('abort_if_not_closed(): ignored (disabled)') return try: self._reset_last_screenshot() if self._running_session: logger.debug('abort_if_not_closed(): Aborting session...') try: self._agent_connector.stop_session(self._running_session, True, False) logger.info('--- Test aborted.') except EyesError as e: logger.info("Failed to abort server session: %s " % e) pass finally: self._running_session = None finally: logger.close()
def _assign_viewport_size(self): if self._viewport_size: logger.debug("Assigning viewport size {0}".format(self._viewport_size)) _viewport_size.set_viewport_size(self._driver, self._viewport_size) else: logger.debug("No viewport size given. Extracting the viewport size from the driver...") self._viewport_size = _viewport_size.get_viewport_size(self._driver) logger.debug("Viewport size {0}".format(self._viewport_size))
def driver(request, browser_config): test_name = request.node.name build_tag = os.environ.get('BUILD_TAG', None) tunnel_id = os.environ.get('TUNNEL_IDENTIFIER', None) username = os.environ.get('SAUCE_USERNAME', None) access_key = os.environ.get('SAUCE_ACCESS_KEY', None) selenium_url = os.environ.get('SELENIUM_SERVER_URL', 'http://127.0.0.1:4444/wd/hub') if 'ondemand.saucelabs.com' in selenium_url: selenium_url = "https://%s:%[email protected]:443/wd/hub" % ( username, access_key) logger.debug('SELENIUM_URL={}'.format(selenium_url)) desired_caps = browser_config.copy() desired_caps['build'] = build_tag desired_caps['tunnelIdentifier'] = tunnel_id desired_caps['name'] = test_name executor = RemoteConnection(selenium_url, resolve_ip=False) browser = webdriver.Remote(command_executor=executor, desired_capabilities=desired_caps) if browser is None: raise WebDriverException("Never created!") yield browser # report results try: browser.execute_script("sauce:job-result=%s" % str(not request.node.rep_call.failed).lower()) except WebDriverException: # we can ignore the exceptions of WebDriverException type -> We're done with tests. logger.info( 'Warning: The driver failed to quit properly. Check test and server side logs.' ) finally: browser.quit()
def set_overflow(self, overflow, stabilization_time=None): """ Sets the overflow of the current context's document element. :param overflow: The overflow value to set. If the given value is None, then overflow will be set to undefined. :param stabilization_time: The time to wait for the page to stabilize after overflow is set. If the value is None, then no waiting will take place. (Milliseconds) :return: The previous overflow value. """ logger.debug("Setting overflow: %s" % overflow) if overflow is None: script = "var origOverflow = document.documentElement.style.overflow; " \ "document.documentElement.style.overflow = undefined; " \ "return origOverflow;" else: script = "var origOverflow = document.documentElement.style.overflow; " \ "document.documentElement.style.overflow = \"{0}\"; " \ "return origOverflow;".format(overflow) # noinspection PyUnresolvedReferences original_overflow = self.execute_script(script) logger.debug("Original overflow: %s" % original_overflow) if stabilization_time is not None: time.sleep(stabilization_time / 1000) return original_overflow
def stop_session(self, running_session, is_aborted, save): """ Stops a running session in the Eyes server. Args: running_session (RunningSession): The session to stop. is_aborted (boolean): Whether the server should mark this session as aborted. save (boolean): Whether the session should be automatically saved if it is not aborted. Returns: TestResults: Test results of the stopped session. Raises: see :mod:'requests' """ logger.debug('Stop session called..') session_uri = "%s/%d" % (self._endpoint_uri, running_session['session_id']) params = {'aborted': is_aborted, 'updateBaseline': save, 'apiKey': self.api_key} response = AgentConnector._send_long_request("stop_session", requests.delete, session_uri, params=params, verify=False, headers=AgentConnector._DEFAULT_HEADERS, timeout=AgentConnector._TIMEOUT) pr = _parse_response_with_json_data(response) return TestResults(pr['steps'], pr['matches'], pr['mismatches'], pr['missing'], pr['exactMatches'], pr['strictMatches'], pr['contentMatches'], pr['layoutMatches'], pr['noneMatches'])
def _run(self, prepare_action, run_once_after_wait=False, retry_timeout=-1): if retry_timeout < 0: retry_timeout = self._default_retry_timeout logger.debug("Matching timeout set to: {0}".format(retry_timeout)) start = time.time() if run_once_after_wait or retry_timeout == 0: logger.debug("Matching once...") # If the load time is 0, the sleep would immediately return anyway. time.sleep(retry_timeout) data = prepare_action() as_expected = self._agent_connector.match_window(self._running_session, data) result = {"as_expected": as_expected, "screenshot": self._screenshot} else: result = self._run_with_intervals(prepare_action, retry_timeout) logger.debug("Match result: {0}".format(result["as_expected"])) elapsed_time = time.time() - start logger.debug("_run(): Completed in %.2f seconds" % elapsed_time) return result
def quadrant_rotate(self, num_quadrants): """ Rotates the image by 90 degrees clockwise or counter-clockwise. :param int num_quadrants: The number of rotations to perform. """ # Divide the continuous sequence of bytes in each row into pixel groups (since values within a single pixel # should maintain order). logger.debug('Dividing into chunks...') pixels = list(map(lambda bytes_row: general_utils.divide_to_chunks(bytes_row, self.pixel_size), self.pixel_bytes)) logger.debug('Done! Rotating pixels...') rotated_pixels = quadrant_rotate(pixels, num_quadrants) logger.debug('Done! flattening chunks back to bytes...') # Unite the pixel groups back to continuous pixel bytes for each row. rotated_pixel_bytes = list(map(lambda pixels_row: general_utils.join_chunks(pixels_row), rotated_pixels)) logger.debug('Done!') self.pixel_bytes = rotated_pixel_bytes self._update_size(len(rotated_pixel_bytes[0]) / self.pixel_size, len(rotated_pixel_bytes))
def _run(self, prepare_action, run_once_after_wait=False, retry_timeout=-1): if 0 < retry_timeout < MatchWindowTask.MINIMUM_MATCH_TIMEOUT: raise ValueError("Match timeout must be at least 60ms, got {} instead.".format(retry_timeout)) if retry_timeout < 0: retry_timeout = self._default_retry_timeout else: retry_timeout /= 1000.0 logger.debug("Match timeout set to: {0} seconds".format(retry_timeout)) start = time.time() if run_once_after_wait or retry_timeout == 0: logger.debug("Matching once...") # If the load time is 0, the sleep would immediately return anyway. time.sleep(retry_timeout) data = prepare_action() as_expected = self._agent_connector.match_window(self._running_session, data) result = {"as_expected": as_expected, "screenshot": self._screenshot} else: result = self._run_with_intervals(prepare_action, retry_timeout) logger.debug("Match result: {0}".format(result["as_expected"])) elapsed_time = time.time() - start logger.debug("_run(): Completed in {0:.1f} seconds".format(elapsed_time)) return result
def _assign_viewport_size(self): # When setting the viewport size we need to be in the default content frame original_frame_chain = self._driver.get_frame_chain() self._driver.switch_to.default_content() try: if self._viewport_size: logger.debug("Assigning viewport size {0}".format(self._viewport_size)) _viewport_size.set_viewport_size(self._driver, self._viewport_size) else: logger.debug("No viewport size given. Extracting the viewport size from the driver...") self._viewport_size = _viewport_size.get_viewport_size(self._driver) logger.debug("Viewport size {0}".format(self._viewport_size)) except EyesError as e: # Going back to the frame we started at self._driver.switch_to.frames(original_frame_chain) raise TestFailedError('Failed to assign viewport size!')
def _assign_viewport_size(self): # When setting the viewport size we need to be in the default content frame original_frame_chain = self._driver.get_frame_chain() self._driver.switch_to.default_content() try: if self._viewport_size: logger.debug("Assigning viewport size {0}".format(self._viewport_size)) _viewport_size.set_viewport_size(self._driver, self._viewport_size) else: logger.debug("No viewport size given. Extracting the viewport size from the driver...") self._viewport_size = _viewport_size.get_viewport_size(self._driver) logger.debug("Viewport size {0}".format(self._viewport_size)) except EyesError as e: # Going back to the frame we started at self._driver.switch_to.frames(original_frame_chain) raise TestFailedError('Failed to assign viewport size!')
def get_screenshot_as_base64(self): """ Gets the screenshot of the current window as a base64 encoded string which is useful in embedded images in HTML. """ screenshot64 = self.driver.get_screenshot_as_base64() display_rotation = self.get_display_rotation() if display_rotation != 0: logger.info('Rotation required.') num_quadrants = int(-(display_rotation / 90)) logger.debug('decoding base64...') screenshot_bytes = base64.b64decode(screenshot64) logger.debug('Done! Creating image object...') screenshot = _image_utils.png_image_from_bytes(screenshot_bytes) logger.debug('Done! Rotating...') screenshot.quadrant_rotate(num_quadrants) screenshot64 = screenshot.get_base64() return screenshot64
def get_screenshot_as_base64(self): """ Gets the screenshot of the current window as a base64 encoded string which is useful in embedded images in HTML. :Usage: driver.get_screenshot_as_base64() """ screenshot64 = self.driver.get_screenshot_as_base64() display_rotation = self.get_display_rotation() if display_rotation != 0: logger.info('Rotation required.') num_quadrants = int(-(display_rotation / 90)) logger.debug('decoding base64...') screenshot_bytes = base64.b64decode(screenshot64) logger.debug('Done! Creating image object...') screenshot = _image_utils.png_image_from_bytes(screenshot_bytes) logger.debug('Done! Rotating...') screenshot.quadrant_rotate(num_quadrants) screenshot64 = screenshot.get_base64() return screenshot64
def get_full_page_screenshot(self): logger.info('getting full page screenshot..') # Saving the current frame reference and moving to the outermost frame. original_frame = self.get_frame_chain() original_scroll_position = self.get_current_scroll_position() self.switch_to.default_content() self.scroll_to(Point(0, 0)) current_scroll_position = self.get_current_scroll_position() if current_scroll_position.x != 0 or current_scroll_position.y != 0: raise EyesError('Couldn\'t scroll to the top/left part of the screen!') entire_page_size = self.get_entire_page_size() # Starting with the screenshot at 0,0 part64 = self.get_screenshot_as_base64() screenshot = _image_utils.png_image_from_bytes(base64.b64decode(part64)) # IMPORTANT This is required! Since when calculating the screenshot parts for full size, # we use a screenshot size which is a bit smaller (see comment below). if (screenshot.width >= entire_page_size['width']) and \ (screenshot.height >= entire_page_size['height']): self.switch_to.frames(original_frame) self.scroll_to(original_scroll_position) return screenshot # We use a smaller size than the actual screenshot size in order to eliminate duplication # of bottom scroll bars, as well as footer-like elements with fixed position. screenshot_part_size = {'width': screenshot.width, 'height': max(screenshot.height - self._MAX_SCROLL_BAR_SIZE, self._MIN_SCREENSHOT_PART_HEIGHT)} logger.debug("Total size: {0}, Screenshot part size: {1}".format(entire_page_size, screenshot_part_size)) entire_page = Region(0, 0, entire_page_size['width'], entire_page_size['height']) screenshot_parts = entire_page.get_sub_regions(screenshot_part_size) # Starting with the screenshot we already captured at (0,0). stitched_image = screenshot for part in screenshot_parts: # Since we already took the screenshot for 0,0 if part.left == 0 and part.top == 0: logger.debug('Skipping screenshot for 0,0 (already taken)') continue logger.debug("Taking screenshot for {0}".format(part)) # Scroll to the part's top/left and give it time to stabilize. self.scroll_to(Point(part.left, part.top)) time.sleep(0.1) # Since screen size might cause the scroll to reach only part of the way current_scroll_position = self.get_current_scroll_position() logger.debug("Scrolled To ({0},{1})".format(current_scroll_position.x, current_scroll_position.y)) part64 = self.get_screenshot_as_base64() part_image = _image_utils.png_image_from_bytes(base64.b64decode(part64)) stitched_image.paste(current_scroll_position.x, current_scroll_position.y, part_image.pixel_bytes) self.switch_to.frames(original_frame) self.scroll_to(original_scroll_position) return stitched_image
def _run_with_intervals(self, prepare_action, retry_timeout): """ Includes retries in case the screenshot does not match. """ logger.debug('Matching with intervals...') # We intentionally take the first screenshot before starting the timer, to allow the page # just a tad more time to stabilize. data = prepare_action(ignore_mismatch=True) # Start the timer. start = time.time() logger.debug('First match attempt...') as_expected = self._agent_connector.match_window(self._running_session, data) if as_expected: return {"as_expected": True, "screenshot": self._screenshot} retry = time.time() - start logger.debug("Failed. Elapsed time: {0:.1f} seconds".format(retry)) while retry < retry_timeout: logger.debug('Matching...') time.sleep(self._MATCH_INTERVAL) data = prepare_action(ignore_mismatch=True) as_expected = self._agent_connector.match_window(self._running_session, data) if as_expected: return {"as_expected": True, "screenshot": self._screenshot} retry = time.time() - start logger.debug("Elapsed time: {0:.1f} seconds".format(retry)) # One last try logger.debug('One last matching attempt...') data = prepare_action() as_expected = self._agent_connector.match_window(self._running_session, data) return {"as_expected": as_expected, "screenshot": self._screenshot}
def set_viewport_size(driver, required_size): if 'width' not in required_size or 'height' not in required_size: raise EyesError('Size must have width & height keys!') logger.debug("set_viewport_size({0})".format(required_size)) starting_size = required_size # When setting the viewport size we need to be in the default content frame original_frame_chain = driver.get_frame_chain() driver.switch_to.default_content() driver.set_window_size(required_size['width'], required_size['height']) if not _verify_size(driver.get_window_size, starting_size): error_message = "Failed to set browser size!" logger.info(error_message) # Going back to the frame we started at driver.switch_to.frames(original_frame_chain) raise TestFailedError(error_message) current_viewport_size = get_viewport_size(driver) logger.debug("set_viewport_size(): initial viewport size: {0}".format(current_viewport_size)) current_browser_size = driver.get_window_size() width_to_set = (2 * current_browser_size['width']) - current_viewport_size['width'] height_to_set = (2 * current_browser_size['height']) - current_viewport_size['height'] driver.set_window_size(width_to_set, height_to_set) if not _verify_size(lambda: get_viewport_size(driver), required_size): current_viewport_size = get_viewport_size(driver) logger.debug("set_viewport_size(): viewport size: {0}".format(current_viewport_size)) logger.debug("set_viewport_size(): attempting one more time...") current_browser_size = driver.get_window_size() updated_width = current_browser_size['width'] + (required_size['width'] - current_viewport_size['width']) updated_height = current_browser_size['height'] + (required_size['height'] - current_viewport_size['height']) updated_browser_size = {'width': updated_width, 'height': updated_height} logger.debug("set_viewport_size(): browser size: {0}".format(current_browser_size)) logger.debug("set_viewport_size(): required browser size: {0}".format(updated_browser_size)) driver.set_window_size(updated_width, updated_height) if not _verify_size(lambda: get_viewport_size(driver), required_size): current_viewport_size = get_viewport_size(driver) logger.debug("set_viewport_size(): viewport size: {0}".format(current_viewport_size)) error_message = "Failed to set viewport size." logger.info(error_message) # Going back to the frame we started at driver.switch_to.frames(original_frame_chain) raise TestFailedError(error_message) # Going back to the frame we started at driver.switch_to.frames(original_frame_chain)
def get_full_page_screenshot(self): logger.info('getting full page screenshot..') # Saving the current frame reference and moving to the outermost frame. original_frame = self.get_frame_chain() self.switch_to.default_content() self.reset_origin() entire_page_size = self.get_entire_page_size() # Starting with the screenshot at 0,0 part64 = self.get_screenshot_as_base64() screenshot = _image_utils.png_image_from_bytes( base64.b64decode(part64)) # IMPORTANT This is required! Since when calculating the screenshot parts for full size, # we use a screenshot size which is a bit smaller (see comment below). if (screenshot.width >= entire_page_size['width']) and \ (screenshot.height >= entire_page_size['height']): self.restore_origin() self.switch_to.frames(original_frame) return screenshot # We use a smaller size than the actual screenshot size in order to eliminate duplication # of bottom scroll bars, as well as footer-like elements with fixed position. screenshot_part_size = { 'width': screenshot.width, 'height': max(screenshot.height - self._MAX_SCROLL_BAR_SIZE, self._MIN_SCREENSHOT_PART_HEIGHT) } logger.debug("Total size: {0}, Screenshot part size: {1}".format( entire_page_size, screenshot_part_size)) entire_page = Region(0, 0, entire_page_size['width'], entire_page_size['height']) screenshot_parts = entire_page.get_sub_regions(screenshot_part_size) # Starting with the screenshot we already captured at (0,0). stitched_image = screenshot self.save_position() for part in screenshot_parts: # Since we already took the screenshot for 0,0 if part.left == 0 and part.top == 0: logger.debug('Skipping screenshot for 0,0 (already taken)') continue logger.debug("Taking screenshot for {0}".format(part)) # Scroll to the part's top/left and give it time to stabilize. self.scroll_to(Point(part.left, part.top)) time.sleep(0.1) # Since screen size might cause the scroll to reach only part of the way current_scroll_position = self.get_current_position() logger.debug("Scrolled To ({0},{1})".format( current_scroll_position.x, current_scroll_position.y)) part64 = self.get_screenshot_as_base64() part_image = _image_utils.png_image_from_bytes( base64.b64decode(part64)) stitched_image.paste(current_scroll_position.x, current_scroll_position.y, part_image.pixel_bytes) self.restore_position() self.restore_origin() self.switch_to.frames(original_frame) return stitched_image
def set_viewport_size(driver, required_size): _BROWSER_SIZE_CALCULATION_RETRIES = 2 _BROWSER_SET_SIZE_RETRIES = 3 _BROWSER_STABILIZATION_WAIT = 1 # Seconds logger.debug("set_viewport_size({0})".format(required_size)) if 'width' not in required_size or 'height' not in required_size: raise EyesError('Size must have width & height keys!') actual_viewport_size = get_viewport_size(driver) logger.debug("Current viewport size: {}".format(actual_viewport_size)) if actual_viewport_size == required_size: return # If the browser was initially maximized, we might need to repeat the process (border size for maximized browser is # sometimes different than non-maximized). for _ in range(_BROWSER_SIZE_CALCULATION_RETRIES): # We move the window to (0,0) to have the best chance to be able to set the viewport size as requested. driver.set_window_position(0, 0) browser_size = driver.get_window_size() logger.debug("Current browser size: {}".format(browser_size)) required_browser_size = { 'width': browser_size['width'] + (required_size['width'] - actual_viewport_size['width']), 'height': browser_size['height'] + (required_size['height'] - actual_viewport_size['height']) } logger.debug( "Trying to set browser size to: {}".format(required_browser_size)) for retry in range(_BROWSER_SET_SIZE_RETRIES): driver.set_window_size(required_browser_size['width'], required_browser_size['height']) time.sleep(_BROWSER_STABILIZATION_WAIT) browser_size = driver.get_window_size() if (browser_size['width'] == required_browser_size['width'] and browser_size['height'] == required_browser_size['height']): break logger.debug("Current browser size: {}".format(browser_size)) else: raise EyesError('Failed to set browser size!') actual_viewport_size = get_viewport_size(driver) logger.debug("Current viewport size: {}".format(actual_viewport_size)) if actual_viewport_size == required_size: return else: raise EyesError('Failed to set the viewport size.')
def _run_with_intervals(self, prepare_action, retry_timeout): """ Includes retries in case the screenshot does not match. """ logger.debug('Matching with intervals...') # We intentionally take the first screenshot before starting the timer, to allow the page # just a tad more time to stabilize. data = prepare_action(ignore_mismatch=True) # Start the timer. start = time.time() logger.debug('First match attempt...') as_expected = self._agent_connector.match_window(self._running_session, data) if as_expected: return {"as_expected": True, "screenshot": self._screenshot} retry = time.time() - start logger.debug("Failed. Elapsed time: {0}".format(retry)) while retry < retry_timeout: logger.debug('Matching...') time.sleep(self._MATCH_INTERVAL) data = prepare_action(ignore_mismatch=True) as_expected = self._agent_connector.match_window(self._running_session, data) if as_expected: return {"as_expected": True, "screenshot": self._screenshot} retry = time.time() - start logger.debug("Elapsed time: {0}".format(retry)) # One last try logger.debug('One last matching attempt...') data = prepare_action() as_expected = self._agent_connector.match_window(self._running_session, data) return {"as_expected": as_expected, "screenshot": self._screenshot}
def set_viewport_size(driver, required_size): _BROWSER_SIZE_CALCULATION_RETRIES = 2 _BROWSER_SET_SIZE_RETRIES = 3 _BROWSER_STABILIZATION_WAIT = 1 # Seconds logger.debug("set_viewport_size({0})".format(required_size)) if 'width' not in required_size or 'height' not in required_size: raise EyesError('Size must have width & height keys!') actual_viewport_size = get_viewport_size(driver) logger.debug("Current viewport size: {}".format(actual_viewport_size)) if actual_viewport_size == required_size: return # If the browser was initially maximized, we might need to repeat the process (border size for maximized browser is # sometimes different than non-maximized). for _ in range(_BROWSER_SIZE_CALCULATION_RETRIES): # We move the window to (0,0) to have the best chance to be able to set the viewport size as requested. driver.set_window_position(0, 0) browser_size = driver.get_window_size() logger.debug("Current browser size: {}".format(browser_size)) required_browser_size = { 'width': browser_size['width'] + (required_size['width'] - actual_viewport_size['width']), 'height': browser_size['height'] + (required_size['height'] - actual_viewport_size['height']) } logger.debug("Trying to set browser size to: {}".format(required_browser_size)) for retry in range(_BROWSER_SET_SIZE_RETRIES): driver.set_window_size(required_browser_size['width'], required_browser_size['height']) time.sleep(_BROWSER_STABILIZATION_WAIT) browser_size = driver.get_window_size() if browser_size == required_browser_size: break logger.debug("Current browser size: {}".format(browser_size)) else: raise EyesError('Failed to set browser size!') actual_viewport_size = get_viewport_size(driver) logger.debug("Current viewport size: {}".format(actual_viewport_size)) if actual_viewport_size == required_size: return else: raise EyesError('Failed to set the viewport size.')