예제 #1
0
    def check_region_by_selector(self,
                                 by,
                                 value,
                                 tag=None,
                                 match_timeout=-1,
                                 target=None,
                                 stitch_content=False):
        # type: (Text, Text, Optional[Text], int, Optional[Target], bool) -> None
        """
        Takes a snapshot of the region of the element found by calling find_element
        (by, value) and matches it with the expected output.

        :param by: The way by which an element to be validated should be found
                   (e.g., By.ID).
        :param value: The value identifying the element using the "by" type.
        :param tag: Description of the visual validation checkpoint.
        :param match_timeout: Timeout for the visual validation checkpoint
                              (milliseconds).
        :param target: The target for the check_window call
        :return: None
        """
        logger.debug("calling 'check_region_by_selector'...")
        # hack: prevent stale element exception by saving viewport value
        # before catching element
        self._driver.get_default_content_viewport_size()
        self.check_region_by_element(
            self._driver.find_element(by, value),
            tag,
            match_timeout,
            target,
            stitch_content,
        )
예제 #2
0
def _get_frame_dom(driver, args_obj):
    # type: (EyesWebDriver, tp.Dict) -> tp.Dict
    dom_tree = driver.execute_script(_CAPTURE_FRAME_SCRIPT, args_obj)
    base_url = driver.current_url  # type: ignore
    logger.debug('Traverse DOM Tree')
    _traverse_dom_tree(driver, args_obj, dom_tree, -1, base_url)
    return dom_tree
def is_landscape_orientation(driver):
    if is_mobile_device(driver):
        # could be AppiumRemoteWebDriver
        appium_driver = get_underlying_driver(driver)  # type: WebDriver

        original_context = None
        try:
            # We must be in native context in order to ask for orientation,
            # because of an Appium bug.
            original_context = appium_driver.context
            if (len(appium_driver.contexts) > 1
                    and not original_context.uppar() == "NATIVE_APP"):
                appium_driver.switch_to.context("NATIVE_APP")
            else:
                original_context = None
        except WebDriverException:
            original_context = None

        try:
            orieintation = appium_driver.orientation
            return orieintation.lower() == "landscape"
        except Exception:
            logger.debug(
                "WARNING: Couldn't get device orientation. Assuming Portrait.")
        finally:
            if original_context is not None:
                appium_driver.switch_to.context(original_context)

        return False
예제 #4
0
    def set_overflow(self, overflow, stabilization_time=None):
        """
        Sets the overflow of the current element.

        :param overflow: The overflow value to set. If the given value is None,
                then overflow will be set to undefined.
        :param stabilization_time: The time to wait for the page to stabilize after
                overflow is set. If the value is None, then no waiting will take place.
                 (Milliseconds)
        :return: The previous overflow value.
        """
        logger.debug("Setting overflow: %s" % overflow)
        if overflow is None:
            script = (
                "var elem = arguments[0]; var origOverflow = elem.style.overflow; "
                "elem.style.overflow = undefined; "
                "return origOverflow;")
        else:
            script = (
                "var elem = arguments[0]; var origOverflow = elem.style.overflow; "
                'elem.style.overflow = "{0}"; '
                "return origOverflow;".format(overflow))
        # noinspection PyUnresolvedReferences
        original_overflow = self._driver.execute_script(script, self.element)
        logger.debug("Original overflow: %s" % original_overflow)
        if stabilization_time is not None:
            time.sleep(stabilization_time / 1000)
        return original_overflow
예제 #5
0
    def check_region_in_frame_by_selector(self, frame_reference,  # type: FrameReference
                                          by,  # type: tp.Text
                                          value,  # type: tp.Text
                                          tag=None,  # type: tp.Optional[tp.Text]
                                          match_timeout=-1,  # type: int
                                          target=None,  # type: tp.Optional[Target]
                                          stitch_content=False  # type: bool
                                          ):
        # type: (...) -> None
        """
        Checks a region within a frame, and returns to the current frame.

        :param frame_reference: (int/str/WebElement) A reference to the frame in which the region should be checked.
        :param by: (By) The way by which an element to be validated should be found (e.g., By.ID).
        :param value: (str) The value identifying the element using the "by" type.
        :param tag: (str) Description of the visual validation checkpoint.
        :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds).
        :param target: (Target) The target for the check_window call
        :return: None
        """
        # TODO: remove this disable
        if self.is_disabled:
            logger.info('check_region_in_frame_by_selector(): ignored (disabled)')
            return
        logger.info("check_region_in_frame_by_selector('%s')" % tag)

        # Switching to the relevant frame
        with self._driver.switch_to.frame_and_back(frame_reference):
            logger.debug("calling 'check_region_by_selector'...")
            self.check_region_by_selector(by, value, tag, match_timeout, target, stitch_content)
