Commit 955282f9 by Eric Fischer

Merge pull request #12165 from edx/efischer/hide_timed_exams

TNL-4366 Hide Timed Exams
parents bc28df90 ea77c3ec
......@@ -83,7 +83,8 @@ def register_special_exams(course_key):
due_date=timed_exam.due,
is_proctored=timed_exam.is_proctored_exam,
is_practice_exam=timed_exam.is_practice_exam,
is_active=True
is_active=True,
hide_after_due=timed_exam.hide_after_due,
)
msg = 'Updated timed exam {exam_id}'.format(exam_id=exam['id'])
log.info(msg)
......@@ -97,7 +98,8 @@ def register_special_exams(course_key):
due_date=timed_exam.due,
is_proctored=timed_exam.is_proctored_exam,
is_practice_exam=timed_exam.is_practice_exam,
is_active=True
is_active=True,
hide_after_due=timed_exam.hide_after_due,
)
msg = 'Created new timed exam {exam_id}'.format(exam_id=exam_id)
log.info(msg)
......
......@@ -53,6 +53,10 @@ class TestProctoredExams(ModuleStoreTestCase):
exam_review_policy = get_review_policy_by_exam_id(exam['id'])
self.assertEqual(exam_review_policy['review_policy'], sequence.exam_review_rules)
if not exam['is_proctored'] and not exam['is_practice_exam']:
# the hide after due value only applies to timed exams
self.assertEqual(exam['hide_after_due'], sequence.hide_after_due)
self.assertEqual(exam['course_id'], unicode(self.course.id))
self.assertEqual(exam['content_id'], unicode(sequence.location))
self.assertEqual(exam['exam_name'], sequence.display_name)
......@@ -62,13 +66,14 @@ class TestProctoredExams(ModuleStoreTestCase):
self.assertEqual(exam['is_active'], expected_active)
@ddt.data(
(True, 10, True, False, True, False),
(True, 10, False, False, True, False),
(True, 10, True, True, True, True),
(True, 10, True, False, True, False, False),
(True, 10, False, False, True, False, False),
(True, 10, False, False, True, False, True),
(True, 10, True, True, True, True, False),
)
@ddt.unpack
def test_publishing_exam(self, is_time_limited, default_time_limit_minutes,
is_proctored_exam, is_practice_exam, expected_active, republish):
def test_publishing_exam(self, is_time_limited, default_time_limit_minutes, is_proctored_exam,
is_practice_exam, expected_active, republish, hide_after_due):
"""
Happy path testing to see that when a course is published which contains
a proctored exam, it will also put an entry into the exam tables
......@@ -85,7 +90,8 @@ class TestProctoredExams(ModuleStoreTestCase):
is_proctored_exam=is_proctored_exam,
is_practice_exam=is_practice_exam,
due=datetime.now(UTC) + timedelta(minutes=default_time_limit_minutes + 1),
exam_review_rules="allow_use_of_paper"
exam_review_rules="allow_use_of_paper",
hide_after_due=hide_after_due,
)
listen_for_course_publish(self, self.course.id)
......@@ -117,7 +123,8 @@ class TestProctoredExams(ModuleStoreTestCase):
graded=True,
is_time_limited=True,
default_time_limit_minutes=10,
is_proctored_exam=True
is_proctored_exam=True,
hide_after_due=False,
)
listen_for_course_publish(self, self.course.id)
......@@ -147,7 +154,8 @@ class TestProctoredExams(ModuleStoreTestCase):
graded=True,
is_time_limited=True,
default_time_limit_minutes=10,
is_proctored_exam=True
is_proctored_exam=True,
hide_after_due=False,
)
listen_for_course_publish(self, self.course.id)
......@@ -182,7 +190,8 @@ class TestProctoredExams(ModuleStoreTestCase):
graded=True,
is_time_limited=True,
default_time_limit_minutes=10,
is_proctored_exam=True
is_proctored_exam=True,
hide_after_due=False,
)
listen_for_course_publish(self, self.course.id)
......@@ -218,7 +227,8 @@ class TestProctoredExams(ModuleStoreTestCase):
is_time_limited=True,
default_time_limit_minutes=10,
is_proctored_exam=True,
exam_review_rules="allow_use_of_paper"
exam_review_rules="allow_use_of_paper",
hide_after_due=False,
)
listen_for_course_publish(self, self.course.id)
......
......@@ -935,7 +935,8 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"is_practice_exam": xblock.is_practice_exam,
"is_time_limited": xblock.is_time_limited,
"exam_review_rules": xblock.exam_review_rules,
"default_time_limit_minutes": xblock.default_time_limit_minutes
"default_time_limit_minutes": xblock.default_time_limit_minutes,
"hide_after_due": xblock.hide_after_due,
})
# Update with gating info
......
......@@ -50,6 +50,7 @@ class CourseMetadata(object):
'is_time_limited',
'is_practice_exam',
'exam_review_rules',
'hide_after_due',
'self_paced',
'chrome',
'default_tab',
......
......@@ -332,41 +332,47 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
templateName: 'timed-examination-preference-editor',
className: 'edit-settings-timed-examination',
events : {
'change #id_not_timed': 'notTimedExam',
'change #id_timed_exam': 'setTimedExam',
'change #id_practice_exam': 'setPracticeExam',
'change #id_proctored_exam': 'setProctoredExam',
'focusout #id_time_limit': 'timeLimitFocusout'
'change input.no_special_exam': 'notTimedExam',
'change input.timed_exam': 'setTimedExam',
'change input.practice_exam': 'setPracticeExam',
'change input.proctored_exam': 'setProctoredExam',
'focusout .field-time-limit input': 'timeLimitFocusout'
},
notTimedExam: function (event) {
event.preventDefault();
this.$('#id_time_limit_div').hide();
this.$('.exam-review-rules-list-fields').hide();
this.$('#id_time_limit').val('00:00');
},
selectSpecialExam: function (showRulesField) {
this.$('#id_time_limit_div').show();
if (!this.isValidTimeLimit(this.$('#id_time_limit').val())) {
this.$('#id_time_limit').val('00:30');
this.$('.exam-options').hide();
this.$('.field-time-limit input').val('00:00');
},
selectSpecialExam: function (showRulesField, showHideAfterDueField) {
this.$('.exam-options').show();
this.$('.field-time-limit').show();
if (!this.isValidTimeLimit(this.$('.field-time-limit input').val())) {
this.$('.field-time-limit input').val('00:30');
}
if (showRulesField) {
this.$('.exam-review-rules-list-fields').show();
this.$('.field-exam-review-rules').show();
}
else {
this.$('.exam-review-rules-list-fields').hide();
this.$('.field-exam-review-rules').hide();
}
if (showHideAfterDueField) {
this.$('.field-hide-after-due').show();
}
else {
this.$('.field-hide-after-due').hide();
}
},
setTimedExam: function (event) {
event.preventDefault();
this.selectSpecialExam(false);
this.selectSpecialExam(false, true);
},
setPracticeExam: function (event) {
event.preventDefault();
this.selectSpecialExam(false);
this.selectSpecialExam(false, false);
},
setProctoredExam: function (event) {
event.preventDefault();
this.selectSpecialExam(true);
this.selectSpecialExam(true, false);
},
timeLimitFocusout: function(event) {
event.preventDefault();
......@@ -389,43 +395,51 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
this.setExamTime(this.model.get('default_time_limit_minutes'));
this.setReviewRules(this.model.get('exam_review_rules'));
this.setHideAfterDue(this.model.get('hide_after_due'));
},
setExamType: function(is_time_limited, is_proctored_exam, is_practice_exam) {
this.$('.field-time-limit').hide();
this.$('.field-exam-review-rules').hide();
this.$('.field-hide-after-due').hide();
if (!is_time_limited) {
this.$("#id_not_timed").prop('checked', true);
this.$('input.no_special_exam').prop('checked', true);
return;
}
this.$('#id_time_limit_div').show();
this.$('.exam-review-rules-list-fields').hide();
this.$('.field-time-limit').show();
if (this.options.enable_proctored_exams && is_proctored_exam) {
if (is_practice_exam) {
this.$('#id_practice_exam').prop('checked', true);
this.$('input.practice_exam').prop('checked', true);
} else {
this.$('#id_proctored_exam').prop('checked', true);
this.$('.exam-review-rules-list-fields').show();
this.$('input.proctored_exam').prop('checked', true);
this.$('.field-exam-review-rules').show();
}
} else {
// Since we have an early exit at the top of the method
// if the subsection is not time limited, then
// here we rightfully assume that it just a timed exam
this.$("#id_timed_exam").prop('checked', true);
this.$('input.timed_exam').prop('checked', true);
this.$('.field-hide-after-due').show();
}
},
setExamTime: function(value) {
var time = this.convertTimeLimitMinutesToString(value);
this.$('#id_time_limit').val(time);
this.$('.field-time-limit input').val(time);
},
setReviewRules: function (value) {
this.$('#id_exam_review_rules').val(value);
this.$('.field-exam-review-rules textarea').val(value);
},
setHideAfterDue: function(value) {
this.$('.field-hide-after-due input').prop('checked', value);
},
isValidTimeLimit: function(time_limit) {
var pattern = new RegExp('^\\d{1,2}:[0-5][0-9]$');
return pattern.test(time_limit) && time_limit !== "00:00";
},
getExamTimeLimit: function () {
return this.$('#id_time_limit').val();
return this.$('.field-time-limit input').val();
},
convertTimeLimitMinutesToString: function (timeLimitMinutes) {
var hoursStr = "" + Math.floor(timeLimitMinutes / 60);
......@@ -444,21 +458,22 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
var is_practice_exam;
var is_proctored_exam;
var time_limit = this.getExamTimeLimit();
var exam_review_rules = this.$('#id_exam_review_rules').val();
var exam_review_rules = this.$('.field-exam-review-rules textarea').val();
var hide_after_due = this.$('.field-hide-after-due input').is(':checked');
if (this.$("#id_not_timed").is(':checked')){
if (this.$('input.no_special_exam').is(':checked')){
is_time_limited = false;
is_practice_exam = false;
is_proctored_exam = false;
} else if (this.$("#id_timed_exam").is(':checked')){
} else if (this.$('input.timed_exam').is(':checked')){
is_time_limited = true;
is_practice_exam = false;
is_proctored_exam = false;
} else if (this.$("#id_proctored_exam").is(':checked')){
} else if (this.$('input.proctored_exam').is(':checked')){
is_time_limited = true;
is_practice_exam = false;
is_proctored_exam = true;
} else if (this.$("#id_practice_exam").is(':checked')){
} else if (this.$('input.practice_exam').is(':checked')){
is_time_limited = true;
is_practice_exam = true;
is_proctored_exam = true;
......@@ -469,6 +484,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
'is_practice_exam': is_practice_exam,
'is_time_limited': is_time_limited,
'exam_review_rules': exam_review_rules,
'hide_after_due': hide_after_due,
// We have to use the legacy field name
// as the Ajax handler directly populates
// the xBlocks fields. We will have to
......
......@@ -564,7 +564,7 @@
}
.list-fields {
.field-message {
color: $gray;
color: $gray-d1;
font-size: ($baseline/2);
}
.field {
......
......@@ -303,7 +303,7 @@ $outline-indent-width: $baseline;
%outline-item-status {
@extend %t-copy-sub2;
@extend %t-strong;
color: $color-copy-base;
color: $gray-d1;
.icon {
@extend %t-icon5;
......@@ -576,12 +576,16 @@ $outline-indent-width: $baseline;
> .subsection-status .status-timed-proctored-exam {
opacity: 1.0;
}
> .subsection-status .status-hide-after-due {
opacity: 1.0;
}
}
// status - grading
.status-grading, .status-timed-proctored-exam {
.status-grading, .status-timed-proctored-exam, .status-hide-after-due {
@include transition(opacity $tmg-f2 ease-in-out 0s);
opacity: 0.65;
opacity: 0.75;
}
.status-grading-value, .status-proctored-exam-value {
......
<form>
<h3 class="modal-section-title"><%= gettext('Set as a Special Exam') %></h3>
<div class="modal-section-content has-actions">
<div class='exam-time-list-fields'>
<ul class="list-fields list-input">
<ul class="list-fields list-input exam-types" role="group" aria-label="<%- gettext('Exam Types') %>">
<li class="field-radio">
<label class="label">
<input type="radio" name="exam_type" class="input input-radio no_special_exam" checked="checked"/>
<%- gettext('None') %>
</label>
</li>
<li class="field-radio">
<label class="label">
<input type="radio" name="exam_type" class="input input-radio timed_exam"
aria-describedby="timed-exam-description" />
<%- gettext('Timed') %>
</label>
<p class='field-message' id='timed-exam-description'> <%- gettext('Use a timed exam to limit the time learners can spend on problems in this subsection. Learners must submit answers before the time expires. You can allow additional time for individual learners through the Instructor Dashboard.') %> </p>
</li>
<% if (enable_proctored_exam) { %>
<li class="field-radio">
<input type="radio" id="id_not_timed" name="proctored" class="input input-radio" checked="checked"/>
<label for="id_not_timed" class="label">
<%- gettext('None') %>
<label class="label">
<input type="radio" name="exam_type" class="input input-radio proctored_exam"
aria-describedby="proctored-exam-description" />
<%- gettext('Proctored') %>
</label>
<p class='field-message' id='proctored-exam-description'> <%- gettext('Proctored exams are timed and they record video of each learner taking the exam. The videos are then reviewed to ensure that learners follow all examination rules.') %> </p>
</li>
</ul>
</div>
<div class='exam-time-list-fields'>
<ul class="list-fields list-input">
<li class="field-radio">
<input type="radio" id="id_timed_exam" name="proctored" class="input input-radio" />
<label for="id_timed_exam" class="label" aria-describedby="timed-exam-description">
<%- gettext('Timed') %>
<label class="label">
<input type="radio" name="exam_type" class="input input-radio practice_exam"
aria-describedby="practice-exam-description"/>
<%- gettext('Practice Proctored') %>
</label>
</li>
<p class='field-message' id='timed-exam-description'> <%- gettext('Use a timed exam to limit the time learners can spend on problems in this subsection. Learners must submit answers before the time expires. You can allow additional time for individual learners through the Instructor Dashboard.') %> </p>
</ul>
</div>
<% if (enable_proctored_exam) { %>
<div class='exam-time-list-fields'>
<ul class="list-fields list-input">
<li class="field-radio">
<input type="radio" id="id_proctored_exam" name="proctored" class="input input-radio" />
<label for="id_proctored_exam" class="label" aria-describedby="proctored-exam-description">
<%- gettext('Proctored') %>
</label>
</li>
<p class='field-message' id='proctored-exam-description'> <%- gettext('Proctored exams are timed and they record video of each learner taking the exam. The videos are then reviewed to ensure that learners follow all examination rules.') %> </p>
</ul>
</div>
<div class='exam-time-list-fields'>
<ul class="list-fields list-input">
<li class="field-radio">
<input type="radio" id="id_practice_exam" name="proctored" class="input input-radio" />
<label for="id_practice_exam" class="label" aria-describedby="practice-exam-description">
<%- gettext('Practice Proctored') %>
</label>
</li>
<p class='field-message' id='practice-exam-description'> <%- gettext("Use a practice proctored exam to introduce learners to the proctoring tools and processes. Results of a practice exam do not affect a learner's grade.") %> </p>
</ul>
</div>
<% } %>
<div class='exam-time-list-fields is-hidden' id='id_time_limit_div'>
<ul class="list-fields list-input time-limit">
<li class="field field-text field-time-limit">
<label for="id_time_limit" class="label"><%- gettext('Time Allotted (HH:MM):') %> </label>
<input type="text" id="id_time_limit" name="time_limit"
value="" aria-describedby="time-limit-description"
placeholder="HH:MM" class="time_limit release-time time input input-text" autocomplete="off" />
</li>
<% } %>
</ul>
<ul class="list-fields list-input exam-options">
<li class="field field-text field-time-limit">
<label class="label">
<%- gettext('Time Allotted (HH:MM):') %>
<input type="text" value="" aria-describedby="time-limit-description" placeholder="HH:MM"
class="time_limit release-time time input input-text" autocomplete="off" />
</label>
<p class='field-message' id='time-limit-description'><%- gettext('Select a time allotment for the exam. If it is over 24 hours, type in the amount of time. You can grant individual learners extra time to complete the exam through the Instructor Dashboard.') %></p>
</ul>
</div>
<div class='exam-review-rules-list-fields is-hidden'>
<ul class="list-fields list-input exam-review-rules">
<li class="field field-text field-exam-review-rules">
<label for="id_exam_review_rules" class="label"><%- gettext('Review Rules') %> </label>
<textarea id="id_exam_review_rules" cols="50" maxlength="255" name="review_rules" aria-describedby="review-rules-description"
</li>
<li class="field field-text field-exam-review-rules">
<label class="label">
<%- gettext('Review Rules') %>
<textarea cols="50" maxlength="255" aria-describedby="review-rules-description"
class="review-rules input input-text" autocomplete="off" />
</li>
</label>
<p class='field-message' id='review-rules-description'><%- gettext('Specify any additional rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed.') %></p>
</ul>
</div>
</li>
<li class="field-checkbox field-hide-after-due">
<label class="label">
<input type="checkbox" class="input input-checkbox"
aria-describedby="hide-after-due-description"/>
<%- gettext('Hide Exam After Due Date') %>
</label>
<p class='field-message' id='hide-after-due-description'><%- gettext('By default, submitted exams are available for review after the due date has passed. Select this option to keep exams hidden after that date.') %></p>
</li>
</ul>
</div>
</form>
......@@ -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=_(
......
......@@ -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):
......
......@@ -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
......
......@@ -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,94 +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):
"""
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
"""
LogoutPage(self.browser).visit()
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
self.course_outline.visit()
self.courseware_page.stop_timed_exam()
self.assertTrue(self.courseware_page.has_submitted_exam_message())
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
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()
last_week = (datetime.today() - timedelta(days=7)).strftime("%m/%d/%Y")
self.course_outline.change_problem_due_date(last_week)
self.course_outline.open_subsection_settings_dialog()
self.course_outline.select_advanced_tab()
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.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())
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_exam_review_rules_field_is_not_visible_with_other_than_proctored_exam(self):
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 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
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)
......@@ -367,33 +315,25 @@ class ProctoredExamTest(UniqueCourseTest):
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.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.assertFalse(self.course_outline.hide_after_due_field_visible())
self.course_outline.select_practice_exam()
self.course_outline.select_timed_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
self.assertTrue(self.course_outline.hide_after_due_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.course_outline.select_proctored_exam()
self.assertTrue(self.course_outline.time_allotted_field_visible())
self.assertTrue(self.course_outline.exam_review_rules_field_visible())
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):
......
......@@ -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()
......@@ -89,8 +89,8 @@ git+https://github.com/edx/xblock-utils.git@v1.0.2#egg=xblock-utils==1.0.2
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
-e git+https://github.com/edx/edx-reverification-block.git@0.0.5#egg=edx-reverification-block==0.0.5
git+https://github.com/edx/edx-user-state-client.git@1.0.1#egg=edx-user-state-client==1.0.1
git+https://github.com/edx/edx-proctoring.git@0.12.15#egg=edx-proctoring==0.12.15
git+https://github.com/edx/xblock-lti-consumer.git@v1.0.6#egg=xblock-lti-consumer==1.0.6
git+https://github.com/edx/edx-proctoring.git@0.12.16#egg=edx-proctoring==0.12.16
# Third Party XBlocks
-e git+https://github.com/mitodl/edx-sga@172a90fd2738f8142c10478356b2d9ed3e55334a#egg=edx-sga
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment