TNL-5466 tests for changing grade policy
and moving progress page tests into separate file
This commit is contained in:
@@ -162,7 +162,7 @@ class ProblemPage(PageObject):
|
||||
"""
|
||||
Click the choice input(radio, checkbox or option) where value matches `choice_value` in choice group.
|
||||
"""
|
||||
self.q(css='div.problem .choicegroup input[value="' + choice_value + '"]').click()
|
||||
self.q(css='div.problem .choicegroup input[value="' + choice_value + '"]').first.click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
def is_correct(self):
|
||||
|
||||
@@ -67,6 +67,13 @@ class ProgressPage(CoursePage):
|
||||
# Retrieve the scores for the section
|
||||
return self._section_scores(chapter_index, section_index)
|
||||
|
||||
def text_on_page(self, text):
|
||||
"""
|
||||
Return whether the given text appears
|
||||
on the page.
|
||||
"""
|
||||
return text in self.q(css=".view-in-course").html[0]
|
||||
|
||||
def _chapter_index(self, title):
|
||||
"""
|
||||
Return the CSS index of the chapter with `title`.
|
||||
|
||||
@@ -17,6 +17,9 @@ from bok_choy.javascript import js_defined
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
from bok_choy.promise import EmptyPromise, Promise
|
||||
from bok_choy.page_object import XSS_INJECTION
|
||||
from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory
|
||||
from common.test.acceptance.pages.studio.auto_auth import AutoAuthPage
|
||||
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from pymongo import MongoClient, ASCENDING
|
||||
from openedx.core.lib.tests.assertions.events import assert_event_matches, is_matching_event, EventMatchTolerates
|
||||
@@ -350,6 +353,32 @@ def is_404_page(browser):
|
||||
return 'Page not found (404)' in browser.find_element_by_tag_name('h1').text
|
||||
|
||||
|
||||
def create_multiple_choice_problem(problem_name):
|
||||
"""
|
||||
Return the Multiple Choice Problem Descriptor, given the name of the problem.
|
||||
"""
|
||||
factory = MultipleChoiceResponseXMLFactory()
|
||||
xml_data = factory.build_xml(
|
||||
question_text='The correct answer is Choice 2',
|
||||
choices=[False, False, True, False],
|
||||
choice_names=['choice_0', 'choice_1', 'choice_2', 'choice_3']
|
||||
)
|
||||
|
||||
return XBlockFixtureDesc(
|
||||
'problem',
|
||||
problem_name,
|
||||
data=xml_data,
|
||||
metadata={'rerandomize': 'always'}
|
||||
)
|
||||
|
||||
|
||||
def auto_auth(browser, username, email, staff, course_id):
|
||||
"""
|
||||
Logout and login with given credentials.
|
||||
"""
|
||||
AutoAuthPage(browser, username=username, email=email, course_id=course_id, staff=staff).visit()
|
||||
|
||||
|
||||
class EventsTestMixin(TestCase):
|
||||
"""
|
||||
Helpers and setup for running tests that evaluate events emitted
|
||||
|
||||
@@ -3,58 +3,26 @@
|
||||
End-to-end tests for the LMS.
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import ddt
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory
|
||||
from ..helpers import UniqueCourseTest, EventsTestMixin
|
||||
from ...pages.studio.auto_auth import AutoAuthPage
|
||||
from ...pages.lms.create_mode import ModeCreationPage
|
||||
from ...pages.studio.component_editor import ComponentEditorView
|
||||
from ...pages.studio.overview import CourseOutlinePage
|
||||
from ...pages.studio.utils import type_in_codemirror
|
||||
from ...pages.lms.courseware import CoursewarePage, CoursewareSequentialTabPage
|
||||
from ...pages.lms.course_nav import CourseNavPage
|
||||
from ...pages.lms.instructor_dashboard import InstructorDashboardPage
|
||||
from ...pages.lms.problem import ProblemPage
|
||||
from ..helpers import UniqueCourseTest, EventsTestMixin, auto_auth, create_multiple_choice_problem
|
||||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from ...pages.common.logout import LogoutPage
|
||||
from ...pages.lms.course_nav import CourseNavPage
|
||||
from ...pages.lms.courseware import CoursewarePage, CoursewareSequentialTabPage
|
||||
from ...pages.lms.create_mode import ModeCreationPage
|
||||
from ...pages.lms.dashboard import DashboardPage
|
||||
from ...pages.lms.pay_and_verify import PaymentAndVerificationFlow, FakePaymentPage
|
||||
from ...pages.lms.problem import ProblemPage
|
||||
from ...pages.lms.progress import ProgressPage
|
||||
from ...pages.lms.staff_view import StaffPage
|
||||
from ...pages.lms.track_selection import TrackSelectionPage
|
||||
from ...pages.lms.pay_and_verify import PaymentAndVerificationFlow, FakePaymentPage
|
||||
from ...pages.lms.dashboard import DashboardPage
|
||||
from ...pages.lms.progress import ProgressPage
|
||||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
|
||||
|
||||
def create_multiple_choice_problem(problem_name):
|
||||
"""
|
||||
Return the Multiple Choice Problem Descriptor, given the name of the problem.
|
||||
"""
|
||||
factory = MultipleChoiceResponseXMLFactory()
|
||||
xml_data = factory.build_xml(
|
||||
question_text='The correct answer is Choice 2',
|
||||
choices=[False, False, True, False],
|
||||
choice_names=['choice_0', 'choice_1', 'choice_2', 'choice_3']
|
||||
)
|
||||
|
||||
return XBlockFixtureDesc(
|
||||
'problem',
|
||||
problem_name,
|
||||
data=xml_data,
|
||||
metadata={'rerandomize': 'always'}
|
||||
)
|
||||
|
||||
|
||||
def _auto_auth(browser, username, email, staff, course_id):
|
||||
"""
|
||||
Logout and login with given credentials.
|
||||
"""
|
||||
AutoAuthPage(browser, username=username, email=email,
|
||||
course_id=course_id, staff=staff).visit()
|
||||
from ...pages.studio.auto_auth import AutoAuthPage
|
||||
from ...pages.studio.overview import CourseOutlinePage
|
||||
|
||||
|
||||
class CoursewareTest(UniqueCourseTest):
|
||||
@@ -97,7 +65,7 @@ class CoursewareTest(UniqueCourseTest):
|
||||
).install()
|
||||
|
||||
# Auto-auth register for the course.
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
|
||||
def _goto_problem_page(self):
|
||||
"""
|
||||
@@ -121,7 +89,7 @@ class CoursewareTest(UniqueCourseTest):
|
||||
|
||||
# Logout and login as a staff user.
|
||||
LogoutPage(self.browser).visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
|
||||
# Visit course outline page in studio.
|
||||
self.course_outline.visit()
|
||||
@@ -131,7 +99,7 @@ class CoursewareTest(UniqueCourseTest):
|
||||
|
||||
# Logout and login as a student.
|
||||
LogoutPage(self.browser).visit()
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
|
||||
# Visit courseware as a student.
|
||||
self.courseware_page.visit()
|
||||
@@ -210,14 +178,14 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
).visit()
|
||||
|
||||
# Auto-auth register for the course.
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
|
||||
def _login_as_a_verified_user(self):
|
||||
"""
|
||||
login as a verififed user
|
||||
"""
|
||||
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
|
||||
# the track selection page cannot be visited. see the other tests to see if any prereq is there.
|
||||
# Navigate to the track selection page
|
||||
@@ -240,7 +208,7 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
Then I can view all settings related to Proctored and timed exams
|
||||
"""
|
||||
LogoutPage(self.browser).visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
self.course_outline.visit()
|
||||
|
||||
self.course_outline.open_subsection_settings_dialog()
|
||||
@@ -256,7 +224,7 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
Then I can see an option to take the exam as a proctored exam.
|
||||
"""
|
||||
LogoutPage(self.browser).visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
self.course_outline.visit()
|
||||
self.course_outline.open_subsection_settings_dialog()
|
||||
|
||||
@@ -275,7 +243,7 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
then take it as student"
|
||||
"""
|
||||
LogoutPage(self.browser).visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
self.course_outline.visit()
|
||||
self.course_outline.open_subsection_settings_dialog()
|
||||
|
||||
@@ -314,13 +282,13 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
self._setup_and_take_timed_exam(hide_after_due)
|
||||
|
||||
LogoutPage(self.browser).visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
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()
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
self.courseware_page.visit()
|
||||
self.assertEqual(self.courseware_page.has_submitted_exam_message(), hide_after_due)
|
||||
|
||||
@@ -335,7 +303,7 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
self._setup_and_take_timed_exam()
|
||||
|
||||
LogoutPage(self.browser).visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
self.courseware_page.visit()
|
||||
staff_page = StaffPage(self.browser, self.course_id)
|
||||
self.assertEqual(staff_page.staff_view_mode, 'Staff')
|
||||
@@ -357,7 +325,7 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
Practice: True, False
|
||||
"""
|
||||
LogoutPage(self.browser).visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
self.course_outline.visit()
|
||||
|
||||
self.course_outline.open_subsection_settings_dialog()
|
||||
@@ -853,7 +821,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
|
||||
self._setup_subsection()
|
||||
|
||||
# Auto-auth register for the course.
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
|
||||
def _setup_subsection(self):
|
||||
"""
|
||||
@@ -861,7 +829,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
|
||||
it as a student.
|
||||
"""
|
||||
self.logout_page.visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
self.course_outline.visit()
|
||||
self.course_outline.open_subsection_settings_dialog()
|
||||
|
||||
@@ -869,7 +837,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
|
||||
self.course_outline.make_subsection_hidden_after_due_date()
|
||||
|
||||
self.logout_page.visit()
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
self.courseware_page.visit()
|
||||
|
||||
self.logout_page.visit()
|
||||
@@ -893,7 +861,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
|
||||
Then I should be able to see my grade on the progress page
|
||||
"""
|
||||
self.logout_page.visit()
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
self.courseware_page.visit()
|
||||
self.assertFalse(self.courseware_page.content_hidden_past_due_date())
|
||||
|
||||
@@ -901,230 +869,15 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
|
||||
self.assertEqual(self.progress_page.scores('Test Section 1', 'Test Subsection 1'), [(0, 1)])
|
||||
|
||||
self.logout_page.visit()
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
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.logout_page.visit()
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
self.courseware_page.visit()
|
||||
self.assertTrue(self.courseware_page.content_hidden_past_due_date())
|
||||
|
||||
self.progress_page.visit()
|
||||
self.assertEqual(self.progress_page.scores('Test Section 1', 'Test Subsection 1'), [(0, 1)])
|
||||
|
||||
|
||||
class ProgressPageBaseTest(UniqueCourseTest):
|
||||
"""
|
||||
Provides utility methods for tests retrieving
|
||||
scores from the progress page.
|
||||
"""
|
||||
USERNAME = "STUDENT_TESTER"
|
||||
EMAIL = "student101@example.com"
|
||||
SECTION_NAME = 'Test Section 1'
|
||||
SUBSECTION_NAME = 'Test Subsection 1'
|
||||
UNIT_NAME = 'Test Unit 1'
|
||||
PROBLEM_NAME = 'Test Problem 1'
|
||||
|
||||
def setUp(self):
|
||||
super(ProgressPageBaseTest, self).setUp()
|
||||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||||
self.problem_page = ProblemPage(self.browser) # pylint: disable=attribute-defined-outside-init
|
||||
self.progress_page = ProgressPage(self.browser, self.course_id)
|
||||
self.logout_page = LogoutPage(self.browser)
|
||||
|
||||
self.course_outline = CourseOutlinePage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run']
|
||||
)
|
||||
|
||||
# Install a course with sections/problems, tabs, updates, and handouts
|
||||
course_fix = CourseFixture(
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run'],
|
||||
self.course_info['display_name']
|
||||
)
|
||||
|
||||
course_fix.add_children(
|
||||
XBlockFixtureDesc('chapter', self.SECTION_NAME).add_children(
|
||||
XBlockFixtureDesc('sequential', self.SUBSECTION_NAME).add_children(
|
||||
XBlockFixtureDesc('vertical', self.UNIT_NAME).add_children(
|
||||
create_multiple_choice_problem(self.PROBLEM_NAME)
|
||||
)
|
||||
)
|
||||
)
|
||||
).install()
|
||||
|
||||
# Auto-auth register for the course.
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
|
||||
def _answer_problem_correctly(self):
|
||||
"""
|
||||
Submit a correct answer to the problem.
|
||||
"""
|
||||
self.courseware_page.go_to_sequential_position(1)
|
||||
self.problem_page.click_choice('choice_choice_2')
|
||||
self.problem_page.click_check()
|
||||
|
||||
def _get_section_score(self):
|
||||
"""
|
||||
Return a list of scores from the progress page.
|
||||
"""
|
||||
self.progress_page.visit()
|
||||
return self.progress_page.section_score(self.SECTION_NAME, self.SUBSECTION_NAME)
|
||||
|
||||
def _get_scores(self):
|
||||
"""
|
||||
Return a list of scores from the progress page.
|
||||
"""
|
||||
self.progress_page.visit()
|
||||
return self.progress_page.scores(self.SECTION_NAME, self.SUBSECTION_NAME)
|
||||
|
||||
@contextmanager
|
||||
def _logged_in_session(self, staff=False):
|
||||
"""
|
||||
Ensure that the user is logged in and out appropriately at the beginning
|
||||
and end of the current test.
|
||||
"""
|
||||
self.logout_page.visit()
|
||||
try:
|
||||
if staff:
|
||||
_auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
else:
|
||||
_auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
yield
|
||||
finally:
|
||||
self.logout_page.visit()
|
||||
|
||||
|
||||
class ProgressPageTest(ProgressPageBaseTest):
|
||||
"""
|
||||
Test that the progress page reports scores from completed assessments.
|
||||
"""
|
||||
def test_progress_page_shows_scored_problems(self):
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_scores(), [(0, 1)])
|
||||
self.assertEqual(self._get_section_score(), (0, 1))
|
||||
self.courseware_page.visit()
|
||||
self._answer_problem_correctly()
|
||||
self.assertEqual(self._get_scores(), [(1, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 1))
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class PersistentGradesTest(ProgressPageBaseTest):
|
||||
"""
|
||||
Test that grades for completed assessments are persisted
|
||||
when various edits are made.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(PersistentGradesTest, self).setUp()
|
||||
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
|
||||
|
||||
def _change_subsection_structure(self):
|
||||
"""
|
||||
Adds a unit to the subsection, which
|
||||
should not affect a persisted subsectiong grade.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
subsection = self.course_outline.section(self.SECTION_NAME).subsection(self.SUBSECTION_NAME)
|
||||
subsection.expand_subsection()
|
||||
subsection.add_unit()
|
||||
|
||||
def _set_staff_lock_on_subsection(self, locked):
|
||||
"""
|
||||
Sets staff lock for a subsection, which should hide the
|
||||
subsection score from students on the progress page.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
subsection = self.course_outline.section_at(0).subsection_at(0)
|
||||
subsection.set_staff_lock(locked)
|
||||
self.assertEqual(subsection.has_staff_lock_warning, locked)
|
||||
|
||||
def _change_weight_for_problem(self):
|
||||
"""
|
||||
Changes the weight of the problem, which should not affect
|
||||
persisted grades.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
self.course_outline.section_at(0).subsection_at(0).expand_subsection()
|
||||
unit = self.course_outline.section_at(0).subsection_at(0).unit(self.UNIT_NAME).go_to()
|
||||
container = unit.xblocks[0].go_to_container()
|
||||
component = container.xblocks[0].children[0]
|
||||
|
||||
component.edit()
|
||||
component_editor = ComponentEditorView(self.browser, component.locator)
|
||||
component_editor.set_field_value_and_save('Problem Weight', 5)
|
||||
|
||||
def _rescore_for_all(self):
|
||||
"""
|
||||
Triggers a rescore for all students. Currently unimplemented.
|
||||
"""
|
||||
pass
|
||||
|
||||
def _edit_problem_content(self):
|
||||
"""
|
||||
Replaces the content of a problem with other html.
|
||||
Should not affect persisted grades.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
self.course_outline.section_at(0).subsection_at(0).expand_subsection()
|
||||
unit = self.course_outline.section_at(0).subsection_at(0).unit(self.UNIT_NAME).go_to()
|
||||
component = unit.xblocks[1]
|
||||
edit_view = component.edit()
|
||||
|
||||
modified_content = "<p>modified content</p>"
|
||||
# Set content in the CodeMirror editor.
|
||||
type_in_codemirror(self, 0, modified_content)
|
||||
|
||||
edit_view.q(css='.action-save').click()
|
||||
|
||||
@ddt.data(
|
||||
_edit_problem_content,
|
||||
_change_subsection_structure,
|
||||
_change_weight_for_problem,
|
||||
_rescore_for_all
|
||||
)
|
||||
def test_content_changes_do_not_change_score(self, edit):
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_scores(), [(0, 1)])
|
||||
self.assertEqual(self._get_section_score(), (0, 1))
|
||||
self.courseware_page.visit()
|
||||
self._answer_problem_correctly()
|
||||
self.assertEqual(self._get_scores(), [(1, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 1))
|
||||
|
||||
edit(self)
|
||||
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_scores(), [(1, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 1))
|
||||
|
||||
def test_visibility_change_does_affect_score(self):
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_scores(), [(0, 1)])
|
||||
self.assertEqual(self._get_section_score(), (0, 1))
|
||||
self.courseware_page.visit()
|
||||
self._answer_problem_correctly()
|
||||
self.assertEqual(self._get_scores(), [(1, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 1))
|
||||
|
||||
self._set_staff_lock_on_subsection(True)
|
||||
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_scores(), None)
|
||||
self.assertEqual(self._get_section_score(), None)
|
||||
|
||||
self._set_staff_lock_on_subsection(False)
|
||||
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_scores(), [(1, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 1))
|
||||
|
||||
273
common/test/acceptance/tests/lms/test_progress_page.py
Normal file
273
common/test/acceptance/tests/lms/test_progress_page.py
Normal file
@@ -0,0 +1,273 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
End-to-end tests for the LMS that utilize the
|
||||
progress page.
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import ddt
|
||||
|
||||
from ..helpers import UniqueCourseTest, auto_auth, create_multiple_choice_problem
|
||||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from ...pages.common.logout import LogoutPage
|
||||
from ...pages.lms.courseware import CoursewarePage
|
||||
from ...pages.lms.instructor_dashboard import InstructorDashboardPage
|
||||
from ...pages.lms.problem import ProblemPage
|
||||
from ...pages.lms.progress import ProgressPage
|
||||
from ...pages.studio.component_editor import ComponentEditorView
|
||||
from ...pages.studio.utils import type_in_codemirror
|
||||
from ...pages.studio.overview import CourseOutlinePage
|
||||
|
||||
|
||||
class ProgressPageBaseTest(UniqueCourseTest):
|
||||
"""
|
||||
Provides utility methods for tests retrieving
|
||||
scores from the progress page.
|
||||
"""
|
||||
USERNAME = "STUDENT_TESTER"
|
||||
EMAIL = "student101@example.com"
|
||||
SECTION_NAME = 'Test Section 1'
|
||||
SUBSECTION_NAME = 'Test Subsection 1'
|
||||
UNIT_NAME = 'Test Unit 1'
|
||||
PROBLEM_NAME = 'Test Problem 1'
|
||||
PROBLEM_NAME_2 = 'Test Problem 2'
|
||||
|
||||
def setUp(self):
|
||||
super(ProgressPageBaseTest, self).setUp()
|
||||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||||
self.problem_page = ProblemPage(self.browser) # pylint: disable=attribute-defined-outside-init
|
||||
self.progress_page = ProgressPage(self.browser, self.course_id)
|
||||
self.logout_page = LogoutPage(self.browser)
|
||||
|
||||
self.course_outline = CourseOutlinePage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run']
|
||||
)
|
||||
|
||||
# Install a course with problems
|
||||
self.course_fix = CourseFixture(
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run'],
|
||||
self.course_info['display_name']
|
||||
)
|
||||
|
||||
self.course_fix.add_children(
|
||||
XBlockFixtureDesc('chapter', self.SECTION_NAME).add_children(
|
||||
XBlockFixtureDesc('sequential', self.SUBSECTION_NAME).add_children(
|
||||
XBlockFixtureDesc('vertical', self.UNIT_NAME).add_children(
|
||||
create_multiple_choice_problem(self.PROBLEM_NAME),
|
||||
create_multiple_choice_problem(self.PROBLEM_NAME_2)
|
||||
)
|
||||
)
|
||||
)
|
||||
).install()
|
||||
|
||||
# Auto-auth register for the course.
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
|
||||
def _answer_problem_correctly(self):
|
||||
"""
|
||||
Submit a correct answer to the problem.
|
||||
"""
|
||||
self.courseware_page.go_to_sequential_position(1)
|
||||
self.problem_page.click_choice('choice_choice_2')
|
||||
self.problem_page.click_check()
|
||||
|
||||
def _get_section_score(self):
|
||||
"""
|
||||
Return a list of scores from the progress page.
|
||||
"""
|
||||
self.progress_page.visit()
|
||||
return self.progress_page.section_score(self.SECTION_NAME, self.SUBSECTION_NAME)
|
||||
|
||||
def _get_problem_scores(self):
|
||||
"""
|
||||
Return a list of scores from the progress page.
|
||||
"""
|
||||
self.progress_page.visit()
|
||||
return self.progress_page.scores(self.SECTION_NAME, self.SUBSECTION_NAME)
|
||||
|
||||
def _check_progress_page_with_scored_problem(self):
|
||||
"""
|
||||
Checks the progress page before and after answering
|
||||
the course's first problem correctly.
|
||||
"""
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_problem_scores(), [(0, 1), (0, 1)])
|
||||
self.assertEqual(self._get_section_score(), (0, 2))
|
||||
self.courseware_page.visit()
|
||||
self._answer_problem_correctly()
|
||||
self.assertEqual(self._get_problem_scores(), [(1, 1), (0, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 2))
|
||||
|
||||
@contextmanager
|
||||
def _logged_in_session(self, staff=False):
|
||||
"""
|
||||
Ensure that the user is logged in and out appropriately at the beginning
|
||||
and end of the current test.
|
||||
"""
|
||||
self.logout_page.visit()
|
||||
try:
|
||||
if staff:
|
||||
auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
|
||||
else:
|
||||
auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
|
||||
yield
|
||||
finally:
|
||||
self.logout_page.visit()
|
||||
|
||||
|
||||
class ProgressPageTest(ProgressPageBaseTest):
|
||||
"""
|
||||
Test that the progress page reports scores from completed assessments.
|
||||
"""
|
||||
def test_progress_page_shows_scored_problems(self):
|
||||
self._check_progress_page_with_scored_problem()
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class PersistentGradesTest(ProgressPageBaseTest):
|
||||
"""
|
||||
Test that grades for completed assessments are persisted
|
||||
when various edits are made.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(PersistentGradesTest, self).setUp()
|
||||
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
|
||||
|
||||
def _change_subsection_structure(self):
|
||||
"""
|
||||
Adds a unit to the subsection, which
|
||||
should not affect a persisted subsection grade.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
subsection = self.course_outline.section(self.SECTION_NAME).subsection(self.SUBSECTION_NAME)
|
||||
subsection.expand_subsection()
|
||||
subsection.add_unit()
|
||||
|
||||
def _set_staff_lock_on_subsection(self, locked):
|
||||
"""
|
||||
Sets staff lock for a subsection, which should hide the
|
||||
subsection score from students on the progress page.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
subsection = self.course_outline.section_at(0).subsection_at(0)
|
||||
subsection.set_staff_lock(locked)
|
||||
self.assertEqual(subsection.has_staff_lock_warning, locked)
|
||||
|
||||
def _change_weight_for_problem(self):
|
||||
"""
|
||||
Changes the weight of the problem, which should not affect
|
||||
persisted grades.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
self.course_outline.section_at(0).subsection_at(0).expand_subsection()
|
||||
unit = self.course_outline.section_at(0).subsection_at(0).unit(self.UNIT_NAME).go_to()
|
||||
component = unit.xblocks[1]
|
||||
component.edit()
|
||||
component_editor = ComponentEditorView(self.browser, component.locator)
|
||||
component_editor.set_field_value_and_save('Problem Weight', 5)
|
||||
|
||||
def _edit_problem_content(self):
|
||||
"""
|
||||
Replaces the content of a problem with other html.
|
||||
Should not affect persisted grades.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
self.course_outline.section_at(0).subsection_at(0).expand_subsection()
|
||||
unit = self.course_outline.section_at(0).subsection_at(0).unit(self.UNIT_NAME).go_to()
|
||||
component = unit.xblocks[1]
|
||||
modal = component.edit()
|
||||
|
||||
# Set content in the CodeMirror editor.
|
||||
modified_content = "<p>modified content</p>"
|
||||
type_in_codemirror(self, 0, modified_content)
|
||||
modal.q(css='.action-save').click()
|
||||
|
||||
@ddt.data(
|
||||
_edit_problem_content,
|
||||
_change_subsection_structure,
|
||||
_change_weight_for_problem
|
||||
)
|
||||
def test_content_changes_do_not_change_score(self, edit):
|
||||
with self._logged_in_session():
|
||||
self._check_progress_page_with_scored_problem()
|
||||
|
||||
edit(self)
|
||||
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_problem_scores(), [(1, 1), (0, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 2))
|
||||
|
||||
def test_visibility_change_does_affect_score(self):
|
||||
with self._logged_in_session():
|
||||
self._check_progress_page_with_scored_problem()
|
||||
|
||||
self._set_staff_lock_on_subsection(True)
|
||||
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_problem_scores(), None)
|
||||
self.assertEqual(self._get_section_score(), None)
|
||||
|
||||
self._set_staff_lock_on_subsection(False)
|
||||
|
||||
with self._logged_in_session():
|
||||
self.assertEqual(self._get_problem_scores(), [(1, 1), (0, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 2))
|
||||
|
||||
|
||||
class SubsectionGradingPolicyTest(ProgressPageBaseTest):
|
||||
"""
|
||||
Tests changing a subsection's 'graded' field
|
||||
and the effect it has on the progress page.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(SubsectionGradingPolicyTest, self).setUp()
|
||||
self._set_policy_for_subsection("Homework")
|
||||
|
||||
def _set_policy_for_subsection(self, policy):
|
||||
"""
|
||||
Set the grading policy for the
|
||||
subsection in the test.
|
||||
"""
|
||||
with self._logged_in_session(staff=True):
|
||||
self.course_outline.visit()
|
||||
modal = self.course_outline.section_at(0).subsection_at(0).edit()
|
||||
modal.policy = policy
|
||||
modal.save()
|
||||
|
||||
def _check_scores_and_page_text(self, problem_scores, section_score, text):
|
||||
"""
|
||||
Asserts that the given problem and section scores, and text,
|
||||
appear on the progress page.
|
||||
"""
|
||||
self.assertEqual(self._get_problem_scores(), problem_scores)
|
||||
self.assertEqual(self._get_section_score(), section_score)
|
||||
self.assertTrue(self.progress_page.text_on_page(text))
|
||||
|
||||
def test_subsection_grading_policy_on_progress_page(self):
|
||||
with self._logged_in_session():
|
||||
self._check_scores_and_page_text([(0, 1), (0, 1)], (0, 2), "Homework 1 - Test Subsection 1 - 0% (0/2)")
|
||||
self.courseware_page.visit()
|
||||
self._answer_problem_correctly()
|
||||
self._check_scores_and_page_text([(1, 1), (0, 1)], (1, 2), "Homework 1 - Test Subsection 1 - 50% (1/2)")
|
||||
|
||||
self._set_policy_for_subsection("Not Graded")
|
||||
|
||||
with self._logged_in_session():
|
||||
self.progress_page.visit()
|
||||
self.assertEqual(self._get_problem_scores(), [(1, 1), (0, 1)])
|
||||
self.assertEqual(self._get_section_score(), (1, 2))
|
||||
self.assertFalse(self.progress_page.text_on_page("Homework 1 - Test Subsection 1"))
|
||||
|
||||
self._set_policy_for_subsection("Homework")
|
||||
with self._logged_in_session():
|
||||
self._check_scores_and_page_text([(1, 1), (0, 1)], (1, 2), "Homework 1 - Test Subsection 1 - 50% (1/2)")
|
||||
Reference in New Issue
Block a user