713 lines
25 KiB
Python
713 lines
25 KiB
Python
"""
|
|
Container page in Studio
|
|
"""
|
|
|
|
|
|
from bok_choy.page_object import PageObject
|
|
from bok_choy.promise import EmptyPromise, Promise
|
|
|
|
from common.test.acceptance.pages.common.utils import click_css, confirm_prompt
|
|
from common.test.acceptance.pages.studio import BASE_URL
|
|
from common.test.acceptance.pages.studio.utils import HelpMixin, set_input_value_and_save, type_in_codemirror
|
|
from common.test.acceptance.tests.helpers import click_and_wait_for_window
|
|
|
|
|
|
class ContainerPage(PageObject, HelpMixin):
|
|
"""
|
|
Container page in Studio
|
|
"""
|
|
NAME_SELECTOR = '.page-header-title'
|
|
NAME_INPUT_SELECTOR = '.wrapper-xblock-field .xblock-field-input'
|
|
NAME_FIELD_WRAPPER_SELECTOR = '.wrapper-xblock-field'
|
|
ADD_MISSING_GROUPS_SELECTOR = '.notification-action-button[data-notification-action="add-missing-groups"]'
|
|
|
|
def __init__(self, browser, locator):
|
|
super().__init__(browser)
|
|
self.locator = locator
|
|
|
|
@property
|
|
def url(self):
|
|
"""URL to the container page for an xblock."""
|
|
return f"{BASE_URL}/container/{self.locator}"
|
|
|
|
@property
|
|
def name(self): # lint-amnesty, pylint: disable=missing-function-docstring
|
|
titles = self.q(css=self.NAME_SELECTOR).text
|
|
if titles:
|
|
return titles[0]
|
|
else:
|
|
return None
|
|
|
|
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=f'{XBlockWrapper.BODY_SELECTOR} [data-request-token="{request_token}"]').results) # lint-amnesty, pylint: disable=line-too-long
|
|
# Wait until all components have been loaded and marked as either initialized or failed.
|
|
# See:
|
|
# - common/static/js/xblock/core.js 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)
|
|
|
|
def _loading_spinner_hidden():
|
|
""" promise function to check loading spinner state """
|
|
is_spinner_hidden = self.q(css='div.ui-loading.is-hidden').present
|
|
return is_spinner_hidden, is_spinner_hidden
|
|
|
|
# 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
|
|
Promise(_loading_spinner_hidden, 'loading spinner is hidden.').fulfill() and
|
|
Promise(_is_finished_loading, 'Finished rendering the xblock wrappers.').fulfill()
|
|
)
|
|
|
|
def wait_for_component_menu(self):
|
|
"""
|
|
Waits until the menu bar of components is present on the page.
|
|
"""
|
|
EmptyPromise(
|
|
lambda: self.q(css='div.add-xblock-component').present,
|
|
'Wait for the menu of components to be present'
|
|
).fulfill()
|
|
|
|
@property
|
|
def xblocks(self):
|
|
"""
|
|
Return a list of xblocks loaded on the container page.
|
|
"""
|
|
return self._get_xblocks()
|
|
|
|
@property
|
|
def inactive_xblocks(self):
|
|
"""
|
|
Return a list of inactive xblocks loaded on the container page.
|
|
"""
|
|
return self._get_xblocks(".is-inactive ")
|
|
|
|
@property
|
|
def active_xblocks(self):
|
|
"""
|
|
Return a list of active xblocks loaded on the container page.
|
|
"""
|
|
return self._get_xblocks(".is-active ")
|
|
|
|
@property
|
|
def displayed_children(self):
|
|
"""
|
|
Return a list of displayed xblocks loaded on the container page.
|
|
"""
|
|
return self._get_xblocks()[0].children
|
|
|
|
@property
|
|
def publish_title(self):
|
|
"""
|
|
Returns the title as displayed on the publishing sidebar component.
|
|
"""
|
|
return self.q(css='.pub-status').first.text[0]
|
|
|
|
@property
|
|
def release_title(self):
|
|
"""
|
|
Returns the title before the release date in the publishing sidebar component.
|
|
"""
|
|
return self.q(css='.wrapper-release .title').first.text[0]
|
|
|
|
@property
|
|
def release_date(self):
|
|
"""
|
|
Returns the release date of the unit (with ancestor inherited from), as displayed
|
|
in the publishing sidebar component.
|
|
"""
|
|
return self.q(css='.wrapper-release .copy').first.text[0]
|
|
|
|
@property
|
|
def last_saved_text(self):
|
|
"""
|
|
Returns the last saved message as displayed in the publishing sidebar component.
|
|
"""
|
|
return self.q(css='.wrapper-last-draft').first.text[0]
|
|
|
|
@property
|
|
def last_published_text(self):
|
|
"""
|
|
Returns the last published message as displayed in the sidebar.
|
|
"""
|
|
return self.q(css='.wrapper-last-publish').first.text[0]
|
|
|
|
@property
|
|
def currently_visible_to_students(self):
|
|
"""
|
|
Returns True if the unit is marked as currently visible to students
|
|
(meaning that a warning is being displayed).
|
|
"""
|
|
warnings = self.q(css='.container-message .warning')
|
|
if not warnings.is_present():
|
|
return False
|
|
warning_text = warnings.first.text[0]
|
|
return warning_text == "Caution: The last published version of this unit is live. By publishing changes you will change the student experience." # lint-amnesty, pylint: disable=line-too-long
|
|
|
|
def shows_inherited_staff_lock(self, parent_type=None, parent_name=None): # lint-amnesty, pylint: disable=unused-argument
|
|
"""
|
|
Returns True if the unit inherits staff lock from a section or subsection.
|
|
"""
|
|
return self.q(css='.bit-publishing .wrapper-visibility .copy .inherited-from').visible
|
|
|
|
@property
|
|
def sidebar_visibility_message(self):
|
|
"""
|
|
Returns the text within the sidebar visibility section.
|
|
"""
|
|
return self.q(css='.bit-publishing .wrapper-visibility').first.text[0]
|
|
|
|
@property
|
|
def publish_action(self):
|
|
"""
|
|
Returns the link for publishing a unit.
|
|
"""
|
|
self.scroll_to_element('.action-publish')
|
|
return self.q(css='.action-publish').first
|
|
|
|
def publish(self):
|
|
"""
|
|
Publishes the container.
|
|
"""
|
|
self.scroll_to_element('.action-publish')
|
|
click_css(self, '.action-publish', 0, require_notification=False) # lint-amnesty, pylint: disable=unexpected-keyword-arg
|
|
|
|
def discard_changes(self):
|
|
"""
|
|
Discards draft changes (which will then re-render the page).
|
|
"""
|
|
self.scroll_to_element('a.action-discard')
|
|
click_css(self, 'a.action-discard', 0, require_notification=False) # lint-amnesty, pylint: disable=unexpected-keyword-arg
|
|
confirm_prompt(self)
|
|
self.wait_for_ajax()
|
|
|
|
@property
|
|
def xblock_titles(self):
|
|
"""
|
|
Get titles of x-block present on the page.
|
|
Returns:
|
|
list: A list of X-block titles
|
|
"""
|
|
return self.q(css='.wrapper-xblock .level-element .header-details').text
|
|
|
|
@property
|
|
def content_html(self):
|
|
"""
|
|
Gets the html of HTML module
|
|
Returns:
|
|
list: A list containing inner HTMl
|
|
"""
|
|
self.wait_for_element_visibility('.xmodule_HtmlBlock', 'Xblock content is visible')
|
|
html = self.q(css='.xmodule_HtmlBlock').html
|
|
html = html[0].strip()
|
|
return html
|
|
|
|
@property
|
|
def is_staff_locked(self):
|
|
""" Returns True if staff lock is currently enabled, False otherwise """
|
|
for attr in self.q(css='a.action-staff-lock>.fa').attrs('class'):
|
|
if 'fa-check-square-o' in attr:
|
|
return True
|
|
return False
|
|
|
|
def toggle_staff_lock(self, inherits_staff_lock=False):
|
|
"""
|
|
Toggles "hide from students" which enables or disables a staff-only lock.
|
|
|
|
Returns True if the lock is now enabled, else False.
|
|
"""
|
|
was_locked_initially = self.is_staff_locked
|
|
if not was_locked_initially:
|
|
self.q(css='a.action-staff-lock').first.click()
|
|
else:
|
|
click_css(self, 'a.action-staff-lock', 0, require_notification=False) # lint-amnesty, pylint: disable=unexpected-keyword-arg
|
|
if not inherits_staff_lock:
|
|
confirm_prompt(self)
|
|
self.wait_for_ajax()
|
|
return not was_locked_initially
|
|
|
|
def view_published_version(self):
|
|
"""
|
|
Clicks "View Live Version", which will open the published version of the unit page in the LMS.
|
|
|
|
Switches the browser to the newly opened LMS window.
|
|
"""
|
|
click_and_wait_for_window(self, self.q(css='.button-view').first)
|
|
self._switch_to_lms()
|
|
|
|
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 preview(self):
|
|
"""
|
|
Clicks "Preview", which will open the draft version of the unit page in the LMS.
|
|
|
|
Switches the browser to the newly opened LMS window.
|
|
"""
|
|
self.q(css='.button-preview').first.click()
|
|
self._switch_to_lms()
|
|
|
|
def _switch_to_lms(self):
|
|
"""
|
|
Assumes LMS has opened-- switches to that window.
|
|
"""
|
|
browser_window_handles = self.browser.window_handles
|
|
# Switch to browser window that shows HTML Unit in LMS
|
|
# The last handle represents the latest windows opened
|
|
self.browser.switch_to_window(browser_window_handles[-1])
|
|
|
|
def _get_xblocks(self, prefix=""):
|
|
return self.q(css=prefix + XBlockWrapper.BODY_SELECTOR).map(
|
|
lambda el: XBlockWrapper(self.browser, el.get_attribute('data-locator'))).results
|
|
|
|
def duplicate(self, source_index):
|
|
"""
|
|
Duplicate the item with index source_index (based on vertical placement in page).
|
|
"""
|
|
click_css(self, '.duplicate-button', source_index)
|
|
|
|
def delete(self, source_index):
|
|
"""
|
|
Delete the item with index source_index (based on vertical placement in page).
|
|
Only visible items are counted in the source_index.
|
|
The index of the first item is 0.
|
|
"""
|
|
# Click the delete button
|
|
click_css(self, '.delete-button', source_index, require_notification=False) # lint-amnesty, pylint: disable=unexpected-keyword-arg
|
|
# Click the confirmation dialog button
|
|
confirm_prompt(self)
|
|
|
|
def edit(self):
|
|
"""
|
|
Clicks the "edit" button for the first component on the page.
|
|
"""
|
|
return _click_edit(self, '.edit-button', '.xblock-studio_view')
|
|
|
|
def edit_visibility(self):
|
|
"""
|
|
Clicks the edit visibility button for this container.
|
|
"""
|
|
return _click_edit(self, '.access-button', '.xblock-visibility_view')
|
|
|
|
def verify_confirmation_message(self, message, verify_hidden=False):
|
|
"""
|
|
Verify for confirmation message is present or hidden.
|
|
"""
|
|
def _verify_message():
|
|
""" promise function to check confirmation message state """
|
|
text = self.q(css='#page-alert .alert.confirmation #alert-confirmation-title').text
|
|
return text and message not in text[0] if verify_hidden else text and message in text[0]
|
|
|
|
self.wait_for(_verify_message, description='confirmation message {status}'.format(
|
|
status='hidden' if verify_hidden else 'present'
|
|
))
|
|
|
|
def click_undo_move_link(self):
|
|
"""
|
|
Click undo move link.
|
|
"""
|
|
click_css(self, '#page-alert .alert.confirmation .nav-actions .action-primary')
|
|
|
|
def click_take_me_there_link(self):
|
|
"""
|
|
Click take me there link.
|
|
"""
|
|
click_css(self, '#page-alert .alert.confirmation .nav-actions .action-secondary', require_notification=False) # lint-amnesty, pylint: disable=unexpected-keyword-arg
|
|
|
|
def add_missing_groups(self):
|
|
"""
|
|
Click the "add missing groups" link.
|
|
Note that this does an ajax call.
|
|
"""
|
|
self.q(css=self.ADD_MISSING_GROUPS_SELECTOR).first.click()
|
|
self.wait_for_ajax()
|
|
|
|
# Wait until all xblocks rendered.
|
|
self.wait_for_page()
|
|
|
|
def missing_groups_button_present(self):
|
|
"""
|
|
Returns True if the "add missing groups" button is present.
|
|
"""
|
|
return self.q(css=self.ADD_MISSING_GROUPS_SELECTOR).present
|
|
|
|
def get_xblock_information_message(self):
|
|
"""
|
|
Returns an information message for the container page.
|
|
"""
|
|
return self.q(css=".xblock-message.information").first.text[0]
|
|
|
|
def get_xblock_access_message(self):
|
|
"""
|
|
Returns a message detailing the access to the specified unit
|
|
"""
|
|
access_message = self.q(css=".access-message").first
|
|
if access_message:
|
|
return access_message.text[0]
|
|
else:
|
|
return ""
|
|
|
|
def is_inline_editing_display_name(self):
|
|
"""
|
|
Return whether this container's display name is in its editable form.
|
|
"""
|
|
return "is-editing" in self.q(css=self.NAME_FIELD_WRAPPER_SELECTOR).first.attrs("class")[0]
|
|
|
|
def get_category_tab_names(self, category_type):
|
|
"""
|
|
Returns list of tab name in a category.
|
|
|
|
Arguments:
|
|
category_type (str): category type
|
|
|
|
Returns:
|
|
list
|
|
"""
|
|
self.q(css=f'.add-xblock-component-button[data-type={category_type}]').first.click()
|
|
return self.q(css=f'.{category_type}-type-tabs>li>a').text
|
|
|
|
def get_category_tab_components(self, category_type, tab_index):
|
|
"""
|
|
Return list of component names in a tab in a category.
|
|
|
|
Arguments:
|
|
category_type (str): category type
|
|
tab_index (int): tab index in a category
|
|
|
|
Returns:
|
|
list
|
|
"""
|
|
css = '#tab{tab_index} button[data-category={category_type}] span'.format(
|
|
tab_index=tab_index,
|
|
category_type=category_type
|
|
)
|
|
return self.q(css=css).html
|
|
|
|
def set_name(self, name):
|
|
"""
|
|
Set the name of the unit.
|
|
"""
|
|
set_input_value_and_save(self, self.NAME_INPUT_SELECTOR, name)
|
|
self.wait_for_ajax()
|
|
|
|
|
|
class XBlockWrapper(PageObject):
|
|
"""
|
|
A PageObject representing a wrapper around an XBlock child shown on the Studio container page.
|
|
"""
|
|
url = None
|
|
BODY_SELECTOR = '.studio-xblock-wrapper'
|
|
NAME_SELECTOR = '.xblock-display-name'
|
|
VALIDATION_SELECTOR = '.xblock-message.validation'
|
|
COMPONENT_BUTTONS = {
|
|
'basic_tab': '.editor-tabs li.inner_tab_wrap:nth-child(1) > a',
|
|
'advanced_tab': '.editor-tabs li.inner_tab_wrap:nth-child(2) > a',
|
|
'settings_tab': '.editor-modes .settings-button',
|
|
'save_settings': '.action-save',
|
|
}
|
|
|
|
def __init__(self, browser, locator):
|
|
super().__init__(browser)
|
|
self.locator = locator
|
|
|
|
def is_browser_on_page(self):
|
|
return self.q(css=f'{self.BODY_SELECTOR}[data-locator="{self.locator}"]').present
|
|
|
|
def _bounded_selector(self, selector):
|
|
"""
|
|
Return `selector`, but limited to this particular `CourseOutlineChild` context
|
|
"""
|
|
return '{}[data-locator="{}"] {}'.format(
|
|
self.BODY_SELECTOR,
|
|
self.locator,
|
|
selector
|
|
)
|
|
|
|
@property
|
|
def student_content(self):
|
|
"""
|
|
Returns the text content of the xblock as displayed on the container page.
|
|
"""
|
|
return self.q(css=self._bounded_selector('.xblock-student_view'))[0].text
|
|
|
|
@property
|
|
def author_content(self):
|
|
"""
|
|
Returns the text content of the xblock as displayed on the container page.
|
|
(For blocks which implement a distinct author_view).
|
|
"""
|
|
return self.q(css=self._bounded_selector('.xblock-author_view'))[0].text
|
|
|
|
@property
|
|
def name(self): # lint-amnesty, pylint: disable=missing-function-docstring
|
|
titles = self.q(css=self._bounded_selector(self.NAME_SELECTOR)).text
|
|
if titles:
|
|
return titles[0]
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def children(self):
|
|
"""
|
|
Will return any first-generation descendant xblocks of this xblock.
|
|
"""
|
|
descendants = self.q(css=self._bounded_selector(self.BODY_SELECTOR)).filter(lambda el: el.is_displayed()).map(
|
|
lambda el: XBlockWrapper(self.browser, el.get_attribute('data-locator'))).results
|
|
|
|
# Now remove any non-direct descendants.
|
|
grandkids = []
|
|
for descendant in descendants:
|
|
grandkids.extend(descendant.children)
|
|
|
|
grand_locators = [grandkid.locator for grandkid in grandkids]
|
|
return [descendant for descendant in descendants if descendant.locator not in grand_locators]
|
|
|
|
@property
|
|
def has_validation_message(self):
|
|
""" Is a validation warning/error/message shown? """
|
|
return self.q(css=self._bounded_selector(self.VALIDATION_SELECTOR)).present
|
|
|
|
def _validation_paragraph(self, css_class):
|
|
""" Helper method to return the <p> element of a validation warning """
|
|
return self.q(css=self._bounded_selector(f'{self.VALIDATION_SELECTOR} p.{css_class}'))
|
|
|
|
@property
|
|
def has_validation_warning(self):
|
|
""" Is a validation warning shown? """
|
|
return self._validation_paragraph('warning').present
|
|
|
|
@property
|
|
def has_validation_error(self):
|
|
""" Is a validation error shown? """
|
|
return self._validation_paragraph('error').present
|
|
|
|
@property
|
|
def has_validation_not_configured_warning(self):
|
|
""" Is a validation "not configured" message shown? """
|
|
return self._validation_paragraph('not-configured').present
|
|
|
|
@property
|
|
def validation_warning_text(self):
|
|
""" Get the text of the validation warning. """
|
|
return self._validation_paragraph('warning').text[0]
|
|
|
|
@property
|
|
def validation_error_text(self):
|
|
""" Get the text of the validation error. """
|
|
return self._validation_paragraph('error').text[0]
|
|
|
|
@property
|
|
def validation_error_messages(self):
|
|
return self.q(css=self._bounded_selector(f'{self.VALIDATION_SELECTOR} .xblock-message-item.error')).text
|
|
|
|
@property
|
|
def validation_not_configured_warning_text(self):
|
|
""" Get the text of the validation "not configured" message. """
|
|
return self._validation_paragraph('not-configured').text[0]
|
|
|
|
@property
|
|
def preview_selector(self):
|
|
return self._bounded_selector('.xblock-student_view,.xblock-author_view')
|
|
|
|
@property
|
|
def has_group_visibility_set(self):
|
|
return self.q(css=self._bounded_selector('.wrapper-xblock.has-group-visibility-set')).is_present()
|
|
|
|
@property
|
|
def has_duplicate_button(self):
|
|
"""
|
|
Returns true if this xblock has a 'duplicate' button
|
|
"""
|
|
return self.q(css=self._bounded_selector('.duplicate-button'))
|
|
|
|
@property
|
|
def has_delete_button(self):
|
|
"""
|
|
Returns true if this xblock has a 'delete' button
|
|
"""
|
|
return self.q(css=self._bounded_selector('.delete-button'))
|
|
|
|
@property
|
|
def has_edit_visibility_button(self):
|
|
"""
|
|
Returns true if this xblock has an 'edit visibility' button
|
|
:return:
|
|
"""
|
|
return self.q(css=self._bounded_selector('.access-button')).is_present()
|
|
|
|
@property
|
|
def has_move_modal_button(self):
|
|
"""
|
|
Returns True if this xblock has move modal button else False
|
|
"""
|
|
return self.q(css=self._bounded_selector('.move-button')).is_present()
|
|
|
|
@property
|
|
def get_partition_group_message(self):
|
|
"""
|
|
Returns the message about user partition group visibility, shown under the display name
|
|
(if not present, returns None).
|
|
"""
|
|
message = self.q(css=self._bounded_selector('.xblock-group-visibility-label'))
|
|
return None if len(message) == 0 else message.first.text[0]
|
|
|
|
def go_to_container(self):
|
|
"""
|
|
Open the container page linked to by this xblock, and return
|
|
an initialized :class:`.ContainerPage` for that xblock.
|
|
"""
|
|
return ContainerPage(self.browser, self.locator).visit()
|
|
|
|
def edit(self):
|
|
"""
|
|
Clicks the "edit" button for this xblock.
|
|
"""
|
|
return _click_edit(self, '.edit-button', '.xblock-studio_view', self._bounded_selector)
|
|
|
|
def edit_visibility(self):
|
|
"""
|
|
Clicks the edit visibility button for this xblock.
|
|
"""
|
|
return _click_edit(self, '.access-button', '.xblock-visibility_view', self._bounded_selector)
|
|
|
|
def open_advanced_tab(self):
|
|
"""
|
|
Click on Advanced Tab.
|
|
"""
|
|
self._click_button('advanced_tab')
|
|
|
|
def open_basic_tab(self):
|
|
"""
|
|
Click on Basic Tab.
|
|
"""
|
|
self._click_button('basic_tab')
|
|
|
|
def open_settings_tab(self):
|
|
"""
|
|
If editing, click on the "Settings" tab
|
|
"""
|
|
self._click_button('settings_tab')
|
|
|
|
def open_move_modal(self):
|
|
"""
|
|
Opens the move modal.
|
|
"""
|
|
click_css(self, '.move-button', require_notification=False) # lint-amnesty, pylint: disable=unexpected-keyword-arg
|
|
self.wait_for(
|
|
lambda: self.q(css='.modal-window.move-modal').visible, description='move modal is visible'
|
|
)
|
|
|
|
def set_field_val(self, field_display_name, field_value):
|
|
"""
|
|
If editing, set the value of a field.
|
|
"""
|
|
selector = f'{self.editor_selector} li.field label:contains("{field_display_name}") + input'
|
|
script = "$(arguments[0]).val(arguments[1]).change();"
|
|
self.browser.execute_script(script, selector, field_value)
|
|
|
|
def reset_field_val(self, field_display_name):
|
|
"""
|
|
If editing, reset the value of a field to its default.
|
|
"""
|
|
scope = f'{self.editor_selector} li.field label:contains("{field_display_name}")'
|
|
script = "$(arguments[0]).siblings('.setting-clear').click();"
|
|
self.browser.execute_script(script, scope)
|
|
|
|
def set_codemirror_text(self, text, index=0):
|
|
"""
|
|
Set the text of a CodeMirror editor that is part of this xblock's settings.
|
|
"""
|
|
type_in_codemirror(self, index, text, find_prefix=f'$("{self.editor_selector}").find')
|
|
|
|
def set_license(self, license_type):
|
|
"""
|
|
Uses the UI to set the course's license to the given license_type (str)
|
|
"""
|
|
css_selector = (
|
|
"ul.license-types li[data-license={license_type}] button"
|
|
).format(license_type=license_type)
|
|
self.wait_for_element_presence(
|
|
css_selector,
|
|
f"{license_type} button is present"
|
|
)
|
|
self.q(css=css_selector).click()
|
|
|
|
def save_settings(self):
|
|
"""
|
|
Click on settings Save button.
|
|
"""
|
|
self._click_button('save_settings')
|
|
|
|
@property
|
|
def editor_selector(self):
|
|
return '.xblock-studio_view'
|
|
|
|
def _click_button(self, button_name):
|
|
"""
|
|
Click on a button as specified by `button_name`
|
|
|
|
Arguments:
|
|
button_name (str): button name
|
|
|
|
"""
|
|
self.q(css=self.COMPONENT_BUTTONS[button_name]).first.click()
|
|
self.wait_for_ajax()
|
|
|
|
def go_to_group_configuration_page(self):
|
|
"""
|
|
Go to the Group Configuration used by the component.
|
|
"""
|
|
self.q(css=self._bounded_selector('span.message-text a')).first.click()
|
|
|
|
def is_placeholder(self):
|
|
"""
|
|
Checks to see if the XBlock is rendered as a placeholder without a preview.
|
|
"""
|
|
return not self.q(css=self._bounded_selector('.wrapper-xblock article')).present
|
|
|
|
@property
|
|
def group_configuration_link_name(self):
|
|
"""
|
|
Get Group Configuration name from link.
|
|
"""
|
|
return self.q(css=self._bounded_selector('span.message-text a')).first.text[0]
|
|
|
|
|
|
def _click_edit(page_object, button_css, view_css, bounded_selector=lambda x: x):
|
|
"""
|
|
Click on the first editing button found and wait for the Studio editor to be present.
|
|
"""
|
|
page_object.q(css=bounded_selector(button_css)).first.click()
|
|
EmptyPromise(
|
|
lambda: page_object.q(css=view_css).present,
|
|
'Wait for the Studio editor to be present'
|
|
).fulfill()
|
|
|
|
return page_object
|