def _viewport_screenshot(self, scale_provider): # type: (ScaleProvider) -> EyesWebDriverScreenshot logger.info('Viewport screenshot requested') screenshot64 = self._driver.get_screesnhot_as_base64_from_main_frame( self.seconds_to_wait_screenshot) screenshot = image_utils.image_from_bytes( base64.b64decode(screenshot64)) scale_provider.update_scale_ratio(screenshot.width) pixel_ratio = 1 / scale_provider.scale_ratio if pixel_ratio != 1.0: screenshot = image_utils.scale_image(screenshot, 1.0 / pixel_ratio) return EyesWebDriverScreenshot.create_from_image( screenshot, self._driver).get_viewport_screenshot()
def _viewport_screenshot(self, scale_provider): # type: (ScaleProvider) -> EyesWebDriverScreenshot logger.info("Viewport screenshot requested") self._driver._wait_before_screenshot(self._seconds_to_wait_screenshot) if not self._driver.is_mobile_device(): image64 = self._driver.get_screesnhot_as_base64_from_main_frame() else: image64 = self._driver.get_screenshot_as_base64() image = image_utils.image_from_bytes(base64.b64decode(image64)) scale_provider.update_scale_ratio(image.width) pixel_ratio = 1 / scale_provider.scale_ratio if pixel_ratio != 1.0: image = image_utils.scale_image(image, 1.0 / pixel_ratio) return EyesWebDriverScreenshot.create_from_image( image, self._driver).get_viewport_screenshot()
def __init__(self, driver, screenshot=None, screenshot64=None, is_viewport_screenshot=None, frame_location_in_screenshot=None): # type: (EyesWebDriver, Image.Image, None, tp.Optional[bool], tp.Optional[Point]) -> None """ Initializes a Screenshot instance. Either screenshot or screenshot64 must NOT be None. Should not be used directly. Use create_from_image/create_from_base64 instead. :param driver: EyesWebDriver instance which handles the session from which the screenshot was retrieved. :param screenshot: image instance. If screenshot64 is None, this variable must NOT be none. :param screenshot64: The base64 representation of a png image. If screenshot is None, this variable must NOT be none. :param is_viewport_screenshot: Whether the screenshot object represents a viewport screenshot or a full screenshot. :param frame_location_in_screenshot: The location of the frame relative to the top,left of the screenshot. :raise EyesError: If the screenshots are None. """ if screenshot is None and screenshot64 is None: raise EyesError("both screenshot and screenshot64 are None!") if screenshot64: screenshot = image_utils.image_from_bytes( base64.b64decode(screenshot64)) # initializing of screenshot super(EyesWebDriverScreenshot, self).__init__(image=screenshot) self._driver = driver self._viewport_size = driver.get_default_content_viewport_size( ) # type: ViewPort self._frame_chain = driver.get_frame_chain() if self._frame_chain: chain_len = len(self._frame_chain) self._frame_size = self._frame_chain[chain_len - 1].size else: try: self._frame_size = driver.get_entire_page_size() except WebDriverException: # For Appium, we can't get the "entire page size", so we use the viewport size. self._frame_size = self._viewport_size # For native Appium Apps we can't get the scroll position, so we use (0,0) try: self._scroll_position = driver.get_current_position() except (WebDriverException, EyesError): self._scroll_position = Point(0, 0) if is_viewport_screenshot is None: is_viewport_screenshot = ( self._screenshot.width <= self._viewport_size['width'] and self._screenshot.height <= self._viewport_size['height']) self._is_viewport_screenshot = is_viewport_screenshot if frame_location_in_screenshot is None: if self._frame_chain: frame_location_in_screenshot = EyesWebDriverScreenshot \ .calc_frame_location_in_screenshot(self._frame_chain, is_viewport_screenshot) else: # The frame is the default content frame_location_in_screenshot = Point(0, 0) if self._is_viewport_screenshot: frame_location_in_screenshot.offset( -self._scroll_position.x, -self._scroll_position.y) self._frame_location_in_screenshot = frame_location_in_screenshot self._frame_screenshot_intersect = Region( frame_location_in_screenshot.x, frame_location_in_screenshot.y, self._frame_size['width'], self._frame_size['height']) self._frame_screenshot_intersect.intersect( Region(width=self._screenshot.width, height=self._screenshot.height))
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
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.get_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) 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.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.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
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