def check_region_in_frame_by_selector(self, frame_reference, by, value, tag=None, match_timeout=-1): """ Checks a region within a frame, and returns to the current frame. Args: :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). Returns: None """ 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) # We need to temporarily save the hide_scrollbars value, since we'll change it to make sure that hide_scrollbars # will NOT be called twice (once outside the frame and once inside the frame). original_hide_scrollbars_value = self.hide_scrollbars if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self.hide_scrollbars = False # Switching to the relevant frame self._driver.switch_to.frame(frame_reference) logger.debug("calling 'check_region_by_selector'...") self.check_region_by_selector(by, value, tag, match_timeout) # Switching back to our original frame self._driver.switch_to.parent_frame() if original_hide_scrollbars_value: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self.hide_scrollbars = original_hide_scrollbars_value
def add_text_trigger_by_element(self, element, text): """ 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 EyesFrame.is_same_frame_chain(self._driver.get_frame_chain(), self._last_screenshot.get_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)
def check_region_in_frame_by_selector(self, frame_reference, by, value, tag=None, match_timeout=-1): """ 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). :return: None """ 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) # We need to temporarily save the hide_scrollbars value, since we'll change it to make sure that hide_scrollbars # will NOT be called twice (once outside the frame and once inside the frame). original_hide_scrollbars_value = self.hide_scrollbars if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self.hide_scrollbars = False # Switching to the relevant frame self._driver.switch_to.frame(frame_reference) logger.debug("calling 'check_region_by_selector'...") self.check_region_by_selector(by, value, tag, match_timeout) # Switching back to our original frame self._driver.switch_to.parent_frame() if original_hide_scrollbars_value: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self.hide_scrollbars = original_hide_scrollbars_value
def check_region(self, region, tag=None, match_timeout=-1): """ Takes a snapshot of the given region from the browser using the web driver and matches it with the expected output. If the current context is a frame, the region is offsetted relative to the frame. Args: :param region: (Region) The region which will be visually validated. The coordinates are relative to the viewport of the current frame. :param tag: (str) Description of the visual validation checkpoint. :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds). Returns: None """ if self.is_disabled: logger.info('check_region(): ignored (disabled)') return logger.info("check_region([%s], '%s')" % (region, tag)) if region.is_empty(): raise EyesError("region cannot be empty!") if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self._prepare_to_check() result = self._match_window_task.match_region(region, match_timeout, tag, self.force_full_page_screenshot, self._user_inputs, self._should_match_once_on_timeout) if self.hide_scrollbars: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self._handle_match_result(result, tag)
def add_mouse_trigger_by_element(self, action, element): """ Adds a mouse trigger. Args: action: (string) Mouse action (click, double click etc.) element: (WebElement) 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 EyesFrame.is_same_frame_chain(self._driver.get_frame_chain(), self._last_screenshot.get_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)
def check_region(self, region, tag=None, match_timeout=-1): """ Takes a snapshot of the given region from the browser using the web driver and matches it with the expected output. If the current context is a frame, the region is offsetted relative to the frame. :param region: (Region) The region which will be visually validated. The coordinates are relative to the viewport of the current frame. :param tag: (str) Description of the visual validation checkpoint. :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds). :return: None """ if self.is_disabled: logger.info('check_region(): ignored (disabled)') return logger.info("check_region([%s], '%s')" % (region, tag)) if region.is_empty(): raise EyesError("region cannot be empty!") if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self._prepare_to_check() result = self._match_window_task.match_region(region, match_timeout, tag, self.force_full_page_screenshot, self._user_inputs, self._should_match_once_on_timeout) if self.hide_scrollbars: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self._handle_match_result(result, tag)
def add_text_trigger_by_element(self, element, text): """ Adds a text trigger. Args: element: (WebElement) The element to which the text was sent. text: (str) 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 EyesFrame.is_same_frame_chain(self._driver.get_frame_chain(), self._last_screenshot.get_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)
def add_mouse_trigger_by_element(self, action, element): """ 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 EyesFrame.is_same_frame_chain(self._driver.get_frame_chain(), self._last_screenshot.get_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)
def check_window(self, tag=None, specific_match_timeout=-1): """ Takes a snapshot from the browser using the web driver and matches it with the expected output. Args: (str) tag: (Optional) Description of the visual validation checkpoint. (float) specific_match_timeout: (Optional) Timeout for the visual validation checkpoint (given in order to let the current page time to stabilize). Returns: None """ if self.is_disabled: logger.info("check_window(%s): ignored (disabled)" % tag) return logger.info("check_window('%s')" % tag) if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self._prepare_to_check() result = self._match_window_task.match_window(specific_match_timeout, tag, self.force_full_page_screenshot, self._user_inputs, self._should_match_once_on_timeout) if self.hide_scrollbars: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self._handle_match_result(result, tag)
def check_region_by_element(self, element, tag=None, match_timeout=-1): """ Takes a snapshot of the region of the given element from the browser using the web driver and matches it with the expected output. Args: :param element: (WebElement) The element which region will be visually validated. :param tag: (str) Description of the visual validation checkpoint. :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds). Returns: None """ if self.is_disabled: logger.info('check_region_by_element(): ignored (disabled)') return logger.info("check_region_by_element('%s')" % tag) if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self._prepare_to_check() result = self._match_window_task.match_element(element, match_timeout, tag, self.force_full_page_screenshot, self._user_inputs, self._should_match_once_on_timeout) if self.hide_scrollbars: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self._handle_match_result(result, tag)
def check_region_by_element(self, element, tag=None, match_timeout=-1): """ Takes a snapshot of the region of the given element from the browser using the web driver and matches it with the expected output. :param element: (WebElement) The element which region will be visually validated. :param tag: (str) Description of the visual validation checkpoint. :param match_timeout: (int) Timeout for the visual validation checkpoint (milliseconds). :return: None """ if self.is_disabled: logger.info('check_region_by_element(): ignored (disabled)') return logger.info("check_region_by_element('%s')" % tag) if self.hide_scrollbars: original_overflow = self._driver.hide_scrollbars() self._prepare_to_check() result = self._match_window_task.match_element(element, match_timeout, tag, self.force_full_page_screenshot, self._user_inputs, self._should_match_once_on_timeout) if self.hide_scrollbars: # noinspection PyUnboundLocalVariable self._driver.set_overflow(original_overflow) self._handle_match_result(result, tag)
def _handle_match_result(self, result, tag): self._last_screenshot = result['screenshot'] as_expected = result['as_expected'] self._user_inputs = [] if not as_expected: self._should_match_once_on_timeout = True if not self._running_session['is_new_session']: logger.info("Window mismatch %s" % tag) if self.failure_reports == FailureReports.IMMEDIATE: raise TestFailedError("Mismatch found in '%s' of '%s'" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName']))
def get_viewport_size(driver): """ Tries to get the viewport size using Javascript. If fails, gets the entire browser window size! :param driver: The webdriver to use for getting the viewport size. """ # noinspection PyBroadException try: width, height = driver.execute_script(_JS_GET_VIEWPORT_SIZE) return {'width': width, 'height': height} except: logger.info('Failed to get viewport size. Only window size is available') return driver.get_window_size()
def _get_environment(self): """ Application environment is the environment (e.g., the host OS) which runs the application under test. :return: The current application environment. """ os = self.host_os # If no host OS was set, check for mobile OS. if os is None: logger.info('No OS set, checking for mobile OS...') # Since in Python Appium driver is the same for Android and iOS, we need to use the desired # capabilities to figure this out. if self._driver.is_mobile_device(): platform_name = self._driver.get_platform_name() logger.info(platform_name + ' detected') platform_version = self._driver.get_platform_version() if platform_version is not None: # Notice that Python's "split" function's +limit+ is the the maximum splits performed # whereas in Ruby it is the maximum number of elements in the result (which is why they are set # differently). major_version = platform_version.split('.', 1)[0] os = platform_name + ' ' + major_version else: os = platform_name logger.info("Setting OS: " + os) else: logger.info('No mobile OS detected.') app_env = {'os': os, 'hostingApp': self.host_app, 'displaySize': self._viewport_size, 'inferred': self._get_inferred_environment()} return app_env
def get_viewport_size(driver): """ Tries to get the viewport size using Javascript. If fails, gets the entire browser window size! :param driver: The webdriver to use for getting the viewport size. """ # noinspection PyBroadException try: width, height = driver.execute_script(_JS_GET_VIEWPORT_SIZE) return {'width': width, 'height': height} except: logger.info( 'Failed to get viewport size. Only window size is available') return driver.get_window_size()
def get_viewport_size(driver): """ Tries to get the viewport size using Javascript. If fails, gets the entire browser window size! """ # noinspection PyBroadException try: width = extract_viewport_width(driver) height = extract_viewport_height(driver) return {'width': width, 'height': height} except: logger.info('Failed to get viewport size. Only window size is available') browser_size = driver.get_window_size() return {'width': browser_size['width'], 'height': browser_size['height']}
def check_region_by_selector(self, by, value, tag=None, match_timeout=-1): """ 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). :return: None """ if self.is_disabled: logger.info('check_region_by_selector(): ignored (disabled)') return logger.debug("calling 'check_region_by_element'...") self.check_region_by_element(self._driver.find_element(by, value), tag, match_timeout)
def set_viewport_size(driver, required_size): if 'width' not in required_size or 'height' not in required_size: raise EyesError('Size must have width & height keys!') logger.debug("set_viewport_size({0})".format(required_size)) starting_size = required_size # When setting the viewport size we need to be in the default content frame original_frame_chain = driver.get_frame_chain() driver.switch_to.default_content() driver.set_window_size(required_size['width'], required_size['height']) if not _verify_size(driver.get_window_size, starting_size): error_message = "Failed to set browser size!" logger.info(error_message) # Going back to the frame we started at driver.switch_to.frames(original_frame_chain) raise TestFailedError(error_message) current_viewport_size = get_viewport_size(driver) logger.debug("set_viewport_size(): initial viewport size: {0}".format(current_viewport_size)) current_browser_size = driver.get_window_size() width_to_set = (2 * current_browser_size['width']) - current_viewport_size['width'] height_to_set = (2 * current_browser_size['height']) - current_viewport_size['height'] driver.set_window_size(width_to_set, height_to_set) if not _verify_size(lambda: get_viewport_size(driver), required_size): current_viewport_size = get_viewport_size(driver) logger.debug("set_viewport_size(): viewport size: {0}".format(current_viewport_size)) logger.debug("set_viewport_size(): attempting one more time...") current_browser_size = driver.get_window_size() updated_width = current_browser_size['width'] + (required_size['width'] - current_viewport_size['width']) updated_height = current_browser_size['height'] + (required_size['height'] - current_viewport_size['height']) updated_browser_size = {'width': updated_width, 'height': updated_height} logger.debug("set_viewport_size(): browser size: {0}".format(current_browser_size)) logger.debug("set_viewport_size(): required browser size: {0}".format(updated_browser_size)) driver.set_window_size(updated_width, updated_height) if not _verify_size(lambda: get_viewport_size(driver), required_size): current_viewport_size = get_viewport_size(driver) logger.debug("set_viewport_size(): viewport size: {0}".format(current_viewport_size)) error_message = "Failed to set viewport size." logger.info(error_message) # Going back to the frame we started at driver.switch_to.frames(original_frame_chain) raise TestFailedError(error_message) # Going back to the frame we started at driver.switch_to.frames(original_frame_chain)
def open(self, driver, app_name, test_name, viewport_size=None): """ Starts a test. Args: params (dictionary): app_name (str): The name of the application under test. test_name (str): The test name. (Optional) viewport_size ({width, height}): The client's viewport size (i.e., the visible part of the document's body) or None to allow any viewport size. Returns: An updated web driver Raises: EyesError """ logger.open_() if self.is_disabled: logger.debug('open(): ignored (disabled)') return driver if self.api_key is None: raise EyesError("API key not set! Log in to https://applitools.com to obtain your" " API Key and use 'api_key' to set it.") if isinstance(driver, RemoteWebDriver): self._driver = EyesWebDriver(driver, self) elif 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: raise EyesError("driver is not a RemoteWebDriver ({0})".format(driver.__class__)) logger.info("open(%s, %s, %s, %s)" % (app_name, test_name, viewport_size, self.failure_reports)) if self.is_open(): self.abort_if_not_closed() raise EyesError('a test is already running') self._app_name = app_name self._test_name = test_name self._viewport_size = viewport_size self._is_open = True return self._driver
def check_region_by_selector(self, by, value, tag=None, match_timeout=-1): """ Takes a snapshot of the region of the element found by calling find_element(by, value) and matches it with the expected output. Args: :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). Returns: None """ if self.is_disabled: logger.info('check_region_by_selector(): ignored (disabled)') return logger.debug("calling 'check_region_by_element'...") self.check_region_by_element(self._driver.find_element(by, value), tag, match_timeout)
def get_screenshot_as_base64(self): """ 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('decoding base64...') screenshot_bytes = base64.b64decode(screenshot64) logger.debug('Done! Creating image object...') screenshot = _image_utils.png_image_from_bytes(screenshot_bytes) logger.debug('Done! Rotating...') screenshot.quadrant_rotate(num_quadrants) screenshot64 = screenshot.get_base64() return screenshot64
def check_region_by_selector(self, by, value, tag=None, specific_match_timeout=-1): """ Takes a snapshot of the region of the element found by calling find_element(by, value) and matches it with the expected output. Args: (By) by: The way by which an element to be validated should be found (e.g., By.ID). (str) selector: The value identifying the element using the "by" type. (str) tag: (Optional) Description of the visual validation checkpoint. (float) specific_match_timeout: (Optional) Timeout for the visual validation checkpoint (given in order to let the current page time to stabilize). Returns: None """ if self.is_disabled: logger.info('check_region_by_selector(): ignored (disabled)') return logger.debug("calling 'check_region_by_element'...") self.check_region_by_element(self._driver.find_element(by, value), tag, specific_match_timeout)
def get_screenshot_as_base64(self): """ Gets the screenshot of the current window as a base64 encoded string which is useful in embedded images in HTML. :Usage: driver.get_screenshot_as_base64() """ 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('decoding base64...') screenshot_bytes = base64.b64decode(screenshot64) logger.debug('Done! Creating image object...') screenshot = _image_utils.png_image_from_bytes(screenshot_bytes) logger.debug('Done! Rotating...') screenshot.quadrant_rotate(num_quadrants) screenshot64 = screenshot.get_base64() return screenshot64
def open(self, driver, app_name, test_name, viewport_size=None): """ Starts a test. :param driver: The webdriver to use. :param app_name: The name of the application under test. :param test_name: The test name. :param viewport_size: The client's viewport size (i.e., the visible part of the document's body) or None to allow any viewport size. :return: An updated web driver :raise EyesError: If the session was already open. """ logger.open_() if self.is_disabled: logger.debug('open(): ignored (disabled)') return driver if self.api_key is None: raise EyesError("API key not set! Log in to https://applitools.com to obtain your" " API Key and use 'api_key' to set it.") 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) logger.info("open(%s, %s, %s, %s)" % (app_name, test_name, viewport_size, self.failure_reports)) if self.is_open(): self.abort_if_not_closed() raise EyesError('a test is already running') self._app_name = app_name self._test_name = test_name self._viewport_size = viewport_size self._is_open = True return self._driver
def abort_if_not_closed(self): """ If a test is running, aborts it. Otherwise, does nothing. """ if self.is_disabled: logger.debug('abort_if_not_closed(): ignored (disabled)') return try: self._reset_last_screenshot() if self._running_session: logger.debug('abort_if_not_closed(): Aborting session...') try: self._agent_connector.stop_session(self._running_session, True, False) logger.info('--- Test aborted.') except EyesError as e: logger.info("Failed to abort server session: %s " % e) pass finally: self._running_session = None finally: logger.close()
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) selenium_url = os.environ.get('SELENIUM_SERVER_URL', 'http://127.0.0.1:4444/wd/hub') if 'ondemand.saucelabs.com' in selenium_url: 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()
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() original_scroll_position = self.get_current_scroll_position() self.switch_to.default_content() self.scroll_to(Point(0, 0)) current_scroll_position = self.get_current_scroll_position() if current_scroll_position.x != 0 or current_scroll_position.y != 0: raise EyesError('Couldn\'t scroll to the top/left part of the screen!') 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.switch_to.frames(original_frame) self.scroll_to(original_scroll_position) 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 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_scroll_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.switch_to.frames(original_frame) self.scroll_to(original_scroll_position) return stitched_image
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
def close(self, raise_ex=True): """ Ends the test. Args: (boolean) raise_ex: If true, an exception will be raised for failed/new tests. Returns: The test results. """ if self.is_disabled: logger.debug('close(): ignored (disabled)') return try: self._is_open = False logger.debug('close()') self._reset_last_screenshot() # If there's no running session, we simply return the default test results. if not self._running_session: logger.debug('close(): Server session was not started') logger.info('close(): --- Empty test ended.') return TestResults() results_url = self._running_session['session_url'] is_new_session = self._running_session['is_new_session'] should_save = (is_new_session and self.save_new_tests) or \ ((not is_new_session) and self.save_failed_tests) logger.debug("close(): automatically save session? %s" % should_save) logger.info('close(): Closing session...') results = self._agent_connector.stop_session(self._running_session, False, should_save) results.is_new = is_new_session results.url = results_url logger.info("close(): %s" % results) self._running_session = None if not is_new_session and (0 < results.mismatches or 0 < results.missing): # Test failed logger.info("--- Failed test ended. See details at " + results_url) if raise_ex: message = "'%s' of '%s'. See details at %s" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName'], results_url) raise TestFailedError(message, results) return results if is_new_session: # New test instructions = "Please approve the new baseline at %s" % results_url logger.info("--- New test ended. %s" % instructions) if raise_ex and self.fail_on_new_test: message = "'%s' of '%s'. %s" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName'], instructions) raise NewTestError(message, results) return results # Test passed logger.info('--- Test passed.') return results finally: logger.close()
def close(self, raise_ex=True): """ Ends the test. :param raise_ex: If true, an exception will be raised for failed/new tests. :return: The test results. """ if self.is_disabled: logger.debug('close(): ignored (disabled)') return try: self._is_open = False logger.debug('close()') self._reset_last_screenshot() # If there's no running session, we simply return the default test results. if not self._running_session: logger.debug('close(): Server session was not started') logger.info('close(): --- Empty test ended.') return TestResults() results_url = self._running_session['session_url'] is_new_session = self._running_session['is_new_session'] should_save = (is_new_session and self.save_new_tests) or \ ((not is_new_session) and self.save_failed_tests) logger.debug("close(): automatically save session? %s" % should_save) logger.info('close(): Closing session...') results = self._agent_connector.stop_session(self._running_session, False, should_save) results.is_new = is_new_session results.url = results_url logger.info("close(): %s" % results) self._running_session = None if not is_new_session and (0 < results.mismatches or 0 < results.missing): # Test failed logger.info("--- Failed test ended. See details at " + results_url) if raise_ex: message = "'%s' of '%s'. See details at %s" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName'], results_url) raise TestFailedError(message, results) return results if is_new_session: # New test instructions = "Please approve the new baseline at %s" % results_url logger.info("--- New test ended. %s" % instructions) if raise_ex and self.fail_on_new_test: message = "'%s' of '%s'. %s" % (self._start_info['scenarioIdOrName'], self._start_info['appIdOrName'], instructions) raise NewTestError(message, results) return results # Test passed logger.info('--- Test passed.') return results finally: logger.close()