예제 #6
0
    def set_overflow(self, overflow, stabilization_time=None):
        # type: (tp.Text, tp.Optional[int]) -> tp.Text
        """
        Sets the overflow of the current context's document element.

        :param overflow: The overflow value to set. If the given value is None, then overflow will be set to
                         undefined.
        :param stabilization_time: The time to wait for the page to stabilize after overflow is set. If the value is
                                    None, then no waiting will take place. (Milliseconds)
        :return: The previous overflow value.
        """
        logger.debug("Setting overflow: %s" % overflow)
        if overflow is None:
            script = "var origOverflow = document.documentElement.style.overflow; " \
                     "document.documentElement.style.overflow = undefined; " \
                     "return origOverflow;"
        else:
            script = "var origOverflow = document.documentElement.style.overflow; " \
                     "document.documentElement.style.overflow = \"{0}\"; " \
                     "return origOverflow;".format(overflow)
        # noinspection PyUnresolvedReferences
        original_overflow = self.driver.execute_script(script)
        logger.debug("Original overflow: %s" % original_overflow)
        if stabilization_time is not None:
            time.sleep(stabilization_time / 1000)

        eyes_selenium_utils.add_data_overflow_to_element(
            self.driver, None, original_overflow)
        return original_overflow
예제 #7
0
def _traverse_dom_tree(driver, args_obj, dom_tree, frame_index, base_url):
    # type: (EyesWebDriver, dict, dict, int, str) -> None
    logger.debug('Traverse DOM Tree: index_tree {}'.format(frame_index))

    tag_name = dom_tree.get('tagName', None)  # type: str
    if not tag_name:
        return None

    if frame_index > -1:
        driver.switch_to.frame(frame_index)
        dom = driver.execute_script(_CAPTURE_FRAME_SCRIPT, args_obj)
        dom_tree['childNodes'] = dom

        src_url = None
        attrs_node = dom_tree.get('attributes', None)
        if attrs_node:
            src_url = attrs_node.get('eyes_core', None)

        if src_url is None:
            logger.warning('IFRAME WITH NO SRC')

        _traverse_dom_tree(driver, args_obj, dom, -1, src_url)
        driver.switch_to.parent_frame()

    is_html = tag_name.upper() == 'HTML'
    if is_html:
        logger.debug('Traverse DOM Tree: Inside HTML')
        css = _get_frame_bundled_css(driver, base_url)
        dom_tree['css'] = css

    _loop(driver, args_obj, dom_tree, base_url)
예제 #8
0
    def _update_scaling_params(self):
        # type: () -> Optional[ScaleProvider]
        if self._device_pixel_ratio != self._UNKNOWN_DEVICE_PIXEL_RATIO:
            logger.debug("Device pixel ratio was already changed")
            return None

        logger.info("Trying to extract device pixel ratio...")
        try:
            device_pixel_ratio = image_utils.get_device_pixel_ratio(
                self._driver)
        except Exception as e:
            logger.info(
                "Failed to extract device pixel ratio! Using default. Error %s "
                % e)
            device_pixel_ratio = self._DEFAULT_DEVICE_PIXEL_RATIO
        logger.info("Device pixel ratio: {}".format(device_pixel_ratio))

        logger.info("Setting scale provider...")
        try:
            scale_provider = ContextBasedScaleProvider(
                top_level_context_entire_size=self._driver.
                get_entire_page_size(),
                viewport_size=self._get_viewport_size(),
                device_pixel_ratio=device_pixel_ratio,
                # always False as in Java version
                is_mobile_device=False,
            )  # type: ScaleProvider
        except Exception:
            # This can happen in Appium for example.
            logger.info("Failed to set ContextBasedScaleProvider.")
            logger.info("Using FixedScaleProvider instead...")
            scale_provider = FixedScaleProvider(1 / device_pixel_ratio)
        logger.info("Done!")
        return scale_provider
