This will remove imports from __future__ that are no longer needed. https://docs.python.org/3.5/library/2to3.html#2to3fixer-future
256 lines
8.8 KiB
Python
256 lines
8.8 KiB
Python
"""
|
|
Library edit page in Studio
|
|
"""
|
|
|
|
|
|
import six
|
|
from bok_choy.javascript import js_defined, wait_for_js
|
|
from bok_choy.page_object import PageObject
|
|
from bok_choy.promise import EmptyPromise
|
|
from selenium.webdriver.common.keys import Keys
|
|
from selenium.webdriver.support.select import Select
|
|
|
|
from common.test.acceptance.pages.common.utils import confirm_prompt, sync_on_notification
|
|
from common.test.acceptance.pages.studio import BASE_URL
|
|
from common.test.acceptance.pages.studio.container import XBlockWrapper
|
|
from common.test.acceptance.pages.studio.pagination import PaginatedMixin
|
|
from common.test.acceptance.pages.studio.users import UsersPageMixin
|
|
from common.test.acceptance.pages.studio.utils import HelpMixin
|
|
from common.test.acceptance.pages.studio.xblock_editor import XBlockEditorView
|
|
|
|
|
|
class LibraryPage(PageObject, HelpMixin):
|
|
"""
|
|
Base page for Library pages. Defaults URL to the edit page.
|
|
"""
|
|
def __init__(self, browser, locator):
|
|
super(LibraryPage, self).__init__(browser)
|
|
self.locator = locator
|
|
|
|
@property
|
|
def url(self):
|
|
"""
|
|
URL to the library edit page for the given library.
|
|
"""
|
|
return "{}/library/{}".format(BASE_URL, six.text_type(self.locator))
|
|
|
|
def is_browser_on_page(self):
|
|
"""
|
|
Returns True iff the browser has loaded the library edit page.
|
|
"""
|
|
return self.q(css='body.view-library').present
|
|
|
|
|
|
class LibraryEditPage(LibraryPage, PaginatedMixin, UsersPageMixin):
|
|
"""
|
|
Library edit page in Studio
|
|
"""
|
|
|
|
def get_header_title(self):
|
|
"""
|
|
The text of the main heading (H1) visible on the page.
|
|
"""
|
|
return self.q(css='h1.page-header-title').text
|
|
|
|
def wait_until_ready(self):
|
|
"""
|
|
When the page first loads, there is a loading indicator and most
|
|
functionality is not yet available. This waits for that loading to
|
|
finish.
|
|
|
|
Always call this before using the page. It also disables animations
|
|
for improved test reliability.
|
|
"""
|
|
self.wait_for_ajax()
|
|
super(LibraryEditPage, self).wait_until_ready()
|
|
|
|
@property
|
|
def xblocks(self):
|
|
"""
|
|
Return a list of xblocks loaded on the container page.
|
|
"""
|
|
return self._get_xblocks()
|
|
|
|
def are_previews_showing(self):
|
|
"""
|
|
Determines whether or not previews are showing for XBlocks
|
|
"""
|
|
return all([not xblock.is_placeholder() for xblock in self.xblocks])
|
|
|
|
def toggle_previews(self):
|
|
"""
|
|
Clicks the preview toggling button and waits for the previews to appear or disappear.
|
|
"""
|
|
toggle = not self.are_previews_showing()
|
|
self.q(css='.toggle-preview-button').click()
|
|
EmptyPromise(
|
|
lambda: self.are_previews_showing() == toggle,
|
|
u'Preview is visible: %s' % toggle,
|
|
timeout=30
|
|
).fulfill()
|
|
self.wait_until_ready()
|
|
|
|
def click_duplicate_button(self, xblock_id):
|
|
"""
|
|
Click on the duplicate button for the given XBlock
|
|
"""
|
|
self._action_btn_for_xblock_id(xblock_id, "duplicate").click()
|
|
sync_on_notification(self)
|
|
self.wait_for_ajax()
|
|
|
|
def click_delete_button(self, xblock_id, confirm=True):
|
|
"""
|
|
Click on the delete button for the given XBlock
|
|
"""
|
|
self._action_btn_for_xblock_id(xblock_id, "delete").click()
|
|
if confirm:
|
|
confirm_prompt(self) # this will also sync_on_notification()
|
|
self.wait_for_ajax()
|
|
|
|
def _get_xblocks(self):
|
|
"""
|
|
Create an XBlockWrapper for each XBlock div found on the page.
|
|
"""
|
|
prefix = '.wrapper-xblock.level-page '
|
|
return self.q(css=prefix + XBlockWrapper.BODY_SELECTOR).map(
|
|
lambda el: XBlockWrapper(self.browser, el.get_attribute('data-locator'))
|
|
).results
|
|
|
|
def _div_for_xblock_id(self, xblock_id):
|
|
"""
|
|
Given an XBlock's usage locator as a string, return the WebElement for
|
|
that block's wrapper div.
|
|
"""
|
|
return self.q(css='.wrapper-xblock.level-page .studio-xblock-wrapper').filter(
|
|
lambda el: el.get_attribute('data-locator') == xblock_id
|
|
)
|
|
|
|
def _action_btn_for_xblock_id(self, xblock_id, action):
|
|
"""
|
|
Given an XBlock's usage locator as a string, return one of its action
|
|
buttons.
|
|
action is 'edit', 'duplicate', or 'delete'
|
|
"""
|
|
return self._div_for_xblock_id(xblock_id)[0].find_element_by_css_selector(
|
|
u'.header-actions .{action}-button.action-button'.format(action=action)
|
|
)
|
|
|
|
|
|
class StudioLibraryContentEditor(XBlockEditorView):
|
|
"""
|
|
Library Content XBlock Modal edit window
|
|
"""
|
|
# Labels used to identify the fields on the edit modal:
|
|
LIBRARY_LABEL = "Library"
|
|
COUNT_LABEL = "Count"
|
|
PROBLEM_TYPE_LABEL = "Problem Type"
|
|
|
|
@property
|
|
def library_name(self):
|
|
""" Gets name of library """
|
|
return self.get_selected_option_text(self.LIBRARY_LABEL)
|
|
|
|
@library_name.setter
|
|
def library_name(self, library_name):
|
|
"""
|
|
Select a library from the library select box
|
|
"""
|
|
self.set_select_value(self.LIBRARY_LABEL, library_name)
|
|
EmptyPromise(lambda: self.library_name == library_name, "library_name is updated in modal.").fulfill()
|
|
|
|
@property
|
|
def count(self):
|
|
"""
|
|
Gets value of children count input
|
|
"""
|
|
return int(self.get_setting_element(self.COUNT_LABEL).get_attribute('value'))
|
|
|
|
@count.setter
|
|
def count(self, count):
|
|
"""
|
|
Sets value of children count input
|
|
"""
|
|
count_text = self.get_setting_element(self.COUNT_LABEL)
|
|
count_text.send_keys(Keys.CONTROL, "a")
|
|
count_text.send_keys(Keys.BACK_SPACE)
|
|
count_text.send_keys(count)
|
|
EmptyPromise(lambda: self.count == count, "count is updated in modal.").fulfill()
|
|
|
|
@property
|
|
def capa_type(self):
|
|
"""
|
|
Gets value of CAPA type select
|
|
"""
|
|
return self.get_setting_element(self.PROBLEM_TYPE_LABEL).get_attribute('value')
|
|
|
|
@capa_type.setter
|
|
def capa_type(self, value):
|
|
"""
|
|
Sets value of CAPA type select
|
|
"""
|
|
self.set_select_value(self.PROBLEM_TYPE_LABEL, value)
|
|
EmptyPromise(lambda: self.capa_type == value, "problem type is updated in modal.").fulfill()
|
|
|
|
def set_select_value(self, label, value):
|
|
"""
|
|
Sets the select with given label (display name) to the specified value
|
|
"""
|
|
elem = self.get_setting_element(label)
|
|
select = Select(elem)
|
|
select.select_by_value(value)
|
|
|
|
|
|
@js_defined('window.LibraryContentAuthorView')
|
|
class StudioLibraryContainerXBlockWrapper(XBlockWrapper):
|
|
"""
|
|
Wraps :class:`.container.XBlockWrapper` for use with LibraryContent blocks
|
|
"""
|
|
url = None
|
|
|
|
def is_browser_on_page(self):
|
|
"""
|
|
Returns true iff the library content area has been loaded
|
|
"""
|
|
return self.q(css='article.content-primary').visible
|
|
|
|
def is_finished_loading(self):
|
|
"""
|
|
Returns true iff the Loading indicator is not visible
|
|
"""
|
|
return not self.q(css='div.ui-loading').visible
|
|
|
|
@classmethod
|
|
def from_xblock_wrapper(cls, xblock_wrapper):
|
|
"""
|
|
Factory method: creates :class:`.StudioLibraryContainerXBlockWrapper` from :class:`.container.XBlockWrapper`
|
|
"""
|
|
return cls(xblock_wrapper.browser, xblock_wrapper.locator)
|
|
|
|
def get_body_paragraphs(self):
|
|
"""
|
|
Gets library content body paragraphs
|
|
"""
|
|
return self.q(css=self._bounded_selector(".xblock-message-area p"))
|
|
|
|
@wait_for_js # Wait for the fragment.initialize_js('LibraryContentAuthorView') call to finish
|
|
def refresh_children(self):
|
|
"""
|
|
Click "Update now..." button
|
|
"""
|
|
btn_selector = self._bounded_selector(".library-update-btn")
|
|
self.wait_for_element_presence(btn_selector, 'Update now button is present.')
|
|
self.q(css=btn_selector).first.click()
|
|
|
|
# This causes a reload (see cms/static/xmodule_js/public/js/library_content_edit.js)
|
|
# Check that the ajax request that caused the reload is done.
|
|
self.wait_for_ajax()
|
|
# Then check that we are still on the right page.
|
|
self.wait_for(lambda: self.is_browser_on_page(), 'StudioLibraryContainerXBlockWrapper has reloaded.')
|
|
# Wait longer than the default 60 seconds, because this was intermittently failing on jenkins
|
|
# with the screenshot showing that the Loading indicator was still visible. See TE-745.
|
|
self.wait_for(lambda: self.is_finished_loading(), 'Loading indicator is not visible.', timeout=120)
|
|
|
|
# And wait to make sure the ajax post has finished.
|
|
self.wait_for_ajax()
|
|
self.wait_for_element_absence(btn_selector, 'Wait for the XBlock to finish reloading')
|