def collect_test_results(tests, should_raise_exception): # type: (Dict[RunningTest, TestResults], bool) -> List[TestResultContainer] all_results = [] for test, test_result in iteritems(tests): if test.pending_exceptions: logger.error( "During test execution above exception raised. \n {:s}".join( str(e) for e in test.pending_exceptions)) if test.has_checks: exception = None else: exception = TestFailedError("Test has no checks") if test_result: scenario_id_or_name = test_result.name app_id_or_name = test_result.app_name if test_result.is_unresolved and not test_result.is_new: exception = DiffsFoundError(test_result, scenario_id_or_name, app_id_or_name) if test_result.is_new: exception = NewTestError(test_result, scenario_id_or_name, app_id_or_name) if test_result.is_failed: exception = TestFailedError(test_result, scenario_id_or_name, app_id_or_name) else: exception = TestFailedError("Test haven't finished correctly") all_results.append( TestResultContainer(test_result, test.browser_info, exception)) if exception and should_raise_exception: raise exception return all_results
def __call__(self): # type: () -> Optional[TestResults] logger.debug("%s called %s" % (self.__class__.__name__, self.name)) res = None try: if callable(self.func_to_run): logger.debug("VGTask().func_to_run: {}".format( self.func_to_run.__name__)) res = self.func_to_run() if callable(self.callback): logger.debug("VGTask().callback: {}".format( self.callback.__name__)) self.callback(res) except Exception as e: logger.error("Failed to execute task! \n\t %s" % self.name) logger.exception(e) if callable(self.error_callback): logger.debug("VGTask().error_callback: {}".format( self.error_callback.__name__)) self.error_callback(e) finally: if callable(self.complete_callback): logger.debug("VGTask().complete_callback: {}".format( self.complete_callback.__name__)) self.complete_callback() return res
def obj_came(obj): def cleaned_params(params, fields): return { key: val for key, val in iteritems(params) if key in fields } params = make_snake(dict(obj)) convidenced = defaultdict(int) for kls, fields in iteritems(klasses): fields = tuple(fields) if len(klasses) == 1: return kls(**cleaned_params(params, fields)) if set(params.keys()) == set(fields): return kls(**cleaned_params(params, fields)) for key in params.keys(): if key in fields: convidenced[(kls, fields)] += 1 try: kls, fields = sorted(convidenced, reverse=True)[0] return kls(**cleaned_params(params, fields)) except IndexError: logger.error("Failed to convert: {} to any class".format(obj)) return params
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 get_urls_from_css_resource(bytes_text): # type: (bytes) -> List[Text] def is_import_node(n): return n.type == "at-rule" and n.lower_at_keyword == "import" def is_font_node(n): return n.type == "at-rule" and n.lower_at_keyword == "font-face" try: rules, encoding = tinycss2.parse_stylesheet_bytes(css_bytes=bytes_text, skip_comments=True, skip_whitespace=True) except Exception: logger.error("Failed to read CSS string") return [] urls = [] for rule in rules: tags = rule.content if is_import_node(rule): logger.debug("The node has @import") tags = rule.prelude if is_font_node(rule): logger.debug("The node has @font-face") tags = rule.content if tags: urls.extend(list(_url_from_tags(tags))) return urls
def perform(self): # noqa # type: () -> List[RenderStatusResults] def get_and_put_resource(url, running_render): # type: (str, RunningRender) -> VGResource logger.debug( "get_and_put_resource({0}, render_id={1}) call".format( url, running_render.render_id)) resource = self.request_resources.get(url) self.eyes_connector.render_put_resource(running_render, resource) return resource requests = self.prepare_data_for_rg(self.script) fetch_fails = 0 render_requests = None already_force_putted = False while True: try: self.put_cache.process_all() render_requests = self.eyes_connector.render(*requests) except Exception as e: logger.exception(e) fetch_fails += 1 datetime_utils.sleep( 1500, msg="/render throws exception... sleeping for 1.5s") if not render_requests: logger.error("running_renders is null") continue need_more_dom = need_more_resources = False for i, running_render in enumerate(render_requests): requests[i].render_id = running_render.render_id need_more_dom = running_render.need_more_dom need_more_resources = (running_render.render_status == RenderStatus.NEED_MORE_RESOURCE) get_and_put_resource_wtih_render = partial( get_and_put_resource, running_render=running_render) dom_resource = requests[i].dom.resource if self.is_force_put_needed and not already_force_putted: for url in self.request_resources: self.put_cache.fetch_and_store( url, get_and_put_resource_wtih_render) already_force_putted = True if need_more_resources: for url in running_render.need_more_resources: self.put_cache.fetch_and_store( url, get_and_put_resource_wtih_render) if need_more_dom: self.eyes_connector.render_put_resource( running_render, dom_resource) still_running = (need_more_resources or need_more_dom or fetch_fails > self.MAX_FAILS_COUNT) if not still_running: break return self.poll_render_status(requests)
def _process_future(self, val): if isinstance(val, Future): try: val = val.result() except HTTPError as e: logger.error("Resource haven't been downloaded.") logger.exception(e) except Exception as e: self.executor.shutdown() raise e return val
def delete_session(self, test_results): # type: (TestResults) -> None argument_guard.not_none(test_results) if None in (test_results.id, test_results.batch_id, test_results.secret_token): logger.error("Can't delete session, results are None") return self._com.request( "delete", "{}/{}/{}".format( self.API_SESSIONS_BATCHES, test_results.batch_id, test_results.id ), params={"AccessToken": test_results.secret_token}, ).raise_for_status()
def _init_driver(self, driver): # type: (AnyWebDriver) -> None 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: self._driver = EyesWebDriver(driver, self) if self._driver.is_mobile_app and not isinstance(driver, AppiumWebDriver): logger.error("To test a mobile app you need to use appium webdriver") if Feature.SCALE_MOBILE_APP in self.configure.features: raise EyesError( "For mobile app testing the appium driver should be used" )
def render_task_succeeded(render_status): # type: (RenderStatusResults) -> None logger.debug("render_task_succeeded: task.uuid: {}".format( render_task.uuid)) if render_status: self.eyes.render_status_for_task(render_task.uuid, render_status) for vgr in render_status.selector_regions: if vgr.error: logger.error(vgr.error) else: self.regions.append(vgr.to_region()) self.watch_render[render_task] = True if self.all_tasks_completed(self.watch_render): self.becomes_rendered()
def close(self, raise_ex=True): # noqa # type: (Optional[bool]) -> Optional[TestResults] if not self.test_list: return TestResults() logger.debug("VisualGridEyes.close()\n\t test_list %s" % self.test_list) self.close_async() while True: states = list(set(t.state for t in self.test_list)) logger.debug("Current test states: \n {}".format(states)) if len(states) == 1 and states[0] == "completed": break datetime_utils.sleep( 1500, msg="Waiting for state completed in VisualGridEyes.close") self._is_opened = False for test in self.test_list: if test.pending_exceptions: logger.error( "During test execution above exception raised. \n {:s}". join(str(e) for e in test.pending_exceptions)) if raise_ex: for test in self.test_list: if test.test_result is None: raise TestFailedError("Test haven't finished correctly") results = test.test_result scenario_id_or_name = results.name app_id_or_name = results.app_name if results.is_unresolved and not results.is_new: raise DiffsFoundError(results, scenario_id_or_name, app_id_or_name) if results.is_new: raise NewTestError(results, scenario_id_or_name, app_id_or_name) if results.is_failed: raise TestFailedError(results, scenario_id_or_name, app_id_or_name) all_results = [t.test_result for t in self.test_list if t.test_result] if not all_results: return TestResults() return all_results[0]
def _try_upload_data(self, bytes_data, content_type, media_type): # type: (bytes, Text, Text) -> Optional[Text] argument_guard.not_none(bytes_data) rendering_info = self.render_info() if rendering_info and rendering_info.results_url: try: target_url = rendering_info.results_url guid = uuid.uuid4() target_url = target_url.replace("__random__", str(guid)) logger.debug("Uploading {} to {}".format(media_type, target_url)) if self._upload_data( bytes_data, rendering_info, target_url, content_type, media_type ): return target_url except Exception as e: logger.error("Error uploading {}".format(media_type)) logger.exception(e)
def render_task_succeeded(render_statuses): # type: (List[RenderStatusResults]) -> None logger.debug( "render_task_succeeded: task.uuid: {}".format(render_task.uuid) ) logger.debug( "render_task_succeeded: task.uuid: {}".format(render_task.uuid) ) render_status = render_statuses[render_index] if render_status: if not render_status.device_size: render_status.device_size = self.browser_info.viewport_size self.eyes.render_status_for_task(render_task.uuid, render_status) if render_status.status == RenderStatus.RENDERED: for vgr in render_status.selector_regions: if vgr.error: logger.error(vgr.error) else: self.regions[render_task].append(vgr.to_region()) self.watch_render[render_task] = True if self.all_tasks_completed(self.watch_render): self.becomes_rendered() elif render_status and render_status.status == RenderStatus.ERROR: self.watch_render[render_task] = True del self.task_queue[:] del self.open_queue[:] del self.close_queue[:] self.watch_open = {} self.watch_task = {} self.watch_close = {} self.abort() if self.all_tasks_completed(self.watch_render): self.becomes_tested() else: logger.error( "Wrong render status! Render returned status {}".format( render_status ) ) self.becomes_completed()
def get_urls_from_css_resource(bytes_text): # type: (bytes) -> List[Text] def is_import_node(n): return n.prelude and n.type == "at-rule" and n.lower_at_keyword == "import" try: rules, encoding = tinycss2.parse_stylesheet_bytes(css_bytes=bytes_text, skip_comments=True, skip_whitespace=True) except Exception: logger.error("Failed to read CSS string") return [] urls = [] for rule in rules: if is_import_node(rule): extracted = _url_from_tags(rule.prelude, ("url", "string")) elif rule.content: extracted = _url_from_tags(rule.content, ("url", )) else: continue urls.extend(list(extracted)) return urls
def render_task_succeeded(render_status): # type: (List[RenderStatusResults]) -> None logger.debug( "render_task_succeeded: task.uuid: {}".format(render_task.uuid) ) if render_status: self.running_test.eyes.render_status_for_task( render_task.uuid, render_status ) if render_status.status == RenderStatus.RENDERED: for vgr in render_status.selector_regions: if vgr.error: logger.error(vgr.error) else: self.regions.append(vgr.to_region()) logger.debug( "render_task_succeeded: uuid: {}\n\tregions {}".format( render_task.uuid, self.regions ) ) # schedule check task self.queue.append(self._check_task(render_task, tag)) elif render_status and render_status.status == RenderStatus.ERROR: self.running_test.task_queue.clear() self.running_test.open_queue.clear() self.running_test.close_queue.clear() self.running_test.watch_open = {} self.running_test.watch_task = {} self.running_test.watch_close = {} self.running_test.abort() self.running_test.becomes_tested() else: logger.error( "Wrong render status! Render returned status {}".format( render_status ) ) self.running_test.becomes_completed()
def _get_all_test_results_impl(self, should_raise_exception=True): # type: (bool) -> TestResultsSummary while True: states = list(set(t.state for t in self._get_all_running_tests())) logger.debug("Current test states: \n {}".format(states)) if len(states) == 1 and states[0] == "completed": break datetime_utils.sleep( 1500, msg="Waiting for state completed in get_all_test_results_impl", ) all_results = [] for test, test_result in iteritems(self._all_test_result): if test.pending_exceptions: logger.error( "During test execution above exception raised. \n {:s}". join(str(e) for e in test.pending_exceptions)) exception = None if test.test_result is None: exception = TestFailedError("Test haven't finished correctly") scenario_id_or_name = test_result.name app_id_or_name = test_result.app_name if test_result and test_result.is_unresolved and not test_result.is_new: exception = DiffsFoundError(test_result, scenario_id_or_name, app_id_or_name) if test_result and test_result.is_new: exception = NewTestError(test_result, scenario_id_or_name, app_id_or_name) if test_result and test_result.is_failed: exception = TestFailedError(test_result, scenario_id_or_name, app_id_or_name) all_results.append( TestResultContainer(test_result, test.browser_info, exception)) if exception and should_raise_exception: raise exception return TestResultsSummary(all_results)
def set_viewport_size(driver, required_size): # noqa # type: (AnyWebDriver, ViewPort) -> None actual_viewport_size = get_viewport_size(driver) if actual_viewport_size == required_size: logger.info("Required viewport size already set") return None logger.info( "Actual Viewport Size: {}\n\tTrying to set viewport size to: {}". format(str(actual_viewport_size), str(required_size))) try: # 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) except WebDriverException: logger.warning("Failed to move the browser window to (0,0)") if set_browser_size_by_viewport_size(driver, actual_viewport_size, required_size): return None # Additional attempt. This Solves the "maximized browser" bug # (border size for maximized browser sometimes different than # non-maximized, so the original browser size calculation is # wrong). logger.info("Trying workaround for maximization...") if set_browser_size_by_viewport_size(driver, actual_viewport_size, required_size): return None width_diff = abs(actual_viewport_size["width"] - required_size["width"]) width_step = -1 if width_diff > 0 else 1 # -1 for smaller size, 1 for larger height_diff = abs(actual_viewport_size["height"] - required_size["height"]) height_step = -1 if height_diff > 0 else 1 browser_size = get_window_size(driver) curr_width_change = 0 curr_height_change = 0 if width_diff <= _MAX_DIFF and height_diff <= _MAX_DIFF: logger.info("Trying workaround for zoom...") last_required_browser_size = None while (abs(curr_width_change) <= width_diff and abs(curr_height_change) <= height_diff): if abs(curr_width_change) <= width_diff: curr_width_change += width_step if abs(curr_height_change) <= height_diff: curr_height_change += height_step required_browser_size = dict( width=browser_size["width"] + curr_width_change, height=browser_size["height"] + curr_height_change, ) if required_browser_size == last_required_browser_size: logger.info( "Browser size is as required but viewport size does not match!" ) logger.info("Browser size: {}, Viewport size: {}".format( required_browser_size, actual_viewport_size)) logger.info("Stopping viewport size attempts.") break set_browser_size(driver, required_browser_size) last_required_browser_size = required_browser_size actual_viewport_size = get_viewport_size(driver) logger.info( "Current viewport size: {}".format(actual_viewport_size)) if actual_viewport_size == required_size: return None else: logger.info("Zoom workaround failed.") # Attempt to fix by minimizing window logger.info("Trying workaround for minimization...") try: # some webdriver's don't support minimize_window driver.minimize_window() except WebDriverException as e: logger.exception(e) if set_browser_size_by_viewport_size(driver, actual_viewport_size, required_size): return None logger.error("Minimization workaround failed.") raise EyesError("Failed to set the viewport size.")
def perform(self): # noqa # type: () -> List[RenderStatusResults] requests = self.prepare_data_for_rg(self.script) fetch_fails = 0 render_requests = None already_force_putted = False while True: try: self.put_cache.wait_for_all_uploaded() render_requests = self.eyes_connector.render(*requests) except Exception: logger.exception("During rendering for requests {}".format(requests)) fetch_fails += 1 datetime_utils.sleep( 1500, msg="/render throws exception... sleeping for 1.5s" ) if fetch_fails > self.MAX_FAILS_COUNT: raise EyesError( "Render is failed. Max count retries reached for {}".format( requests ) ) if not render_requests: logger.error("running_renders is null") continue need_more_dom = need_more_resources = False for i, running_render in enumerate(render_requests): requests[i].render_id = running_render.render_id need_more_dom = running_render.need_more_dom need_more_resources = ( running_render.render_status == RenderStatus.NEED_MORE_RESOURCE ) dom_resource = requests[i].dom.resource if self.is_force_put_needed and not already_force_putted: self.put_cache.put( self.full_request_resources, self.full_request_resources, running_render.render_id, self.eyes_connector, force=True, ) already_force_putted = True if need_more_resources: self.put_cache.put( running_render.need_more_resources, self.full_request_resources, running_render.render_id, self.eyes_connector, ) if need_more_dom: self.eyes_connector.render_put_resource( running_render.render_id, dom_resource ) still_running = ( need_more_resources or need_more_dom or fetch_fails > self.MAX_FAILS_COUNT ) if not still_running: break return self.poll_render_status(requests)