Add bokchoy tests for outline page
This commit is contained in:
@@ -125,6 +125,7 @@ define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_
|
||||
// as it cannot visually effect the other sections.
|
||||
if (childCategory === 'chapter' && children && children.length > 1) {
|
||||
childView.$el.remove();
|
||||
children.splice(children.indexOf(childView.model), 1);
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
});
|
||||
this.nameEditor.render();
|
||||
if (this.options.action === 'new') {
|
||||
this.nameEditor.$('.xblock-field-value').click();
|
||||
this.nameEditor.$('.xblock-field-value-edit').click();
|
||||
}
|
||||
this.xblockView = new ContainerView({
|
||||
el: this.$('.wrapper-xblock'),
|
||||
|
||||
@@ -194,7 +194,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
|
||||
}
|
||||
ViewUtils.setScrollOffset(locatorElement, scrollOffset);
|
||||
if (editDisplayName) {
|
||||
locatorElement.find('> .wrapper-xblock-header .xblock-field-value').click();
|
||||
locatorElement.find('> .wrapper-xblock-header .xblock-field-value-edit').click();
|
||||
}
|
||||
}
|
||||
this.initialState = null;
|
||||
|
||||
@@ -8,7 +8,7 @@ from . import BASE_URL
|
||||
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
|
||||
from utils import click_css, wait_for_notification
|
||||
from utils import click_css, wait_for_notification, confirm_prompt
|
||||
|
||||
|
||||
class ContainerPage(PageObject):
|
||||
@@ -16,6 +16,8 @@ class ContainerPage(PageObject):
|
||||
Container page in Studio
|
||||
"""
|
||||
NAME_SELECTOR = '.page-header-title'
|
||||
NAME_INPUT_SELECTOR = '.page-header .xblock-field-input'
|
||||
NAME_FIELD_WRAPPER_SELECTOR = '.page-header .wrapper-xblock-field'
|
||||
|
||||
def __init__(self, browser, locator):
|
||||
super(ContainerPage, self).__init__(browser)
|
||||
@@ -134,7 +136,7 @@ class ContainerPage(PageObject):
|
||||
Discards draft changes (which will then re-render the page).
|
||||
"""
|
||||
click_css(self, 'a.action-discard', 0, require_notification=False)
|
||||
self.q(css='a.button.action-primary').first.click()
|
||||
confirm_prompt(self)
|
||||
self.wait_for_ajax()
|
||||
|
||||
def toggle_staff_lock(self):
|
||||
@@ -149,7 +151,7 @@ class ContainerPage(PageObject):
|
||||
self.q(css='a.action-staff-lock').first.click()
|
||||
else:
|
||||
click_css(self, 'a.action-staff-lock', 0, require_notification=False)
|
||||
self.q(css='a.button.action-primary').first.click()
|
||||
confirm_prompt(self)
|
||||
self.wait_for_ajax()
|
||||
return not was_locked_initially
|
||||
|
||||
@@ -218,16 +220,8 @@ class ContainerPage(PageObject):
|
||||
"""
|
||||
# Click the delete button
|
||||
click_css(self, 'a.delete-button', source_index, require_notification=False)
|
||||
|
||||
# Wait for the warning prompt to appear
|
||||
self.wait_for_element_visibility('#prompt-warning', 'Deletion warning prompt is visible')
|
||||
|
||||
# Make sure the delete button is there
|
||||
confirmation_button_css = '#prompt-warning a.button.action-primary'
|
||||
self.wait_for_element_visibility(confirmation_button_css, 'Confirmation dialog button is visible')
|
||||
|
||||
# Click the confirmation dialog button
|
||||
click_css(self, confirmation_button_css, 0)
|
||||
confirm_prompt(self)
|
||||
|
||||
def edit(self):
|
||||
"""
|
||||
@@ -255,6 +249,12 @@ class ContainerPage(PageObject):
|
||||
"""
|
||||
return self.q(css=".xblock-message.information").first.text[0]
|
||||
|
||||
def is_inline_editing_display_name(self):
|
||||
"""
|
||||
Return whether this container's display name is in its editable form.
|
||||
"""
|
||||
return "is-editing" in self.q(css=self.NAME_FIELD_WRAPPER_SELECTOR).first.attrs("class")[0]
|
||||
|
||||
|
||||
class XBlockWrapper(PageObject):
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,7 @@ from bok_choy.promise import EmptyPromise
|
||||
|
||||
from .course_page import CoursePage
|
||||
from .container import ContainerPage
|
||||
from .utils import set_input_value_and_save
|
||||
from .utils import set_input_value_and_save, click_css, confirm_prompt
|
||||
|
||||
|
||||
class CourseOutlineItem(object):
|
||||
@@ -17,9 +17,13 @@ class CourseOutlineItem(object):
|
||||
EDIT_BUTTON_SELECTOR = '.xblock-title .xblock-field-value-edit'
|
||||
NAME_SELECTOR = '.xblock-title .xblock-field-value'
|
||||
NAME_INPUT_SELECTOR = '.xblock-title .xblock-field-input'
|
||||
NAME_FIELD_WRAPPER_SELECTOR = '.xblock-title .wrapper-xblock-field'
|
||||
|
||||
def __repr__(self):
|
||||
return "{}(<browser>, {!r})".format(self.__class__.__name__, self.locator)
|
||||
# CourseOutlineItem is also used as a mixin for CourseOutlinePage, which doesn't have a locator
|
||||
# Check for the existence of a locator so that errors when navigating to the course outline page don't show up
|
||||
# as errors in the repr method instead.
|
||||
return "{}(<browser>, {!r})".format(self.__class__.__name__, self.locator if hasattr(self, 'locator') else None)
|
||||
|
||||
def _bounded_selector(self, selector):
|
||||
"""
|
||||
@@ -50,6 +54,14 @@ class CourseOutlineItem(object):
|
||||
set_input_value_and_save(self, self._bounded_selector(self.NAME_INPUT_SELECTOR), new_name)
|
||||
self.wait_for_ajax()
|
||||
|
||||
def in_editable_form(self):
|
||||
"""
|
||||
Return whether this outline item's display name is in its editable form.
|
||||
"""
|
||||
return "is-editing" in self.q(
|
||||
css=self._bounded_selector(self.NAME_FIELD_WRAPPER_SELECTOR)
|
||||
)[0].get_attribute("class")
|
||||
|
||||
|
||||
class CourseOutlineContainer(CourseOutlineItem):
|
||||
"""
|
||||
@@ -76,6 +88,15 @@ class CourseOutlineContainer(CourseOutlineItem):
|
||||
).attrs('data-locator')[0]
|
||||
)
|
||||
|
||||
def children(self, child_class=None):
|
||||
"""
|
||||
Returns all the children page objects of class child_class.
|
||||
"""
|
||||
if not child_class:
|
||||
child_class = self.CHILD_CLASS
|
||||
return self.q(css=child_class.BODY_SELECTOR).map(
|
||||
lambda el: child_class(self.browser, el.get_attribute('data-locator'))).results
|
||||
|
||||
def child_at(self, index, child_class=None):
|
||||
"""
|
||||
Returns the child at the specified index.
|
||||
@@ -84,11 +105,47 @@ class CourseOutlineContainer(CourseOutlineItem):
|
||||
if not child_class:
|
||||
child_class = self.CHILD_CLASS
|
||||
|
||||
return child_class(
|
||||
self.browser,
|
||||
self.q(css=child_class.BODY_SELECTOR).attrs('data-locator')[index]
|
||||
return self.children(child_class)[index]
|
||||
|
||||
def add_child(self, require_notification=True):
|
||||
"""
|
||||
Adds a child to this xblock, waiting for notifications.
|
||||
"""
|
||||
click_css(
|
||||
self,
|
||||
self._bounded_selector(".add-xblock-component a.add-button"),
|
||||
require_notification=require_notification,
|
||||
)
|
||||
|
||||
def toggle_expand(self):
|
||||
"""
|
||||
Toggle the expansion of this subsection.
|
||||
"""
|
||||
|
||||
self.browser.execute_script("jQuery.fx.off = true;")
|
||||
|
||||
def subsection_expanded():
|
||||
add_button = self.q(css=self._bounded_selector('> .add-xblock-component a.add-button')).first.results
|
||||
return add_button and add_button[0].is_displayed()
|
||||
|
||||
currently_expanded = subsection_expanded()
|
||||
|
||||
self.q(css=self._bounded_selector('.ui-toggle-expansion')).first.click()
|
||||
|
||||
EmptyPromise(
|
||||
lambda: subsection_expanded() != currently_expanded,
|
||||
"Check that the container {} has been toggled".format(self.locator)
|
||||
).fulfill()
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def is_collapsed(self):
|
||||
"""
|
||||
Return whether this outline item is currently collapsed.
|
||||
"""
|
||||
return "collapsed" in self.q(css=self._bounded_selector('')).first.attrs("class")[0]
|
||||
|
||||
|
||||
class CourseOutlineChild(PageObject, CourseOutlineItem):
|
||||
"""
|
||||
@@ -101,10 +158,17 @@ class CourseOutlineChild(PageObject, CourseOutlineItem):
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='{}[data-locator="{}"]'.format(self.BODY_SELECTOR, self.locator)).present
|
||||
|
||||
def delete(self, cancel=False):
|
||||
"""
|
||||
Clicks the delete button, then cancels at the confirmation prompt if cancel is True.
|
||||
"""
|
||||
click_css(self, self._bounded_selector('.delete-button'), require_notification=False)
|
||||
confirm_prompt(self, cancel)
|
||||
|
||||
|
||||
class CourseOutlineUnit(CourseOutlineChild):
|
||||
"""
|
||||
PageObject that wraps a unit link on the Studio Course Overview page.
|
||||
PageObject that wraps a unit link on the Studio Course Outline page.
|
||||
"""
|
||||
url = None
|
||||
BODY_SELECTOR = '.outline-item-unit'
|
||||
@@ -123,7 +187,7 @@ class CourseOutlineUnit(CourseOutlineChild):
|
||||
|
||||
class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
|
||||
"""
|
||||
:class`.PageObject` that wraps a subsection block on the Studio Course Overview page.
|
||||
:class`.PageObject` that wraps a subsection block on the Studio Course Outline page.
|
||||
"""
|
||||
url = None
|
||||
|
||||
@@ -136,31 +200,28 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
|
||||
"""
|
||||
return self.child(title)
|
||||
|
||||
def toggle_expand(self):
|
||||
def units(self):
|
||||
"""
|
||||
Toggle the expansion of this subsection.
|
||||
Returns the units in this subsection.
|
||||
"""
|
||||
self.browser.execute_script("jQuery.fx.off = true;")
|
||||
return self.children()
|
||||
|
||||
def subsection_expanded():
|
||||
add_button = self.q(css=self._bounded_selector('.add-button')).first.results
|
||||
return add_button and add_button[0].is_displayed()
|
||||
def unit_at(self, index):
|
||||
"""
|
||||
Returns the CourseOutlineUnit at the specified index.
|
||||
"""
|
||||
return self.child_at(index)
|
||||
|
||||
currently_expanded = subsection_expanded()
|
||||
|
||||
self.q(css=self._bounded_selector('.ui-toggle-expansion')).first.click()
|
||||
|
||||
EmptyPromise(
|
||||
lambda: subsection_expanded() != currently_expanded,
|
||||
"Check that the subsection {} has been toggled".format(self.locator)
|
||||
).fulfill()
|
||||
|
||||
return self
|
||||
def add_unit(self):
|
||||
"""
|
||||
Adds a unit to this subsection
|
||||
"""
|
||||
self.add_child(require_notification=False)
|
||||
|
||||
|
||||
class CourseOutlineSection(CourseOutlineChild, CourseOutlineContainer):
|
||||
"""
|
||||
:class`.PageObject` that wraps a section block on the Studio Course Overview page.
|
||||
:class`.PageObject` that wraps a section block on the Studio Course Outline page.
|
||||
"""
|
||||
url = None
|
||||
BODY_SELECTOR = '.outline-item-section'
|
||||
@@ -172,6 +233,33 @@ class CourseOutlineSection(CourseOutlineChild, CourseOutlineContainer):
|
||||
"""
|
||||
return self.child(title)
|
||||
|
||||
def subsections(self):
|
||||
"""
|
||||
Returns a list of the CourseOutlineSubsections of this section
|
||||
"""
|
||||
return self.children()
|
||||
|
||||
def subsection_at(self, index):
|
||||
"""
|
||||
Returns the CourseOutlineSubsection at the specified index.
|
||||
"""
|
||||
return self.child_at(index)
|
||||
|
||||
def add_subsection(self):
|
||||
"""
|
||||
Adds a subsection to this section
|
||||
"""
|
||||
self.add_child()
|
||||
|
||||
|
||||
class ExpandCollapseLinkState:
|
||||
"""
|
||||
Represents the three states that the expand/collapse link can be in
|
||||
"""
|
||||
MISSING = 0
|
||||
COLLAPSE = 1
|
||||
EXPAND = 2
|
||||
|
||||
|
||||
class CourseOutlinePage(CoursePage, CourseOutlineContainer):
|
||||
"""
|
||||
@@ -179,10 +267,19 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
|
||||
"""
|
||||
url_path = "course"
|
||||
CHILD_CLASS = CourseOutlineSection
|
||||
EXPAND_COLLAPSE_CSS = '.toggle-button-expand-collapse'
|
||||
BOTTOM_ADD_SECTION_BUTTON = '.course-outline > .add-xblock-component .add-button'
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='body.view-outline').present
|
||||
|
||||
def view_live(self):
|
||||
"""
|
||||
Clicks the "View Live" link and switches to the new tab
|
||||
"""
|
||||
click_css(self, '.view-live-button', require_notification=False)
|
||||
self.browser.switch_to_window(self.browser.window_handles[-1])
|
||||
|
||||
def section(self, title):
|
||||
"""
|
||||
Return the :class:`.CourseOutlineSection` with the title `title`.
|
||||
@@ -194,7 +291,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
|
||||
Returns the :class:`.CourseOutlineSection` at the specified index.
|
||||
"""
|
||||
return self.child_at(index)
|
||||
|
||||
|
||||
def click_section_name(self, parent_css=''):
|
||||
"""
|
||||
Find and click on first section name in course outline
|
||||
@@ -229,3 +326,54 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
|
||||
Open release date edit modal of first section in course outline
|
||||
"""
|
||||
self.q(css='div.section-published-date a.edit-release-date').first.click()
|
||||
|
||||
def sections(self):
|
||||
"""
|
||||
Returns the sections of this course outline page.
|
||||
"""
|
||||
return self.children()
|
||||
|
||||
def add_section_from_top_button(self):
|
||||
"""
|
||||
Clicks the button for adding a section which resides at the top of the screen.
|
||||
"""
|
||||
click_css(self, '.wrapper-mast nav.nav-actions .add-button')
|
||||
|
||||
def add_section_from_bottom_button(self):
|
||||
"""
|
||||
Clicks the button for adding a section which resides at the bottom of the screen.
|
||||
"""
|
||||
click_css(self, self.BOTTOM_ADD_SECTION_BUTTON)
|
||||
|
||||
def toggle_expand_collapse(self):
|
||||
"""
|
||||
Toggles whether all sections are expanded or collapsed
|
||||
"""
|
||||
self.q(css=self.EXPAND_COLLAPSE_CSS).click()
|
||||
|
||||
@property
|
||||
def bottom_add_section_button(self):
|
||||
"""
|
||||
Returns the query representing the bottom add section button.
|
||||
"""
|
||||
return self.q(css=self.BOTTOM_ADD_SECTION_BUTTON).first
|
||||
|
||||
@property
|
||||
def has_no_content_message(self):
|
||||
"""
|
||||
Returns true if a message informing the user that the course has no content is visible
|
||||
"""
|
||||
return self.q(css='.course-outline .no-content').is_present()
|
||||
|
||||
@property
|
||||
def expand_collapse_link_state(self):
|
||||
"""
|
||||
Returns the current state of the expand/collapse link
|
||||
"""
|
||||
link = self.q(css=self.EXPAND_COLLAPSE_CSS)[0]
|
||||
if not link.is_displayed():
|
||||
return ExpandCollapseLinkState.MISSING
|
||||
elif "collapse-all" in link.get_attribute("class"):
|
||||
return ExpandCollapseLinkState.COLLAPSE
|
||||
else:
|
||||
return ExpandCollapseLinkState.EXPAND
|
||||
|
||||
@@ -139,3 +139,14 @@ def set_input_value_and_save(page, css, value):
|
||||
action = action.send_keys(Keys.BACKSPACE)
|
||||
# Send the new text, then hit the enter key so that the change event is triggered).
|
||||
action.send_keys(value).send_keys(Keys.ENTER).perform()
|
||||
|
||||
|
||||
def confirm_prompt(page, cancel=False):
|
||||
"""
|
||||
Ensures that a modal prompt and confirmation button are visible, then clicks the button. The prompt is canceled iff
|
||||
cancel is True.
|
||||
"""
|
||||
page.wait_for_element_visibility('.prompt', 'Prompt is visible')
|
||||
confirmation_button_css = '.prompt .action-' + ('secondary' if cancel else 'primary')
|
||||
page.wait_for_element_visibility(confirmation_button_css, 'Confirmation button is visible')
|
||||
click_css(page, confirmation_button_css, require_notification=(not cancel))
|
||||
|
||||
@@ -6,18 +6,17 @@ displaying containers within units.
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from ..pages.studio.overview import CourseOutlinePage
|
||||
from ..fixtures.course import XBlockFixtureDesc
|
||||
|
||||
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.lms.courseware import CoursewarePage
|
||||
from ..pages.lms.staff_view import StaffPage
|
||||
|
||||
from unittest import skip
|
||||
from acceptance.tests.base_studio_test import StudioCourseTest
|
||||
import datetime
|
||||
from bok_choy.promise import Promise, EmptyPromise
|
||||
from acceptance.tests.base_studio_test import StudioCourseTest
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
@@ -388,23 +387,17 @@ class UnitPublishingTest(ContainerBase):
|
||||
LAST_PUBLISHED = 'Last published'
|
||||
LAST_SAVED = 'Draft saved on'
|
||||
|
||||
def setup_fixtures(self):
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
"""
|
||||
Sets up a course structure with a unit and a single HTML child.
|
||||
"""
|
||||
|
||||
self.html_content = '<p><strong>Body of HTML Unit.</strong></p>'
|
||||
self.courseware = CoursewarePage(self.browser, self.course_id)
|
||||
|
||||
course_fix = CourseFixture(
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run'],
|
||||
self.course_info['display_name']
|
||||
)
|
||||
past_start_date = datetime.datetime(1974, 6, 22)
|
||||
self.past_start_date_text = "Jun 22, 1974 at 00:00 UTC"
|
||||
|
||||
course_fix.add_children(
|
||||
course_fixture.add_children(
|
||||
XBlockFixtureDesc('chapter', 'Test Section').add_children(
|
||||
XBlockFixtureDesc('sequential', 'Test Subsection').add_children(
|
||||
XBlockFixtureDesc('vertical', 'Test Unit').add_children(
|
||||
@@ -426,9 +419,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
)
|
||||
)
|
||||
)
|
||||
).install()
|
||||
|
||||
self.user = course_fix.user
|
||||
)
|
||||
|
||||
def test_publishing(self):
|
||||
"""
|
||||
@@ -495,7 +486,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
Then I see the published content in LMS
|
||||
"""
|
||||
unit = self.go_to_unit_page()
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
self._verify_components_visible(['html'])
|
||||
|
||||
def test_view_live_changes(self):
|
||||
@@ -510,7 +501,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
"""
|
||||
unit = self.go_to_unit_page()
|
||||
add_discussion(unit)
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
self._verify_components_visible(['html'])
|
||||
self.assertEqual(self.html_content, self.courseware.xblock_component_html_content(0))
|
||||
|
||||
@@ -527,7 +518,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
unit = self.go_to_unit_page()
|
||||
add_discussion(unit)
|
||||
unit.publish_action.click()
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
self._verify_components_visible(['html', 'discussion'])
|
||||
|
||||
def test_initially_unlocked_visible_to_students(self):
|
||||
@@ -547,7 +538,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
self._verify_release_date_info(
|
||||
unit, self.RELEASE_TITLE_RELEASED, self.past_start_date_text + ' with Section "Unlocked Section"'
|
||||
)
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
self._verify_student_view_visible(['problem'])
|
||||
|
||||
def test_locked_visible_to_staff_only(self):
|
||||
@@ -567,7 +558,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
self.assertTrue(checked)
|
||||
self.assertFalse(unit.currently_visible_to_students)
|
||||
self._verify_publish_title(unit, self.LOCKED_STATUS)
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
# Will initially be in staff view, locked component should be visible.
|
||||
self._verify_components_visible(['problem'])
|
||||
# Switch to student view and verify not visible
|
||||
@@ -591,7 +582,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
unit, self.RELEASE_TITLE_RELEASED,
|
||||
self.past_start_date_text + ' with Subsection "Subsection With Locked Unit"'
|
||||
)
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
self._verify_student_view_locked()
|
||||
|
||||
def test_unlocked_visible_to_all(self):
|
||||
@@ -611,7 +602,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
self.assertFalse(checked)
|
||||
self._verify_publish_title(unit, self.PUBLISHED_STATUS)
|
||||
self.assertTrue(unit.currently_visible_to_students)
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
# Will initially be in staff view, components always visible.
|
||||
self._verify_components_visible(['discussion'])
|
||||
# Switch to student view and verify visible.
|
||||
@@ -641,7 +632,7 @@ class UnitPublishingTest(ContainerBase):
|
||||
unit.publish_action.click()
|
||||
unit.wait_for_ajax()
|
||||
self._verify_publish_title(unit, self.PUBLISHED_STATUS)
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
self.assertTrue(modified_content in self.courseware.xblock_component_html_content(0))
|
||||
|
||||
def test_delete_child_in_published_unit(self):
|
||||
@@ -662,9 +653,16 @@ class UnitPublishingTest(ContainerBase):
|
||||
unit.publish_action.click()
|
||||
unit.wait_for_ajax()
|
||||
self._verify_publish_title(unit, self.PUBLISHED_STATUS)
|
||||
unit.view_published_version()
|
||||
self._view_published_version(unit)
|
||||
self.assertEqual(0, self.courseware.num_xblock_components)
|
||||
|
||||
def _view_published_version(self, unit):
|
||||
"""
|
||||
Goes to the published version, then waits for the browser to load the page.
|
||||
"""
|
||||
unit.view_published_version()
|
||||
self.courseware.wait_for_page()
|
||||
|
||||
def _verify_and_return_staff_page(self):
|
||||
"""
|
||||
Verifies that the browser is on the staff page and returns a StaffPage.
|
||||
|
||||
@@ -113,57 +113,6 @@ class CoursePagesTest(StudioCourseTest):
|
||||
page.visit()
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class CourseSectionTest(StudioCourseTest):
|
||||
"""
|
||||
Tests that verify the sections name editable only inside headers in Studio Course Outline 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(CourseSectionTest, self).setUp()
|
||||
self.course_outline_page = CourseOutlinePage(
|
||||
self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']
|
||||
)
|
||||
self.course_outline_page.visit()
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
""" Populates the course fixture with a test section """
|
||||
course_fixture.add_children(
|
||||
XBlockFixtureDesc('chapter', 'Test Section')
|
||||
)
|
||||
|
||||
def test_section_name_editable_in_course_outline(self):
|
||||
"""
|
||||
Check that section name is editable on course outline page.
|
||||
"""
|
||||
new_name = u"Test Section New"
|
||||
section = self.course_outline_page.section_at(0)
|
||||
self.assertEqual(section.name, u"Test Section")
|
||||
section.change_name(new_name)
|
||||
self.browser.refresh()
|
||||
self.assertEqual(section.name, new_name)
|
||||
|
||||
# TODO: re-enable when release date support is added back
|
||||
# def test_section_name_not_editable_inside_modal(self):
|
||||
# """
|
||||
# Check that section name is not editable inside "Section Release Date" modal on course outline page.
|
||||
# """
|
||||
# parent_css='div.modal-window'
|
||||
# self.course_outline_page.click_release_date()
|
||||
# section_name = self.course_outline_page.get_section_name(parent_css)[0]
|
||||
# self.assertEqual(section_name, '"Test Section"')
|
||||
# self.course_outline_page.click_section_name(parent_css)
|
||||
# section_name_edit_form = self.course_outline_page.section_name_edit_form_present(parent_css)
|
||||
# self.assertFalse(section_name_edit_form)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class DiscussionPreviewTest(StudioCourseTest):
|
||||
"""
|
||||
Tests that Inline Discussions are rendered with a custom preview in Studio
|
||||
|
||||
574
common/test/acceptance/tests/test_studio_outline.py
Normal file
574
common/test/acceptance/tests/test_studio_outline.py
Normal file
@@ -0,0 +1,574 @@
|
||||
"""
|
||||
Acceptance tests for studio related to the outline page.
|
||||
"""
|
||||
|
||||
from bok_choy.promise import EmptyPromise
|
||||
|
||||
from ..pages.studio.overview import CourseOutlinePage, ContainerPage, ExpandCollapseLinkState
|
||||
from ..pages.lms.courseware import CoursewarePage
|
||||
from ..fixtures.course import XBlockFixtureDesc
|
||||
|
||||
from acceptance.tests.base_studio_test import StudioCourseTest
|
||||
|
||||
|
||||
class CourseOutlineTest(StudioCourseTest):
|
||||
"""
|
||||
Base class for all course outline tests
|
||||
"""
|
||||
|
||||
COURSE_ID_SEPARATOR = "."
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Install a course with no content using a fixture.
|
||||
"""
|
||||
super(CourseOutlineTest, self).setUp()
|
||||
self.course_outline_page = CourseOutlinePage(
|
||||
self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']
|
||||
)
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
""" Install a course with sections/problems, tabs, updates, and handouts """
|
||||
course_fixture.add_children(
|
||||
XBlockFixtureDesc('chapter', 'Test Section').add_children(
|
||||
XBlockFixtureDesc('sequential', 'Test Subsection').add_children(
|
||||
XBlockFixtureDesc('vertical', 'Test Unit').add_children(
|
||||
XBlockFixtureDesc('html', 'Test HTML Component'),
|
||||
XBlockFixtureDesc('discussion', 'Test Discussion Component')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class EditNamesTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Click-to-edit section/subsection names
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def set_name_and_verify(self, item, old_name, new_name, expected_name):
|
||||
"""
|
||||
Changes the display name of item from old_name to new_name, then verifies that its value is expected_name.
|
||||
"""
|
||||
self.assertEqual(item.name, old_name)
|
||||
item.change_name(new_name)
|
||||
self.assertFalse(item.in_editable_form())
|
||||
self.assertEqual(item.name, expected_name)
|
||||
|
||||
def test_edit_section_name(self):
|
||||
"""
|
||||
Scenario: Click-to-edit section name
|
||||
Given that I have created a section
|
||||
When I click on the name of section
|
||||
Then the section name becomes editable
|
||||
And given that I have edited the section name
|
||||
When I click outside of the edited section name
|
||||
Then the section name saves
|
||||
And becomes non-editable
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.set_name_and_verify(
|
||||
self.course_outline_page.section_at(0),
|
||||
'Test Section',
|
||||
'Changed',
|
||||
'Changed'
|
||||
)
|
||||
|
||||
def test_edit_subsection_name(self):
|
||||
"""
|
||||
Scenario: Click-to-edit subsection name
|
||||
Given that I have created a subsection
|
||||
When I click on the name of subsection
|
||||
Then the subsection name becomes editable
|
||||
And given that I have edited the subsection name
|
||||
When I click outside of the edited subsection name
|
||||
Then the subsection name saves
|
||||
And becomes non-editable
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.set_name_and_verify(
|
||||
self.course_outline_page.section_at(0).subsection_at(0),
|
||||
'Test Subsection',
|
||||
'Changed',
|
||||
'Changed'
|
||||
)
|
||||
|
||||
def test_edit_empty_section_name(self):
|
||||
"""
|
||||
Scenario: Click-to-edit section name, enter empty name
|
||||
Given that I have created a section
|
||||
And I have clicked to edit the name of the section
|
||||
And I have entered an empty section name
|
||||
When I click outside of the edited section name
|
||||
Then the section name does not change
|
||||
And becomes non-editable
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.set_name_and_verify(
|
||||
self.course_outline_page.section_at(0),
|
||||
'Test Section',
|
||||
'',
|
||||
'Test Section'
|
||||
)
|
||||
|
||||
def test_edit_empty_subsection_name(self):
|
||||
"""
|
||||
Scenario: Click-to-edit subsection name, enter empty name
|
||||
Given that I have created a subsection
|
||||
And I have clicked to edit the name of the subsection
|
||||
And I have entered an empty subsection name
|
||||
When I click outside of the edited subsection name
|
||||
Then the subsection name does not change
|
||||
And becomes non-editable
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.set_name_and_verify(
|
||||
self.course_outline_page.section_at(0).subsection_at(0),
|
||||
'Test Subsection',
|
||||
'',
|
||||
'Test Subsection'
|
||||
)
|
||||
|
||||
|
||||
class CreateSectionsTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Create new sections/subsections/units
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
""" Start with a completely empty course to easily test adding things to it """
|
||||
pass
|
||||
|
||||
def test_create_new_section_from_top_button(self):
|
||||
"""
|
||||
Scenario: Create new section from button at top of page
|
||||
Given that I am on the course outline
|
||||
When I click the "+ Add section" button at the top of the page
|
||||
Then I see a new section added to the bottom of the page
|
||||
And the display name is in its editable form.
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.add_section_from_top_button()
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
||||
self.assertTrue(self.course_outline_page.section_at(0).in_editable_form())
|
||||
|
||||
def test_create_new_section_from_bottom_button(self):
|
||||
"""
|
||||
Scenario: Create new section from button at bottom of page
|
||||
Given that I am on the course outline
|
||||
When I click the "+ Add section" button at the bottom of the page
|
||||
Then I see a new section added to the bottom of the page
|
||||
And the display name is in its editable form.
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.add_section_from_bottom_button()
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
||||
self.assertTrue(self.course_outline_page.section_at(0).in_editable_form())
|
||||
|
||||
def test_create_new_subsection(self):
|
||||
"""
|
||||
Scenario: Create new subsection
|
||||
Given that I have created a section
|
||||
When I click the "+ Add subsection" button in that section
|
||||
Then I see a new subsection added to the bottom of the section
|
||||
And the display name is in its editable form.
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.add_section_from_top_button()
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
||||
self.course_outline_page.section_at(0).add_subsection()
|
||||
subsections = self.course_outline_page.section_at(0).subsections()
|
||||
self.assertEqual(len(subsections), 1)
|
||||
self.assertTrue(subsections[0].in_editable_form())
|
||||
|
||||
def test_create_new_unit(self):
|
||||
"""
|
||||
Scenario: Create new unit
|
||||
Given that I have created a section
|
||||
And that I have created a subsection within that section
|
||||
When I click the "+ Add unit" button in that subsection
|
||||
Then I am redirected to a New Unit page
|
||||
And the display name is in its editable form.
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.add_section_from_top_button()
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
||||
self.course_outline_page.section_at(0).add_subsection()
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1)
|
||||
self.course_outline_page.section_at(0).subsection_at(0).add_unit()
|
||||
unit_page = ContainerPage(self.browser, None)
|
||||
EmptyPromise(unit_page.is_browser_on_page, 'Browser is on the unit page').fulfill()
|
||||
self.assertTrue(unit_page.is_inline_editing_display_name())
|
||||
|
||||
|
||||
class DeleteContentTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Deleting sections/subsections/units
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def test_delete_section(self):
|
||||
"""
|
||||
Scenario: Delete section
|
||||
Given that I am on the course outline
|
||||
When I click the delete button for a section on the course outline
|
||||
Then I should receive a confirmation message, asking me if I really want to delete the section
|
||||
When I click "Yes, I want to delete this component"
|
||||
Then the confirmation message should close
|
||||
And the section should immediately be deleted from the course outline
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
||||
self.course_outline_page.section_at(0).delete()
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 0)
|
||||
|
||||
def test_cancel_delete_section(self):
|
||||
"""
|
||||
Scenario: Cancel delete of section
|
||||
Given that I clicked the delte button for a section on the course outline
|
||||
And I received a confirmation message, asking me if I really want to delete the component
|
||||
When I click "Cancel"
|
||||
Then the confirmation message should close
|
||||
And the section should remain in the course outline
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
||||
self.course_outline_page.section_at(0).delete(cancel=True)
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 1)
|
||||
|
||||
def test_delete_subsection(self):
|
||||
"""
|
||||
Scenario: Delete subsection
|
||||
Given that I am on the course outline
|
||||
When I click the delete button for a subsection on the course outline
|
||||
Then I should receive a confirmation message, asking me if I really want to delete the subsection
|
||||
When I click "Yes, I want to delete this component"
|
||||
Then the confiramtion message should close
|
||||
And the subsection should immediately be deleted from the course outline
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1)
|
||||
self.course_outline_page.section_at(0).subsection_at(0).delete()
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 0)
|
||||
|
||||
def test_cancel_delete_subsection(self):
|
||||
"""
|
||||
Scenario: Cancel delete of subsection
|
||||
Given that I clicked the delete button for a subsection on the course outline
|
||||
And I received a confirmation message, asking me if I really want to delete the subsection
|
||||
When I click "cancel"
|
||||
Then the confirmation message should close
|
||||
And the subsection should remain in the course outline
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1)
|
||||
self.course_outline_page.section_at(0).subsection_at(0).delete(cancel=True)
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1)
|
||||
|
||||
def test_delete_unit(self):
|
||||
"""
|
||||
Scenario: Delete unit
|
||||
Given that I am on the course outline
|
||||
When I click the delete button for a unit on the course outline
|
||||
Then I should receive a confirmation message, asking me if I really want to delete the unit
|
||||
When I click "Yes, I want to delete this unit"
|
||||
Then the confirmation message should close
|
||||
And the unit should immediately be deleted from the course outline
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.section_at(0).subsection_at(0).toggle_expand()
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1)
|
||||
self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).delete()
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 0)
|
||||
|
||||
def test_cancel_delete_unit(self):
|
||||
"""
|
||||
Scenario: Cancel delete of unit
|
||||
Given that I clicked the delete button for a unit on the course outline
|
||||
And I received a confirmation message, asking me if I really want to delete the unit
|
||||
When I click "Cancel"
|
||||
Then the confirmation message should close
|
||||
And the unit should remain in the course outline
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.section_at(0).subsection_at(0).toggle_expand()
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1)
|
||||
self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).delete(cancel=True)
|
||||
self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1)
|
||||
|
||||
def test_delete_all_no_content_message(self):
|
||||
"""
|
||||
Scenario: Delete all sections/subsections/units in a course, "no content" message should appear
|
||||
Given that I delete all sections, subsections, and units in a course
|
||||
When I visit the course outline
|
||||
Then I will see a message that says, "You haven't added any content to this course yet"
|
||||
Add see a + Add Section button
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertFalse(self.course_outline_page.has_no_content_message)
|
||||
self.course_outline_page.section_at(0).delete()
|
||||
self.assertEqual(len(self.course_outline_page.sections()), 0)
|
||||
self.assertTrue(self.course_outline_page.has_no_content_message)
|
||||
|
||||
|
||||
class ExpandCollapseMultipleSectionsTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Courses with multiple sections can expand and collapse all sections.
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
""" Start with a course with two sections """
|
||||
course_fixture.add_children(
|
||||
XBlockFixtureDesc('chapter', 'Test Section').add_children(
|
||||
XBlockFixtureDesc('sequential', 'Test Subsection').add_children(
|
||||
XBlockFixtureDesc('vertical', 'Test Unit')
|
||||
)
|
||||
),
|
||||
XBlockFixtureDesc('chapter', 'Test Section 2').add_children(
|
||||
XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children(
|
||||
XBlockFixtureDesc('vertical', 'Test Unit 2')
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def verify_all_sections(self, collapsed):
|
||||
"""
|
||||
Verifies that all sections are collapsed if collapsed is True, otherwise all expanded.
|
||||
"""
|
||||
for section in self.course_outline_page.sections():
|
||||
self.assertEqual(collapsed, section.is_collapsed)
|
||||
|
||||
def toggle_all_sections(self):
|
||||
"""
|
||||
Toggles the expand collapse state of all sections.
|
||||
"""
|
||||
for section in self.course_outline_page.sections():
|
||||
section.toggle_expand()
|
||||
|
||||
def test_expanded_by_default(self):
|
||||
"""
|
||||
Scenario: The default layout for the outline page is to show sections in expanded view
|
||||
Given I have a course with sections
|
||||
When I navigate to the course outline page
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE)
|
||||
self.verify_all_sections(collapsed=False)
|
||||
|
||||
def test_no_expand_link_for_empty_course(self):
|
||||
"""
|
||||
Scenario: Collapse link is removed after last section of a course is deleted
|
||||
Given I have a course with multiple sections
|
||||
And I navigate to the course outline page
|
||||
When I will confirm all alerts
|
||||
And I press the "section" delete icon
|
||||
Then I do not see the "Collapse All Sections" link
|
||||
And I will see a message that says "You haven't added any content to this course yet"
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
for section in self.course_outline_page.sections():
|
||||
section.delete()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING)
|
||||
self.assertTrue(self.course_outline_page.has_no_content_message)
|
||||
|
||||
def test_collapse_all_when_all_expanded(self):
|
||||
"""
|
||||
Scenario: Collapse all sections when all sections are expanded
|
||||
Given I navigate to the outline page of a course with sections
|
||||
And all sections are expanded
|
||||
When I click the "Collapse All Sections" link
|
||||
Then I see the "Expand All Sections" link
|
||||
And all sections are collapsed
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.verify_all_sections(collapsed=False)
|
||||
self.course_outline_page.toggle_expand_collapse()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND)
|
||||
self.verify_all_sections(collapsed=True)
|
||||
|
||||
def test_collapse_all_when_some_expanded(self):
|
||||
"""
|
||||
Scenario: Collapsing all sections when 1 or more sections are already collapsed
|
||||
Given I navigate to the outline page of a course with sections
|
||||
And all sections are expanded
|
||||
When I collapse the first section
|
||||
And I click the "Collapse All Sections" link
|
||||
Then I see the "Expand All Sections" link
|
||||
And all sections are collapsed
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.verify_all_sections(collapsed=False)
|
||||
self.course_outline_page.section_at(0).toggle_expand()
|
||||
self.course_outline_page.toggle_expand_collapse()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND)
|
||||
self.verify_all_sections(collapsed=True)
|
||||
|
||||
def test_expand_all_when_all_collapsed(self):
|
||||
"""
|
||||
Scenario: Expanding all sections when all sections are collapsed
|
||||
Given I navigate to the outline page of a course with multiple sections
|
||||
And I click the "Collapse All Sections" link
|
||||
When I click the "Expand All Sections" link
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.toggle_expand_collapse()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND)
|
||||
self.course_outline_page.toggle_expand_collapse()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE)
|
||||
self.verify_all_sections(collapsed=False)
|
||||
|
||||
def test_expand_all_when_some_collapsed(self):
|
||||
"""
|
||||
Scenario: Expanding all sections when 1 or more sections are already expanded
|
||||
Given I navigate to the outline page of a course with multiple sections
|
||||
And I click the "Collapse All Sections" link
|
||||
When I expand the first section
|
||||
And I click the "Expand All Sections" link
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.toggle_expand_collapse()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND)
|
||||
self.course_outline_page.section_at(0).toggle_expand()
|
||||
self.course_outline_page.toggle_expand_collapse()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE)
|
||||
self.verify_all_sections(collapsed=False)
|
||||
|
||||
|
||||
class ExpandCollapseSingleSectionTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Courses with a single section can expand and collapse all sections.
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def test_no_expand_link_for_empty_course(self):
|
||||
"""
|
||||
Scenario: Collapse link is removed after last section of a course is deleted
|
||||
Given I have a course with one section
|
||||
And I navigate to the course outline page
|
||||
When I will confirm all alerts
|
||||
And I press the "section" delete icon
|
||||
Then I do not see the "Collapse All Sections" link
|
||||
And I will see a message that says "You haven't added any content to this course yet"
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.section_at(0).delete()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING)
|
||||
self.assertTrue(self.course_outline_page.has_no_content_message)
|
||||
|
||||
|
||||
class ExpandCollapseEmptyTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Courses with no sections initially can expand and collapse all sections after addition.
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
""" Start with an empty course """
|
||||
pass
|
||||
|
||||
def test_no_expand_link_for_empty_course(self):
|
||||
"""
|
||||
Scenario: Expand/collapse for a course with no sections
|
||||
Given I have a course with no sections
|
||||
When I navigate to the course outline page
|
||||
Then I do not see the "Collapse All Sections" link
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING)
|
||||
|
||||
def test_link_appears_after_section_creation(self):
|
||||
"""
|
||||
Scenario: Collapse link appears after creating first section of a course
|
||||
Given I have a course with no sections
|
||||
When I navigate to the course outline page
|
||||
And I add a section
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING)
|
||||
self.course_outline_page.add_section_from_top_button()
|
||||
self.assertEquals(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE)
|
||||
self.assertFalse(self.course_outline_page.section_at(0).is_collapsed)
|
||||
|
||||
|
||||
class DefaultStatesEmptyTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Misc course outline default states/actions when starting with an empty course
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
""" Start with an empty course """
|
||||
pass
|
||||
|
||||
def test_empty_course_message(self):
|
||||
"""
|
||||
Scenario: Empty course state
|
||||
Given that I am in a course with no sections, subsections, nor units
|
||||
When I visit the course outline
|
||||
Then I will see a message that says "You haven't added any content to this course yet"
|
||||
And see a + Add Section button
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.assertTrue(self.course_outline_page.has_no_content_message)
|
||||
self.assertTrue(self.course_outline_page.bottom_add_section_button.is_present())
|
||||
|
||||
|
||||
class DefaultStatesContentTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Misc course outline default states/actions when starting with a course with content
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def test_view_live(self):
|
||||
"""
|
||||
Scenario: View Live version from course outline
|
||||
Given that I am on the course outline
|
||||
When I click the "View Live" button
|
||||
Then a new tab will open to the course on the LMS
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.view_live()
|
||||
courseware = CoursewarePage(self.browser, self.course_id)
|
||||
courseware.wait_for_page()
|
||||
self.assertEqual(courseware.num_xblock_components, 2)
|
||||
self.assertEqual(courseware.xblock_component_type(0), 'html')
|
||||
self.assertEqual(courseware.xblock_component_type(1), 'discussion')
|
||||
|
||||
|
||||
class UnitNavigationTest(CourseOutlineTest):
|
||||
"""
|
||||
Feature: Navigate to units
|
||||
"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
def test_navigate_to_unit(self):
|
||||
"""
|
||||
Scenario: Click unit name to navigate to unit page
|
||||
Given that I have expanded a section/subsection so I can see unit names
|
||||
When I click on a unit name
|
||||
Then I will be taken to the appropriate unit page
|
||||
"""
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.section_at(0).subsection_at(0).toggle_expand()
|
||||
unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).go_to()
|
||||
self.assertTrue(unit.is_browser_on_page)
|
||||
@@ -65,30 +65,22 @@ class SplitTestMixin(object):
|
||||
|
||||
Promise(missing_groups_button_not_present, "Add missing groups button should not be showing.").fulfill()
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class SplitTest(ContainerBase, SplitTestMixin):
|
||||
class SplitTest(ContainerBase):
|
||||
"""
|
||||
Tests for creating and editing split test instances in Studio.
|
||||
"""
|
||||
__test__ = True
|
||||
|
||||
def setUp(self):
|
||||
super(SplitTest, self).setUp()
|
||||
# This line should be called once courseFixture is installed
|
||||
self.course_fixture._update_xblock(self.course_fixture._course_location, {
|
||||
"metadata": {
|
||||
u"user_partitions": [
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
course_fixture.add_advanced_settings(
|
||||
{
|
||||
u"advanced_modules": {"value": ["split_test"]},
|
||||
u"user_partitions": {"value": [
|
||||
UserPartition(0, 'Configuration alpha,beta', 'first', [Group("0", 'alpha'), Group("1", 'beta')]).to_json(),
|
||||
UserPartition(1, 'Configuration 0,1,2', 'second', [Group("0", 'Group 0'), Group("1", 'Group 1'), Group("2", 'Group 2')]).to_json()
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
""" Populates the course """
|
||||
course_fixture.add_advanced_settings(
|
||||
{u"advanced_modules": {"value": ["split_test"]}}
|
||||
]}
|
||||
}
|
||||
)
|
||||
|
||||
course_fixture.add_children(
|
||||
@@ -99,6 +91,16 @@ class SplitTest(ContainerBase, SplitTestMixin):
|
||||
)
|
||||
)
|
||||
|
||||
def verify_add_missing_groups_button_not_present(self, container):
|
||||
"""
|
||||
Checks that the "add missing groups" button/link is not present.
|
||||
"""
|
||||
def missing_groups_button_not_present():
|
||||
button_present = container.missing_groups_button_present()
|
||||
return (not button_present, not button_present)
|
||||
|
||||
Promise(missing_groups_button_not_present, "Add missing groups button should not be showing.").fulfill()
|
||||
|
||||
def create_poorly_configured_split_instance(self):
|
||||
"""
|
||||
Creates a split test instance with a missing group and an inactive group.
|
||||
|
||||
Reference in New Issue
Block a user