Esempio n. 1
0
 def _long_request_check_status(self, response, request_id):
     if (
         response.status_code == requests.codes.ok
         or "Location" not in response.headers
     ):
         # request ends successful or it doesn't support Long request
         return response
     elif response.status_code == requests.codes.accepted:
         # long request here; calling received url to know that request was processed
         url = response.headers["Location"]
         response = self._long_request_loop(url, request_id)
         return self._long_request_check_status(response, request_id)
     elif response.status_code == requests.codes.created:
         # delete url that was used before
         url = response.headers["Location"]
         return self.request(
             "delete",
             url,
             request_id=request_id,
             headers={"Eyes-Date": datetime_utils.current_time_in_rfc1123()},
         )
     elif response.status_code == requests.codes.gone:
         raise EyesError("The server task has gone.")
     else:
         raise EyesError("Unknown error during long request: {}".format(response))
    def render_status_by_id(self, *render_ids):
        # type: (*Text) -> List[RenderStatusResults]
        argument_guard.not_none(render_ids)
        if self._render_info is None:
            raise EyesError("render_info must be fetched first")

        headers = ServerConnector.DEFAULT_HEADERS.copy()
        headers["Content-Type"] = "application/json"
        headers["X-Auth-Token"] = self._render_info.access_token
        url = urljoin(self._render_info.service_url, self.RENDER_STATUS)
        response = self._com.request(
            requests.post,
            url,
            use_api_key=False,
            headers=headers,
            data=json.dumps(render_ids),
        )
        if not response.ok:
            raise EyesError(
                "Error getting server status, {} {}".format(
                    response.status_code, response.content
                )
            )
        # TODO: improve parser to handle similar names
        return json_utils.attr_from_response(response, RenderStatusResults)
    def render_put_resource(self, running_render, resource):
        # type: (RunningRender, VGResource) -> Text
        argument_guard.not_none(running_render)
        argument_guard.not_none(resource)
        if self._render_info is None:
            raise EyesError("render_info must be fetched first")

        content = resource.content
        argument_guard.not_none(content)
        logger.debug("resource hash: {} url: {} render id: {}"
                     "".format(resource.hash, resource.url,
                               running_render.render_id))
        headers = ServerConnector.DEFAULT_HEADERS.copy()
        headers["Content-Type"] = resource.content_type
        headers["X-Auth-Token"] = self._render_info.access_token

        url = urljoin(self._render_info.service_url,
                      self.RESOURCES_SHA_256 + resource.hash)
        response = self._com.request(
            requests.put,
            url,
            use_api_key=False,
            headers=headers,
            data=content,
            params={"render-id": running_render.render_id},
        )
        logger.debug("ServerConnector.put_resource - request succeeded")
        if not response.ok:
            raise EyesError("Error putting resource: {}, {}".format(
                response.status_code, response.content))
        return resource.hash
    def stop_session(self, running_session, is_aborted, save):
        # type: (RunningSession, bool, bool) -> TestResults
        """
        Stops a running session in the Eyes server.

        :param running_session: The session to stop.
        :param is_aborted: Whether the server should mark this session as aborted.
        :param save: Whether the session should be automatically saved if it is not aborted.
        :return: Test results of the stopped session.
        """
        logger.debug("stop_session called.")

        if not self.is_session_started:
            raise EyesError("Session not started")

        params = {"aborted": is_aborted, "updateBaseline": save}
        response = self._com.long_request(
            requests.delete,
            url_resource=urljoin(self.API_SESSIONS_RUNNING, running_session.id),
            params=params,
            headers=ServerConnector.DEFAULT_HEADERS,
        )

        test_results = json_utils.attr_from_response(response, TestResults)
        logger.debug("stop_session(): parsed response: {}".format(test_results))

        # mark that session isn't started
        self._is_session_started = False
        return test_results
    def match_window(self, running_session, match_data):
        # type: (RunningSession, MatchWindowData) -> MatchResult
        """
        Matches the current window to the immediate expected window in the Eyes server.
        Notice that a window might be matched later at the end of the test, even if it
        was not immediately matched in this call.

        :param running_session: The current session that is running.
        :param match_data: The data for the requests.post.
        :return: The parsed response.
        """
        logger.debug("match_window called. {}".format(running_session))

        # logger.debug("Data length: %d, data: %s" % (len(data), repr(data)))
        if not self.is_session_started:
            raise EyesError("Session not started")

        data = prepare_match_data(match_data)
        # Using the default headers, but modifying the "content type" to binary
        headers = ServerConnector.DEFAULT_HEADERS.copy()
        headers["Content-Type"] = "application/octet-stream"
        # TODO: allow to send images as base64
        response = self._com.long_request(
            requests.post,
            url_resource=urljoin(self.API_SESSIONS_RUNNING, running_session.id),
            data=data,
            headers=headers,
        )
        match_result = json_utils.attr_from_response(response, MatchResult)
        return match_result
    def post_dom_snapshot(self, dom_json):
        # type: (Text) -> Optional[Text]
        """
        Upload the DOM of the tested page.
        Return an URL of uploaded resource which should be posted to :py:   `AppOutput`.
        """
        logger.debug("post_dom_snapshot called.")

        if not self.is_session_started:
            raise EyesError("Session not started")

        headers = ServerConnector.DEFAULT_HEADERS.copy()
        headers["Content-Type"] = "application/octet-stream"
        dom_bytes = gzip_compress(dom_json.encode("utf-8"))

        response = self._com.request(
            requests.post,
            url_resource=urljoin(self.API_SESSIONS_RUNNING, "data"),
            data=dom_bytes,
            headers=headers,
        )
        dom_url = None
        if response.ok:
            dom_url = response.headers["Location"]
        return dom_url
