diff --git a/common/test/acceptance/pages/studio/container.py b/common/test/acceptance/pages/studio/container.py
index f9b66da633..9d89cc3d13 100644
--- a/common/test/acceptance/pages/studio/container.py
+++ b/common/test/acceptance/pages/studio/container.py
@@ -39,12 +39,18 @@ class ContainerPage(PageObject):
def is_browser_on_page(self):
def _is_finished_loading():
- # Wait until all components have been loaded.
- # See common/static/coffee/src/xblock/core.coffee which adds the
- # class "xblock-initialized" at the end of initializeBlock
- num_wrappers = len(self.q(css=XBlockWrapper.BODY_SELECTOR).results)
- num_xblocks_init = len(self.q(css='{} .xblock.xblock-initialized'.format(XBlockWrapper.BODY_SELECTOR)).results)
- is_done = num_wrappers == num_xblocks_init
+ is_done = False
+ # Get the request token of the first xblock rendered on the page and assume it is correct.
+ data_request_elements = self.q(css='[data-request-token]')
+ if len(data_request_elements) > 0:
+ request_token = data_request_elements.first.attrs('data-request-token')[0]
+ # Then find the number of Studio xblock wrappers on the page with that request token.
+ num_wrappers = len(self.q(css='{} [data-request-token="{}"]'.format(XBlockWrapper.BODY_SELECTOR, request_token)).results)
+ # Wait until all components have been loaded.
+ # See common/static/coffee/src/xblock/core.coffee which adds the
+ # class "xblock-initialized" at the end of initializeBlock
+ num_xblocks_init = len(self.q(css='{} .xblock.xblock-initialized[data-request-token="{}"]'.format(XBlockWrapper.BODY_SELECTOR, request_token)).results)
+ is_done = num_wrappers == num_xblocks_init
return (is_done, is_done)
# First make sure that an element with the view-container class is present on the page,
diff --git a/common/test/acceptance/tests/base_studio_test.py b/common/test/acceptance/tests/base_studio_test.py
index f41ba7ecb2..824ca6480e 100644
--- a/common/test/acceptance/tests/base_studio_test.py
+++ b/common/test/acceptance/tests/base_studio_test.py
@@ -1,7 +1,7 @@
from ..pages.studio.auto_auth import AutoAuthPage
from ..fixtures.course import CourseFixture
from .helpers import UniqueCourseTest
-
+from ..pages.studio.overview import CourseOutlinePage
class StudioCourseTest(UniqueCourseTest):
@@ -45,3 +45,77 @@ class StudioCourseTest(UniqueCourseTest):
password=user.get('password')
)
self.auth_page.visit()
+
+
+class ContainerBase(StudioCourseTest):
+ """
+ Base class for tests that do operations on the container page.
+ """
+
+ def setUp(self):
+ """
+ Create a unique identifier for the course used in this test.
+ """
+ # Ensure that the superclass sets up
+ super(ContainerBase, self).setUp()
+
+ self.outline = CourseOutlinePage(
+ self.browser,
+ self.course_info['org'],
+ self.course_info['number'],
+ self.course_info['run']
+ )
+
+ def go_to_nested_container_page(self):
+ """
+ Go to the nested container page.
+ """
+ unit = self.go_to_unit_page()
+ # The 0th entry is the unit page itself.
+ container = unit.xblocks[1].go_to_container()
+ return container
+
+ def go_to_unit_page(self, section_name='Test Section', subsection_name='Test Subsection', unit_name='Test Unit'):
+ """
+ Go to the test unit page.
+
+ If make_draft is true, the unit page will be put into draft mode.
+ """
+ self.outline.visit()
+ 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.
+ """
+ container = self.go_to_nested_container_page()
+ action(container)
+
+ self.verify_ordering(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)
diff --git a/common/test/acceptance/tests/test_studio_bad_data.py b/common/test/acceptance/tests/test_studio_bad_data.py
new file mode 100644
index 0000000000..a42a45ebd0
--- /dev/null
+++ b/common/test/acceptance/tests/test_studio_bad_data.py
@@ -0,0 +1,104 @@
+from nose.plugins.attrib import attr
+from .base_studio_test import ContainerBase
+from ..fixtures.course import XBlockFixtureDesc
+
+
+@attr('shard_1')
+class BadComponentTest(ContainerBase):
+ """
+ Tests that components with bad content do not break the Unit page.
+ """
+ __test__ = False
+
+ def get_bad_html_content(self):
+ """
+ Return the "bad" HTML content that has been problematic for Studio.
+ """
+ pass
+
+ def populate_course_fixture(self, course_fixture):
+ """
+ Sets up a course structure with a unit and a HTML component with bad data and a properly constructed problem.
+ """
+
+ course_fixture.add_children(
+ XBlockFixtureDesc('chapter', 'Test Section').add_children(
+ XBlockFixtureDesc('sequential', 'Test Subsection').add_children(
+ XBlockFixtureDesc('vertical', 'Test Unit').add_children(
+ XBlockFixtureDesc('html', 'Unit HTML', data=self.get_bad_html_content()),
+ XBlockFixtureDesc('problem', 'Unit Problem', data='
Copied from LMS HTML component
You can access the experimental Voice Comparison tool at the link below.
+