Add tests that verify Studio can still render with bad HTML content.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
104
common/test/acceptance/tests/test_studio_bad_data.py
Normal file
104
common/test/acceptance/tests/test_studio_bad_data.py
Normal file
@@ -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='<problem></problem>')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def test_html_comp_visible(self):
|
||||
"""
|
||||
Tests that bad HTML data within an HTML component doesn't prevent Studio from
|
||||
displaying the components on the unit page.
|
||||
"""
|
||||
unit = self.go_to_unit_page()
|
||||
self.verify_ordering(unit, [{"": ["Unit HTML", "Unit Problem"]}])
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class CopiedFromLmsBadContentTest(BadComponentTest):
|
||||
"""
|
||||
Tests that components with HTML copied from the LMS (LmsRuntime) do not break the Unit page.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def get_bad_html_content(self):
|
||||
"""
|
||||
Return the "bad" HTML content that has been problematic for Studio.
|
||||
"""
|
||||
return """
|
||||
<div class="xblock xblock-student_view xmodule_display xmodule_HtmlModule xblock-initialized"
|
||||
data-runtime-class="LmsRuntime" data-init="XBlockToXModuleShim" data-block-type="html"
|
||||
data-runtime-version="1" data-type="HTMLModule" data-course-id="GeorgetownX/HUMW-421-01"
|
||||
data-request-token="thisIsNotARealRequestToken"
|
||||
data-usage-id="i4x:;_;_GeorgetownX;_HUMW-421-01;_html;_3010cbbecaa1484da6cf8ba01362346a">
|
||||
<p>Copied from LMS HTML component</p></div>
|
||||
"""
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class CopiedFromStudioBadContentTest(BadComponentTest):
|
||||
"""
|
||||
Tests that components with HTML copied from the Studio (containing "ui-sortable" class) do not break the Unit page.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def get_bad_html_content(self):
|
||||
"""
|
||||
Return the "bad" HTML content that has been problematic for Studio.
|
||||
"""
|
||||
return """
|
||||
<ol class="components ui-sortable">
|
||||
<li class="component" data-locator="i4x://Wellesley_College/100/html/6390f1fd3fe640d49580b8415fe1330b"
|
||||
data-course-key="Wellesley_College/100/2014_Summer">
|
||||
<div class="xblock xblock-student_view xmodule_display xmodule_HtmlModule xblock-initialized"
|
||||
data-runtime-class="PreviewRuntime" data-init="XBlockToXModuleShim" data-runtime-version="1"
|
||||
data-request-token="thisIsNotARealRequestToken"
|
||||
data-usage-id="i4x://Wellesley_College/100/html/6390f1fd3fe640d49580b8415fe1330b"
|
||||
data-type="HTMLModule" data-block-type="html">
|
||||
<h2>VOICE COMPARISON </h2>
|
||||
<p>You can access the experimental <strong >Voice Comparison</strong> tool at the link below.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
"""
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class JSErrorBadContentTest(BadComponentTest):
|
||||
"""
|
||||
Tests that components that throw JS errors do not break the Unit page.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def get_bad_html_content(self):
|
||||
"""
|
||||
Return the "bad" HTML content that has been problematic for Studio.
|
||||
"""
|
||||
return "<script>var doesNotExist = BadGlobal.foo;</script>"
|
||||
@@ -5,8 +5,6 @@ displaying containers within units.
|
||||
"""
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from ..pages.studio.overview import CourseOutlinePage
|
||||
|
||||
from ..fixtures.course import XBlockFixtureDesc
|
||||
from ..pages.studio.component_editor import ComponentEditorView
|
||||
from ..pages.studio.html_component_editor import HtmlComponentEditorView
|
||||
@@ -16,87 +14,10 @@ from ..pages.lms.staff_view import StaffPage
|
||||
|
||||
import datetime
|
||||
from bok_choy.promise import Promise, EmptyPromise
|
||||
from .base_studio_test import StudioCourseTest
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class ContainerBase(StudioCourseTest):
|
||||
"""
|
||||
Base class for tests that do operations on the container page.
|
||||
"""
|
||||
__test__ = False
|
||||
|
||||
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)
|
||||
from .base_studio_test import ContainerBase
|
||||
|
||||
|
||||
class NestedVerticalTest(ContainerBase):
|
||||
__test__ = False
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
"""
|
||||
@@ -151,7 +72,6 @@ class DragAndDropTest(NestedVerticalTest):
|
||||
"""
|
||||
Tests of reordering within the container page.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def drag_and_verify(self, source, target, expected_ordering):
|
||||
self.do_action_and_verify(
|
||||
@@ -232,7 +152,6 @@ class AddComponentTest(NestedVerticalTest):
|
||||
"""
|
||||
Tests of adding a component to the container page.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def add_and_verify(self, menu_index, expected_ordering):
|
||||
self.do_action_and_verify(
|
||||
@@ -273,7 +192,6 @@ class DuplicateComponentTest(NestedVerticalTest):
|
||||
"""
|
||||
Tests of duplicating a component on the container page.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def duplicate_and_verify(self, source_index, expected_ordering):
|
||||
self.do_action_and_verify(
|
||||
@@ -320,7 +238,6 @@ class DeleteComponentTest(NestedVerticalTest):
|
||||
"""
|
||||
Tests of deleting a component from the container page.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def delete_and_verify(self, source_index, expected_ordering):
|
||||
self.do_action_and_verify(
|
||||
@@ -344,7 +261,6 @@ class EditContainerTest(NestedVerticalTest):
|
||||
"""
|
||||
Tests of editing a container.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def modify_display_name_and_verify(self, component):
|
||||
"""
|
||||
@@ -377,7 +293,6 @@ class UnitPublishingTest(ContainerBase):
|
||||
"""
|
||||
Tests of the publishing control and related widgets on the Unit page.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
PUBLISHED_STATUS = "Publishing Status\nPublished (not yet released)"
|
||||
PUBLISHED_LIVE_STATUS = "Publishing Status\nPublished and Live"
|
||||
|
||||
Reference in New Issue
Block a user