Esempio n. 7
0
    def _open_base(self):
        if self.configure.is_disabled:
            logger.debug("open_base(): ignored (disabled)")
            return
        logger.open_()
        self._log_open_base()

        retry = 0
        while retry < self._MAX_ITERATIONS:
            try:
                self._validate_session_open()
                self._init_providers()

                self._is_viewport_size_set = False

                self._before_open()
                try:
                    if self.configure.viewport_size:
                        self._ensure_running_session()
                except Exception as e:
                    logger.exception(e)
                    retry += 1
                    continue

                self._is_opened = True
                self._after_open()
                return None
            except EyesError as e:
                logger.exception(e)
                logger.close()
                raise e

        raise EyesError("eyes.open_base() failed")
Esempio n. 8
0
    def _upload_data(
        self, data_bytes, rendering_info, target_url, content_type, media_type
    ):
        # type: (bytes, RenderingInfo, Text, Text, Text) -> bool
        headers = ServerConnector.DEFAULT_HEADERS.copy()
        headers["Content-Type"] = content_type
        headers["Content-Length"] = str(len(data_bytes))
        headers["Media-Type"] = media_type
        headers["X-Auth-Token"] = rendering_info.access_token
        headers["x-ms-blob-type"] = "BlockBlob"

        timeout_sec = datetime_utils.to_sec(self._com.timeout_ms)
        response = self.client_session.request(
            "put",
            target_url,
            data=data_bytes,
            headers=headers,
            timeout=timeout_sec,
            verify=False,
        )
        if response.status_code in [requests.codes.ok, requests.codes.created]:
            logger.debug("Upload Status Code: {}".format(response.status_code))
            return True
        raise EyesError(
            "Failed to Upload. Status Code: {}".format(response.status_code)
        )
 def update_config(self, conf):
     if conf.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.")
     self._com.server_url = conf.server_url
     self._com.api_key = conf.api_key
     self._com.timeout_ms = conf._timeout
Esempio n. 10
0
def get_image_part(image, region):
    # type: (Image.Image, Region) -> Image.Image
    """
    Get a copy of the part of the image given by region.

    :return: The part of the image.
    """
    if region.is_empty:
        raise EyesError("region is empty!")
    return image.crop(box=(region.left, region.top, region.right, region.bottom))
    def match_window(self, running_session, match_data):
        # type: (RunningSession, MatchWindowData) -> MatchResult
        """
        Matches the current window to the immediate expected window in the Eyes server.
        Notice that a window might be matched later at the end of the test, even if it
        was not immediately matched in this call.

        :param running_session: The current session that is running.
        :param match_data: The data for the requests.post.
        :return: The parsed response.
        """
        logger.debug("match_window called. {}".format(running_session))

        # logger.debug("Data length: %d, data: %s" % (len(data), repr(data)))
        if not self.is_session_started:
            raise EyesError("Session not started")
        app_output = match_data.app_output
        # when screenshot_url is present we don't need to upload again
        if app_output.screenshot_url is None and app_output.screenshot_bytes:
            app_output.screenshot_url = self.try_upload_image(
                match_data.app_output.screenshot_bytes)

        if app_output.screenshot_url is None:
            raise EyesError(
                "MatchWindow failed: could not upload image to storage service."
            )
        logger.info("Screenshot image URL: {}".format(
            app_output.screenshot_url))
        data = json_utils.to_json(match_data)
        headers = ServerConnector.DEFAULT_HEADERS.copy()
        response = self._com.long_request(
            "post",
            url_resource=urljoin(self.API_SESSIONS_RUNNING,
                                 running_session.id),
            data=data,
            headers=headers,
        )
        return json_utils.attr_from_response(response, MatchResult)