예제 #9
0
    def open(self, driver, app_name, test_name, viewport_size=None):
        # type: (AnyWebDriver, Text, Text, Optional[ViewPort]) -> EyesWebDriver
        if self.is_disabled:
            logger.debug("open(): ignored (disabled)")
            return driver

        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:
            if not isinstance(driver, RemoteWebDriver):
                logger.info(
                    "WARNING: driver is not a RemoteWebDriver (class: {0})".
                    format(driver.__class__))
            self._driver = EyesWebDriver(driver, self, self._stitch_mode)

        if viewport_size is not None:
            self._viewport_size = viewport_size
            eyes_selenium_utils.set_viewport_size(self._driver, viewport_size)

        self._ensure_viewport_size()
        self._open_base(app_name, test_name, viewport_size)

        return self._driver
예제 #10
0
 def set_position(self, location):
     translate_command = "translate(-{}px, -{}px)".format(location.x, location.y)
     logger.debug(translate_command)
     transform_list = dict(
         (key, translate_command) for key in self._JS_TRANSFORM_KEYS
     )
     self._set_transform(transform_list)
     self._current_position = location.clone()
예제 #11
0
    def hide_scrollbars(self):
        # type: () -> tp.Text
        """
        Hides the scrollbars of the current element.

        :return: The previous value of the overflow property (could be None).
        """
        logger.debug("EyesWebElement.HideScrollbars()")
        return self.set_overflow("hidden")
예제 #12
0
    def timed(*args, **kw):
        ts = time.time()
        result = method(*args, **kw)
        te = time.time()

        if 'log_time' in kw:
            name = kw.get('log_name', method.__name__.upper())
            kw['log_time'][name] = int((te - ts) * 1000)
        else:
            logger.debug('%r  %2.2f ms' % (method.__name__, (te - ts) * 1000))
        return result
예제 #13
0
    def hide_scrollbars(self):
        # type: () -> tp.Text
        """
        Hides the scrollbars of the current context's document element.

        :return: The previous value of the overflow property (could be None).
        """
        logger.debug('HideScrollbars() called. Waiting for page load...')
        self.wait_for_page_load()
        logger.debug('About to hide scrollbars')
        return self.set_overflow('hidden')
def set_browser_size_by_viewport_size(driver, actual_viewport_size,
                                      required_size):
    # type: (AnyWebDriver, ViewPort, ViewPort) -> bool

    browser_size = get_window_size(driver)
    logger.debug("Current browser size: {}".format(browser_size))
    required_browser_size = dict(
        width=browser_size['width'] +
        (required_size['width'] - actual_viewport_size['width']),
        height=browser_size['height'] +
        (required_size['height'] - actual_viewport_size['height']))
    return set_browser_size(driver, required_browser_size)
예제 #15
0
    def add_text_trigger_by_element(self, element, text):
        # type: (AnyWebElement, Text) -> None
        """
        Adds a text trigger.

        :param element: The element to which the text was sent.
        :param text: The trigger's text.
        """
        if self.is_disabled:
            logger.debug("add_text_trigger: Ignoring '%s' (disabled)" % text)
            return
        # Triggers are activated on the last checked window.
        if self._last_screenshot is None:
            logger.debug("add_text_trigger: Ignoring '%s' (no screenshot)" %
                         text)
            return
        if not self._driver.frame_chain == self._last_screenshot.frame_chain:
            logger.debug("add_text_trigger: Ignoring %s (different frame)" %
                         text)
            return
        control = self._last_screenshot.get_intersected_region_by_element(
            element)
        # Making sure the trigger is within the last screenshot bounds
        if control.is_empty:
            logger.debug("add_text_trigger: Ignoring %s (out of bounds)" %
                         text)
            return
        trigger = TextTrigger(control, text)
        self._user_inputs.append(trigger)
        logger.info("add_text_trigger: Added %s" % trigger)
