Commit ea2cfaae by Andy Armstrong

Merge pull request #747 from edx/andya/add-staff-tools

Implement ORA staff toolbar
parents 2f4a8093 454d16e0
...@@ -37,5 +37,6 @@ script: ...@@ -37,5 +37,6 @@ script:
branches: branches:
only: only:
- master - master
- ora-staff-grading
after_success: after_success:
coveralls coveralls
...@@ -18,3 +18,4 @@ Omar Al-Ithawi <oithawi@qrf.org> ...@@ -18,3 +18,4 @@ Omar Al-Ithawi <oithawi@qrf.org>
Ahsan Ulhaq <ahsan@edx.org> Ahsan Ulhaq <ahsan@edx.org>
Ben Patterson <bpatterson@edx.org> Ben Patterson <bpatterson@edx.org>
Eric Fischer <efischer@edx.org> Eric Fischer <efischer@edx.org>
Andy Armstrong <andya@edx.org>
...@@ -83,7 +83,7 @@ files: ...@@ -83,7 +83,7 @@ files:
make javascript make javascript
# Combine/minify CSS (from Sass) # Combine/minify CSS (from Sass)
./scripts/sass.sh make sass
Make sure you commit the combined/minified files to the git repository! Make sure you commit the combined/minified files to the git repository!
...@@ -107,13 +107,13 @@ To run just the JavaScript tests: ...@@ -107,13 +107,13 @@ To run just the JavaScript tests:
.. code:: bash .. code:: bash
./scripts/test-js.sh make test-js
To run the JavaScript tests in Chrome so you can use the debugger: To run the JavaScript tests in Chrome so you can use the debugger:
.. code:: bash .. code:: bash
./scripts/js-debugger.sh make test-js-debug
There are also acceptance and accessibility tests that run can be run against a sandbox. For more information, about how to run these from your machine, check out `test/acceptance/README.rst <https://github.com/edx/edx-ora2/blob/master/test/acceptance/README.rst/>`__. There are also acceptance and accessibility tests that run can be run against a sandbox. For more information, about how to run these from your machine, check out `test/acceptance/README.rst <https://github.com/edx/edx-ora2/blob/master/test/acceptance/README.rst/>`__.
......
...@@ -39,8 +39,8 @@ ...@@ -39,8 +39,8 @@
{% endfor %} {% endfor %}
</ol> </ol>
{% if show_staff_debug_info %} {% if show_staff_area %}
<div id="openassessment__staff-info"></div> <div id="openassessment__staff-area"></div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
......
...@@ -29,7 +29,7 @@ from openassessment.xblock.self_assessment_mixin import SelfAssessmentMixin ...@@ -29,7 +29,7 @@ from openassessment.xblock.self_assessment_mixin import SelfAssessmentMixin
from openassessment.xblock.submission_mixin import SubmissionMixin from openassessment.xblock.submission_mixin import SubmissionMixin
from openassessment.xblock.studio_mixin import StudioMixin from openassessment.xblock.studio_mixin import StudioMixin
from openassessment.xblock.xml import parse_from_xml, serialize_content_to_xml from openassessment.xblock.xml import parse_from_xml, serialize_content_to_xml
from openassessment.xblock.staff_info_mixin import StaffInfoMixin from openassessment.xblock.staff_area_mixin import StaffAreaMixin
from openassessment.xblock.workflow_mixin import WorkflowMixin from openassessment.xblock.workflow_mixin import WorkflowMixin
from openassessment.workflow.errors import AssessmentWorkflowError from openassessment.workflow.errors import AssessmentWorkflowError
from openassessment.xblock.student_training_mixin import StudentTrainingMixin from openassessment.xblock.student_training_mixin import StudentTrainingMixin
...@@ -103,7 +103,7 @@ class OpenAssessmentBlock( ...@@ -103,7 +103,7 @@ class OpenAssessmentBlock(
StudioMixin, StudioMixin,
GradeMixin, GradeMixin,
LeaderboardMixin, LeaderboardMixin,
StaffInfoMixin, StaffAreaMixin,
WorkflowMixin, WorkflowMixin,
StudentTrainingMixin, StudentTrainingMixin,
LmsCompatibilityMixin, LmsCompatibilityMixin,
...@@ -301,7 +301,7 @@ class OpenAssessmentBlock( ...@@ -301,7 +301,7 @@ class OpenAssessmentBlock(
"title": self.title, "title": self.title,
"prompts": self.prompts, "prompts": self.prompts,
"rubric_assessments": ui_models, "rubric_assessments": ui_models,
"show_staff_debug_info": self.is_course_staff and not self.in_studio_preview, "show_staff_area": self.is_course_staff and not self.in_studio_preview,
} }
template = get_template("openassessmentblock/oa_base.html") template = get_template("openassessmentblock/oa_base.html")
context = Context(context_dict) context = Context(context_dict)
......
""" """
The Staff Info View mixin renders all the staff-specific information used to The Staff Area View mixin renders all the staff-specific information used to
determine the flow of the problem. determine the flow of the problem.
""" """
import copy import copy
...@@ -75,9 +75,8 @@ def require_course_staff(error_key, with_json_handler=False): ...@@ -75,9 +75,8 @@ def require_course_staff(error_key, with_json_handler=False):
@wraps(func) @wraps(func)
def _wrapped(xblock, *args, **kwargs): # pylint: disable=C0111 def _wrapped(xblock, *args, **kwargs): # pylint: disable=C0111
permission_errors = { permission_errors = {
"STAFF_INFO": xblock._(u"You do not have permission to access staff information"), "STAFF_AREA": xblock._(u"You do not have permission to access the staff area"),
"STUDENT_INFO": xblock._(u"You do not have permission to access learner information."), "STUDENT_INFO": xblock._(u"You do not have permission to access learner information."),
} }
if not xblock.is_course_staff and with_json_handler: if not xblock.is_course_staff and with_json_handler:
...@@ -90,14 +89,14 @@ def require_course_staff(error_key, with_json_handler=False): ...@@ -90,14 +89,14 @@ def require_course_staff(error_key, with_json_handler=False):
return _decorator return _decorator
class StaffInfoMixin(object): class StaffAreaMixin(object):
""" """
Display debug information to course and global staff. Display debug information to course and global staff.
""" """
@XBlock.handler @XBlock.handler
@require_course_staff("STAFF_INFO") @require_course_staff("STAFF_AREA")
def render_staff_info(self, data, suffix=''): # pylint: disable=W0613 def render_staff_area(self, data, suffix=''): # pylint: disable=W0613
""" """
Template context dictionary for course staff debug panel. Template context dictionary for course staff debug panel.
...@@ -113,7 +112,7 @@ class StaffInfoMixin(object): ...@@ -113,7 +112,7 @@ class StaffInfoMixin(object):
Gets the path and context for the staff section of the ORA XBlock. Gets the path and context for the staff section of the ORA XBlock.
""" """
context = {} context = {}
path = 'openassessmentblock/staff_debug/staff_debug.html' path = 'openassessmentblock/staff_area/staff_area.html'
student_item = self.get_student_item_dict() student_item = self.get_student_item_dict()
...@@ -145,14 +144,12 @@ class StaffInfoMixin(object): ...@@ -145,14 +144,12 @@ class StaffInfoMixin(object):
student_item['item_id'] student_item['item_id']
) )
# Include release/due dates for each step in the problem
context['step_dates'] = list()
# Include Latex setting # Include Latex setting
context['allow_latex'] = self.allow_latex context['allow_latex'] = self.allow_latex
steps = ['submission'] + self.assessment_steps # Include release/due dates for each step in the problem
for step in steps: context['step_dates'] = list()
for step in ['submission'] + self.assessment_steps:
if step == 'example-based-assessment': if step == 'example-based-assessment':
continue continue
...@@ -300,7 +297,7 @@ class StaffInfoMixin(object): ...@@ -300,7 +297,7 @@ class StaffInfoMixin(object):
for criterion in context["rubric_criteria"]: for criterion in context["rubric_criteria"]:
criterion["total_value"] = max_scores[criterion["name"]] criterion["total_value"] = max_scores[criterion["name"]]
path = 'openassessmentblock/staff_debug/student_info.html' path = 'openassessmentblock/staff_area/student_info.html'
return path, context return path, context
@XBlock.json_handler @XBlock.json_handler
...@@ -376,12 +373,15 @@ class StaffInfoMixin(object): ...@@ -376,12 +373,15 @@ class StaffInfoMixin(object):
cancelled_by_id=student_item_dict['student_id'], cancelled_by_id=student_item_dict['student_id'],
assessment_requirements=assessment_requirements assessment_requirements=assessment_requirements
) )
return {"success": True, 'msg': self._( return {
u"The learner submission has been removed from peer assessment. " "success": True,
u"The learner receives a grade of zero unless you reset " 'msg': self._(
u"the learner's attempts for the problem to allow them to " u"The learner submission has been removed from peer assessment. "
u"resubmit a response." u"The learner receives a grade of zero unless you reset "
)} u"the learner's attempts for the problem to allow them to "
u"resubmit a response."
)
}
except ( except (
AssessmentWorkflowError, AssessmentWorkflowError,
AssessmentWorkflowInternalError AssessmentWorkflowInternalError
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{ {
"template": "openassessmentblock/oa_base.html", "template": "openassessmentblock/oa_base.html",
"context": { "context": {
"show_staff_debug_info": false, "show_staff_area": false,
"title": "Test title", "title": "Test title",
"question": "Test prompt", "question": "Test prompt",
"rubric_criteria": [], "rubric_criteria": [],
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
{ {
"template": "openassessmentblock/oa_base.html", "template": "openassessmentblock/oa_base.html",
"context": { "context": {
"show_staff_debug_info": true, "show_staff_area": true,
"title": "Test title", "title": "Test title",
"question": "Test prompt", "question": "Test prompt",
"rubric_criteria": [], "rubric_criteria": [],
...@@ -651,7 +651,7 @@ ...@@ -651,7 +651,7 @@
"output": "oa_edit_student_training.html" "output": "oa_edit_student_training.html"
}, },
{ {
"template": "openassessmentblock/staff_debug/staff_debug.html", "template": "openassessmentblock/staff_area/staff_area.html",
"context": { "context": {
"status_counts": { "status_counts": {
"self": 1, "self": 1,
...@@ -679,10 +679,10 @@ ...@@ -679,10 +679,10 @@
} }
] ]
}, },
"output": "oa_staff_info.html" "output": "oa_staff_area.html"
}, },
{ {
"template": "openassessmentblock/staff_debug/student_info.html", "template": "openassessmentblock/staff_area/student_info.html",
"context": { "context": {
"submission": { "submission": {
"image_url": "/test-url", "image_url": "/test-url",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -20,20 +20,20 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -20,20 +20,20 @@ describe("OpenAssessment.ResponseView", function() {
function(defer) { defer.rejectWith(this, ["ERROR"]); } function(defer) { defer.rejectWith(this, ["ERROR"]); }
).promise(); ).promise();
this.save = function(submission) { this.save = function() {
return successPromise; return successPromise;
}; };
this.submit = function(submission) { this.submit = function() {
return successPromise; return successPromise;
}; };
this.render = function(step) { this.render = function() {
return successPromise; return successPromise;
}; };
this.uploadUrlError = false; this.uploadUrlError = false;
this.getUploadUrl = function(contentType) { this.getUploadUrl = function() {
return this.uploadUrlError ? errorPromise : successPromiseWithUrl; return this.uploadUrlError ? errorPromise : successPromiseWithUrl;
}; };
...@@ -66,9 +66,9 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -66,9 +66,9 @@ describe("OpenAssessment.ResponseView", function() {
this.loadAssessmentModules = function() {}; this.loadAssessmentModules = function() {};
this.peerView = { load: function() {} }; this.peerView = { load: function() {} };
this.gradeView = { load: function() {} }; this.gradeView = { load: function() {} };
this.showLoadError = function(msg) {}; this.showLoadError = function() {};
this.toggleActionError = function(msg, step) {}; this.toggleActionError = function() {};
this.setUpCollapseExpand = function(sel) {}; this.setUpCollapseExpand = function() {};
}; };
// Stubs // Stubs
...@@ -100,7 +100,7 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -100,7 +100,7 @@ describe("OpenAssessment.ResponseView", function() {
// Create stub objects // Create stub objects
server = new StubServer(); server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex') server.renderLatex = jasmine.createSpy('renderLatex');
fileUploader = new StubFileUploader(); fileUploader = new StubFileUploader();
baseView = new StubBaseView(); baseView = new StubBaseView();
...@@ -228,7 +228,7 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -228,7 +228,7 @@ describe("OpenAssessment.ResponseView", function() {
// Prevent the server's response from resolving, // Prevent the server's response from resolving,
// so we can see what happens before view gets re-rendered. // so we can see what happens before view gets re-rendered.
spyOn(server, 'submit').and.callFake(function() { spyOn(server, 'submit').and.callFake(function() {
return $.Deferred(function(defer) {}).promise(); return $.Deferred(function() {}).promise();
}); });
view.response(['Test response 1', 'Test response 2']); view.response(['Test response 1', 'Test response 2']);
......
/**
Tests for OpenAssessment Student Training view.
**/
describe("OpenAssessment.StaffInfoView", function() {
// Stub server that returns dummy data for the staff info view
var StubServer = function() {
// Remember which fragments have been loaded
this.fragmentsLoaded = [];
// Render the template for the staff info view
this.render = function(component) {
var server = this;
this.fragmentsLoaded.push(component);
return $.Deferred(function(defer) {
fragment = readFixtures("oa_staff_info.html");
defer.resolveWith(this, [fragment]);
});
};
this.scheduleTraining = function() {
var server = this;
return $.Deferred(function(defer) {
defer.resolveWith(server, [server.data]);
}).promise();
};
this.rescheduleUnfinishedTasks = function() {
var server = this;
return $.Deferred(function(defer) {
defer.resolveWith(server, [server.data]);
}).promise();
};
var successPromise = $.Deferred(
function(defer) { defer.resolve(); }
).promise();
this.cancelSubmission = function(submissionUUID) {
return successPromise;
};
this.data = {};
};
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
this.scrollToTop = function() {};
this.loadAssessmentModules = function() {};
this.loadMessageView = function() {};
};
// Stubs
var baseView = null;
var server = null;
/**
Initialize the staff info view, then check whether it makes
an AJAX call to load the staff info section.
**/
var assertStaffInfoAjaxCall = function(shouldCall) {
// Load the staff info view
var el = $("#openassessment-base").get(0);
var view = new OpenAssessment.StaffInfoView(el, server, baseView);
view.load();
// Check whether it tried to load staff info from the server
var expectedFragments = [];
if (shouldCall) { expectedFragments = ['staff_info']; }
expect(server.fragmentsLoaded).toEqual(expectedFragments);
};
beforeEach(function() {
// Create a new stub server
server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex')
// Create the stub base view
baseView = new StubBaseView();
});
it("schedules training of AI classifiers", function() {
server.data = {
"success": true,
"workflow_uuid": "abc123",
"msg": "Great success."
};
spyOn(server, 'scheduleTraining').and.callThrough();
// Load the fixture
loadFixtures('oa_base.html');
// Load the view
var el = $("#openassessment-base").get(0);
var view = new OpenAssessment.StaffInfoView(el, server, baseView);
view.load();
// Submit the assessment
view.scheduleTraining();
// Expect that the assessment was sent to the server
expect(server.scheduleTraining).toHaveBeenCalled();
});
it("Loads staff info if the page contains a course staff section", function() {
// Load the fixture for the container page that DOES include a course staff section
loadFixtures('oa_base_course_staff.html');
assertStaffInfoAjaxCall(true);
});
it("Does NOT load staff info if the page does NOT contain a course staff section", function() {
// Load the fixture for the container page that does NOT include a course staff section
loadFixtures('oa_base.html');
assertStaffInfoAjaxCall(false);
});
it("reschedules training of AI tasks", function() {
server.data = {
"success": true,
"workflow_uuid": "abc123",
"msg": "Great success."
};
var el = $("#openassessment-base").get(0);
var view = new OpenAssessment.StaffInfoView(el, server, baseView);
view.load();
spyOn(server, 'rescheduleUnfinishedTasks').and.callThrough();
// Test the Rescheduling
view.rescheduleUnfinishedTasks();
// Expect that the server was instructed to reschedule Unifinished Taks
expect(server.rescheduleUnfinishedTasks).toHaveBeenCalled();
});
it("reschedules training of AI tasks", function() {
server.data = {
"success": false,
"workflow_uuid": "abc123",
"errMsg": "Stupendous Failure."
};
var el = $("#openassessment-base").get(0);
var view = new OpenAssessment.StaffInfoView(el, server, baseView);
view.load();
spyOn(server, 'rescheduleUnfinishedTasks').and.callThrough();
// Test the Rescheduling
view.rescheduleUnfinishedTasks();
// Expect that the server was instructed to reschedule Unifinished Taks
expect(server.rescheduleUnfinishedTasks).toHaveBeenCalled();
});
it("updates submission cancellation button when comments changes", function() {
// Prevent the server's response from resolving,
// so we can see what happens before view gets re-rendered.
spyOn(server, 'cancelSubmission').and.callFake(function() {
return $.Deferred(function(defer) {}).promise();
});
// Load the fixture
loadFixtures('oa_student_info.html');
var el = $("#openassessment-base").get(0);
var view = new OpenAssessment.StaffInfoView(el, server, baseView);
// comments is blank --> cancel submission button disabled
view.comment('');
view.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(false);
// Response is whitespace --> cancel submission button disabled
view.comment(' \n \n ');
view.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(false);
// Response is not blank --> cancel submission button enabled
view.comment('Cancellation reason.');
view.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(true);
});
it("submits the cancel submission comments to the server", function() {
spyOn(server, 'cancelSubmission').and.callThrough();
// Load the fixture
loadFixtures('oa_student_info.html');
var el = $("#openassessment-base").get(0);
var view = new OpenAssessment.StaffInfoView(el, server, baseView);
view.comment('Cancellation reason.');
view.cancelSubmission('Bob');
expect(server.cancelSubmission).toHaveBeenCalledWith('Bob', 'Cancellation reason.');
});
});
...@@ -22,8 +22,8 @@ OpenAssessment.BaseView = function(runtime, element, server) { ...@@ -22,8 +22,8 @@ OpenAssessment.BaseView = function(runtime, element, server) {
this.gradeView = new OpenAssessment.GradeView(this.element, this.server, this); this.gradeView = new OpenAssessment.GradeView(this.element, this.server, this);
this.leaderboardView = new OpenAssessment.LeaderboardView(this.element, this.server, this); this.leaderboardView = new OpenAssessment.LeaderboardView(this.element, this.server, this);
this.messageView = new OpenAssessment.MessageView(this.element, this.server, this); this.messageView = new OpenAssessment.MessageView(this.element, this.server, this);
// Staff only information about student progress. // Staff-only area with information and tools for managing student submissions
this.staffInfoView = new OpenAssessment.StaffInfoView(this.element, this.server, this); this.staffAreaView = new OpenAssessment.StaffAreaView(this.element, this.server, this);
}; };
...@@ -62,7 +62,7 @@ OpenAssessment.BaseView.prototype = { ...@@ -62,7 +62,7 @@ OpenAssessment.BaseView.prototype = {
load: function() { load: function() {
this.responseView.load(); this.responseView.load();
this.loadAssessmentModules(); this.loadAssessmentModules();
this.staffInfoView.load(); this.staffAreaView.load();
}, },
/** /**
......
...@@ -26,6 +26,83 @@ ...@@ -26,6 +26,83 @@
// -------------------- // --------------------
// Developer styles for Staff Section // Developer styles for Staff Section
// -------------------- // --------------------
// edX constants
$lighter-base-font-color: rgb(100,100,100);
$link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.org/elements/colors/
.wrapper--openassessment .wrapper--staff-area {
width: 100%;
overflow: auto;
margin-top: $baseline-v;
padding-top: 20px;
.wrapper--staff-toolbar {
position: relative;
text-align: right;
margin: 0 0 8px;
padding: 10px;
.ui-staff__button {
margin-left: ($baseline-v/2);
padding: ($baseline-v/4) ($baseline-v/2);
border-radius: ($baseline-v/4);
text-transform: uppercase;
color: $lighter-base-font-color;
background-color: $shadow-l2;
// Remove button styling
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 12px;
-webkit-appearance: none;
background-image: none;
text-shadow: none;
box-shadow: none;
border: none;
border-image: none;
&.is--active {
color: white;
background-color: $edx-pink;
&:hover {
background-color: $edx-pink-d1;
}
}
&:hover {
color: white;
background-color: $link-hover;
}
}
}
.wrapper--ui-staff {
margin-top: 0;
.ui-staff_close_button {
margin: 0;
padding: 0;
border: 0;
float: right;
background-color: $staff-bg;
// Remove button styling
font-size: 12px;
-webkit-appearance: none;
background-image: none;
text-shadow: none;
box-shadow: none;
border: none;
border-image: none;
&:hover {
color: $white;
}
}
}
}
.staff-info__status { .staff-info__status {
.action--submit { .action--submit {
@extend %btn--secondary; @extend %btn--secondary;
......
{ {
"name": "edx-ora2", "name": "edx-ora2",
"version": "0.0.1", "version": "0.2.0",
"repository": "https://github.com/edx/edx-ora2.git", "repository": "https://github.com/edx/edx-ora2.git",
"devDependencies": { "devDependencies": {
"karma": "^0.12.16", "karma": "^0.12.16",
......
...@@ -4,4 +4,3 @@ cd `dirname $BASH_SOURCE` && cd .. ...@@ -4,4 +4,3 @@ cd `dirname $BASH_SOURCE` && cd ..
echo "Starting JavaScript tests in a browser..." echo "Starting JavaScript tests in a browser..."
./node_modules/karma/bin/karma start --single-run=false --browsers Chrome --reporters=html --autoWatch ./node_modules/karma/bin/karma start --single-run=false --browsers Chrome --reporters=html --autoWatch
...@@ -42,7 +42,7 @@ def load_requirements(*requirements_paths): ...@@ -42,7 +42,7 @@ def load_requirements(*requirements_paths):
setup( setup(
name='ora2', name='ora2',
version='0.0.1', version='0.2.0',
author='edX', author='edX',
url='http://github.com/edx/edx-ora2', url='http://github.com/edx/edx-ora2',
description='edx-ora2', description='edx-ora2',
......
...@@ -3,18 +3,20 @@ UI-level acceptance tests for OpenAssessment accessibility. ...@@ -3,18 +3,20 @@ UI-level acceptance tests for OpenAssessment accessibility.
""" """
import os import os
import unittest import unittest
from tests import OpenAssessmentTest from tests import OpenAssessmentTest, StaffAreaPage
class OpenAssessmentAxsTest(OpenAssessmentTest): class OpenAssessmentA11yTest(OpenAssessmentTest):
""" """
UI-level acceptance tests for Open Assessment accessibility. UI-level acceptance tests for Open Assessment accessibility.
""" """
def _check_axs(self): def setUp(self, problem_type, staff=False):
super(OpenAssessmentA11yTest, self).setUp(problem_type, staff=staff)
self.auto_auth_page.visit() self.auto_auth_page.visit()
self.submission_page.visit()
self.submission_page.a11y_audit.config.set_rules({ def _check_a11y(self, page):
page.a11y_audit.config.set_rules({
"ignore": [ "ignore": [
"aria-valid-attr", # TODO: AC-199 "aria-valid-attr", # TODO: AC-199
"color-contrast", # TODO: AC-198 "color-contrast", # TODO: AC-198
...@@ -22,42 +24,70 @@ class OpenAssessmentAxsTest(OpenAssessmentTest): ...@@ -22,42 +24,70 @@ class OpenAssessmentAxsTest(OpenAssessmentTest):
"link-name", # TODO: AC-196 "link-name", # TODO: AC-196
] ]
}) })
report = self.submission_page.a11y_audit.check_for_accessibility_errors() page.a11y_audit.check_for_accessibility_errors()
class SelfAssessmentAxsTest(OpenAssessmentAxsTest): class SelfAssessmentA11yTest(OpenAssessmentA11yTest):
""" """
Test the accessibility of the self-assessment flow. Test the accessibility of the self-assessment flow.
""" """
def setUp(self): def setUp(self):
super(SelfAssessmentAxsTest, self).setUp('self_only') super(SelfAssessmentA11yTest, self).setUp('self_only')
def test_self_assessment_axs(self): def test_self_assessment_a11y(self):
self._check_axs() self.submission_page.visit()
self._check_a11y(self.submission_page)
class PeerAssessmentAxsTest(OpenAssessmentAxsTest): class PeerAssessmentA11yTest(OpenAssessmentA11yTest):
""" """
Test the accessibility of the peer-assessment flow. Test the accessibility of the peer-assessment flow.
""" """
def setUp(self): def setUp(self):
super(PeerAssessmentAxsTest, self).setUp('peer_only') super(PeerAssessmentA11yTest, self).setUp('peer_only')
def test_peer_assessment_axs(self): def test_peer_assessment_a11y(self):
self._check_axs() self.submission_page.visit()
self._check_a11y(self.submission_page)
class StudentTrainingAxsTest(OpenAssessmentAxsTest): class StudentTrainingA11yTest(OpenAssessmentA11yTest):
""" """
Test the accessibility of student training (the "learning to assess" step). Test the accessibility of student training (the "learning to assess" step).
""" """
def setUp(self): def setUp(self):
super(StudentTrainingAxsTest, self).setUp('student_training') super(StudentTrainingA11yTest, self).setUp('student_training')
def test_student_training_a11y(self):
self.submission_page.visit()
self._check_a11y(self.submission_page)
def test_student_training_axs(self): class StaffAreaA11yTest(OpenAssessmentA11yTest):
self._check_axs() """
Test the accessibility of the staff area.
"""
def setUp(self):
super(StaffAreaA11yTest, self).setUp('peer_only', staff=True)
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
def test_staff_tools_panel_a11y(self):
"""
Check the accessibility of the "Staff Tools" panel
"""
self.staff_area_page.visit()
self.staff_area_page.click_staff_toolbar_button("staff-tools")
self._check_a11y(self.staff_area_page)
def test_staff_info_panel_a11y(self):
"""
Check the accessibility of the "Staff Info" panel
"""
self.staff_area_page.visit()
self.staff_area_page.click_staff_toolbar_button("staff-info")
self._check_a11y(self.staff_area_page)
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -296,3 +296,43 @@ class GradePage(OpenAssessmentPage): ...@@ -296,3 +296,43 @@ class GradePage(OpenAssessmentPage):
""" """
score_candidates = [int(x) for x in self.q(css=".grade__value__earned").text] score_candidates = [int(x) for x in self.q(css=".grade__value__earned").text]
return score_candidates[0] if len(score_candidates) > 0 else None return score_candidates[0] if len(score_candidates) > 0 else None
class StaffAreaPage(OpenAssessmentPage):
"""
Page object representing the "submission" step in an ORA problem.
"""
def is_browser_on_page(self):
return self.q(css="#openassessment__staff-area").is_present()
@property
def selected_button_names(self):
"""
Returns the names of the selected toolbar buttons.
"""
buttons = self.q(css=".ui-staff__button")
return [button.text for button in buttons if u'is--active' in button.get_attribute('class')]
@property
def visible_staff_panels(self):
"""
Returns the ids of the visible staff panels
"""
panels = self.q(css=".wrapper--ui-staff")
return [panel.get_attribute('id') for panel in panels if u'is--hidden' not in panel.get_attribute('class')]
def click_staff_toolbar_button(self, button_name):
"""
Presses the button to show the panel with the specified name.
:return:
"""
buttons = self.q(css=".button-{button_name}".format(button_name=button_name))
buttons.first.click()
def click_staff_panel_close_button(self, panel_name):
"""
Presses the close button on the staff panel with the specified name.
:return:
"""
self.q(css=".wrapper--{panel_name} .ui-staff_close_button".format(panel_name=panel_name)).click()
""" """
UI-level acceptance tests for OpenAssessment. UI-level acceptance tests for OpenAssessment.
""" """
import ddt
import os import os
import unittest import unittest
import time import time
from functools import wraps from functools import wraps
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from bok_choy.web_app_test import WebAppTest from bok_choy.web_app_test import WebAppTest
from bok_choy.promise import BrokenPromise from bok_choy.promise import BrokenPromise
from auto_auth import AutoAuthPage from auto_auth import AutoAuthPage
from pages import ( from pages import (
SubmissionPage, AssessmentPage, GradePage SubmissionPage, AssessmentPage, GradePage, StaffAreaPage
) )
...@@ -65,24 +67,25 @@ class OpenAssessmentTest(WebAppTest): ...@@ -65,24 +67,25 @@ class OpenAssessmentTest(WebAppTest):
OPTIONS_SELECTED = [1, 2] OPTIONS_SELECTED = [1, 2]
EXPECTED_SCORE = 6 EXPECTED_SCORE = 6
def setUp(self, problem_type): def setUp(self, problem_type, staff=False):
""" """
Configure page objects to test Open Assessment. Configure page objects to test Open Assessment.
Args: Args:
problem_type (str): The type of problem being tested, problem_type (str): The type of problem being tested,
used to choose which part of the course to load. used to choose which part of the course to load.
staff (bool): If True, runs the test with a staff user (defaults to False).
""" """
super(OpenAssessmentTest, self).setUp() super(OpenAssessmentTest, self).setUp()
problem_loc = self.PROBLEM_LOCATIONS[problem_type] self.problem_loc = self.PROBLEM_LOCATIONS[problem_type]
self.auto_auth_page = AutoAuthPage(self.browser, course_id=self.TEST_COURSE_ID) self.auto_auth_page = AutoAuthPage(self.browser, course_id=self.TEST_COURSE_ID, staff=staff)
self.submission_page = SubmissionPage(self.browser, problem_loc) self.submission_page = SubmissionPage(self.browser, self.problem_loc)
self.self_asmnt_page = AssessmentPage('self-assessment', self.browser, problem_loc) self.self_asmnt_page = AssessmentPage('self-assessment', self.browser, self.problem_loc)
self.peer_asmnt_page = AssessmentPage('peer-assessment', self.browser, problem_loc) self.peer_asmnt_page = AssessmentPage('peer-assessment', self.browser, self.problem_loc)
self.student_training_page = AssessmentPage('student-training', self.browser, problem_loc) self.student_training_page = AssessmentPage('student-training', self.browser, self.problem_loc)
self.grade_page = GradePage(self.browser, problem_loc) self.grade_page = GradePage(self.browser, self.problem_loc)
class SelfAssessmentTest(OpenAssessmentTest): class SelfAssessmentTest(OpenAssessmentTest):
...@@ -206,6 +209,79 @@ class StudentTrainingTest(OpenAssessmentTest): ...@@ -206,6 +209,79 @@ class StudentTrainingTest(OpenAssessmentTest):
self.fail("Student training was not marked complete.") self.fail("Student training was not marked complete.")
class StaffAreaTest(OpenAssessmentTest):
"""
Test the staff area.
"""
def setUp(self):
super(StaffAreaTest, self).setUp('peer_only', staff=True)
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
@retry()
@attr('acceptance')
def test_staff_area_buttons(self):
"""
Scenario: the staff area buttons should behave correctly
Given I am viewing the staff area of an ORA problem
Then none of the buttons should be active
When I click the "Staff Tools" button
Then only the "Staff Tools" button should be active
When I click the "Staff Info" button
Then only the "Staff Info" button should be active
When I click the "Staff Info" button again
Then none of the buttons should be active
"""
self.auto_auth_page.visit()
self.staff_area_page.visit()
self.assertEqual(self.staff_area_page.selected_button_names, [])
self.staff_area_page.click_staff_toolbar_button("staff-tools")
self.assertEqual(self.staff_area_page.selected_button_names, ["STAFF TOOLS"])
self.staff_area_page.click_staff_toolbar_button("staff-info")
self.assertEqual(self.staff_area_page.selected_button_names, ["STAFF INFO"])
self.staff_area_page.click_staff_toolbar_button("staff-info")
self.assertEqual(self.staff_area_page.selected_button_names, [])
@retry()
@attr('acceptance')
@ddt.data(
("staff-tools", "STAFF TOOLS"),
("staff-info", "STAFF INFO"),
)
@ddt.unpack
def test_staff_tools_panel(self, button_name, button_label):
"""
Scenario: the staff tools panel should be shown correctly
Given I am viewing the staff area of an ORA problem
Then none of the panels should be shown
When I click the staff button
Then only the related panel should be shown
When I click the close button in the panel
Then none of the panels should be shown
"""
self.auto_auth_page.visit()
self.staff_area_page.visit()
# Verify that there is no selected panel initially
self.assertEqual(self.staff_area_page.selected_button_names, [])
self.assertEqual(self.staff_area_page.visible_staff_panels, [])
# Click on the button and verify that the panel has opened
self.staff_area_page.click_staff_toolbar_button(button_name)
self.assertEqual(self.staff_area_page.selected_button_names, [button_label])
self.assertEqual(
self.staff_area_page.visible_staff_panels,
[u'openassessment__{button_name}'.format(button_name=button_name)]
)
# Click 'Close' and verify that the panel has been closed
self.staff_area_page.click_staff_panel_close_button("staff-tools")
self.assertEqual(self.staff_area_page.selected_button_names, [])
self.assertEqual(self.staff_area_page.visible_staff_panels, [])
if __name__ == "__main__": if __name__ == "__main__":
# Configure the screenshot directory # Configure the screenshot directory
......
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