Hide After Due setting for Timed Exams

TNL-4366
Changes in studio to allow the hide_after_due setting to be utilized.

Includes:
    -python changes to contentstore, where the data is stored.
    -refactoring of timed-examination-preference-editor.underscore, to add this
        setting and make the editor more accessible.
    -javascript changes to link the new setting to the correct data model.
    -sass updates to fix a11y issues on the editor modal.
    -addition of post-due visibility information to course outline in studio.
    -new tests: python, js, acceptance, and a11y
This commit is contained in:
Eric Fischer
2016-04-21 09:26:22 -04:00
committed by Clinton Blackburn
parent 58b6175580
commit d48e88ba8a
15 changed files with 480 additions and 337 deletions

View File

@@ -97,6 +97,16 @@ class ProctoringFields(object):
scope=Scope.settings,
)
hide_after_due = Boolean(
display_name=_("Hide Exam Results After Due Date"),
help=_(
"This setting overrides the default behavior of showing exam results after the due date has passed."
" Currently only supported for timed exams."
),
default=False,
scope=Scope.settings,
)
is_practice_exam = Boolean(
display_name=_("Is Practice Exam"),
help=_(

View File

@@ -200,7 +200,14 @@ class CoursewarePage(CoursePage):
self.q(css='button.start-timed-exam[data-start-immediately="false"]').first.click()
# Wait for the unique exam code to appear.
# elf.wait_for_element_presence(".proctored-exam-code", "unique exam code")
# self.wait_for_element_presence(".proctored-exam-code", "unique exam code")
def has_submitted_exam_message(self):
"""
Returns whether the "you have submitted your exam" message is present.
This being true implies "the exam contents and results are hidden".
"""
return self.q(css="div.proctored-exam.completed").visible
@property
def entrance_exam_message_selector(self):

View File

@@ -549,7 +549,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
self.q(css=".subsection-header-actions .configure-button").nth(index).click()
self.wait_for_element_presence('.course-outline-modal', 'Subsection settings modal is present.')
def change_problem_release_date_in_studio(self):
def change_problem_release_date(self):
"""
Sets a new start date
"""
@@ -558,26 +558,39 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
self.q(css=".action-save").first.click()
self.wait_for_ajax()
def change_problem_due_date(self, date):
"""
Sets a new due date.
Expects date to be a string that will be accepted by the input (for example, '01/01/1970')
"""
self.q(css=".subsection-header-actions .configure-button").first.click()
self.q(css="#due_date").fill(date)
self.q(css=".action-save").first.click()
self.wait_for_ajax()
def select_advanced_tab(self):
"""
Select the advanced settings tab
"""
self.q(css=".settings-tab-button[data-tab='advanced']").first.click()
self.wait_for_element_presence('#id_not_timed', 'Special exam settings fields not present.')
self.wait_for_element_presence('input.no_special_exam', 'Special exam settings fields not present.')
def make_exam_proctored(self):
"""
Makes a Proctored exam.
"""
self.q(css="#id_proctored_exam").first.click()
self.q(css="input.proctored_exam").first.click()
self.q(css=".action-save").first.click()
self.wait_for_ajax()
def make_exam_timed(self):
def make_exam_timed(self, hide_after_due=False):
"""
Makes a timed exam.
"""
self.q(css="#id_timed_exam").first.click()
self.q(css="input.timed_exam").first.click()
if hide_after_due:
self.q(css='.field-hide-after-due input').first.click()
self.q(css=".action-save").first.click()
self.wait_for_ajax()
@@ -585,37 +598,43 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
"""
Choose "none" exam but do not press enter
"""
self.q(css="#id_not_timed").first.click()
self.q(css="input.no_special_exam").first.click()
def select_timed_exam(self):
"""
Choose a timed exam but do not press enter
"""
self.q(css="#id_timed_exam").first.click()
self.q(css="input.timed_exam").first.click()
def select_proctored_exam(self):
"""
Choose a proctored exam but do not press enter
"""
self.q(css="#id_proctored_exam").first.click()
self.q(css="input.proctored_exam").first.click()
def select_practice_exam(self):
"""
Choose a practice exam but do not press enter
"""
self.q(css="#id_practice_exam").first.click()
self.q(css="input.practice_exam").first.click()
def time_allotted_field_visible(self):
"""
returns whether the time allotted field is visible
"""
return self.q(css="#id_time_limit_div").visible
return self.q(css=".field-time-limit").visible
def exam_review_rules_field_visible(self):
"""
Returns whether the review rules field is visible
"""
return self.q(css=".exam-review-rules-list-fields").visible
return self.q(css=".field-exam-review-rules").visible
def hide_after_due_field_visible(self):
"""
Returns whether the hide after due field is visible
"""
return self.q(css=".field-hide-after-due").visible
def proctoring_items_are_displayed(self):
"""
@@ -623,19 +642,19 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
"""
# The None radio button
if not self.q(css="#id_not_timed").present:
if not self.q(css="input.no_special_exam").present:
return False
# The Timed exam radio button
if not self.q(css="#id_timed_exam").present:
if not self.q(css="input.timed_exam").present:
return False
# The Proctored exam radio button
if not self.q(css="#id_proctored_exam").present:
if not self.q(css="input.proctored_exam").present:
return False
# The Practice exam radio button
if not self.q(css="#id_practice_exam").present:
if not self.q(css="input.practice_exam").present:
return False
return True

View File

@@ -5,6 +5,8 @@ End-to-end tests for the LMS.
import json
from nose.plugins.attrib import attr
from datetime import datetime, timedelta
import ddt
from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory
from ..helpers import UniqueCourseTest, EventsTestMixin
@@ -98,7 +100,7 @@ class CoursewareTest(UniqueCourseTest):
self.course_outline.visit()
# Set release date for subsection in future.
self.course_outline.change_problem_release_date_in_studio()
self.course_outline.change_problem_release_date()
# Logout and login as a student.
LogoutPage(self.browser).visit()
@@ -127,6 +129,7 @@ class CoursewareTest(UniqueCourseTest):
self.assertEqual(courseware_page_breadcrumb, expected_breadcrumb)
@ddt.ddt
class ProctoredExamTest(UniqueCourseTest):
"""
Test courseware.
@@ -246,7 +249,8 @@ class ProctoredExamTest(UniqueCourseTest):
self.courseware_page.visit()
self.assertTrue(self.courseware_page.can_start_proctored_exam)
def test_timed_exam_flow(self):
@ddt.data(True, False)
def test_timed_exam_flow(self, hide_after_due):
"""
Given that I am a staff member on the exam settings section
select advanced settings tab
@@ -255,6 +259,12 @@ class ProctoredExamTest(UniqueCourseTest):
And visit the courseware as a verified student.
And I start the timed exam
Then I am taken to the exam with a timer bar showing
When I finish the exam
Then I see the exam submitted dialog in place of the exam
When I log back into studio as a staff member
And change the problem's due date to be in the past
And log back in as the original verified student
Then I see the exam or message in accordance with the hide_after_due setting
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
@@ -262,7 +272,7 @@ class ProctoredExamTest(UniqueCourseTest):
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
self.course_outline.make_exam_timed()
self.course_outline.make_exam_timed(hide_after_due=hide_after_due)
LogoutPage(self.browser).visit()
self._login_as_a_verified_user()
@@ -271,14 +281,32 @@ class ProctoredExamTest(UniqueCourseTest):
self.courseware_page.start_timed_exam()
self.assertTrue(self.courseware_page.is_timer_bar_present)
def test_time_allotted_field_is_not_visible_with_none_exam(self):
self.courseware_page.stop_timed_exam()
self.assertTrue(self.courseware_page.has_submitted_exam_message())
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
self.course_outline.visit()
last_week = (datetime.today() - timedelta(days=7)).strftime("%m/%d/%Y")
self.course_outline.change_problem_due_date(last_week)
LogoutPage(self.browser).visit()
self._auto_auth(self.USERNAME, self.EMAIL, False)
self.courseware_page.visit()
self.assertEqual(self.courseware_page.has_submitted_exam_message(), hide_after_due)
def test_field_visiblity_with_all_exam_types(self):
"""
Given that I am a staff member
And I have visited the course outline page in studio.
And the subsection edit dialog is open
select advanced settings tab
When I select the 'None' exams radio button
Then the time allotted text field becomes invisible
For each of None, Timed, Proctored, and Practice exam types
The time allotted, review rules, and hide after due fields have proper visibility
None: False, False, False
Timed: True, False, True
Proctored: True, True, False
Practice: True, False, False
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
@@ -289,111 +317,23 @@ class ProctoredExamTest(UniqueCourseTest):
self.course_outline.select_none_exam()
self.assertFalse(self.course_outline.time_allotted_field_visible())
def test_time_allotted_field_is_visible_with_timed_exam(self):
"""
Given that I am a staff member
And I have visited the course outline page in studio.
And the subsection edit dialog is open
select advanced settings tab
When I select the timed exams radio button
Then the time allotted text field becomes visible
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.assertFalse(self.course_outline.hide_after_due_field_visible())
self.course_outline.select_timed_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible())
def test_time_allotted_field_is_visible_with_proctored_exam(self):
"""
Given that I am a staff member
And I have visited the course outline page in studio.
And the subsection edit dialog is open
select advanced settings tab
When I select the proctored exams radio button
Then the time allotted text field becomes visible
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.assertTrue(self.course_outline.hide_after_due_field_visible())
self.course_outline.select_proctored_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible())
def test_exam_review_rules_field_is_visible_with_proctored_exam(self):
"""
Given that I am a staff member
And I have visited the course outline page in studio.
And the subsection edit dialog is open
select advanced settings tab
When I select the proctored exams radio button
Then the review rules textarea field becomes visible
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
self.course_outline.select_proctored_exam()
self.assertTrue(self.course_outline.exam_review_rules_field_visible())
def test_exam_review_rules_field_is_not_visible_with_other_than_proctored_exam(self):
"""
Given that I am a staff member
And I have visited the course outline page in studio.
And the subsection edit dialog is open
select advanced settings tab
When I select the timed exams radio button
Then the review rules textarea field is not visible
When I select the none exam radio button
Then the review rules textarea field is not visible
When I select the practice exam radio button
Then the review rules textarea field is not visible
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
self.course_outline.select_timed_exam()
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.course_outline.select_none_exam()
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.course_outline.select_practice_exam()
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
def test_time_allotted_field_is_visible_with_practice_exam(self):
"""
Given that I am a staff member
And I have visited the course outline page in studio.
And the subsection edit dialog is open
select advanced settings tab
When I select the practice exams radio button
Then the time allotted text field becomes visible
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
self.assertFalse(self.course_outline.hide_after_due_field_visible())
self.course_outline.select_practice_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.assertFalse(self.course_outline.hide_after_due_field_visible())
class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin):