예제 #16
0
    def add_mouse_trigger_by_element(self, action, element):
        # type: (Text, AnyWebElement) -> None
        """
        Adds a mouse trigger.

        :param action: Mouse action (click, double click etc.)
        :param element: The element on which the action was performed.
        """
        if self.is_disabled:
            logger.debug("add_mouse_trigger: Ignoring %s (disabled)" % action)
            return
        # Triggers are activated on the last checked window.
        if self._last_screenshot is None:
            logger.debug("add_mouse_trigger: Ignoring %s (no screenshot)" %
                         action)
            return
        if not self._driver.frame_chain == self._last_screenshot.frame_chain:
            logger.debug("add_mouse_trigger: Ignoring %s (different frame)" %
                         action)
            return
        control = self._last_screenshot.get_intersected_region_by_element(
            element)
        # Making sure the trigger is within the last screenshot bounds
        if control.is_empty:
            logger.debug("add_mouse_trigger: Ignoring %s (out of bounds)" %
                         action)
            return
        cursor = control.middle_offset
        trigger = MouseTrigger(action, control, cursor)
        self._user_inputs.append(trigger)
        logger.info("add_mouse_trigger: Added %s" % trigger)
예제 #17
0
    def wait_for_page_load(self, timeout=3, throw_on_timeout=False):
        # type: (int, bool) -> None
        """
        Waits for the current document to be "loaded".

        :param timeout: The maximum time to wait, in seconds.
        :param throw_on_timeout: Whether to throw an exception when timeout is reached.
        """
        # noinspection PyBroadException
        try:
            WebDriverWait(self.driver, timeout) \
                .until(lambda driver: driver.execute_script('return document.readyState') == 'complete')
        except Exception:
            logger.debug('Page load timeout reached!')
            if throw_on_timeout:
                raise
예제 #18
0
def get_full_window_dom(driver, return_as_dict=False):
    # type: (EyesWebDriver, bool) -> tp.Union[str, dict]

    dom_tree = json.loads(
        driver.execute_script(_CAPTURE_FRAME_SCRIPT, _ARGS_OBJ),
        object_pairs_hook=OrderedDict,
    )

    logger.debug("Traverse DOM Tree")
    _traverse_dom_tree(driver, {
        "childNodes": [dom_tree],
        "tagName": "OUTER_HTML"
    })

    if return_as_dict:
        return dom_tree

    return json.dumps(dom_tree)
예제 #19
0
def driver(request, browser_config):
    test_name = request.node.name
    build_tag = os.environ.get("BUILD_TAG", None)
    tunnel_id = os.environ.get("TUNNEL_IDENTIFIER", None)
    username = os.environ.get("SAUCE_USERNAME", None)
    access_key = os.environ.get("SAUCE_ACCESS_KEY", None)

    force_remote = request.config.getoption("remote", default=False)
    selenium_url = os.environ.get("SELENIUM_SERVER_URL", "http://127.0.0.1:4444/wd/hub")
    if "ondemand.saucelabs.com" in selenium_url or force_remote:
        selenium_url = "https://%s:%[email protected]:443/wd/hub" % (
            username,
            access_key,
        )
    logger.debug("SELENIUM_URL={}".format(selenium_url))

    desired_caps = browser_config.copy()
    desired_caps["build"] = build_tag
    desired_caps["tunnelIdentifier"] = tunnel_id
    desired_caps["name"] = test_name

    executor = RemoteConnection(selenium_url, resolve_ip=False)
    browser = webdriver.Remote(
        command_executor=executor, desired_capabilities=desired_caps
    )
    if browser is None:
        raise WebDriverException("Never created!")

    yield browser

    # report results
    try:
        browser.execute_script(
            "sauce:job-result=%s" % str(not request.node.rep_call.failed).lower()
        )
    except WebDriverException:
        # we can ignore the exceptions of WebDriverException type -> We're done with tests.
        logger.info(
            "Warning: The driver failed to quit properly. Check test and server side logs."
        )
    finally:
        browser.quit()
예제 #20
0
    def get_screenshot_as_base64(self):
        # type: () -> tp.Text
        """
        Gets the screenshot of the current window as a base64 encoded string
           which is useful in embedded images in HTML.
        """
        screenshot64 = self.driver.get_screenshot_as_base64()
        display_rotation = self.get_display_rotation()
        if display_rotation != 0:
            logger.info('Rotation required.')
            # num_quadrants = int(-(display_rotation / 90))
            logger.debug('Done! Creating image object...')
            screenshot = image_utils.image_from_base64(screenshot64)

            # rotating
            if display_rotation == -90:
                screenshot64 = image_utils.get_base64(screenshot.rotate(90))
            logger.debug('Done! Rotating...')

        return screenshot64
