Backbone version of the course outline page
STUD-1726
This commit is contained in:
@@ -6,12 +6,54 @@ from bok_choy.promise import EmptyPromise
|
||||
|
||||
from .course_page import CoursePage
|
||||
from .container import ContainerPage
|
||||
from .utils import set_input_value_and_save
|
||||
|
||||
|
||||
class CourseOutlineContainer(object):
|
||||
class CourseOutlineItem(object):
|
||||
"""
|
||||
A mixin class for any :class:`PageObject` shown in a course outline.
|
||||
"""
|
||||
BODY_SELECTOR = None
|
||||
NAME_SELECTOR = '.xblock-title .xblock-field-value'
|
||||
NAME_INPUT_SELECTOR = '.xblock-title .xblock-field-input'
|
||||
|
||||
def __repr__(self):
|
||||
return "{}(<browser>, {!r})".format(self.__class__.__name__, self.locator)
|
||||
|
||||
def _bounded_selector(self, selector):
|
||||
"""
|
||||
Returns `selector`, but limited to this particular `CourseOutlineItem` context
|
||||
"""
|
||||
return '{}[data-locator="{}"] {}'.format(
|
||||
self.BODY_SELECTOR,
|
||||
self.locator,
|
||||
selector
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Returns the display name of this object.
|
||||
"""
|
||||
name_element = self.q(css=self._bounded_selector(self.NAME_SELECTOR)).first
|
||||
if name_element:
|
||||
return name_element.text[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def change_name(self, new_name):
|
||||
"""
|
||||
Changes the container's name.
|
||||
"""
|
||||
self.q(css=self._bounded_selector(self.NAME_SELECTOR)).first.click()
|
||||
set_input_value_and_save(self, self._bounded_selector(self.NAME_INPUT_SELECTOR), new_name)
|
||||
self.wait_for_ajax()
|
||||
|
||||
|
||||
class CourseOutlineContainer(CourseOutlineItem):
|
||||
"""
|
||||
A mixin to a CourseOutline page object that adds the ability to load
|
||||
a child page object by title.
|
||||
a child page object by title or by index.
|
||||
|
||||
CHILD_CLASS must be a :class:`CourseOutlineChild` subclass.
|
||||
"""
|
||||
@@ -33,15 +75,24 @@ class CourseOutlineContainer(object):
|
||||
).attrs('data-locator')[0]
|
||||
)
|
||||
|
||||
def child_at(self, index, child_class=None):
|
||||
"""
|
||||
Returns the child at the specified index.
|
||||
:type self: object
|
||||
"""
|
||||
if not child_class:
|
||||
child_class = self.CHILD_CLASS
|
||||
|
||||
class CourseOutlineChild(PageObject):
|
||||
"""
|
||||
A mixin to a CourseOutline page object that will be used as a child of
|
||||
:class:`CourseOutlineContainer`.
|
||||
"""
|
||||
NAME_SELECTOR = None
|
||||
BODY_SELECTOR = None
|
||||
return child_class(
|
||||
self.browser,
|
||||
self.q(css=child_class.BODY_SELECTOR).attrs('data-locator')[index]
|
||||
)
|
||||
|
||||
|
||||
class CourseOutlineChild(PageObject, CourseOutlineItem):
|
||||
"""
|
||||
A page object that will be used as a child of :class:`CourseOutlineContainer`.
|
||||
"""
|
||||
def __init__(self, browser, locator):
|
||||
super(CourseOutlineChild, self).__init__(browser)
|
||||
self.locator = locator
|
||||
@@ -49,38 +100,14 @@ class CourseOutlineChild(PageObject):
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='{}[data-locator="{}"]'.format(self.BODY_SELECTOR, self.locator)).present
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Return the display name of this object.
|
||||
"""
|
||||
titles = self.q(css=self._bounded_selector(self.NAME_SELECTOR)).text
|
||||
if titles:
|
||||
return titles[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return "{}(<browser>, {!r})".format(self.__class__.__name__, self.locator)
|
||||
|
||||
def _bounded_selector(self, selector):
|
||||
"""
|
||||
Return `selector`, but limited to this particular `CourseOutlineChild` context
|
||||
"""
|
||||
return '{}[data-locator="{}"] {}'.format(
|
||||
self.BODY_SELECTOR,
|
||||
self.locator,
|
||||
selector
|
||||
)
|
||||
|
||||
|
||||
class CourseOutlineUnit(CourseOutlineChild):
|
||||
"""
|
||||
PageObject that wraps a unit link on the Studio Course Overview page.
|
||||
"""
|
||||
url = None
|
||||
BODY_SELECTOR = '.courseware-unit'
|
||||
NAME_SELECTOR = '.unit-name'
|
||||
BODY_SELECTOR = '.outline-item-unit'
|
||||
NAME_SELECTOR = '.xblock-title a'
|
||||
|
||||
def go_to(self):
|
||||
"""
|
||||
@@ -88,7 +115,7 @@ class CourseOutlineUnit(CourseOutlineChild):
|
||||
an initialized :class:`.ContainerPage` for that unit.
|
||||
"""
|
||||
return ContainerPage(self.browser, self.locator).visit()
|
||||
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css=self.BODY_SELECTOR).present
|
||||
|
||||
@@ -99,8 +126,7 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
|
||||
"""
|
||||
url = None
|
||||
|
||||
BODY_SELECTOR = '.courseware-subsection'
|
||||
NAME_SELECTOR = '.subsection-name-value'
|
||||
BODY_SELECTOR = '.outline-item-subsection'
|
||||
CHILD_CLASS = CourseOutlineUnit
|
||||
|
||||
def unit(self, title):
|
||||
@@ -116,15 +142,12 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
|
||||
self.browser.execute_script("jQuery.fx.off = true;")
|
||||
|
||||
def subsection_expanded():
|
||||
return all(
|
||||
self.q(css=self._bounded_selector('.new-unit-item'))
|
||||
.map(lambda el: el.is_displayed())
|
||||
.results
|
||||
)
|
||||
add_button = self.q(css=self._bounded_selector('.add-button')).first.results
|
||||
return add_button and add_button[0].is_displayed()
|
||||
|
||||
currently_expanded = subsection_expanded()
|
||||
|
||||
self.q(css=self._bounded_selector('.expand-collapse')).first.click()
|
||||
self.q(css=self._bounded_selector('.ui-toggle-expansion')).first.click()
|
||||
|
||||
EmptyPromise(
|
||||
lambda: subsection_expanded() != currently_expanded,
|
||||
@@ -139,8 +162,7 @@ class CourseOutlineSection(CourseOutlineChild, CourseOutlineContainer):
|
||||
:class`.PageObject` that wraps a section block on the Studio Course Overview page.
|
||||
"""
|
||||
url = None
|
||||
BODY_SELECTOR = '.courseware-section'
|
||||
NAME_SELECTOR = '.section-name-span'
|
||||
BODY_SELECTOR = '.outline-item-section'
|
||||
CHILD_CLASS = CourseOutlineSubsection
|
||||
|
||||
def subsection(self, title):
|
||||
@@ -165,6 +187,12 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
|
||||
Return the :class:`.CourseOutlineSection` with the title `title`.
|
||||
"""
|
||||
return self.child(title)
|
||||
|
||||
def section_at(self, index):
|
||||
"""
|
||||
Returns the :class:`.CourseOutlineSection` at the specified index.
|
||||
"""
|
||||
return self.child_at(index)
|
||||
|
||||
def click_section_name(self, parent_css=''):
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""
|
||||
Utility methods useful for Studio page tests.
|
||||
"""
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from bok_choy.promise import EmptyPromise
|
||||
|
||||
from ...tests.helpers import disable_animations
|
||||
|
||||
|
||||
@@ -122,3 +125,17 @@ def confirm_prompt(page, cancel=False):
|
||||
confirmation_button_css = '.prompt .action-' + ('secondary' if cancel else 'primary')
|
||||
page.wait_for_element_visibility(confirmation_button_css, 'Confirmation button is visible')
|
||||
click_css(page, confirmation_button_css, require_notification=(not cancel))
|
||||
|
||||
|
||||
def set_input_value_and_save(page, css, value):
|
||||
"""
|
||||
Sets the text field with given label (display name) to the specified value, and presses Save.
|
||||
"""
|
||||
input_element = page.q(css=css).results[0]
|
||||
# Click in the input to give it the focus
|
||||
action = ActionChains(page.browser).click(input_element)
|
||||
# Delete all of the characters that are currently there
|
||||
for _x in range(0, len(input_element.get_attribute('value'))):
|
||||
action = action.send_keys(Keys.BACKSPACE)
|
||||
# Send the new text, then hit the enter key so that the change event is triggered).
|
||||
action.send_keys(value).send_keys(Keys.ENTER).perform()
|
||||
|
||||
@@ -142,23 +142,25 @@ class CourseSectionTest(StudioCourseTest):
|
||||
"""
|
||||
Check that section name is editable on course outline page.
|
||||
"""
|
||||
section_name = self.course_outline_page.get_section_name()[0]
|
||||
self.assertEqual(section_name, "Test Section")
|
||||
self.course_outline_page.change_section_name("Test Section New")
|
||||
section_name = self.course_outline_page.get_section_name(page_refresh=True)[0]
|
||||
self.assertEqual(section_name, "Test Section New")
|
||||
new_name = u"Test Section New"
|
||||
section = self.course_outline_page.section_at(0)
|
||||
self.assertEqual(section.name, u"Test Section")
|
||||
section.change_name(new_name)
|
||||
self.browser.refresh()
|
||||
self.assertEqual(section.name, new_name)
|
||||
|
||||
def test_section_name_not_editable_inside_modal(self):
|
||||
"""
|
||||
Check that section name is not editable inside "Section Release Date" modal on course outline page.
|
||||
"""
|
||||
parent_css='div.modal-window'
|
||||
self.course_outline_page.click_release_date()
|
||||
section_name = self.course_outline_page.get_section_name(parent_css)[0]
|
||||
self.assertEqual(section_name, '"Test Section"')
|
||||
self.course_outline_page.click_section_name(parent_css)
|
||||
section_name_edit_form = self.course_outline_page.section_name_edit_form_present(parent_css)
|
||||
self.assertFalse(section_name_edit_form)
|
||||
# TODO: re-enable when release date support is added back
|
||||
# def test_section_name_not_editable_inside_modal(self):
|
||||
# """
|
||||
# Check that section name is not editable inside "Section Release Date" modal on course outline page.
|
||||
# """
|
||||
# parent_css='div.modal-window'
|
||||
# self.course_outline_page.click_release_date()
|
||||
# section_name = self.course_outline_page.get_section_name(parent_css)[0]
|
||||
# self.assertEqual(section_name, '"Test Section"')
|
||||
# self.course_outline_page.click_section_name(parent_css)
|
||||
# section_name_edit_form = self.course_outline_page.section_name_edit_form_present(parent_css)
|
||||
# self.assertFalse(section_name_edit_form)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
|
||||
Reference in New Issue
Block a user