class DebugScreenshotsProvider(object): """Interface for saving debug screenshots.""" _prefix = attr.ib( converter=str, factory=lambda: get_env_with_prefix("DEBUG_SCREENSHOT_PREFIX", "screenshot_"), ) _path = attr.ib( converter=str, factory=lambda: get_env_with_prefix("DEBUG_SCREENSHOT_PATH", "") ) def __attrs_post_init__(self): self._image_counter = 0 @property def prefix(self): return self._prefix @prefix.setter def prefix(self, value): if value: self._prefix = value @property def path(self): return self._path @path.setter def path(self, value): if value: self._path = value.rstrip("/") @abstractmethod def save(self, image, suffix): pass
def test_available_envs_with_get_env_with_prefix(env_name, value): assert None is general_utils.get_env_with_prefix(env_name) os.environ[env_name] = value assert value == general_utils.get_env_with_prefix(env_name) del os.environ[env_name] os.environ["bamboo_{}".format(env_name)] = value assert value == general_utils.get_env_with_prefix(env_name) os.environ[env_name + "random"] = value assert value == general_utils.get_env_with_prefix("WRONG_KEY", default=value) assert "" == general_utils.get_env_with_prefix("WRONG_KEY", default="")
def __init__(self, name=None, started_at=None, batch_sequence_name=None): # type: (Optional[Text], Optional[datetime], Optional[Text]) -> None self.id = get_env_with_prefix("APPLITOOLS_BATCH_ID", str(uuid.uuid4())) # type: Text self.name = get_env_with_prefix("APPLITOOLS_BATCH_NAME") self.started_at = datetime.now(UTC) self.sequence_name = get_env_with_prefix("APPLITOOLS_BATCH_SEQUENCE") if name: self.name = name if started_at: self.started_at = started_at if batch_sequence_name: self.sequence_name = batch_sequence_name
class BatchClose(object): api_key = attr.ib(factory=lambda: get_env_with_prefix( "APPLITOOLS_API_KEY", None)) # type: Optional[Text] server_url = attr.ib(default=DEFAULT_SERVER_URL) # type: Text proxy = attr.ib(default=None) # type: Optional[ProxySettings] def set_url(self, url): # type: (Text) -> BatchClose self.server_url = url return self def set_api_key(self, api_key): # type: (Text) -> BatchClose self.api_key = api_key return self def set_proxy(self, proxy): # type: (ProxySettings) -> BatchClose argument_guard.is_a(proxy, ProxySettings) self.proxy = proxy return self def set_batch_ids(self, *ids): # type: (Union[Text, List[Text]]) -> _EnabledBatchClose if isinstance(ids[0], list): ids = ids[0] return _EnabledBatchClose(ids, self.server_url, self.api_key, self.proxy)
def __init__(self, name=None, started_at=None, batch_sequence_name=None): # type: (Optional[Text], Optional[datetime], Optional[Text]) -> None self.id = get_env_with_prefix("APPLITOOLS_BATCH_ID", PROCESS_DEFAULT_BATCH_ID) # type: Text self.name = get_env_with_prefix("APPLITOOLS_BATCH_NAME") # type: Text self.started_at = datetime.now(UTC) # type: datetime self.sequence_name = get_env_with_prefix( "APPLITOOLS_BATCH_SEQUENCE") # type: Optional[Text] self.notify_on_completion = str2bool( get_env_with_prefix("APPLITOOLS_BATCH_NOTIFY")) # type: bool self.properties = [] # type: List[Dict[Text,Text]] if name: self.name = name if started_at: self.started_at = started_at if batch_sequence_name: self.sequence_name = batch_sequence_name
def __init__(self): self._force_put = str2bool( get_env_with_prefix("APPLITOOLS_UFG_FORCE_PUT_RESOURCES")) kwargs = {} if sys.version_info[:2] >= (3, 6): kwargs["thread_name_prefix"] = self.__class__.__name__ self._executor = ThreadPoolExecutor(**kwargs) self._lock = threading.Lock() self._sent_hashes = set()
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))
class RenderTask(VGTask): MAX_FAILS_COUNT = 5 MAX_ITERATIONS = 2400 # poll_render_status for 1 hour script = attr.ib(hash=False, repr=False) # type: Dict[str, Any] resource_cache = attr.ib(hash=False, repr=False) # type: ResourceCache put_cache = attr.ib(hash=False, repr=False) eyes_connector = attr.ib(hash=False, repr=False) # type: EyesConnector rendering_info = attr.ib() # type: RenderingInfo region_selectors = attr.ib( hash=False, factory=list) # type: List[List[VisualGridSelector]] size_mode = attr.ib(default=None) region_to_check = attr.ib(hash=False, default=None) # type: Region script_hooks = attr.ib(hash=False, default=None) # type: Optional[Dict] agent_id = attr.ib(default=None) # type: Optional[Text] selector = attr.ib(hash=False, default=None) # type: Optional[VisualGridSelector] func_to_run = attr.ib(default=None, hash=False, repr=False) # type: Callable running_tests = attr.ib(init=False, hash=False, factory=list) # type: List[RunningTest] is_force_put_needed = attr.ib(default=str2bool( get_env_with_prefix( "APPLITOOLS_UFG_FORCE_PUT_RESOURCES"))) # type: bool request_options = attr.ib(hash=False, factory=dict) # type: Dict[str, Any] def __attrs_post_init__(self): # type: () -> None self.func_to_run = self.perform # type: Callable self.full_request_resources = {} # type: Dict[Text, VGResource] 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.full_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: 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) 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.full_request_resources: self.put_cache.fetch_and_store( url, get_and_put_resource_wtih_render, force=True) 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 prepare_data_for_rg(self, data): # type: (Dict) -> List[RenderRequest] self.full_request_resources = {} dom = self.parse_frame_dom_resources(data) return self.prepare_rg_requests(dom, self.full_request_resources) def prepare_rg_requests(self, dom, request_resources): # type: (RGridDom, Dict) -> List[RenderRequest] if self.size_mode == "region" and self.region_to_check is None: raise EyesError("Region to check should be present") if self.size_mode == "selector" and not isinstance( self.selector, VisualGridSelector): raise EyesError("Selector should be present") requests = [] region = None for running_test in self.running_tests: if self.region_to_check: region = dict( x=self.region_to_check.x, y=self.region_to_check.y, width=self.region_to_check.width, height=self.region_to_check.height, ) r_info = RenderInfo.from_( size_mode=self.size_mode, selector=self.selector, region=region, render_browser_info=running_test.browser_info, ) requests.append( RenderRequest( webhook=self.rendering_info.results_url, agent_id=self.agent_id, url=dom.url, stitching_service=self.rendering_info. stitching_service_url, dom=dom, resources=request_resources, render_info=r_info, browser_name=running_test.browser_info.browser, platform_name=running_test.browser_info.platform, script_hooks=self.script_hooks, selectors_to_find_regions_for=list( chain(*self.region_selectors)), send_dom=running_test.configuration.send_dom, options=self.request_options, )) return requests def parse_frame_dom_resources(self, data): # noqa # type: (Dict) -> RGridDom base_url = data["url"] resource_urls = data.get("resourceUrls", []) all_blobs = data.get("blobs", []) frames = data.get("frames", []) logger.debug(""" parse_frame_dom_resources() call base_url: {base_url} count blobs: {blobs_num} count resource urls: {resource_urls_num} count frames: {frames_num} """.format( base_url=base_url, blobs_num=len(all_blobs), resource_urls_num=len(resource_urls), frames_num=len(frames), )) frame_request_resources = {} discovered_resources_urls = set() def handle_resources(content_type, content, resource_url): # type: (Optional[Text], bytes, Text) -> NoReturn logger.debug("handle_resources({0}, {1}) call".format( content_type, resource_url)) if not content_type: logger.debug( "content_type is empty. Skip handling of resources") return for url in collect_urls_from_(content_type, content): target_url = apply_base_url(url, base_url, resource_url) discovered_resources_urls.add(target_url) def get_resource(link): # type: (Text) -> VGResource logger.debug("get_resource({0}) call".format(link)) response = self.eyes_connector.download_resource(link) return VGResource.from_response(link, response, on_created=handle_resources) for f_data in frames: f_data["url"] = apply_base_url(f_data["url"], base_url) frame_request_resources[f_data[ "url"]] = self.parse_frame_dom_resources(f_data).resource for blob in all_blobs: resource = VGResource.from_blob(blob, on_created=handle_resources) if resource.url.rstrip("#") == base_url: continue frame_request_resources[resource.url] = resource for r_url in set(resource_urls).union(discovered_resources_urls): self.resource_cache.fetch_and_store(r_url, get_resource) self.resource_cache.process_all() # some discovered urls becomes available only after resources processed for r_url in discovered_resources_urls: self.resource_cache.fetch_and_store(r_url, get_resource) for r_url in set(resource_urls).union(discovered_resources_urls): val = self.resource_cache[r_url] if val is None: logger.debug("No response for {}".format(r_url)) continue frame_request_resources[r_url] = val self.full_request_resources.update(frame_request_resources) return RGridDom(url=base_url, dom_nodes=data["cdt"], resources=frame_request_resources) def poll_render_status(self, requests): # type: (List[RenderRequest]) -> List[RenderStatusResults] logger.debug( "poll_render_status call with Requests {}".format(requests)) iterations = 0 statuses = [] # type: List[RenderStatusResults] fails_count = 0 finished = False while True: if finished: break while True: try: statuses = self.eyes_connector.render_status_by_id( *[rq.render_id for rq in requests]) except Exception as e: logger.exception(e) datetime_utils.sleep( 1000, msg="/render-status throws exception... sleeping for 1s" ) fails_count += 1 finally: iterations += 1 datetime_utils.sleep(1500, msg="Rendering...") if iterations > self.MAX_ITERATIONS: raise EyesError( "Max iterations in poll_render_status has been reached " "for render_id: \n {}".format("\n".join( s.render_id for s in statuses))) if statuses or 0 < fails_count < 3: break finished = bool(statuses and (all(s.status != RenderStatus.RENDERING for s in statuses) or iterations > self.MAX_ITERATIONS or False)) return statuses def add_running_test(self, running_test): # type: (RunningTest) -> int if running_test in self.running_tests: raise EyesError("The running test {} already exists in the render " "task".format(running_test)) self.running_tests.append(running_test) return len(self.running_tests) - 1
from __future__ import absolute_import import functools import logging import os import sys import typing as tp import warnings from logging import Logger from applitools.common.utils.general_utils import get_env_with_prefix _DEFAULT_EYES_LOGGER_NAME = "eyes" _DEFAULT_EYES_FORMATTER = logging.Formatter( "%(asctime)s [%(levelname)s] %(threadName)-9s) %(name)s: %(message)s") _DEFAULT_LOGGER_LEVEL = int(get_env_with_prefix("LOGGER_LEVEL", logging.INFO)) _DEBUG_SCREENSHOT_PREFIX = get_env_with_prefix("DEBUG_SCREENSHOT_PREFIX", "screenshot_") _DEBUG_SCREENSHOT_PATH = get_env_with_prefix("DEBUG_SCREENSHOT_PATH", ".") __all__ = ("StdoutLogger", "FileLogger", "NullLogger") class _Logger(object): """ Simple logger. Supports only info and debug. """ def __init__( self, name=__name__, level=_DEFAULT_LOGGER_LEVEL,
class Configuration(object): batch = attr.ib(factory=BatchInfo) # type: BatchInfo branch_name = attr.ib(factory=lambda: get_env_with_prefix( "APPLITOOLS_BRANCH", None)) # type: Optional[Text] parent_branch_name = attr.ib(factory=lambda: get_env_with_prefix( "APPLITOOLS_PARENT_BRANCH", None)) # type: Optional[Text] baseline_branch_name = attr.ib(factory=lambda: get_env_with_prefix( "APPLITOOLS_BASELINE_BRANCH", None)) # type: Optional[Text] agent_id = attr.ib(default=None) # type: Optional[Text] baseline_env_name = attr.ib(default=None) # type: Optional[Text] environment_name = attr.ib(default=None) # type: Optional[Text] save_diffs = attr.ib(default=None) # type: bool app_name = attr.ib(default=None) # type: Optional[Text] test_name = attr.ib(default=None) # type: Optional[Text] viewport_size = attr.ib( default=None, converter=attr.converters.optional( RectangleSize.from_)) # type: Optional[RectangleSize] session_type = attr.ib(default=SessionType.SEQUENTIAL) # type: SessionType host_app = attr.ib(default=None) # type: Optional[Text] host_os = attr.ib(default=None) # type: Optional[Text] properties = attr.ib(factory=list) # type: List[Dict[Text, Text]] match_timeout = attr.ib(default=DEFAULT_MATCH_TIMEOUT_MS) # type: int # ms is_disabled = attr.ib(default=False) # type: bool save_new_tests = attr.ib(default=True) # type: bool save_failed_tests = attr.ib(default=False) # type: bool failure_reports = attr.ib( default=FailureReports.ON_CLOSE) # type: FailureReports send_dom = attr.ib(default=True) # type: bool default_match_settings = attr.ib( factory=ImageMatchSettings) # type: ImageMatchSettings stitch_overlap = attr.ib(default=5) # type: int api_key = attr.ib(factory=lambda: get_env_with_prefix( "APPLITOOLS_API_KEY", None)) # type: Optional[Text] server_url = attr.ib(factory=lambda: get_env_with_prefix( "APPLITOOLS_SERVER_URL", DEFAULT_SERVER_URL)) # type: Text _timeout = attr.ib( default=DEFAULT_SERVER_REQUEST_TIMEOUT_MS) # type: int # ms @property def enable_patterns(self): return self.default_match_settings.enable_patterns @enable_patterns.setter def enable_patterns(self, value): self.default_match_settings.enable_patterns = value @property def use_dom(self): return self.default_match_settings.use_dom @use_dom.setter def use_dom(self, value): self.default_match_settings.use_dom = value @property def match_level(self): return self.default_match_settings.match_level @match_level.setter def match_level(self, value): self.default_match_settings.match_level = value @property def ignore_displacements(self): return self.default_match_settings.ignore_displacements @ignore_displacements.setter def ignore_displacements(self, value): self.default_match_settings.ignore_displacements = value def set_batch(self, batch): # type: (Self, BatchInfo) -> Self argument_guard.is_a(batch, BatchInfo) self.batch = batch return self def set_branch_name(self, branch_name): # type: (Self, Text) -> Self self.branch_name = branch_name return self def set_agent_id(self, agent_id): # type: (Self, Text) -> Self self.agent_id = agent_id return self def set_parent_branch_name(self, parent_branch_name): # type: (Self, Text) -> Self self.parent_branch_name = parent_branch_name return self def set_baseline_branch_name(self, baseline_branch_name): # type: (Self, Text) -> Self self.baseline_branch_name = baseline_branch_name return self def set_baseline_env_name(self, baseline_env_name): # type: (Self, Text) -> Self self.baseline_env_name = baseline_env_name return self def set_environment_name(self, environment_name): # type: (Self, Text) -> Self self.environment_name = environment_name return self def set_save_diffs(self, save_diffs): # type: (Self, bool) -> Self self.save_diffs = save_diffs return self def set_app_name(self, app_name): # type: (Self, Text) -> Self self.app_name = app_name return self def set_test_name(self, test_name): # type: (Self, Text) -> Self self.test_name = test_name return self def set_viewport_size(self, viewport_size): # type: (Self, ViewPort) -> Self self.viewport_size = viewport_size return self def set_session_type(self, session_type): # type: (Self, SessionType) -> Self self.session_type = session_type return self @property def ignore_caret(self): # type: () -> bool ignore = self.default_match_settings.ignore_caret return True if ignore is None else ignore def set_ignore_caret(self, ignore_caret): # type: (Self, bool) -> Self self.default_match_settings.ignore_caret = ignore_caret return self def set_host_app(self, host_app): # type: (Self, Text) -> Self self.host_app = host_app return self def set_host_os(self, host_os): # type: (Self, Text) -> Self self.host_os = host_os return self def set_match_timeout(self, match_timeout): # type: (Self, int) -> Self self.match_timeout = match_timeout return self def set_match_level(self, match_level): # type: (Self, MatchLevel) -> Self self.match_level = match_level return self def set_ignore_displacements(self, ignore_displacements): # type: (Self, bool) -> Self self.ignore_displacements = ignore_displacements return self def set_save_new_tests(self, save_new_tests): # type: (Self, bool) -> Self self.save_new_tests = save_new_tests return self def set_save_failed_tests(self, save_failed_tests): # type: (Self, bool) -> Self self.save_failed_tests = save_failed_tests return self def set_failure_reports(self, failure_reports): # type: (Self, FailureReports) -> Self self.failure_reports = failure_reports return self def set_send_dom(self, send_dom): # type: (Self, bool) -> Self self.send_dom = send_dom return self def set_use_dom(self, use_dom): # type: (Self, bool) -> Self self.use_dom = use_dom return self def set_enable_patterns(self, enable_patterns): # type: (Self, bool) -> Self self.enable_patterns = enable_patterns return self def set_stitch_overlap(self, stitch_overlap): # type: (Self, int) -> Self self.stitch_overlap = stitch_overlap return self def set_api_key(self, api_key): # type: (Self, Text) -> Self self.api_key = api_key return self def set_server_url(self, server_url): # type: (Self, Text) -> Self self.server_url = server_url return self def set_default_match_settings(self, default_match_settings): # type: (Self, ImageMatchSettings) -> Self self.default_match_settings = default_match_settings return self @match_timeout.validator def _validate1(self, attribute, value): if 0 < value < MINIMUM_MATCH_TIMEOUT_MS: raise ValueError("Match timeout must be at least {} ms.".format( MINIMUM_MATCH_TIMEOUT_MS)) @viewport_size.validator def _validate2(self, attribute, value): if value is None: return None if isinstance(value, RectangleSize) or (isinstance(value, dict) and "width" in value.keys() and "height" in value.keys()): return None raise ValueError("Wrong viewport type settled") @property def is_send_dom(self): # type: () -> bool return self.send_dom def clone(self): # type: () -> Configuration return copy(self)
class Configuration(object): batch = attr.ib(metadata={JsonInclude.NON_NONE: True}, factory=BatchInfo) # type: BatchInfo branch_name = attr.ib( metadata={JsonInclude.NON_NONE: True}, factory=lambda: get_env_with_prefix("APPLITOOLS_BRANCH", None), ) # type: Optional[Text] parent_branch_name = attr.ib( metadata={JsonInclude.NON_NONE: True}, factory=lambda: get_env_with_prefix("APPLITOOLS_PARENT_BRANCH", None), ) # type: Optional[Text] baseline_branch_name = attr.ib( metadata={JsonInclude.NON_NONE: True}, factory=lambda: get_env_with_prefix("APPLITOOLS_BASELINE_BRANCH", None ), ) # type: Optional[Text] agent_id = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: Optional[Text] baseline_env_name = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: Optional[Text] environment_name = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: Optional[Text] save_diffs = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: bool app_name = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: Optional[Text] test_name = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: Optional[Text] viewport_size = attr.ib( metadata={JsonInclude.NON_NONE: True}, default=None, converter=attr.converters.optional(RectangleSize.from_), ) # type: Optional[RectangleSize] session_type = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=SessionType.SEQUENTIAL) # type: SessionType host_app = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: Optional[Text] host_os = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: Optional[Text] properties = attr.ib(metadata={JsonInclude.NON_NONE: True}, factory=list) # type: List[Dict[Text, Text]] match_timeout = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=DEFAULT_MATCH_TIMEOUT_MS) # type: int # ms is_disabled = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=False) # type: bool save_new_tests = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=True) # type: bool save_failed_tests = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=False) # type: bool failure_reports = attr.ib( metadata={JsonInclude.NON_NONE: True}, default=FailureReports.ON_CLOSE) # type: FailureReports send_dom = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=True) # type: bool default_match_settings = attr.ib( metadata={JsonInclude.NON_NONE: True}, factory=ImageMatchSettings) # type: ImageMatchSettings stitch_overlap = attr.ib(metadata={JsonInclude.NON_NONE: True}, default=None) # type: Optional[int] api_key = attr.ib(factory=lambda: get_env_with_prefix( "APPLITOOLS_API_KEY", None)) # type: Optional[Text] server_url = attr.ib( metadata={JsonInclude.NON_NONE: True}, factory=lambda: get_env_with_prefix("APPLITOOLS_SERVER_URL", DEFAULT_SERVER_URL), ) # type: Text _timeout = attr.ib( default=DEFAULT_SERVER_REQUEST_TIMEOUT_MS) # type: int # ms proxy = attr.ib(default=None) # type: ProxySettings save_debug_screenshots = attr.ib(default=False) # type: bool debug_screenshots_path = attr.ib( converter=str, factory=lambda: get_env_with_prefix("DEBUG_SCREENSHOT_PATH", "")) debug_screenshots_prefix = attr.ib( converter=str, factory=lambda: get_env_with_prefix("DEBUG_SCREENSHOT_PREFIX", "screenshot_"), ) @property def enable_patterns(self): # type: () -> bool return self.default_match_settings.enable_patterns @enable_patterns.setter def enable_patterns(self, enable_patterns): # type: (bool) -> None self.default_match_settings.enable_patterns = enable_patterns @property def use_dom(self): # type: () -> bool return self.default_match_settings.use_dom @use_dom.setter def use_dom(self, use_dom): # type: (bool) -> None self.default_match_settings.use_dom = use_dom @property def match_level(self): # type: () -> MatchLevel return self.default_match_settings.match_level @match_level.setter def match_level(self, match_level): # type: (MatchLevel) -> None self.default_match_settings.match_level = match_level @property def ignore_displacements(self): # type: () -> bool return self.default_match_settings.ignore_displacements @ignore_displacements.setter def ignore_displacements(self, ignore_displacements): # type: (bool) -> None self.default_match_settings.ignore_displacements = ignore_displacements def set_batch(self, batch): # type: (Self, BatchInfo) -> Self argument_guard.is_a(batch, BatchInfo) self.batch = batch return self def set_branch_name(self, branch_name): # type: (Self, Text) -> Self self.branch_name = branch_name return self def set_agent_id(self, agent_id): # type: (Self, Text) -> Self self.agent_id = agent_id return self def set_parent_branch_name(self, parent_branch_name): # type: (Self, Text) -> Self self.parent_branch_name = parent_branch_name return self def set_baseline_branch_name(self, baseline_branch_name): # type: (Self, Text) -> Self self.baseline_branch_name = baseline_branch_name return self def set_baseline_env_name(self, baseline_env_name): # type: (Self, Text) -> Self self.baseline_env_name = baseline_env_name return self def set_environment_name(self, environment_name): # type: (Self, Text) -> Self self.environment_name = environment_name return self def set_save_diffs(self, save_diffs): # type: (Self, bool) -> Self self.save_diffs = save_diffs return self def set_app_name(self, app_name): # type: (Self, Text) -> Self self.app_name = app_name return self def set_test_name(self, test_name): # type: (Self, Text) -> Self self.test_name = test_name return self def set_viewport_size(self, viewport_size): # type: (Self, ViewPort) -> Self self.viewport_size = viewport_size return self def set_session_type(self, session_type): # type: (Self, SessionType) -> Self self.session_type = session_type return self @property def ignore_caret(self): # type: () -> bool ignore = self.default_match_settings.ignore_caret return True if ignore is None else ignore def set_ignore_caret(self, ignore_caret): # type: (Self, bool) -> Self self.default_match_settings.ignore_caret = ignore_caret return self def set_host_app(self, host_app): # type: (Self, Text) -> Self self.host_app = host_app return self def set_host_os(self, host_os): # type: (Self, Text) -> Self self.host_os = host_os return self def set_match_timeout(self, match_timeout): # type: (Self, int) -> Self self.match_timeout = match_timeout return self def set_match_level(self, match_level): # type: (Self, MatchLevel) -> Self self.match_level = match_level return self def set_ignore_displacements(self, ignore_displacements): # type: (Self, bool) -> Self self.ignore_displacements = ignore_displacements return self def set_save_new_tests(self, save_new_tests): # type: (Self, bool) -> Self self.save_new_tests = save_new_tests return self def set_save_failed_tests(self, save_failed_tests): # type: (Self, bool) -> Self self.save_failed_tests = save_failed_tests return self def set_failure_reports(self, failure_reports): # type: (Self, FailureReports) -> Self self.failure_reports = failure_reports return self def set_send_dom(self, send_dom): # type: (Self, bool) -> Self self.send_dom = send_dom return self def set_use_dom(self, use_dom): # type: (Self, bool) -> Self self.use_dom = use_dom return self def set_enable_patterns(self, enable_patterns): # type: (Self, bool) -> Self self.enable_patterns = enable_patterns return self def set_stitch_overlap(self, stitch_overlap): # type: (Self, int) -> Self self.stitch_overlap = stitch_overlap return self def set_api_key(self, api_key): # type: (Self, Text) -> Self self.api_key = api_key return self def set_server_url(self, server_url): # type: (Self, Text) -> Self self.server_url = server_url return self def set_default_match_settings(self, default_match_settings): # type: (Self, ImageMatchSettings) -> Self self.default_match_settings = default_match_settings return self @property def accessibility_validation(self): # type: (Self) -> Optional[AccessibilitySettings] return self.default_match_settings.accessibility_settings @accessibility_validation.setter def accessibility_validation(self, accessibility_settings): # type: (Self, Optional[AccessibilitySettings]) -> None if accessibility_settings is None: self.self.default_match_settings.accessibility_settings = None return argument_guard.is_a(accessibility_settings, AccessibilitySettings) self.default_match_settings.accessibility_settings = accessibility_settings def set_accessibility_validation(self, accessibility_settings): # type: (Self, Optional[AccessibilitySettings]) -> Self self.accessibility_validation = accessibility_settings return self @match_timeout.validator def _validate1(self, attribute, value): if 0 < value < MINIMUM_MATCH_TIMEOUT_MS: raise ValueError("Match timeout must be at least {} ms.".format( MINIMUM_MATCH_TIMEOUT_MS)) @viewport_size.validator def _validate2(self, attribute, value): if value is None: return None if isinstance(value, RectangleSize) or (isinstance(value, dict) and "width" in value.keys() and "height" in value.keys()): return None raise ValueError("Wrong viewport type settled") @property def is_send_dom(self): # type: () -> bool return self.send_dom def clone(self): # type: () -> Self # TODO: Remove this huck when get rid of Python2 # deepcopy on python 2 raise an exception so construct manually conf = copy(self) conf.batch = deepcopy(conf.batch) conf.viewport_size = copy(conf.viewport_size) conf.properties = deepcopy(conf.properties) conf.default_match_settings = deepcopy(conf.default_match_settings) return conf def add_property(self, name, value): # type: (Text, Text) -> Self """ Associates a key/value pair with the test. This can be used later for filtering. :param name: (string) The property name. :param value: (string) The property value """ self.properties.append({"name": name, "value": value}) return self def clear_properties(self): # type: () -> Self """ Clears the list of custom properties. """ del self.properties[:] return self def set_proxy(self, proxy): # type: (Optional[ProxySettings]) -> Self argument_guard.is_a(proxy, ProxySettings) self.proxy = proxy return self @deprecated.attribute("Configuration features are not supported") def set_features(self, *_): pass
import re import sys import threading import typing as tp from enum import Enum from typing import Any, Dict, Optional, Text import attr import structlog import structlog.dev from applitools.common.utils import json_utils from applitools.common.utils.general_utils import get_env_with_prefix __all__ = ("StdoutLogger", "FileLogger") _DEFAULT_HANDLER_LEVEL = int(get_env_with_prefix("LOGGER_LEVEL", logging.INFO)) _frames_regex = re.compile( r"function calls leading up to the error, in the order they occurred\.\s*" r"(.*?)" r"\s*The above is a description of an error in a Python program.", re.DOTALL, ) class Stage(Enum): GENERAL = "GENERAL" OPEN = "OPEN" CHECK = "CHECK" CLOSE = "CLOSE" RENDER = "RENDER" RESOURCE_COLLECTION = "RESOURCE_COLLECTION"