예제 #21
0
def get_full_window_dom(driver, return_as_dict=False):
    # type: (EyesWebDriver, bool) -> tp.Union[str, dict]

    dom_tree = json.loads(driver.execute_script(_CAPTURE_FRAME_SCRIPT,
                                                _ARGS_OBJ),
                          object_pairs_hook=OrderedDict)

    logger.debug('Traverse DOM Tree')
    _traverse_dom_tree(driver, {
        'childNodes': [dom_tree],
        'tagName': 'OUTER_HTML'
    })

    # After traversing page could be scrolled down. Reset to origin position
    driver.reset_origin()

    if return_as_dict:
        return dom_tree

    return json.dumps(dom_tree)
예제 #22
0
    def check_region_by_selector(self,
                                 by,
                                 value,
                                 tag=None,
                                 match_timeout=-1,
                                 target=None,
                                 stitch_content=False):
        # type: (str, str, tp.Optional[str], int, tp.Optional[Target], bool) -> None
        """
        Takes a snapshot of the region of the element found by calling find_element(by, value)
        and matches it with the expected output.

        :param by: (By) The way by which an element to be validated should be found (e.g., By.ID).
        :param value: (str) The value identifying the element using the "by" type.
        :param tag: (str) Description of the visual validation checkpoint.
        :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds).
        :param target: (Target) The target for the check_window call
        :return: None
        """
        logger.debug("calling 'check_region_by_element'...")
        self.check_region_by_element(self._driver.find_element(by, value), tag,
                                     match_timeout, target, stitch_content)
예제 #23
0
def _parse_and_serialize_css(node, text, minimize=False):
    # type: (CssNode, tp.Text, bool) -> tp.Generator
    def is_import_node(n):
        return n.type == "at-rule" and n.lower_at_keyword == "import"

    stylesheet = tinycss2.parse_stylesheet(text,
                                           skip_comments=True,
                                           skip_whitespace=True)
    for style_node in stylesheet:
        if is_import_node(style_node):
            for tag in style_node.prelude:
                if tag.type == "url":
                    logger.debug("The node has import")
                    yield CssNode.create_sub_node(parent_node=node,
                                                  href=tag.value)
            continue

        try:
            if minimize:
                try:
                    # remove whitespaces inside blocks
                    style_node.content = [
                        tok for tok in style_node.content
                        if tok.type != "whitespace"
                    ]
                except AttributeError as e:
                    logger.warning(
                        "Cannot serialize item: {}, cause error: {}".format(
                            style_node, str(e)))
            serialized = style_node.serialize()
            if minimize:
                serialized = (serialized.replace("\n", "").replace(
                    "/**/", " ").replace(" {", "{"))

        except TypeError as e:
            logger.warning(str(e))
            continue
        yield CssNode.create_serialized_node(text=serialized)
예제 #24
0
def _parse_and_serialize_css(node, text, minimize=False):
    # type: (CssNode, tp.Text, bool) -> tp.Generator
    is_import_node = lambda n: n.type == 'at-rule' and n.lower_at_keyword == 'import'
    stylesheet = tinycss2.parse_stylesheet(text,
                                           skip_comments=True,
                                           skip_whitespace=True)
    for style_node in stylesheet:
        if is_import_node(style_node):
            for tag in style_node.prelude:
                if tag.type == 'url':
                    logger.debug('The node has import')
                    yield CssNode.create_sub_node(parent_node=node,
                                                  href=tag.value)
            continue

        try:
            if minimize:
                try:
                    # remove whitespaces inside blocks
                    style_node.content = [
                        tok for tok in style_node.content
                        if tok.type != 'whitespace'
                    ]
                except AttributeError as e:
                    logger.warning(
                        "Cannot serialize item: {}, cause error: {}".format(
                            style_node, str(e)))
            serialized = style_node.serialize()
            if minimize:
                serialized = serialized.replace('\n', '').replace(
                    '/**/', ' ').replace(' {', '{')

        except TypeError as e:
            logger.warning(str(e))
            continue
        yield CssNode.create_serialized_node(text=serialized)
