def wait_for_notification(page): """ Waits for the "mini-notification" to appear and disappear on the given page (subclass of PageObject). """ def _is_saving(): num_notifications = len(page.q(css='.wrapper-notification-mini.is-shown')) return (num_notifications == 1, num_notifications) def _is_saving_done(): num_notifications = len(page.q(css='.wrapper-notification-mini.is-hiding')) return (num_notifications == 1, num_notifications) Promise(_is_saving, 'Notification showing.').fulfill() Promise(_is_saving_done, 'Notification hidden.').fulfill()
def is_video_rendered(self, mode): """ Check that if video is rendered in `mode`. Arguments: mode (str): Video mode, `html5` or `youtube`. Returns: bool: Tells if video is rendered in `mode`. """ selector = self.get_element_selector(VIDEO_MODES[mode]) def _is_element_present(): """ Check if a web element is present in DOM. Returns: tuple: (is_satisfied, result)`, where `is_satisfied` is a boolean indicating whether the promise was satisfied, and `result` is a value to return from the fulfilled `Promise`. """ is_present = self.q(css=selector).present return is_present, is_present return Promise( _is_element_present, 'Video Rendering Failed in {0} mode.'.format(mode)).fulfill()
def is_video_rendered(self, mode): """ Check that if video is rendered in `mode`. Arguments: mode (str): Video mode, one of `html5`, `youtube`, `hls`. Returns: bool: Tells if video is rendered in `mode`. """ selector = self.get_element_selector(VIDEO_MODES[mode]) def _is_element_present(): """ Check if a web element is present in DOM. Returns: tuple: (is_satisfied, result)`, where `is_satisfied` is a boolean indicating whether the promise was satisfied, and `result` is a value to return from the fulfilled `Promise`. """ is_present = self.q(css=selector).present # There is no way to get actual HLS video URL. Becuase in hls video # src attribute is not set to original url. https://github.com/video-dev/hls.js/issues/1052 # http://www.streambox.fr/playlists/x36xhzz/x36xhzz.m3u8 becomes # "blob:https://studio-hlsvideo.sandbox.edx.org/0e2e72e0-904e-d946-9ce0-06c542894cda" if mode == 'hls': href_src = self.q(css=selector).attrs('src')[0] is_present = href_src.startswith('blob:') or href_src.startswith('mediasource:') return is_present, is_present return Promise(_is_element_present, u'Video Rendering Failed in {0} mode.'.format(mode)).fulfill()
def wait_for_errors(self): """Wait for errors to be visible, then return them. """ def _check_func(): """Return success status and any errors that occurred.""" errors = self.errors return (bool(errors), errors) return Promise(_check_func, "Errors are visible").fulfill()
def verify_groups(self, container, active_groups, inactive_groups, verify_missing_groups_not_present=True): """ Check that the groups appear and are correctly categorized as to active and inactive. Also checks that the "add missing groups" button/link is not present unless a value of False is passed for verify_missing_groups_not_present. """ def wait_for_xblocks_to_render(): # First xblock is the container for the page, subtract 1. return (len(active_groups) + len(inactive_groups) == len(container.xblocks) - 1, len(active_groups)) Promise(wait_for_xblocks_to_render, "Number of xblocks on the page are incorrect").fulfill() def check_xblock_names(expected_groups, actual_blocks): self.assertEqual(len(expected_groups), len(actual_blocks)) for idx, expected in enumerate(expected_groups): self.assertEqual(expected, actual_blocks[idx].name) check_xblock_names(active_groups, container.active_xblocks) check_xblock_names(inactive_groups, container.inactive_xblocks) # Verify inactive xblocks appear after active xblocks check_xblock_names(active_groups + inactive_groups, container.xblocks[1:]) if verify_missing_groups_not_present: self.verify_add_missing_groups_button_not_present(container)
def _wait_for(self, check_func, desc, result=False, timeout=200, try_interval=0.2): """ Calls the method provided as an argument until the Promise satisfied or BrokenPromise Arguments: check_func (callable): Function that accepts no arguments and returns a boolean indicating whether the promise is fulfilled. desc (str): Description of the Promise, used in log messages. result (bool): Indicates whether we need a results from Promise or not timeout (float): Maximum number of seconds to wait for the Promise to be satisfied before timing out. """ if result: return Promise(check_func, desc, timeout=timeout, try_interval=try_interval).fulfill() else: return EmptyPromise(check_func, desc, timeout=timeout, try_interval=try_interval).fulfill()
def verify_settings(self): """ Verify that video component has correct default settings. """ def _check_settings_length(): """Check video settings""" query = '.wrapper-comp-setting' settings = self.q(css=query).results if len(DEFAULT_SETTINGS) == len(settings): return True, settings return (False, None) settings = Promise(_check_settings_length, 'All video fields are present').fulfill() for counter, setting in enumerate(settings): is_verified = self._verify_setting_entry( setting, DEFAULT_SETTINGS[counter][0], DEFAULT_SETTINGS[counter][1] ) if not is_verified: return is_verified return True
def is_browser_on_page(self): def _xblock_count(class_name, request_token): return len(self.q(css='{body_selector} .xblock.{class_name}[data-request-token="{request_token}"]'.format( body_selector=XBlockWrapper.BODY_SELECTOR, class_name=class_name, request_token=request_token )).results) def _is_finished_loading(): is_done = False # Get the request token of the first xblock rendered on the page and assume it is correct. data_request_elements = self.q(css='[data-request-token]') if len(data_request_elements) > 0: request_token = data_request_elements.first.attrs('data-request-token')[0] # Then find the number of Studio xblock wrappers on the page with that request token. num_wrappers = len(self.q(css='{} [data-request-token="{}"]'.format(XBlockWrapper.BODY_SELECTOR, request_token)).results) # Wait until all components have been loaded and marked as either initialized or failed. # See: # - common/static/coffee/src/xblock/core.coffee which adds the class "xblock-initialized" # at the end of initializeBlock. # - common/static/js/views/xblock.js which adds the class "xblock-initialization-failed" # if the xblock threw an error while initializing. num_initialized_xblocks = _xblock_count('xblock-initialized', request_token) num_failed_xblocks = _xblock_count('xblock-initialization-failed', request_token) is_done = num_wrappers == (num_initialized_xblocks + num_failed_xblocks) return (is_done, is_done) # First make sure that an element with the view-container class is present on the page, # and then wait for the loading spinner to go away and all the xblocks to be initialized. return ( self.q(css='body.view-container').present and self.q(css='div.ui-loading.is-hidden').present and Promise(_is_finished_loading, 'Finished rendering the xblock wrappers.').fulfill() )
def wait_for_success(self): """Wait for a success message to be visible, then return it.""" def _check_func(): """Return success status and any errors that occurred.""" success = self.success return (bool(success), success) return Promise(_check_func, "Success message is visible").fulfill()
def is_click_handler_registered(self): """ Check if the click handler for the add page button has been registered yet. """ setup_script = """ delete window.click_handler_registered; require(['jquery'], function($) { buttonEvents = $._data( $('.button.new-button.new-tab')[0], 'events' ); window.click_handler_registered = ( buttonEvents && buttonEvents.hasOwnProperty('click') ); }); """ stripped_setup_script = ''.join( [line.strip() for line in setup_script.split('\n')]) self.browser.execute_script(stripped_setup_script) def check_func(): """ Return whether the click handler is registered. """ registered = self.browser.execute_script( 'return window.click_handler_registered;') return registered is not None, registered return Promise(check_func, 'Click Handler Registration updated', timeout=30).fulfill()
def _verify_publish_title(self, unit, expected_title): """ Waits for the publish title to change to the expected value. """ def wait_for_title_change(): return (unit.publish_title == expected_title, unit.publish_title) Promise(wait_for_title_change, "Publish title incorrect. Found '" + unit.publish_title + "'").fulfill()
def is_browser_on_page(self): def _is_correct_page(): is_present = ( self.q(css='div.peer-grading-tools').present or self.q(css='div.grading-panel.current-state').present) return is_present, is_present return Promise(_is_correct_page, 'On the peer grading page.').fulfill()
def verify_add_missing_groups_button_not_present(self, container): """ Checks that the "add missing groups" button/link is not present. """ def missing_groups_button_not_present(): button_present = container.missing_groups_button_present() return (not button_present, not button_present) Promise(missing_groups_button_not_present, "Add missing groups button should not be showing.").fulfill()
def wait_for_errors(self): """Wait for errors to be visible, then return them. """ def _check_func(): """Return success status and any errors that occurred.""" errors = self.errors if not errors: self.q(css=".register-button").click() return (bool(errors), errors) return Promise(_check_func, "Errors are visible").fulfill()
def _get_cohort_options(self): """ Returns the available options in the cohort dropdown, including the initial "Select a cohort". """ def check_func(): """Promise Check Function""" query = self.q(css=self._bounded_selector("#cohort-select option")) return len(query) > 0, query return Promise(check_func, "Waiting for cohort selector to populate").fulfill()
def wait_for_auth_status_message(self): """Wait for a status message to be visible following third_party registration, then return it.""" def _check_func(): """Return third party auth status notice message.""" for selector in ['.already-authenticated-msg p', '.status p']: msg_element = self.q(css=selector) if msg_element.visible: return (True, msg_element.text[0]) return (False, None) return Promise(_check_func, "Result of third party auth is visible").fulfill()
def tab_names(self): """ Return the list of available tab names. If no tab names are available, wait for them to load. Raises a `BrokenPromiseError` if the tab names fail to load. """ def _standard_check_func(): tab_names = self.q(css='ol.course-tabs li a').text return (len(tab_names) > 0, tab_names) def _bootstrap_check_func(): tab_names = self.q(css='ul.navbar-nav li a').text return (len(tab_names) > 0, tab_names) if self.is_using_boostrap_style_tabs(): return Promise(_bootstrap_check_func, "Get all tab names").fulfill() else: return Promise(_standard_check_func, "Get all tab names").fulfill()
def is_browser_on_page(self): def _is_correct_page(): is_present = self.q( css='section.calibration-interstitial-page').present return is_present, is_present return Promise( _is_correct_page, 'On the confirmation page for peer calibration and grading.' ).fulfill()
def get_current_page(self): def check_func(): try: current_page = int(self.q(css="nav.discussion-paginator li.current-page").text[0]) except: return False, None return True, current_page return Promise( check_func, 'discussion-paginator current page has text', timeout=5, ).fulfill()
def verify_publish_title(self, expected_title): """ Waits for the publish title to change to the expected value. """ def wait_for_title_change(): """ Promise function to check publish title. """ return (self.publish_title == expected_title, self.publish_title) Promise(wait_for_title_change, "Publish title incorrect. Found '" + self.publish_title + "'").fulfill()
def webcam_capture(self): """Interact with a webcam capture button.""" self.q(css="#webcam_capture_button").click() def _check_func(): next_step_button_classes = self.q(css="#next_step_button").attrs('class') next_step_button_enabled = 'is-disabled' not in next_step_button_classes return (next_step_button_enabled, next_step_button_classes) # Check that the #next_step_button is enabled before returning control to the caller Promise(_check_func, "The 'Next Step' button is enabled.").fulfill()
def tab_names(self): """ Return the list of available tab names. If no tab names are available, wait for them to load. Raises a `BrokenPromiseError` if the tab names fail to load. """ def _check_func(): tab_names = self.q(css='ol.course-tabs li a').text return (len(tab_names) > 0, tab_names) return Promise(_check_func, "Get all tab names").fulfill()
def wait_for_xblock_initialization(page, xblock_css): """ Wait for the xblock with the given CSS to finish initializing. """ def _is_finished_loading(): # Wait for the xblock javascript to finish initializing is_done = page.browser.execute_script( u"return $('{}').data('initialized')".format(xblock_css)) return is_done, is_done return Promise(_is_finished_loading, 'Finished initializing the xblock.').fulfill()
def get_asynch_feedback(self, assessment_type): """ Wait for and retrieve asynchronous feedback (e.g. from AI, instructor, or peer grading) `assessment_type` is either "ai" or "peer". """ # Because the check function involves fairly complicated actions # (navigating through several screens), we give it more time to complete # than the default. return Promise(self._check_feedback_func(assessment_type), 'Got feedback for {0} problem'.format(assessment_type), timeout=600, try_interval=5).fulfill()
def wait_for_events(self, start_time=None, event_filter=None, number_of_matches=1, timeout=None): """ Wait for `number_of_matches` events to pass the `event_filter`. By default, this will look at all events that have been emitted since the beginning of the setup of this mixin. A custom `start_time` can be specified which will limit the events searched to only those emitted after that time. The `event_filter` is expected to be a callable that allows you to filter the event stream and select particular events of interest. A dictionary `event_filter` is also supported, which simply indicates that the event should match that provided expectation. `number_of_matches` lets us know when enough events have been found and it can move on. The function will not return until this many events have passed the filter. If not enough events are found before a timeout expires, then this will raise a `BrokenPromise` error. Note that this simply states that *at least* this many events have been emitted, so `number_of_matches` is simply a lower bound for the size of `captured_events`. Specifying a custom `timeout` can allow you to extend the default 30 second timeout if necessary. """ if start_time is None: start_time = self.start_time if timeout is None: timeout = 30 def check_for_matching_events(): """Gather any events that have been emitted since `start_time`""" return self.matching_events_were_emitted( start_time=start_time, event_filter=event_filter, number_of_matches=number_of_matches) return Promise( check_for_matching_events, # This is a bit of a hack, Promise calls str(description), so I set the description to an object with a # custom __str__ and have it do some intelligent stuff to generate a helpful error message. CollectedEventsDescription( 'Waiting for {number_of_matches} events to match the filter:\n{event_filter}' .format( number_of_matches=number_of_matches, event_filter=self.event_filter_to_descriptive_string( event_filter), ), functools.partial(self.get_matching_events_from_time, start_time=start_time, event_filter={})), timeout=timeout).fulfill()
def is_browser_on_page(self): def _is_finished_loading(): # Wait for the xblock javascript to finish initializing is_done = self.browser.execute_script( "return $({!r}).data('initialized')".format( self.context_selector)) return (is_done, is_done) # First make sure that an element with the view-container class is present on the page, # and then wait to make sure that the xblock has finished initializing. return ( self.q(css='{} .acid-block'.format(self.context_selector)).present and Promise(_is_finished_loading, 'Finished initializing the xblock.').fulfill())
def is_browser_on_page(self): def _is_finished_loading(): # Wait until all components have been loaded is_done = len(self.q(css=XBlockWrapper.BODY_SELECTOR).results) == len( self.q(css='{} .xblock'.format(XBlockWrapper.BODY_SELECTOR)).results) return (is_done, is_done) # First make sure that an element with the view-container class is present on the page, # and then wait to make sure that the xblocks are all there. return ( self.q(css='body.view-container').present and Promise(_is_finished_loading, 'Finished rendering the xblock wrappers.').fulfill() )
def _wait_for(self, check_func, desc, result=False, timeout=200): """ Calls the method provided as an argument until the return value is not False. Arguments: check_func (callable): Function that accepts no arguments and returns a boolean indicating whether the promise is fulfilled. desc (str): Description of the Promise, used in log messages. timeout (float): Maximum number of seconds to wait for the Promise to be satisfied before timing out. """ if result: return Promise(check_func, desc, timeout=timeout).fulfill() else: return EmptyPromise(check_func, desc, timeout=timeout).fulfill()
def is_browser_on_page(self): def _is_finished_loading(): # Wait until all components have been loaded number_of_leaf_xblocks = len(self.q(css='{} .xblock-author_view,.xblock-student_view'.format(Component.BODY_SELECTOR)).results) is_done = len(self.q(css=Component.BODY_SELECTOR).results) == number_of_leaf_xblocks return (is_done, is_done) # First make sure that an element with the view-unit class is present on the page, # and then wait to make sure that the xblocks are all there return ( self.q(css='body.view-unit').present and Promise(_is_finished_loading, 'Finished rendering the xblocks in the unit.').fulfill() )
def _wait_for(self, check_func, desc, result=False, timeout=30): """ Calls the method provided as an argument until the Promise satisfied or BrokenPromise Arguments: check_func (callable): Promise function to be fulfilled. desc (str): Description of the Promise, used in log messages. result (bool): Indicates whether we need result from Promise or not timeout (float): Maximum number of seconds to wait for the Promise to be satisfied before timing out. """ if result: return Promise(check_func, desc, timeout=timeout).fulfill() else: return EmptyPromise(check_func, desc, timeout=timeout).fulfill()