View File

@@ -3,6 +3,9 @@
Acceptance tests for Studio's Setting pages
"""
from __future__ import unicode_literals
import os
from mock import patch
from nose.plugins.attrib import attr
from base_studio_test import StudioCourseTest
@@ -508,3 +511,63 @@ class StudioSettingsA11yTest(StudioCourseTest):
})
self.settings_page.a11y_audit.check_for_accessibility_errors()
@attr('a11y')
class StudioSubsectionSettingsA11yTest(StudioCourseTest):
"""
Class to test accessibility on the subsection settings modals.
"""
def setUp(self): # pylint: disable=arguments-differ
browser = os.environ.get('SELENIUM_BROWSER', 'firefox')
# This test will fail if run using phantomjs < 2.0, due to an issue with bind()
# See https://github.com/ariya/phantomjs/issues/10522 for details.
# The course_outline uses this function, and as such will not fully load when run
# under phantomjs 1.9.8. So, to prevent this test from timing out at course_outline.visit(),
# force the use of firefox vs the standard a11y test usage of phantomjs 1.9.8.
# TODO: remove this block once https://openedx.atlassian.net/browse/TE-1047 is resolved.
if browser == 'phantomjs':
browser = 'firefox'
with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}):
super(StudioSubsectionSettingsA11yTest, self).setUp(is_staff=True)
self.course_outline = CourseOutlinePage(
self.browser,
self.course_info['org'],
self.course_info['number'],
self.course_info['run']
)
def populate_course_fixture(self, course_fixture):
course_fixture.add_advanced_settings({
"enable_proctored_exams": {"value": "true"}
})
course_fixture.add_children(
XBlockFixtureDesc('chapter', 'Test Section 1').add_children(
XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children(
XBlockFixtureDesc('problem', 'Test Problem 1')
)
)
)
def test_special_exams_menu_a11y(self):
"""
Given that I am a staff member
And I am editing settings on the special exams menu
Then that menu is accessible
"""
self.course_outline.visit()
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
# limit the scope of the audit to the special exams tab on the modal dialog
self.course_outline.a11y_audit.config.set_scope(
include=['section.edit-settings-timed-examination']
)
self.course_outline.a11y_audit.check_for_accessibility_errors()