예제 #25
0
 def _assign_viewport_size(self):
     # type: () -> None
     """
     Assign the viewport size we need to be in the default content frame.
     """
     original_frame_chain = self._driver.get_frame_chain()
     self._driver.switch_to.default_content()
     try:
         if self._viewport_size:
             logger.debug("Assigning viewport size {0}".format(
                 self._viewport_size))
             self.set_viewport_size(self._driver, self._viewport_size)
         else:
             logger.debug(
                 "No viewport size given. Extracting the viewport size from the driver..."
             )
             self._viewport_size = self.get_viewport_size()
             logger.debug("Viewport size {0}".format(self._viewport_size))
     except EyesError:
         raise TestFailedError('Failed to assign viewport size!')
     finally:
         # Going back to the frame we started at
         self._driver.switch_to.frames(original_frame_chain)
예제 #26
0
    def get_stitched_screenshot(self, element_region, wait_before_screenshots,
                                scale_provider):
        # type: (Region, int, ScaleProvider) -> Image.Image
        """
        Gets a stitched screenshot for specific element

        :param element_region: Region of required screenshot
        :param wait_before_screenshots: Seconds to wait before taking each screenshot.
        :param scale_provider: Scale image if needed.
        :return: The full element screenshot.
        """
        logger.info('getting stitched element screenshot..')
        self._position_provider = self._eyes._element_position_provider
        entire_size = self._position_provider.get_entire_size()
        logger.debug("Element region: {}".format(element_region))
        # Firefox 60 and above make a screenshot of the current frame when other browsers
        # make a screenshot of the viewport. So we scroll down to frame at _will_switch_to method
        # and add a left margin here.
        # TODO: Refactor code. Use EyesScreenshot
        if self._frame_chain:
            if ((self.browser_name == 'firefox'
                 and self.browser_version < 60.0)
                    or self.browser_name in ('chrome', 'MicrosoftEdge',
                                             'internet explorer', 'safari')):
                element_region.left += int(self._frame_chain.peek.location.x)

        screenshot_part_size = {
            'width':
            element_region.width,
            'height':
            max(element_region.height - self._MAX_SCROLL_BAR_SIZE,
                self._MIN_SCREENSHOT_PART_HEIGHT)
        }
        entire_element = Region(0, 0, entire_size['width'],
                                entire_size['height'])

        screenshot_parts = entire_element.get_sub_regions(screenshot_part_size)
        viewport = self.get_viewport_size()
        screenshot = image_utils.image_from_bytes(
            base64.b64decode(self.get_screenshot_as_base64()))
        scale_provider.update_scale_ratio(screenshot.width)
        pixel_ratio = 1 / scale_provider.scale_ratio
        need_to_scale = True if pixel_ratio != 1.0 else False

        if need_to_scale:
            element_region = element_region.scale(
                scale_provider.device_pixel_ratio)

        # Starting with element region size part of the screenshot. Use it as a size template.
        stitched_image = Image.new(
            'RGBA', (entire_element.width, entire_element.height))
        for part in screenshot_parts:
            logger.debug("Taking screenshot for {0}".format(part))
            # Scroll to the part's top/left and give it time to stabilize.
            self._position_provider.set_position(Point(part.left, part.top))
            EyesWebDriver._wait_before_screenshot(wait_before_screenshots)
            # Since screen size might cause the scroll to reach only part of the way
            current_scroll_position = self._position_provider.get_current_position(
            )
            logger.debug("Scrolled To ({0},{1})".format(
                current_scroll_position.x, current_scroll_position.y))
            part64 = self.get_screenshot_as_base64()
            part_image = image_utils.image_from_bytes(base64.b64decode(part64))
            # Cut to viewport size the full page screenshot of main frame for some browsers
            if self._frame_chain:
                if (self.browser_name == 'firefox'
                        and self.browser_version < 60.0 or self.browser_name
                        in ('internet explorer', 'safari')):
                    # TODO: Refactor this to make main screenshot only once
                    frame_scroll_position = int(
                        self._frame_chain.peek.location.y)
                    part_image = image_utils.get_image_part(
                        part_image,
                        Region(top=frame_scroll_position,
                               height=viewport['height'],
                               width=viewport['width']))
            # We cut original image before scaling to prevent appearing of artifacts
            part_image = image_utils.get_image_part(part_image, element_region)
            if need_to_scale:
                part_image = image_utils.scale_image(part_image,
                                                     1.0 / pixel_ratio)

            # first iteration
            if stitched_image is None:
                stitched_image = part_image
                continue
            stitched_image.paste(part_image,
                                 box=(current_scroll_position.x,
                                      current_scroll_position.y))

        self._position_provider = self._origin_position_provider
        return stitched_image
