diff --git a/common/test/acceptance/pages/lms/problem.py b/common/test/acceptance/pages/lms/problem.py index 2c0e3efafa..02f885db53 100644 --- a/common/test/acceptance/pages/lms/problem.py +++ b/common/test/acceptance/pages/lms/problem.py @@ -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): diff --git a/common/test/acceptance/pages/lms/progress.py b/common/test/acceptance/pages/lms/progress.py index f0a6d5aab6..e923994403 100644 --- a/common/test/acceptance/pages/lms/progress.py +++ b/common/test/acceptance/pages/lms/progress.py @@ -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`. diff --git a/common/test/acceptance/tests/helpers.py b/common/test/acceptance/tests/helpers.py index 98a4259b99..04c6787c35 100644 --- a/common/test/acceptance/tests/helpers.py +++ b/common/test/acceptance/tests/helpers.py @@ -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 diff --git a/common/test/acceptance/tests/lms/test_lms_courseware.py b/common/test/acceptance/tests/lms/test_lms_courseware.py index 8e000d6e4b..282d037e63 100644 --- a/common/test/acceptance/tests/lms/test_lms_courseware.py +++ b/common/test/acceptance/tests/lms/test_lms_courseware.py @@ -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 = "

modified content

" - # 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)) diff --git a/common/test/acceptance/tests/lms/test_progress_page.py b/common/test/acceptance/tests/lms/test_progress_page.py new file mode 100644 index 0000000000..cad30b789c --- /dev/null +++ b/common/test/acceptance/tests/lms/test_progress_page.py @@ -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 = "

modified content

" + 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)")