Commit 5b7f48c5 by sanfordstudent Committed by GitHub

Merge pull request #13495 from edx/beryl/test-explicit-graded

tests for changing grade policy
and moving progress page tests into separate file
parents 4f28b81f a98e33af
...@@ -162,7 +162,7 @@ class ProblemPage(PageObject): ...@@ -162,7 +162,7 @@ class ProblemPage(PageObject):
""" """
Click the choice input(radio, checkbox or option) where value matches `choice_value` in choice group. 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() self.wait_for_ajax()
def is_correct(self): def is_correct(self):
......
...@@ -67,6 +67,13 @@ class ProgressPage(CoursePage): ...@@ -67,6 +67,13 @@ class ProgressPage(CoursePage):
# Retrieve the scores for the section # Retrieve the scores for the section
return self._section_scores(chapter_index, section_index) 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): def _chapter_index(self, title):
""" """
Return the CSS index of the chapter with `title`. Return the CSS index of the chapter with `title`.
......
...@@ -17,6 +17,9 @@ from bok_choy.javascript import js_defined ...@@ -17,6 +17,9 @@ from bok_choy.javascript import js_defined
from bok_choy.web_app_test import WebAppTest from bok_choy.web_app_test import WebAppTest
from bok_choy.promise import EmptyPromise, Promise from bok_choy.promise import EmptyPromise, Promise
from bok_choy.page_object import XSS_INJECTION 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 opaque_keys.edx.locator import CourseLocator
from pymongo import MongoClient, ASCENDING from pymongo import MongoClient, ASCENDING
from openedx.core.lib.tests.assertions.events import assert_event_matches, is_matching_event, EventMatchTolerates from openedx.core.lib.tests.assertions.events import assert_event_matches, is_matching_event, EventMatchTolerates
...@@ -350,6 +353,32 @@ def is_404_page(browser): ...@@ -350,6 +353,32 @@ def is_404_page(browser):
return 'Page not found (404)' in browser.find_element_by_tag_name('h1').text 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): class EventsTestMixin(TestCase):
""" """
Helpers and setup for running tests that evaluate events emitted Helpers and setup for running tests that evaluate events emitted
......
...@@ -3,58 +3,26 @@ ...@@ -3,58 +3,26 @@
End-to-end tests for the LMS. End-to-end tests for the LMS.
""" """
from contextlib import contextmanager
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
import ddt import ddt
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory from ..helpers import UniqueCourseTest, EventsTestMixin, auto_auth, create_multiple_choice_problem
from ..helpers import UniqueCourseTest, EventsTestMixin from ...fixtures.course import CourseFixture, XBlockFixtureDesc
from ...pages.studio.auto_auth import AutoAuthPage from ...pages.common.logout import LogoutPage
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.course_nav import CourseNavPage
from ...pages.lms.instructor_dashboard import InstructorDashboardPage 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.problem import ProblemPage
from ...pages.common.logout import LogoutPage from ...pages.lms.progress import ProgressPage
from ...pages.lms.staff_view import StaffPage from ...pages.lms.staff_view import StaffPage
from ...pages.lms.track_selection import TrackSelectionPage from ...pages.lms.track_selection import TrackSelectionPage
from ...pages.lms.pay_and_verify import PaymentAndVerificationFlow, FakePaymentPage from ...pages.studio.auto_auth import AutoAuthPage
from ...pages.lms.dashboard import DashboardPage from ...pages.studio.overview import CourseOutlinePage
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()
class CoursewareTest(UniqueCourseTest): class CoursewareTest(UniqueCourseTest):
...@@ -97,7 +65,7 @@ class CoursewareTest(UniqueCourseTest): ...@@ -97,7 +65,7 @@ class CoursewareTest(UniqueCourseTest):
).install() ).install()
# Auto-auth register for the course. # 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): def _goto_problem_page(self):
""" """
...@@ -121,7 +89,7 @@ class CoursewareTest(UniqueCourseTest): ...@@ -121,7 +89,7 @@ class CoursewareTest(UniqueCourseTest):
# Logout and login as a staff user. # Logout and login as a staff user.
LogoutPage(self.browser).visit() 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. # Visit course outline page in studio.
self.course_outline.visit() self.course_outline.visit()
...@@ -131,7 +99,7 @@ class CoursewareTest(UniqueCourseTest): ...@@ -131,7 +99,7 @@ class CoursewareTest(UniqueCourseTest):
# Logout and login as a student. # Logout and login as a student.
LogoutPage(self.browser).visit() 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. # Visit courseware as a student.
self.courseware_page.visit() self.courseware_page.visit()
...@@ -210,14 +178,14 @@ class ProctoredExamTest(UniqueCourseTest): ...@@ -210,14 +178,14 @@ class ProctoredExamTest(UniqueCourseTest):
).visit() ).visit()
# Auto-auth register for the course. # 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): def _login_as_a_verified_user(self):
""" """
login as a verififed user 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. # the track selection page cannot be visited. see the other tests to see if any prereq is there.
# Navigate to the track selection page # Navigate to the track selection page
...@@ -240,7 +208,7 @@ class ProctoredExamTest(UniqueCourseTest): ...@@ -240,7 +208,7 @@ class ProctoredExamTest(UniqueCourseTest):
Then I can view all settings related to Proctored and timed exams Then I can view all settings related to Proctored and timed exams
""" """
LogoutPage(self.browser).visit() 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.visit()
self.course_outline.open_subsection_settings_dialog() self.course_outline.open_subsection_settings_dialog()
...@@ -256,7 +224,7 @@ class ProctoredExamTest(UniqueCourseTest): ...@@ -256,7 +224,7 @@ class ProctoredExamTest(UniqueCourseTest):
Then I can see an option to take the exam as a proctored exam. Then I can see an option to take the exam as a proctored exam.
""" """
LogoutPage(self.browser).visit() 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.visit()
self.course_outline.open_subsection_settings_dialog() self.course_outline.open_subsection_settings_dialog()
...@@ -275,7 +243,7 @@ class ProctoredExamTest(UniqueCourseTest): ...@@ -275,7 +243,7 @@ class ProctoredExamTest(UniqueCourseTest):
then take it as student" then take it as student"
""" """
LogoutPage(self.browser).visit() 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.visit()
self.course_outline.open_subsection_settings_dialog() self.course_outline.open_subsection_settings_dialog()
...@@ -314,13 +282,13 @@ class ProctoredExamTest(UniqueCourseTest): ...@@ -314,13 +282,13 @@ class ProctoredExamTest(UniqueCourseTest):
self._setup_and_take_timed_exam(hide_after_due) self._setup_and_take_timed_exam(hide_after_due)
LogoutPage(self.browser).visit() 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.visit()
last_week = (datetime.today() - timedelta(days=7)).strftime("%m/%d/%Y") last_week = (datetime.today() - timedelta(days=7)).strftime("%m/%d/%Y")
self.course_outline.change_problem_due_date(last_week) self.course_outline.change_problem_due_date(last_week)
LogoutPage(self.browser).visit() 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.courseware_page.visit()
self.assertEqual(self.courseware_page.has_submitted_exam_message(), hide_after_due) self.assertEqual(self.courseware_page.has_submitted_exam_message(), hide_after_due)
...@@ -335,7 +303,7 @@ class ProctoredExamTest(UniqueCourseTest): ...@@ -335,7 +303,7 @@ class ProctoredExamTest(UniqueCourseTest):
self._setup_and_take_timed_exam() self._setup_and_take_timed_exam()
LogoutPage(self.browser).visit() 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() self.courseware_page.visit()
staff_page = StaffPage(self.browser, self.course_id) staff_page = StaffPage(self.browser, self.course_id)
self.assertEqual(staff_page.staff_view_mode, 'Staff') self.assertEqual(staff_page.staff_view_mode, 'Staff')
...@@ -357,7 +325,7 @@ class ProctoredExamTest(UniqueCourseTest): ...@@ -357,7 +325,7 @@ class ProctoredExamTest(UniqueCourseTest):
Practice: True, False Practice: True, False
""" """
LogoutPage(self.browser).visit() 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.visit()
self.course_outline.open_subsection_settings_dialog() self.course_outline.open_subsection_settings_dialog()
...@@ -853,7 +821,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest): ...@@ -853,7 +821,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
self._setup_subsection() self._setup_subsection()
# Auto-auth register for the course. # 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): def _setup_subsection(self):
""" """
...@@ -861,7 +829,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest): ...@@ -861,7 +829,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
it as a student. it as a student.
""" """
self.logout_page.visit() 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.visit()
self.course_outline.open_subsection_settings_dialog() self.course_outline.open_subsection_settings_dialog()
...@@ -869,7 +837,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest): ...@@ -869,7 +837,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
self.course_outline.make_subsection_hidden_after_due_date() self.course_outline.make_subsection_hidden_after_due_date()
self.logout_page.visit() 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.courseware_page.visit()
self.logout_page.visit() self.logout_page.visit()
...@@ -893,7 +861,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest): ...@@ -893,7 +861,7 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
Then I should be able to see my grade on the progress page Then I should be able to see my grade on the progress page
""" """
self.logout_page.visit() 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.courseware_page.visit()
self.assertFalse(self.courseware_page.content_hidden_past_due_date()) self.assertFalse(self.courseware_page.content_hidden_past_due_date())
...@@ -901,230 +869,15 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest): ...@@ -901,230 +869,15 @@ class SubsectionHiddenAfterDueDateTest(UniqueCourseTest):
self.assertEqual(self.progress_page.scores('Test Section 1', 'Test Subsection 1'), [(0, 1)]) self.assertEqual(self.progress_page.scores('Test Section 1', 'Test Subsection 1'), [(0, 1)])
self.logout_page.visit() 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.visit()
last_week = (datetime.today() - timedelta(days=7)).strftime("%m/%d/%Y") last_week = (datetime.today() - timedelta(days=7)).strftime("%m/%d/%Y")
self.course_outline.change_problem_due_date(last_week) self.course_outline.change_problem_due_date(last_week)
self.logout_page.visit() 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.courseware_page.visit()
self.assertTrue(self.courseware_page.content_hidden_past_due_date()) self.assertTrue(self.courseware_page.content_hidden_past_due_date())
self.progress_page.visit() self.progress_page.visit()
self.assertEqual(self.progress_page.scores('Test Section 1', 'Test Subsection 1'), [(0, 1)]) 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))
# -*- 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)")
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