示例#1
0
 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')
示例#2
0
    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))
示例#5
0
 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
示例#6
0
    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
示例#7
0
    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
示例#8
0
 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)
示例#9
0
 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')
示例#10
0
 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')
示例#12
0
 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']
示例#13
0
    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)
示例#14
0
 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()
示例#15
0
    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)
示例#17
0
 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)
示例#18
0
 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
示例#19
0
 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)
示例#20
0
    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)
示例#21
0
    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)
示例#22
0
    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
示例#24
0
 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
示例#25
0
    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()
示例#26
0
    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()
示例#27
0
 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)
示例#28
0
    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)
示例#29
0
    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
示例#30
0
 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)
示例#31
0
 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))
示例#33
0
    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
示例#34
0
    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()
示例#35
0
    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()
示例#36
0
 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
示例#43
0
 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!')
示例#44
0
 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!')
示例#45
0
 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
示例#48
0
 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)
示例#50
0
    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
示例#51
0
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.')