Esempio n. 12
0
    def post_dom_capture(self, dom_json):
        # type: (Text) -> Optional[Text]
        """
        Upload the DOM of the tested page.
        Return an URL of uploaded resource which should be posted to :py:   `AppOutput`.
        """
        logger.debug("post_dom_snapshot called.")

        if not self.is_session_started:
            raise EyesError("Session not started")

        dom_bytes = gzip_compress(dom_json.encode("utf-8"))
        return self._try_upload_data(dom_bytes, "application/octet-stream",
                                     "application/json")
Esempio n. 13
0
 def update_config(self, conf, full_agent_id, render_info=None, ua_string=None):
     if conf.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."
         )
     self._com.server_url = conf.server_url
     self._com.api_key = conf.api_key
     self._com.timeout_ms = conf._timeout
     if render_info:
         self._render_info = render_info
     if ua_string:
         self._ua_string = ua_string
     self.DEFAULT_HEADERS["x-applitools-eyes-client"] = full_agent_id
Esempio n. 14
0
 def render_info(self):
     # type: () -> Optional[RenderingInfo]
     logger.debug("render_info() called.")
     headers = ServerConnector.DEFAULT_HEADERS.copy()
     headers["Content-Type"] = "application/json"
     response = self._com.long_request("get", self.RENDER_INFO_PATH, headers=headers)
     if not response.ok:
         raise EyesError(
             "Cannot get render info: \n Status: {}, Content: {}".format(
                 response.status_code, response.content
             )
         )
     self._render_info = json_utils.attr_from_response(response, RenderingInfo)
     return self._render_info
Esempio n. 15
0
    def render(self, *render_requests):
        # type: (*RenderRequest) -> List[RunningRender]
        logger.debug("render called with {}".format(render_requests))
        if self._render_info is None:
            raise EyesError("render_info must be fetched first")

        url = urljoin(self._render_info.service_url, self.RENDER)

        headers = ServerConnector.DEFAULT_HEADERS.copy()
        headers["Content-Type"] = "application/json"
        headers["X-Auth-Token"] = self._render_info.access_token

        data = json_utils.to_json(render_requests)
        response = self._com.request(
            requests.post, url, use_api_key=False, headers=headers, data=data
        )
        if response.ok or response.status_code == requests.codes.not_found:
            return json_utils.attr_from_response(response, RunningRender)
        raise EyesError(
            "ServerConnector.render - unexpected status ({})\n\tcontent{}".format(
                response.status_code, response.content
            )
        )
Esempio n. 16
0
    def close(self, raise_ex=True):
        # type: (bool) -> Optional[TestResults]
        """
        Ends the test.

        :param raise_ex: If true, an exception will be raised for failed/new tests.
        :return: The test results.
        """
        if self.configure.is_disabled:
            logger.debug("close(): ignored (disabled)")
            return None
        try:
            logger.debug("close({})".format(raise_ex))
            if not self._is_opened:
                raise EyesError("Eyes not open")

            self._is_opened = False

            self._reset_last_screenshot()
            self._init_providers(hard_reset=True)

            # 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()

            is_new_session = self._running_session.is_new_session
            results_url = self._running_session.url

            logger.info("close(): Ending server session...")
            should_save = (is_new_session
                           and self.configure.save_new_tests) or (
                               (not is_new_session)
                               and self.configure.save_failed_tests)
            logger.debug("close(): automatically save session? %s" %
                         should_save)
            results = self._server_connector.stop_session(
                self._running_session, False, should_save)
            results.is_new = is_new_session
            results.url = results_url
            self.log_session_results_and_raise_exception(raise_ex, results)

            return results
        finally:
            self._running_session = None
            logger.close()
