def __init__(self, driver): """ Ctor. :param driver: EyesWebDriver instance. """ self.driver = driver self.states = [] self.current_position = Point(0, 0)
def calc_frame_location_in_screenshot(frame_chain, is_viewport_screenshot): """ Returns: (Point) The frame location as it would be on the screenshot. Notice that this value might actually be OUTSIDE the screenshot (e.g, if this is a viewport screenshot and the frame is located outside the viewport). This is not an error. The value can also be negative. """ first_frame = frame_chain[0] location_in_screenshot = Point(first_frame.location['x'], first_frame.location['y']) # We only need to consider the scroll of the default content if the screenshot is a # viewport screenshot. If this is a full page screenshot, the frame location will not # change anyway. if is_viewport_screenshot: location_in_screenshot.x -= first_frame.parent_scroll_position.x location_in_screenshot.y -= first_frame.parent_scroll_position.y # For inner frames we must calculate the scroll inner_frames = frame_chain[1:] for frame in inner_frames: location_in_screenshot.x += frame.location[ 'x'] - frame.parent_scroll_position.x location_in_screenshot.y += frame.location[ 'y'] - frame.parent_scroll_position.y return location_in_screenshot
def reset_origin(self): self._origin_position_provider.push_state() self._origin_position_provider.set_position(Point(0, 0)) current_scroll_position = self._origin_position_provider.get_current_position( ) if current_scroll_position.x != 0 or current_scroll_position.y != 0: self._origin_position_provider.pop_state() raise EyesError( "Couldn't scroll to the top/left part of the screen!")
class CSSTranslatePositionProvider(object): _JS_TRANSFORM_KEYS = ["transform", "-webkit-transform"] def __init__(self, driver): self.driver = driver self.states = [] self.current_position = Point(0, 0) def _execute_script(self, script): return self.driver.execute_script(script) def get_current_position(self): """ Extracts the current scroll position from the browser. Returns: (Point) The scroll position """ return self.current_position.clone() def _set_transform(self, transform_list): script = '' for key, value in transform_list.items(): script += "document.documentElement.style['{}'] = '{}';".format( key, value) self._execute_script(script) def _get_current_transform(self): script = 'return {' for key in self._JS_TRANSFORM_KEYS: script += "'{0}': document.documentElement.style['{0}'],".format( key) script += ' }' return self._execute_script(script) def set_position(self, point): """ Commands the browser to scroll to a given position using javascript. """ translate_command = "translate(-{}px, -{}px)".format(point.x, point.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 = point.clone() def push_state(self): self.states.append(self._get_current_transform()) def pop_state(self): self._set_transform(self.states.pop())
def get_current_position(self): """ Extracts the current scroll position from the browser. Returns: (Point) The scroll position """ try: x, y = self._execute_script(self._JS_GET_CURRENT_SCROLL_POSITION) if x is None or y is None: raise EyesError("Got None as scroll position! ({},{})".format( x, y)) except WebDriverException: raise EyesError("Failed to extract current scroll position!") return Point(x, y)
def get_sub_screenshot_by_region(self, region): """ Returns: (EyesScreenshot) A screenshot object representing the given region part of the image. """ sub_screenshot_region = self.get_intersected_region(region) if sub_screenshot_region.is_empty(): raise OutOfBoundsError( "Region {0} is out of bounds!".format(region)) # If we take a screenshot of a region inside a frame, then the frame's (0,0) is in the # negative offset of the region.. sub_screenshot_frame_location = Point(-region.left, -region.top) # FIXME Calculate relative region location? (same as the java version) screenshot = self._screenshot.get_subimage(sub_screenshot_region) return EyesScreenshot( self._driver, screenshot, is_viewport_screenshot=self._is_viewport_screenshot, frame_location_in_screenshot=sub_screenshot_frame_location)
def __init__(self, driver, screenshot=None, screenshot64=None, is_viewport_screenshot=None, frame_location_in_screenshot=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. Args: driver: EyesWebDriver instance which handles the session from which the screenshot was retrieved. (Optional) screenshot: (PngImage) image instance. If screenshot64 is None, this variable must NOT be none. (Optional) screenshot64: (str) The base64 representation of a png image. If screenshot is None, this variable must NOT be none. (Optional) frame_location_in_screenshot: (Point) The location of the frame relative to the top,left of the screenshot. (Optional) is_viewport_screenshot: (boolean) Whether the screenshot object represents a viewport screenshot or a full screenshot. """ self._screenshot64 = screenshot64 if screenshot: self._screenshot = screenshot elif screenshot64: self._screenshot = _image_utils.png_image_from_bytes(base64.b64decode(screenshot64)) else: raise EyesError("both screenshot and screenshot64 are None!") self._driver = driver self._viewport_size = driver.get_default_content_viewport_size() 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_scroll_position() except WebDriverException: 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 = EyesScreenshot \ .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_full_page_screenshot(self): 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 part64 = self.get_screenshot_as_base64() screenshot = _image_utils.png_image_from_bytes( base64.b64decode(part64)) # 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 = screenshot 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)) time.sleep(0.1) # 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.png_image_from_bytes( base64.b64decode(part64)) stitched_image.paste(current_scroll_position.x, current_scroll_position.y, part_image.pixel_bytes) self.restore_position() self.restore_origin() self.switch_to.frames(original_frame) return stitched_image