def _make_url(base_url, value): # type: (Text, Text) -> Text if is_absolute_url(value) and not is_url_with_scheme(value): # noqa url = urljoin("http://", value) else: url = urljoin(base_url, value) return url
def request( self, method, url_resource, use_api_key=True, request_id=None, **kwargs ): # type: (Text, Text, bool, Optional[UUID], **Any) -> Response req_id = str(uuid.uuid4() if request_id is None else request_id) if url_resource is not None: # makes URL relative url_resource = url_resource.lstrip("/") url_resource = urljoin(self.server_url.rstrip("/"), url_resource) params = {} if use_api_key: params["apiKey"] = self.api_key params.update(kwargs.get("params", {})) headers = self.headers.copy() headers.update(kwargs.get("headers", {})) headers["x-applitools-eyes-client-request-id"] = req_id timeout_sec = kwargs.get("timeout", None) if timeout_sec is None: timeout_sec = datetime_utils.to_sec(self.timeout_ms) response = self.client_session.request( method, url_resource, data=kwargs.get("data", None), verify=False, params=params, headers=headers, timeout=timeout_sec, ) try: response.raise_for_status() except requests.HTTPError as e: logger.exception(e) logger.error("Error response content is: {}".format(response.text)) return response
def request(self, method, url_resource, use_api_key=True, **kwargs): # type: (Callable, Text, bool, **Any) -> Response if url_resource is not None: # makes URL relative url_resource = url_resource.lstrip("/") url_resource = urljoin(self.server_url, url_resource) params = {} if use_api_key: params["apiKey"] = self.api_key params.update(kwargs.get("params", {})) headers = kwargs.get("headers", self.headers).copy() timeout_sec = kwargs.get("timeout", None) if timeout_sec is None: timeout_sec = datetime_utils.to_sec(self.timeout_ms) response = method( url_resource, data=kwargs.get("data", None), verify=False, params=params, headers=headers, timeout=timeout_sec, ) try: response.raise_for_status() except requests.HTTPError as e: logger.exception(e) return response
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 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 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
def send_result_report(test_name, passed, parameters=None, group="selenium"): report_data = copy(REPORT_DATA) report_data["results"] = [prepare_result_data(test_name, passed, parameters)] report_data["group"] = group r = requests.post(urljoin(REPORT_BASE_URL, "/result"), data=json.dumps(report_data)) print("Result report send: {} - {}".format(r.status_code, r.text)) return r
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 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 _apply_base_url(discovered_url, base_url, resource_url=None): url = urlparse(discovered_url) if url.scheme in ["http", "https"] and url.netloc: return discovered_url if resource_url and urlparse(resource_url).netloc != urlparse( base_url).netloc: base_url = resource_url return urljoin(base_url, discovered_url)
def close(self): if self.api_key is None: print("WARNING: BatchClose wont be done cause no APPLITOOLS_API_KEY is set") return if str2bool(get_env_with_prefix("APPLITOOLS_DONT_CLOSE_BATCHES")): print("APPLITOOLS_DONT_CLOSE_BATCHES environment variable set to true.") return for batch_id in self._ids: print("close batch called with {}".format(batch_id)) url = urljoin( self.server_url.rstrip("/"), "api/sessions/batches/{}/close/bypointerid".format( quote_plus(batch_id) ), ) res = requests.delete(url, params={"apiKey": self.api_key}, verify=False) print("delete batch is done with {} status".format(res.status_code))
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 ) )
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)
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 ) )
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 ) )
def _ufg_request(self, method, url_resource, **kwargs): headers = ServerConnector.DEFAULT_HEADERS.copy() headers["Content-Type"] = "application/json" headers["X-Auth-Token"] = self._render_info.access_token full_url = urljoin(self._render_info.service_url, url_resource) return self._com.request(method, full_url, headers=headers, **kwargs)
def _apply_base_url(discovered_url, base_url): url = urlparse(discovered_url) if url.scheme in ["http", "https"] and url.netloc: return discovered_url return urljoin(base_url, discovered_url)