Esempio n. 17
0
    def open_base(
        self,
        app_name,  # type: Text
        test_name,  # type: Text
        viewport_size=None,  # type: Optional[ViewPort]
        session_type=SessionType.SEQUENTIAL,  # type: SessionType
    ):
        # type: (...) -> None
        """
        Starts a test.

        :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.
        :param session_type: The type of test (e.g., Progression for timing tests)
                              or Sequential by default.
        :return: An updated web driver
        :raise EyesError: If the session was already open.
        """
        logger.open_()
        if self.configuration.is_disabled:
            logger.debug("open_base(): ignored (disabled)")
            return

        if self._server_connector is None:
            raise EyesError("Server connector not set.")

        # If there's no default application name, one must be provided for the current test.
        if self.configuration.app_name is None:
            argument_guard.not_none(app_name)
            self.configuration.app_name = app_name

        argument_guard.not_none(test_name)
        self.configuration.test_name = test_name

        logger.info("\nAgent: {}\n".format(self.full_agent_id))
        logger.info(
            "open_base(%s, %s, %s, %s)"
            % (app_name, test_name, viewport_size, self.configuration.failure_reports)
        )
        self.configuration.session_type = session_type
        self.configuration.viewport_size = viewport_size

        self._open_base()
Esempio n. 18
0
 def __ensure_viewport_size(self):
     # type: () -> None
     """
     Assign the viewport size we need to be in the default content frame.
     """
     if self._is_viewport_size_set:
         return
     try:
         if self.configure.viewport_size is None:
             # TODO: ignore if viewport_size settled explicitly
             target_size = self._get_viewport_size()
             self.configure.viewport_size = target_size
         else:
             target_size = self.configure.viewport_size
             self._set_viewport_size(target_size)
         self._is_viewport_size_set = True
     except Exception as e:
         self._is_viewport_size_set = False
         raise_from(EyesError("Viewport has not been setup"), e)
Esempio n. 19
0
 def get_text_in_running_session_image(self, data):
     # type: (TextSettingsData) -> List[Text]
     logger.debug(
         "call",
         _class=self.__class__.__name__,
         _method="extract_text",
         text_region_data=data,
     )
     resp = self._com.long_request(
         "post",
         urljoin(self.API_SESSIONS_RUNNING, "images/text"),
         data=json_utils.to_json(data),
     )
     if resp.ok:
         return resp.json()
     raise EyesError(
         "ServerConnector.extract_text - unexpected status {}".format(
             resp.status_code
         )
     )
Esempio n. 20
0
 def get_text_regions_in_running_session_image(self, data):
     # type: (TextSettingsData) -> PATTERN_TEXT_REGIONS
     logger.debug(
         "call",
         _class=self.__class__.__name__,
         _method="extract_text_regions",
         text_region_data=data,
     )
     resp = self._com.long_request(
         "post",
         urljoin(self.API_SESSIONS_RUNNING, "images/textregions"),
         data=json_utils.to_json(data),
     )
     if resp.ok:
         return {
             pattern: json_utils.attr_from_dict(regions, TextRegion)
             for pattern, regions in iteritems(resp.json())
         }
     raise EyesError(
         "ServerConnector.extract_text_regions - unexpected status {}".format(
             resp.status_code
         )
     )
Esempio n. 21
0
 def limit_parallel_retries(self, method):
     deadline = time() + self._max_retry_time
     with self._condvar:
         while self._retrying:
             self._condvar.wait()
     try:
         return method()
     except EyesServiceUnavailableError:
         with self._condvar:
             while self._retrying:
                 self._condvar.wait()
             self._retrying = True
         try:
             while True:
                 try:
                     return method()
                 except EyesServiceUnavailableError:
                     if time() > deadline:
                         raise EyesError("Session opening timeout reached")
                     sleep(self._sleep_time)
         finally:
             with self._condvar:
                 self._retrying = False
                 self._condvar.notify_all()
Esempio n. 22
0
 def _validate_session_open(self):
     if self.is_open:
         self.abort()
         raise EyesError("A test is already running")
 def __setstate__(self, state):
     raise EyesError("Cannot create MouseTrigger instance from dict!")
Esempio n. 24
0
 def try_upload_image(self, data):
     # type: (bytes) -> Optional[Text]
     try:
         return self._try_upload_data(data, "image/png", "image/png")
     except EyesError as e:
         raise_from(EyesError("Failed to Upload Image"), e)