Beispiel #1
0
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="")
Beispiel #3
0
    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)
Beispiel #5
0
    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
Beispiel #6
0
 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()
Beispiel #7
0
 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))
Beispiel #8
0
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
Beispiel #9
0
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,
Beispiel #10
0
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)
Beispiel #11
0
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
Beispiel #12
0
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"