예제 #27
0
    def get_full_page_screenshot(self, wait_before_screenshots,
                                 scale_provider):
        # type: (Num, ScaleProvider) -> Image.Image
        """
        Gets a full page screenshot.

        :param wait_before_screenshots: Seconds to wait before taking each screenshot.
        :return: The full page screenshot.
        """
        logger.info('getting full page screenshot..')

        # Saving the current frame reference and moving to the outermost frame.
        original_frame = self.frame_chain
        self.switch_to.default_content()

        self.reset_origin()

        entire_page_size = self.get_entire_page_size()

        # Starting with the screenshot at 0,0
        EyesWebDriver._wait_before_screenshot(wait_before_screenshots)
        part64 = self.get_screenshot_as_base64()
        screenshot = image_utils.image_from_bytes(base64.b64decode(part64))

        scale_provider.update_scale_ratio(screenshot.width)
        pixel_ratio = 1.0 / scale_provider.scale_ratio
        need_to_scale = True if pixel_ratio != 1.0 else False
        if need_to_scale:
            screenshot = image_utils.scale_image(screenshot, 1.0 / pixel_ratio)

        # IMPORTANT This is required! Since when calculating the screenshot parts for full size,
        # we use a screenshot size which is a bit smaller (see comment below).
        if (screenshot.width >= entire_page_size['width']) and \
                (screenshot.height >= entire_page_size['height']):
            self.restore_origin()
            self.switch_to.frames(original_frame)
            logger.debug("Entire page has size as screenshot")
            return screenshot

        #  We use a smaller size than the actual screenshot size in order to eliminate duplication
        #  of bottom scroll bars, as well as footer-like elements with fixed position.
        screenshot_part_size = {
            'width':
            screenshot.width,
            'height':
            max(screenshot.height - self._MAX_SCROLL_BAR_SIZE,
                self._MIN_SCREENSHOT_PART_HEIGHT)
        }

        logger.debug("Total size: {0}, Screenshot part size: {1}".format(
            entire_page_size, screenshot_part_size))

        entire_page = Region(0, 0, entire_page_size['width'],
                             entire_page_size['height'])
        screenshot_parts = entire_page.get_sub_regions(screenshot_part_size)

        # Starting with the screenshot we already captured at (0,0).
        stitched_image = Image.new('RGBA',
                                   (entire_page.width, entire_page.height))
        stitched_image.paste(screenshot, box=(0, 0))
        self.save_position()
        for part in screenshot_parts:
            # Since we already took the screenshot for 0,0
            if part.left == 0 and part.top == 0:
                logger.debug('Skipping screenshot for 0,0 (already taken)')
                continue
            logger.debug("Taking screenshot for {0}".format(part))
            # Scroll to the part's top/left and give it time to stabilize.
            self._position_provider.set_position(Point(part.left, part.top))
            # self.scroll_to(Point(part.left, part.top))
            EyesWebDriver._wait_before_screenshot(wait_before_screenshots)
            # Since screen size might cause the scroll to reach only part of the way
            current_scroll_position = self._position_provider.get_current_position(
            )
            logger.debug("Scrolled To ({0},{1})".format(
                current_scroll_position.x, current_scroll_position.y))
            part64 = self.get_screenshot_as_base64()
            part_image = image_utils.image_from_bytes(base64.b64decode(part64))

            if need_to_scale:
                part_image = image_utils.scale_image(part_image,
                                                     1.0 / pixel_ratio)

            stitched_image.paste(part_image,
                                 box=(current_scroll_position.x,
                                      current_scroll_position.y))

        self.restore_position()
        self.restore_origin()
        self.switch_to.frames(original_frame)

        return stitched_image
