Commit 2ce1540a by zubair-arbi

update/cleanup code + move verify_ordering and drag method to studio utils

parent b6a23306
......@@ -6,9 +6,7 @@ from bok_choy.page_object import PageObject
from bok_choy.promise import Promise, EmptyPromise
from . import BASE_URL
from selenium.webdriver.common.action_chains import ActionChains
from utils import click_css, wait_for_notification, confirm_prompt
from utils import click_css, confirm_prompt
class ContainerPage(PageObject):
......@@ -220,26 +218,6 @@ class ContainerPage(PageObject):
return self.q(css=prefix + XBlockWrapper.BODY_SELECTOR).map(
lambda el: XBlockWrapper(self.browser, el.get_attribute('data-locator'))).results
def drag(self, source_index, target_index):
"""
Gets the drag handle with index source_index (relative to the vertical layout of the page)
and drags it to the location of the drag handle with target_index.
This should drag the element with the source_index drag handle BEFORE the
one with the target_index drag handle.
"""
draggables = self.q(css='.drag-handle')
source = draggables[source_index]
target = draggables[target_index]
action = ActionChains(self.browser)
# When dragging before the target element, must take into account that the placeholder
# will appear in the place where the target used to be.
placeholder_height = 40
action.click_and_hold(source).move_to_element_with_offset(
target, 0, placeholder_height
).release().perform()
wait_for_notification(self)
def duplicate(self, source_index):
"""
Duplicate the item with index source_index (based on vertical placement in page).
......
......@@ -12,7 +12,7 @@ from selenium.webdriver.common.action_chains import ActionChains
from .course_page import CoursePage
from .container import ContainerPage
from .utils import set_input_value_and_save, set_input_value, click_css, confirm_prompt, wait_for_notification
from .utils import set_input_value_and_save, set_input_value, click_css, confirm_prompt
class CourseOutlineItem(object):
......@@ -256,6 +256,9 @@ class CourseOutlineChild(PageObject, CourseOutlineItem):
"""
A page object that will be used as a child of :class:`CourseOutlineContainer`.
"""
url = None
BODY_SELECTOR = '.outline-item'
def __init__(self, browser, locator):
super(CourseOutlineChild, self).__init__(browser)
self.locator = locator
......@@ -270,6 +273,39 @@ class CourseOutlineChild(PageObject, CourseOutlineItem):
click_css(self, self._bounded_selector('.delete-button'), require_notification=False)
confirm_prompt(self, cancel)
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 name(self):
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 items of this item.
"""
descendants = self.q(css=self._bounded_selector(self.BODY_SELECTOR)).map(
lambda el: CourseOutlineChild(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 not descendant.locator in grand_locators]
class CourseOutlineUnit(CourseOutlineChild):
"""
......@@ -289,8 +325,11 @@ class CourseOutlineUnit(CourseOutlineChild):
def is_browser_on_page(self):
return self.q(css=self.BODY_SELECTOR).present
def children(self):
return self.q(css=self._bounded_selector(self.BODY_SELECTOR)).map(
lambda el: CourseOutlineUnit(self.browser, el.get_attribute('data-locator'))).results
class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
class CourseOutlineSubsection(CourseOutlineContainer, CourseOutlineChild):
"""
:class`.PageObject` that wraps a subsection block on the Studio Course Outline page.
"""
......@@ -326,7 +365,7 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
self.q(css=self._bounded_selector(self.ADD_BUTTON_SELECTOR)).click()
class CourseOutlineSection(CourseOutlineChild, CourseOutlineContainer):
class CourseOutlineSection(CourseOutlineContainer, CourseOutlineChild):
"""
:class`.PageObject` that wraps a section block on the Studio Course Outline page.
"""
......@@ -512,85 +551,12 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
subsection.toggle_expand()
@property
def outline_items(self):
def xblocks(self):
"""
Return a list of xblocks loaded on the outline page.
"""
return self._get_outline_items()
def _get_outline_items(self, prefix=""):
return self.q(css=prefix + OutlineWrapper.BODY_SELECTOR).map(
lambda el: OutlineWrapper(self.browser, el.get_attribute('data-locator'))).results
def drag(self, source_index, target_index):
"""
Gets the drag handle with index source_index (relative to the vertical layout of the page)
and drags it to the location of the drag handle with target_index.
This should drag the element with the source_index drag handle BEFORE the
one with the target_index drag handle.
"""
draggables = self.q(css='.drag-handle')
source = draggables[source_index]
target = draggables[target_index]
action = ActionChains(self.browser)
# When dragging before the target element, must take into account that the placeholder
# will appear in the place where the target used to be.
placeholder_height = 40
action.click_and_hold(source).move_to_element_with_offset(
target, 0, placeholder_height
).release().perform()
wait_for_notification(self)
class OutlineWrapper(PageObject):
"""
A PageObject representing a wrapper around course outline items shown on the Course Outline page.
"""
url = None
BODY_SELECTOR = '.outline-item'
NAME_SELECTOR = '.item-title'
def __init__(self, browser, locator):
super(OutlineWrapper, self).__init__(browser)
self.locator = locator
def is_browser_on_page(self):
return self.q(css='{}[data-locator="{}"]'.format(self.BODY_SELECTOR, 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
)
return self.children(CourseOutlineChild)
@property
def name(self):
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 items of this item.
"""
descendants = self.q(css=self._bounded_selector(self.BODY_SELECTOR)).map(
lambda el: OutlineWrapper(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 not descendant.locator in grand_locators]
class CourseOutlineModal(object):
MODAL_SELECTOR = ".wrapper-modal-window"
......
......@@ -148,3 +148,48 @@ 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.
"""
set_input_value(page, css, value).send_keys(Keys.ENTER)
def drag(page, source_index, target_index, placeholder_height=0):
"""
Gets the drag handle with index source_index (relative to the vertical layout of the page)
and drags it to the location of the drag handle with target_index.
This should drag the element with the source_index drag handle BEFORE the
one with the target_index drag handle.
"""
draggables = page.q(css='.drag-handle')
source = draggables[source_index]
target = draggables[target_index]
action = ActionChains(page.browser)
action.click_and_hold(source).move_to_element_with_offset(
target, 0, placeholder_height
)
if placeholder_height == 0:
action.release(target).perform()
else:
action.release().perform()
wait_for_notification(page)
def verify_ordering(test_class, page, expected_orderings):
"""
Verifies the expected ordering of xblocks on the page.
"""
xblocks = page.xblocks
blocks_checked = set()
for expected_ordering in expected_orderings:
for xblock in xblocks:
parent = expected_ordering.keys()[0]
if xblock.name == parent:
blocks_checked.add(parent)
children = xblock.children
expected_length = len(expected_ordering.get(parent))
test_class.assertEqual(
expected_length, len(children),
"Number of children incorrect for group {0}. Expected {1} but got {2}.".format(parent, expected_length, len(children)))
for idx, expected in enumerate(expected_ordering.get(parent)):
test_class.assertEqual(expected, children[idx].name)
blocks_checked.add(expected)
break
test_class.assertEqual(len(blocks_checked), len(xblocks))
......@@ -2,6 +2,7 @@ from ..pages.studio.auto_auth import AutoAuthPage
from ..fixtures.course import CourseFixture
from .helpers import UniqueCourseTest
from ..pages.studio.overview import CourseOutlinePage
from ..pages.studio.utils import verify_ordering
class StudioCourseTest(UniqueCourseTest):
"""
......@@ -84,28 +85,6 @@ class ContainerBase(StudioCourseTest):
subsection = self.outline.section(section_name).subsection(subsection_name)
return subsection.toggle_expand().unit(unit_name).go_to()
def verify_ordering(self, container, expected_orderings):
"""
Verifies the expected ordering of xblocks on the page.
"""
xblocks = container.xblocks
blocks_checked = set()
for expected_ordering in expected_orderings:
for xblock in xblocks:
parent = expected_ordering.keys()[0]
if xblock.name == parent:
blocks_checked.add(parent)
children = xblock.children
expected_length = len(expected_ordering.get(parent))
self.assertEqual(
expected_length, len(children),
"Number of children incorrect for group {0}. Expected {1} but got {2}.".format(parent, expected_length, len(children)))
for idx, expected in enumerate(expected_ordering.get(parent)):
self.assertEqual(expected, children[idx].name)
blocks_checked.add(expected)
break
self.assertEqual(len(blocks_checked), len(xblocks))
def do_action_and_verify(self, action, expected_ordering):
"""
Perform the supplied action and then verify the resulting ordering.
......@@ -113,8 +92,8 @@ class ContainerBase(StudioCourseTest):
container = self.go_to_nested_container_page()
action(container)
self.verify_ordering(container, expected_ordering)
verify_ordering(self, container, expected_ordering)
# Reload the page to see that the change was persisted.
container = self.go_to_nested_container_page()
self.verify_ordering(container, expected_ordering)
verify_ordering(self, container, expected_ordering)
from nose.plugins.attrib import attr
from .base_studio_test import ContainerBase
from ..fixtures.course import XBlockFixtureDesc
from ..pages.studio.utils import verify_ordering
@attr('shard_1')
......@@ -38,7 +39,7 @@ class BadComponentTest(ContainerBase):
displaying the components on the unit page.
"""
unit = self.go_to_unit_page()
self.verify_ordering(unit, [{"": ["Unit HTML", "Unit Problem"]}])
verify_ordering(self, unit, [{"": ["Unit HTML", "Unit Problem"]}])
@attr('shard_1')
......
......@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr
from ..fixtures.course import XBlockFixtureDesc
from ..pages.studio.component_editor import ComponentEditorView
from ..pages.studio.html_component_editor import HtmlComponentEditorView
from ..pages.studio.utils import add_discussion
from ..pages.studio.utils import add_discussion, drag
from ..pages.lms.courseware import CoursewarePage
from ..pages.lms.staff_view import StaffPage
......@@ -75,7 +75,7 @@ class DragAndDropTest(NestedVerticalTest):
def drag_and_verify(self, source, target, expected_ordering):
self.do_action_and_verify(
lambda (container): container.drag(source, target),
lambda (container): drag(container, source, target, 40),
expected_ordering
)
......@@ -133,9 +133,9 @@ class DragAndDropTest(NestedVerticalTest):
first_handle = self.group_a_item_1_handle
# Drag newly added video component to top.
container.drag(first_handle + 3, first_handle)
drag(container, first_handle + 3, first_handle, 40)
# Drag duplicated component to top.
container.drag(first_handle + 2, first_handle)
drag(container, first_handle + 2, first_handle, 40)
duplicate_label = self.duplicate_label.format(self.group_a_item_1)
......
......@@ -9,7 +9,7 @@ from pytz import UTC
from bok_choy.promise import EmptyPromise
from ..pages.studio.overview import CourseOutlinePage, ContainerPage, ExpandCollapseLinkState
from ..pages.studio.utils import add_discussion
from ..pages.studio.utils import add_discussion, drag, verify_ordering
from ..pages.lms.courseware import CoursewarePage
from ..pages.lms.course_nav import CourseNavPage
from ..pages.lms.staff_view import StaffPage
......@@ -49,37 +49,10 @@ class CourseOutlineTest(StudioCourseTest):
XBlockFixtureDesc('html', 'Test HTML Component'),
XBlockFixtureDesc('discussion', 'Test Discussion Component')
)
),
XBlockFixtureDesc('sequential', "DropS").add_children(
XBlockFixtureDesc('vertical', "DropV").add_children(
XBlockFixtureDesc('problem', 'Drop Problem 1', data=load_data_str('multiple_choice.xml')),
)
)
)
)
def verify_ordering(self, outline_page, expected_orderings):
"""
Verifies the expected ordering of xblocks on the page.
"""
xblocks = outline_page.outline_items
blocks_checked = set()
for expected_ordering in expected_orderings:
for xblock in xblocks:
parent = expected_ordering.keys()[0]
if xblock.name == parent:
blocks_checked.add(parent)
children = xblock.children
expected_length = len(expected_ordering.get(parent))
self.assertEqual(
expected_length, len(children),
"Number of children incorrect for group {0}. Expected {1} but got {2}.".format(parent, expected_length, len(children)))
for idx, expected in enumerate(expected_ordering.get(parent)):
self.assertEqual(expected, children[idx].name)
blocks_checked.add(expected)
break
self.assertEqual(len(blocks_checked), len(xblocks))
def do_action_and_verify(self, outline_page, action, expected_ordering):
"""
Perform the supplied action and then verify the resulting ordering.
......@@ -88,12 +61,12 @@ class CourseOutlineTest(StudioCourseTest):
outline_page = self.course_outline_page.visit()
action(outline_page)
self.verify_ordering(outline_page, expected_ordering)
verify_ordering(self, outline_page, expected_ordering)
# Reload the page and expand all subsections to see that the change was persisted.
course_outline_page = self.course_outline_page.visit()
course_outline_page.q(css='.outline-item.outline-subsection.is-collapsed .ui-toggle-expansion').click()
self.verify_ordering(course_outline_page, expected_ordering)
outline_page = self.course_outline_page.visit()
outline_page.q(css='.outline-item.outline-subsection.is-collapsed .ui-toggle-expansion').click()
verify_ordering(self, outline_page, expected_ordering)
@attr('shard_2')
......@@ -132,7 +105,7 @@ class CourseOutlineDragAndDropTest(CourseOutlineTest):
def drag_and_verify(self, source, target, expected_ordering, outline_page=None):
self.do_action_and_verify(
outline_page,
lambda (outline): outline.drag(source, target),
lambda (outline): drag(outline, source, target),
expected_ordering
)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment