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 ...@@ -6,9 +6,7 @@ from bok_choy.page_object import PageObject
from bok_choy.promise import Promise, EmptyPromise from bok_choy.promise import Promise, EmptyPromise
from . import BASE_URL from . import BASE_URL
from selenium.webdriver.common.action_chains import ActionChains from utils import click_css, confirm_prompt
from utils import click_css, wait_for_notification, confirm_prompt
class ContainerPage(PageObject): class ContainerPage(PageObject):
...@@ -220,26 +218,6 @@ class ContainerPage(PageObject): ...@@ -220,26 +218,6 @@ class ContainerPage(PageObject):
return self.q(css=prefix + XBlockWrapper.BODY_SELECTOR).map( return self.q(css=prefix + XBlockWrapper.BODY_SELECTOR).map(
lambda el: XBlockWrapper(self.browser, el.get_attribute('data-locator'))).results 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): def duplicate(self, source_index):
""" """
Duplicate the item with index source_index (based on vertical placement in page). 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 ...@@ -12,7 +12,7 @@ from selenium.webdriver.common.action_chains import ActionChains
from .course_page import CoursePage from .course_page import CoursePage
from .container import ContainerPage 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): class CourseOutlineItem(object):
...@@ -256,6 +256,9 @@ class CourseOutlineChild(PageObject, CourseOutlineItem): ...@@ -256,6 +256,9 @@ class CourseOutlineChild(PageObject, CourseOutlineItem):
""" """
A page object that will be used as a child of :class:`CourseOutlineContainer`. A page object that will be used as a child of :class:`CourseOutlineContainer`.
""" """
url = None
BODY_SELECTOR = '.outline-item'
def __init__(self, browser, locator): def __init__(self, browser, locator):
super(CourseOutlineChild, self).__init__(browser) super(CourseOutlineChild, self).__init__(browser)
self.locator = locator self.locator = locator
...@@ -270,6 +273,39 @@ class CourseOutlineChild(PageObject, CourseOutlineItem): ...@@ -270,6 +273,39 @@ class CourseOutlineChild(PageObject, CourseOutlineItem):
click_css(self, self._bounded_selector('.delete-button'), require_notification=False) click_css(self, self._bounded_selector('.delete-button'), require_notification=False)
confirm_prompt(self, cancel) 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): class CourseOutlineUnit(CourseOutlineChild):
""" """
...@@ -289,8 +325,11 @@ class CourseOutlineUnit(CourseOutlineChild): ...@@ -289,8 +325,11 @@ class CourseOutlineUnit(CourseOutlineChild):
def is_browser_on_page(self): def is_browser_on_page(self):
return self.q(css=self.BODY_SELECTOR).present 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. :class`.PageObject` that wraps a subsection block on the Studio Course Outline page.
""" """
...@@ -326,7 +365,7 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer): ...@@ -326,7 +365,7 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
self.q(css=self._bounded_selector(self.ADD_BUTTON_SELECTOR)).click() 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. :class`.PageObject` that wraps a section block on the Studio Course Outline page.
""" """
...@@ -512,85 +551,12 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer): ...@@ -512,85 +551,12 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
subsection.toggle_expand() subsection.toggle_expand()
@property @property
def outline_items(self): def xblocks(self):
""" """
Return a list of xblocks loaded on the outline page. Return a list of xblocks loaded on the outline page.
""" """
return self._get_outline_items() return self.children(CourseOutlineChild)
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
)
@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): class CourseOutlineModal(object):
MODAL_SELECTOR = ".wrapper-modal-window" MODAL_SELECTOR = ".wrapper-modal-window"
......
...@@ -148,3 +148,48 @@ def set_input_value_and_save(page, css, value): ...@@ -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. 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) 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 ...@@ -2,6 +2,7 @@ from ..pages.studio.auto_auth import AutoAuthPage
from ..fixtures.course import CourseFixture from ..fixtures.course import CourseFixture
from .helpers import UniqueCourseTest from .helpers import UniqueCourseTest
from ..pages.studio.overview import CourseOutlinePage from ..pages.studio.overview import CourseOutlinePage
from ..pages.studio.utils import verify_ordering
class StudioCourseTest(UniqueCourseTest): class StudioCourseTest(UniqueCourseTest):
""" """
...@@ -84,28 +85,6 @@ class ContainerBase(StudioCourseTest): ...@@ -84,28 +85,6 @@ class ContainerBase(StudioCourseTest):
subsection = self.outline.section(section_name).subsection(subsection_name) subsection = self.outline.section(section_name).subsection(subsection_name)
return subsection.toggle_expand().unit(unit_name).go_to() 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): def do_action_and_verify(self, action, expected_ordering):
""" """
Perform the supplied action and then verify the resulting ordering. Perform the supplied action and then verify the resulting ordering.
...@@ -113,8 +92,8 @@ class ContainerBase(StudioCourseTest): ...@@ -113,8 +92,8 @@ class ContainerBase(StudioCourseTest):
container = self.go_to_nested_container_page() container = self.go_to_nested_container_page()
action(container) action(container)
self.verify_ordering(container, expected_ordering) verify_ordering(self, container, expected_ordering)
# Reload the page to see that the change was persisted. # Reload the page to see that the change was persisted.
container = self.go_to_nested_container_page() 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 nose.plugins.attrib import attr
from .base_studio_test import ContainerBase from .base_studio_test import ContainerBase
from ..fixtures.course import XBlockFixtureDesc from ..fixtures.course import XBlockFixtureDesc
from ..pages.studio.utils import verify_ordering
@attr('shard_1') @attr('shard_1')
...@@ -38,7 +39,7 @@ class BadComponentTest(ContainerBase): ...@@ -38,7 +39,7 @@ class BadComponentTest(ContainerBase):
displaying the components on the unit page. displaying the components on the unit page.
""" """
unit = self.go_to_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') @attr('shard_1')
......
...@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr ...@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr
from ..fixtures.course import XBlockFixtureDesc from ..fixtures.course import XBlockFixtureDesc
from ..pages.studio.component_editor import ComponentEditorView from ..pages.studio.component_editor import ComponentEditorView
from ..pages.studio.html_component_editor import HtmlComponentEditorView 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.courseware import CoursewarePage
from ..pages.lms.staff_view import StaffPage from ..pages.lms.staff_view import StaffPage
...@@ -75,7 +75,7 @@ class DragAndDropTest(NestedVerticalTest): ...@@ -75,7 +75,7 @@ class DragAndDropTest(NestedVerticalTest):
def drag_and_verify(self, source, target, expected_ordering): def drag_and_verify(self, source, target, expected_ordering):
self.do_action_and_verify( self.do_action_and_verify(
lambda (container): container.drag(source, target), lambda (container): drag(container, source, target, 40),
expected_ordering expected_ordering
) )
...@@ -133,9 +133,9 @@ class DragAndDropTest(NestedVerticalTest): ...@@ -133,9 +133,9 @@ class DragAndDropTest(NestedVerticalTest):
first_handle = self.group_a_item_1_handle first_handle = self.group_a_item_1_handle
# Drag newly added video component to top. # 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. # 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) duplicate_label = self.duplicate_label.format(self.group_a_item_1)
......
...@@ -9,7 +9,7 @@ from pytz import UTC ...@@ -9,7 +9,7 @@ from pytz import UTC
from bok_choy.promise import EmptyPromise from bok_choy.promise import EmptyPromise
from ..pages.studio.overview import CourseOutlinePage, ContainerPage, ExpandCollapseLinkState 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.courseware import CoursewarePage
from ..pages.lms.course_nav import CourseNavPage from ..pages.lms.course_nav import CourseNavPage
from ..pages.lms.staff_view import StaffPage from ..pages.lms.staff_view import StaffPage
...@@ -49,37 +49,10 @@ class CourseOutlineTest(StudioCourseTest): ...@@ -49,37 +49,10 @@ class CourseOutlineTest(StudioCourseTest):
XBlockFixtureDesc('html', 'Test HTML Component'), XBlockFixtureDesc('html', 'Test HTML Component'),
XBlockFixtureDesc('discussion', 'Test Discussion 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): def do_action_and_verify(self, outline_page, action, expected_ordering):
""" """
Perform the supplied action and then verify the resulting ordering. Perform the supplied action and then verify the resulting ordering.
...@@ -88,12 +61,12 @@ class CourseOutlineTest(StudioCourseTest): ...@@ -88,12 +61,12 @@ class CourseOutlineTest(StudioCourseTest):
outline_page = self.course_outline_page.visit() outline_page = self.course_outline_page.visit()
action(outline_page) 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. # Reload the page and expand all subsections to see that the change was persisted.
course_outline_page = self.course_outline_page.visit() outline_page = self.course_outline_page.visit()
course_outline_page.q(css='.outline-item.outline-subsection.is-collapsed .ui-toggle-expansion').click() outline_page.q(css='.outline-item.outline-subsection.is-collapsed .ui-toggle-expansion').click()
self.verify_ordering(course_outline_page, expected_ordering) verify_ordering(self, outline_page, expected_ordering)
@attr('shard_2') @attr('shard_2')
...@@ -132,7 +105,7 @@ class CourseOutlineDragAndDropTest(CourseOutlineTest): ...@@ -132,7 +105,7 @@ class CourseOutlineDragAndDropTest(CourseOutlineTest):
def drag_and_verify(self, source, target, expected_ordering, outline_page=None): def drag_and_verify(self, source, target, expected_ordering, outline_page=None):
self.do_action_and_verify( self.do_action_and_verify(
outline_page, outline_page,
lambda (outline): outline.drag(source, target), lambda (outline): drag(outline, source, target),
expected_ordering 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