예제 #28
0
    def get_stitched_screenshot(self, element, wait_before_screenshots, scale_provider):
        # type: (AnyWebElement, int, ScaleProvider) -> Image.Image
        """
        Gets a stitched screenshot for specific element

        :param wait_before_screenshots: Seconds to wait before taking each screenshot.
        :return: The full page screenshot.
        """
        logger.info('getting stitched element screenshot..')

        self._position_provider = ElementPositionProvider(self.driver, element)
        entire_size = self._position_provider.get_entire_size()

        #  We use a smaller size than the actual screenshot size in order to eliminate duplication
        #  of bottom scroll bars, as well as footer-like elements with fixed position.
        pl = element.location

        # TODO: add correct values for Safari
        # in the safari browser the returned size has absolute value but not relative as
        # in other browsers

        origin_overflow = element.get_overflow()
        element.set_overflow('hidden')

        element_width = element.get_client_width()
        element_height = element.get_client_height()

        border_left_width = element.get_computed_style_int('border-left-width')
        border_top_width = element.get_computed_style_int('border-top-width')
        element_region = Region(pl['x'] + border_left_width,
                                pl['y'] + border_top_width,
                                element_width, element_height)
        logger.debug("Element region: {}".format(element_region))
        # Firefox 60 and above make a screenshot of the current frame when other browsers
        # make a screenshot of the viewport. So we scroll down to frame at _will_switch_to method
        # and add a left margin here.
        # TODO: Refactor code. Use EyesScreenshot
        if self._frames:
            if ((self.browser_name == 'firefox' and self.browser_version < 60.0)
                    or self.browser_name in ('chrome', 'MicrosoftEdge', 'internet explorer', 'safari')):
                element_region.left += int(self._frames[-1].location['x'])

        screenshot_part_size = {'width': element_region.width,
                                'height': max(element_region.height - self._MAX_SCROLL_BAR_SIZE,
                                              self._MIN_SCREENSHOT_PART_HEIGHT)}
        entire_element = Region(0, 0, entire_size['width'], entire_size['height'])

        screenshot_parts = entire_element.get_sub_regions(screenshot_part_size)
        viewport = self.get_viewport_size()
        screenshot = image_utils.image_from_bytes(base64.b64decode(self.get_screenshot_as_base64()))
        scale_provider.update_scale_ratio(screenshot.width)
        pixel_ratio = 1 / scale_provider.scale_ratio
        need_to_scale = True if pixel_ratio != 1.0 else False

        if need_to_scale:
            element_region = element_region.scale(scale_provider.device_pixel_ratio)

        # Starting with element region size part of the screenshot. Use it as a size template.
        stitched_image = Image.new('RGBA', (entire_element.width, entire_element.height))
        for part in screenshot_parts:
            logger.debug("Taking screenshot for {0}".format(part))
            # Scroll to the part's top/left and give it time to stabilize.
            self._position_provider.set_position(Point(part.left, part.top))
            EyesWebDriver._wait_before_screenshot(wait_before_screenshots)
            # Since screen size might cause the scroll to reach only part of the way
            current_scroll_position = self._position_provider.get_current_position()
            logger.debug("Scrolled To ({0},{1})".format(current_scroll_position.x,
                                                        current_scroll_position.y))
            part64 = self.get_screenshot_as_base64()
            part_image = image_utils.image_from_bytes(base64.b64decode(part64))
            # Cut to viewport size the full page screenshot of main frame for some browsers
            if self._frames:
                if (self.browser_name == 'firefox' and self.browser_version < 60.0
                        or self.browser_name in ('internet explorer', 'safari')):
                    # TODO: Refactor this to make main screenshot only once
                    frame_scroll_position = int(self._frames[-1].location['y'])
                    part_image = image_utils.get_image_part(part_image, Region(top=frame_scroll_position,
                                                                               height=viewport['height'],
                                                                               width=viewport['width']))
            # We cut original image before scaling to prevent appearing of artifacts
            part_image = image_utils.get_image_part(part_image, element_region)
            if need_to_scale:
                part_image = image_utils.scale_image(part_image, 1.0 / pixel_ratio)

            # first iteration
            if stitched_image is None:
                stitched_image = part_image
                continue
            stitched_image.paste(part_image, box=(current_scroll_position.x, current_scroll_position.y))

        if origin_overflow:
            element.set_overflow(origin_overflow)

        return stitched_image
예제 #29
0
 def _wait_before_screenshot(seconds):
     logger.debug("Waiting {} ms before taking screenshot..".format(
         int(seconds * 1000)))
     time.sleep(seconds)
     logger.debug("Finished waiting!")
예제 #30
0
 def set_position(self, location):
     scroll_command = "window.scrollTo({0}, {1})".format(location.x, location.y)
     logger.debug(scroll_command)
     self._execute_script(scroll_command)