Bok choy acceptance test for drag and drop.
This commit is contained in:
@@ -6,6 +6,7 @@ from bok_choy.page_object import PageObject
|
||||
from bok_choy.promise import Promise
|
||||
from . import BASE_URL
|
||||
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
|
||||
class ContainerPage(PageObject):
|
||||
"""
|
||||
@@ -44,6 +45,25 @@ class ContainerPage(PageObject):
|
||||
return self.q(css=XBlockWrapper.BODY_SELECTOR).map(
|
||||
lambda el: XBlockWrapper(self.browser, el.get_attribute('data-locator'))).results
|
||||
|
||||
def drag(self, source_index, target_index, after=True):
|
||||
"""
|
||||
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 AFTER the
|
||||
one with the target_index drag handle, unless 'after' is set to False.
|
||||
"""
|
||||
draggables = self.q(css='.drag-handle')
|
||||
source = draggables[source_index]
|
||||
target = draggables[target_index]
|
||||
action = ActionChains(self.browser)
|
||||
action.click_and_hold(source).perform() # pylint: disable=protected-access
|
||||
action.move_to_element_with_offset(
|
||||
target, 0, target.size['height']/2 if after else 0
|
||||
).perform() # pylint: disable=protected-access
|
||||
action.release().perform()
|
||||
# TODO: should wait for "Saving" to go away so we know the operation is complete?
|
||||
|
||||
|
||||
class XBlockWrapper(PageObject):
|
||||
"""
|
||||
@@ -78,6 +98,22 @@ class XBlockWrapper(PageObject):
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
"""
|
||||
Will return any first-generation descendant xblocks of this xblock.
|
||||
"""
|
||||
descendants = self.q(css=self._bounded_selector(self.BODY_SELECTOR)).map(
|
||||
lambda el: XBlockWrapper(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]
|
||||
|
||||
@property
|
||||
def preview_selector(self):
|
||||
return self._bounded_selector('.xblock-student_view')
|
||||
|
||||
@@ -1,156 +1,15 @@
|
||||
"""
|
||||
Acceptance tests for Studio.
|
||||
Acceptance tests for Studio related to the acid xblock.
|
||||
"""
|
||||
from unittest import skip
|
||||
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
|
||||
from ..pages.studio.asset_index import AssetIndexPage
|
||||
from ..pages.studio.auto_auth import AutoAuthPage
|
||||
from ..pages.studio.checklists import ChecklistsPage
|
||||
from ..pages.studio.course_import import ImportPage
|
||||
from ..pages.studio.course_info import CourseUpdatesPage
|
||||
from ..pages.studio.edit_tabs import PagesPage
|
||||
from ..pages.studio.export import ExportPage
|
||||
from ..pages.studio.howitworks import HowitworksPage
|
||||
from ..pages.studio.index import DashboardPage
|
||||
from ..pages.studio.login import LoginPage
|
||||
from ..pages.studio.manage_users import CourseTeamPage
|
||||
from ..pages.studio.overview import CourseOutlinePage
|
||||
from ..pages.studio.settings import SettingsPage
|
||||
from ..pages.studio.settings_advanced import AdvancedSettingsPage
|
||||
from ..pages.studio.settings_graders import GradingPage
|
||||
from ..pages.studio.signup import SignupPage
|
||||
from ..pages.studio.textbooks import TextbooksPage
|
||||
from ..pages.xblock.acid import AcidView
|
||||
from ..fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
|
||||
from .helpers import UniqueCourseTest
|
||||
|
||||
|
||||
class LoggedOutTest(WebAppTest):
|
||||
"""
|
||||
Smoke test for pages in Studio that are visible when logged out.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(LoggedOutTest, self).setUp()
|
||||
self.pages = [LoginPage(self.browser), HowitworksPage(self.browser), SignupPage(self.browser)]
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that all the pages are accessible.
|
||||
Rather than fire up the browser just to check each url,
|
||||
do them all sequentially in this testcase.
|
||||
"""
|
||||
for page in self.pages:
|
||||
page.visit()
|
||||
|
||||
|
||||
class LoggedInPagesTest(WebAppTest):
|
||||
"""
|
||||
Tests that verify the pages in Studio that you can get to when logged
|
||||
in and do not have a course yet.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(LoggedInPagesTest, self).setUp()
|
||||
self.auth_page = AutoAuthPage(self.browser, staff=True)
|
||||
self.dashboard_page = DashboardPage(self.browser)
|
||||
|
||||
def test_dashboard_no_courses(self):
|
||||
"""
|
||||
Make sure that you can get to the dashboard page without a course.
|
||||
"""
|
||||
self.auth_page.visit()
|
||||
self.dashboard_page.visit()
|
||||
|
||||
|
||||
class CoursePagesTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests that verify the pages in Studio that you can get to when logged
|
||||
in and have a course.
|
||||
"""
|
||||
|
||||
COURSE_ID_SEPARATOR = "."
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Install a course with no content using a fixture.
|
||||
"""
|
||||
super(UniqueCourseTest, self).setUp()
|
||||
|
||||
CourseFixture(
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run'],
|
||||
self.course_info['display_name']
|
||||
).install()
|
||||
|
||||
self.auth_page = AutoAuthPage(self.browser, staff=True)
|
||||
|
||||
self.pages = [
|
||||
clz(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'])
|
||||
for clz in [
|
||||
AssetIndexPage, ChecklistsPage, ImportPage, CourseUpdatesPage,
|
||||
PagesPage, ExportPage, CourseTeamPage, CourseOutlinePage, SettingsPage,
|
||||
AdvancedSettingsPage, GradingPage, TextbooksPage
|
||||
]
|
||||
]
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that all these pages are accessible once you have a course.
|
||||
Rather than fire up the browser just to check each url,
|
||||
do them all sequentially in this testcase.
|
||||
"""
|
||||
# Log in
|
||||
self.auth_page.visit()
|
||||
|
||||
# Verify that each page is available
|
||||
for page in self.pages:
|
||||
page.visit()
|
||||
|
||||
|
||||
class DiscussionPreviewTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests that Inline Discussions are rendered with a custom preview in Studio
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(DiscussionPreviewTest, self).setUp()
|
||||
CourseFixture(**self.course_info).add_children(
|
||||
XBlockFixtureDesc("chapter", "Test Section").add_children(
|
||||
XBlockFixtureDesc("sequential", "Test Subsection").add_children(
|
||||
XBlockFixtureDesc("vertical", "Test Unit").add_children(
|
||||
XBlockFixtureDesc(
|
||||
"discussion",
|
||||
"Test Discussion",
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
).install()
|
||||
|
||||
AutoAuthPage(self.browser, staff=True).visit()
|
||||
cop = CourseOutlinePage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run']
|
||||
)
|
||||
cop.visit()
|
||||
self.unit = cop.section('Test Section').subsection('Test Subsection').toggle_expand().unit('Test Unit')
|
||||
self.unit.go_to()
|
||||
|
||||
def test_is_preview(self):
|
||||
"""
|
||||
Ensure that the preview version of the discussion is rendered.
|
||||
"""
|
||||
self.assertTrue(self.unit.q(css=".discussion-preview").present)
|
||||
self.assertFalse(self.unit.q(css=".discussion-show").present)
|
||||
|
||||
|
||||
class XBlockAcidBase(WebAppTest):
|
||||
"""
|
||||
Base class for tests that verify that XBlock integration is working correctly
|
||||
@@ -29,6 +29,15 @@ class ContainerBase(UniqueCourseTest):
|
||||
self.course_info['run']
|
||||
)
|
||||
|
||||
self.container_title = ""
|
||||
self.group_a = "Expand or Collapse\nGroup A"
|
||||
self.group_b = "Expand or Collapse\nGroup B"
|
||||
self.group_empty = "Expand or Collapse\nGroup Empty"
|
||||
self.group_a_item_1 = "Group A Item 1"
|
||||
self.group_a_item_2 = "Group A Item 2"
|
||||
self.group_b_item_1 = "Group B Item 1"
|
||||
self.group_b_item_2 = "Group B Item 2"
|
||||
|
||||
self.setup_fixtures()
|
||||
|
||||
self.auth_page.visit()
|
||||
@@ -47,12 +56,13 @@ class ContainerBase(UniqueCourseTest):
|
||||
XBlockFixtureDesc('vertical', 'Test Unit').add_children(
|
||||
XBlockFixtureDesc('vertical', 'Test Container').add_children(
|
||||
XBlockFixtureDesc('vertical', 'Group A').add_children(
|
||||
XBlockFixtureDesc('html', 'Group A Item 1'),
|
||||
XBlockFixtureDesc('html', 'Group A Item 2')
|
||||
XBlockFixtureDesc('html', self.group_a_item_1),
|
||||
XBlockFixtureDesc('html', self.group_a_item_2)
|
||||
),
|
||||
XBlockFixtureDesc('vertical', 'Group Empty'),
|
||||
XBlockFixtureDesc('vertical', 'Group B').add_children(
|
||||
XBlockFixtureDesc('html', 'Group B Item 1'),
|
||||
XBlockFixtureDesc('html', 'Group B Item 2')
|
||||
XBlockFixtureDesc('html', self.group_b_item_1),
|
||||
XBlockFixtureDesc('html', self.group_b_item_2)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -76,23 +86,77 @@ class DragAndDropTest(ContainerBase):
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def verify_ordering(self, container, expected_ordering):
|
||||
def verify_ordering(self, container, expected_orderings):
|
||||
xblocks = container.xblocks
|
||||
for xblock in xblocks:
|
||||
print xblock.name
|
||||
# TODO: need to verify parenting structure on page. Just checking
|
||||
# the order of the xblocks is not sufficient.
|
||||
for expected_ordering in expected_orderings:
|
||||
for xblock in xblocks:
|
||||
parent = expected_ordering.keys()[0]
|
||||
if xblock.name == 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)
|
||||
|
||||
|
||||
def test_reorder_in_group(self):
|
||||
def drag_and_verify(self, source, target, expected_ordering, after=True):
|
||||
container = self.go_to_container_page(make_draft=True)
|
||||
# Swap Group A Item 1 and Group A Item 2.
|
||||
container.drag(1, 2)
|
||||
container.drag(source, target, after)
|
||||
|
||||
expected_ordering = [{"Group A": ["Group A Item 2", "Group A Item 1"]},
|
||||
{"Group B": ["Group B Item 1", "Group B Item 2"]}]
|
||||
self.verify_ordering(container, expected_ordering)
|
||||
|
||||
# Reload the page to see that the reordering was saved persisted.
|
||||
container = self.go_to_container_page()
|
||||
self.verify_ordering(container, expected_ordering)
|
||||
|
||||
def test_reorder_in_group(self):
|
||||
"""
|
||||
Drag Group B Item 2 before Group B Item 1.
|
||||
"""
|
||||
expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]},
|
||||
{self.group_a: [self.group_a_item_1, self.group_a_item_2]},
|
||||
{self.group_b: [self.group_b_item_2, self.group_b_item_1]},
|
||||
{self.group_empty: []}]
|
||||
self.drag_and_verify(6, 4, expected_ordering)
|
||||
|
||||
def test_drag_to_top(self):
|
||||
"""
|
||||
Drag Group A Item 1 to top level (outside of Group A).
|
||||
"""
|
||||
expected_ordering = [{self.container_title: [self.group_a_item_1, self.group_a, self.group_empty, self.group_b]},
|
||||
{self.group_a: [self.group_a_item_2]},
|
||||
{self.group_b: [self.group_b_item_1, self.group_b_item_2]},
|
||||
{self.group_empty: []}]
|
||||
self.drag_and_verify(1, 0, expected_ordering, False)
|
||||
|
||||
def test_drag_into_different_group(self):
|
||||
"""
|
||||
Drag Group A Item 1 into Group B (last element).
|
||||
"""
|
||||
expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]},
|
||||
{self.group_a: [self.group_a_item_2]},
|
||||
{self.group_b: [self.group_b_item_1, self.group_b_item_2, self.group_a_item_1]},
|
||||
{self.group_empty: []}]
|
||||
self.drag_and_verify(1, 6, expected_ordering)
|
||||
|
||||
def test_drag_group_into_group(self):
|
||||
"""
|
||||
Drag Group B into Group A (last element).
|
||||
"""
|
||||
expected_ordering = [{self.container_title: [self.group_a, self.group_empty]},
|
||||
{self.group_a: [self.group_a_item_1, self.group_a_item_2, self.group_b]},
|
||||
{self.group_b: [self.group_b_item_1, self.group_b_item_2]},
|
||||
{self.group_empty: []}]
|
||||
self.drag_and_verify(4, 2, expected_ordering)
|
||||
|
||||
# Not able to drag into the empty group with automation (difficult even outside of automation).
|
||||
# def test_drag_into_empty(self):
|
||||
# """
|
||||
# Drag Group B Item 1 to Group Empty.
|
||||
# """
|
||||
# expected_ordering = [{self.container_title: [self.group_a, self.group_empty, self.group_b]},
|
||||
# {self.group_a: [self.group_a_item_1, self.group_a_item_2]},
|
||||
# {self.group_b: [self.group_b_item_2]},
|
||||
# {self.group_empty: [self.group_b_item_1]}]
|
||||
# self.drag_and_verify(6, 4, expected_ordering, False)
|
||||
|
||||
148
common/test/acceptance/tests/test_studio_general.py
Normal file
148
common/test/acceptance/tests/test_studio_general.py
Normal file
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
Acceptance tests for Studio.
|
||||
"""
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
|
||||
from ..pages.studio.asset_index import AssetIndexPage
|
||||
from ..pages.studio.auto_auth import AutoAuthPage
|
||||
from ..pages.studio.checklists import ChecklistsPage
|
||||
from ..pages.studio.course_import import ImportPage
|
||||
from ..pages.studio.course_info import CourseUpdatesPage
|
||||
from ..pages.studio.edit_tabs import PagesPage
|
||||
from ..pages.studio.export import ExportPage
|
||||
from ..pages.studio.howitworks import HowitworksPage
|
||||
from ..pages.studio.index import DashboardPage
|
||||
from ..pages.studio.login import LoginPage
|
||||
from ..pages.studio.manage_users import CourseTeamPage
|
||||
from ..pages.studio.overview import CourseOutlinePage
|
||||
from ..pages.studio.settings import SettingsPage
|
||||
from ..pages.studio.settings_advanced import AdvancedSettingsPage
|
||||
from ..pages.studio.settings_graders import GradingPage
|
||||
from ..pages.studio.signup import SignupPage
|
||||
from ..pages.studio.textbooks import TextbooksPage
|
||||
from ..fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
|
||||
from .helpers import UniqueCourseTest
|
||||
|
||||
|
||||
class LoggedOutTest(WebAppTest):
|
||||
"""
|
||||
Smoke test for pages in Studio that are visible when logged out.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(LoggedOutTest, self).setUp()
|
||||
self.pages = [LoginPage(self.browser), HowitworksPage(self.browser), SignupPage(self.browser)]
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that all the pages are accessible.
|
||||
Rather than fire up the browser just to check each url,
|
||||
do them all sequentially in this testcase.
|
||||
"""
|
||||
for page in self.pages:
|
||||
page.visit()
|
||||
|
||||
|
||||
class LoggedInPagesTest(WebAppTest):
|
||||
"""
|
||||
Tests that verify the pages in Studio that you can get to when logged
|
||||
in and do not have a course yet.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(LoggedInPagesTest, self).setUp()
|
||||
self.auth_page = AutoAuthPage(self.browser, staff=True)
|
||||
self.dashboard_page = DashboardPage(self.browser)
|
||||
|
||||
def test_dashboard_no_courses(self):
|
||||
"""
|
||||
Make sure that you can get to the dashboard page without a course.
|
||||
"""
|
||||
self.auth_page.visit()
|
||||
self.dashboard_page.visit()
|
||||
|
||||
|
||||
class CoursePagesTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests that verify the pages in Studio that you can get to when logged
|
||||
in and have a course.
|
||||
"""
|
||||
|
||||
COURSE_ID_SEPARATOR = "."
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Install a course with no content using a fixture.
|
||||
"""
|
||||
super(UniqueCourseTest, self).setUp()
|
||||
|
||||
CourseFixture(
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run'],
|
||||
self.course_info['display_name']
|
||||
).install()
|
||||
|
||||
self.auth_page = AutoAuthPage(self.browser, staff=True)
|
||||
|
||||
self.pages = [
|
||||
clz(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'])
|
||||
for clz in [
|
||||
AssetIndexPage, ChecklistsPage, ImportPage, CourseUpdatesPage,
|
||||
PagesPage, ExportPage, CourseTeamPage, CourseOutlinePage, SettingsPage,
|
||||
AdvancedSettingsPage, GradingPage, TextbooksPage
|
||||
]
|
||||
]
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that all these pages are accessible once you have a course.
|
||||
Rather than fire up the browser just to check each url,
|
||||
do them all sequentially in this testcase.
|
||||
"""
|
||||
# Log in
|
||||
self.auth_page.visit()
|
||||
|
||||
# Verify that each page is available
|
||||
for page in self.pages:
|
||||
page.visit()
|
||||
|
||||
|
||||
class DiscussionPreviewTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests that Inline Discussions are rendered with a custom preview in Studio
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(DiscussionPreviewTest, self).setUp()
|
||||
CourseFixture(**self.course_info).add_children(
|
||||
XBlockFixtureDesc("chapter", "Test Section").add_children(
|
||||
XBlockFixtureDesc("sequential", "Test Subsection").add_children(
|
||||
XBlockFixtureDesc("vertical", "Test Unit").add_children(
|
||||
XBlockFixtureDesc(
|
||||
"discussion",
|
||||
"Test Discussion",
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
).install()
|
||||
|
||||
AutoAuthPage(self.browser, staff=True).visit()
|
||||
cop = CourseOutlinePage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run']
|
||||
)
|
||||
cop.visit()
|
||||
self.unit = cop.section('Test Section').subsection('Test Subsection').toggle_expand().unit('Test Unit')
|
||||
self.unit.go_to()
|
||||
|
||||
def test_is_preview(self):
|
||||
"""
|
||||
Ensure that the preview version of the discussion is rendered.
|
||||
"""
|
||||
self.assertTrue(self.unit.q(css=".discussion-preview").present)
|
||||
self.assertFalse(self.unit.q(css=".discussion-show").present)
|
||||
Reference in New Issue
Block a user