Files
edx-platform/common/test/acceptance/pages/studio/library.py
Feanil Patel 9cf2f9f298 Run 2to3 -f future . -w
This will remove imports from __future__ that are no longer needed.

https://docs.python.org/3.5/library/2to3.html#2to3fixer-future
2019-12-30 10:35:30 -05:00

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')