""" End-to-end tests for the LMS that utilize the progress page. """ from contextlib import contextmanager from ...fixtures.course import CourseFixture, XBlockFixtureDesc from ...pages.common.logout import LogoutPage from ...pages.lms.courseware import CoursewarePage from ...pages.lms.problem import ProblemPage from ...pages.lms.progress import ProgressPage from ...pages.studio.overview import CourseOutlinePage as StudioCourseOutlinePage from ..helpers import ( UniqueCourseTest, auto_auth, create_multiple_choice_problem, ) 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().setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) self.problem_page = ProblemPage(self.browser) self.progress_page = ProgressPage(self.browser, self.course_id) self.logout_page = LogoutPage(self.browser) self.studio_course_outline = StudioCourseOutlinePage( 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.problem1 = create_multiple_choice_problem(self.PROBLEM_NAME) self.problem2 = create_multiple_choice_problem(self.PROBLEM_NAME_2) 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(self.problem1, self.problem2) ) ), XBlockFixtureDesc('chapter', "Lab Section").add_children( XBlockFixtureDesc('sequential', "Lab Subsection").add_children( XBlockFixtureDesc('vertical', "Lab Unit").add_children( create_multiple_choice_problem("Lab Exercise") ) ) ) ).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._answer_problem(choice=2) def _answer_problem(self, choice): """ Submit the given choice for the problem. """ self.courseware_page.go_to_sequential_position(1) self.problem_page.click_choice(f'choice_choice_{choice}') self.problem_page.click_submit() 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) # lint-amnesty, pylint: disable=no-member 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) # lint-amnesty, pylint: disable=no-member @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. But if there's an error, don't log out before capturing a screenshot. """ self.logout_page.visit() 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 self.logout_page.visit() class SubsectionGradingPolicyBase(ProgressPageBaseTest): """ Base class for testing a subsection and its impact to the progress page """ def setUp(self): super().setUp() self._set_policy_for_subsection("Homework", 0) self._set_policy_for_subsection("Lab", 1) def _set_policy_for_subsection(self, policy, section=0): """ Set the grading policy for the first subsection in the specified section. If a section index is not provided, 0 is assumed. """ with self._logged_in_session(staff=True): self.studio_course_outline.visit() modal = self.studio_course_outline.section_at(section).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. """ assert self._get_problem_scores() == problem_scores assert self._get_section_score() == section_score assert self.progress_page.text_on_page(text) # lint-amnesty, pylint: disable=no-member def _check_tick_text(self, index, sr_text, label, label_hidden=True): """ Check the label and sr text for a horizontal (X-axis) tick. """ assert sr_text == self.progress_page.x_tick_sr_text(index) assert [label, ('true' if label_hidden else None)] == self.progress_page.x_tick_label(index) class SubsectionGradingPolicyA11yTest(SubsectionGradingPolicyBase): """ Class to test the accessibility of subsection grading """ a11y = True def test_axis_a11y(self): """ Tests that the progress chart axes have appropriate a11y (screenreader) markup. """ with self._logged_in_session(): self.courseware_page.visit() # Answer the first HW problem (the unit contains 2 problems, only one will be answered correctly) self._answer_problem_correctly() self.courseware_page.click_next_button_on_top() # Answer the first Lab problem (unit only contains a single problem) self._answer_problem_correctly() self.progress_page.a11y_audit.config.set_rules({ "ignore": [ 'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865 'region', # TODO: AC-932 ] }) self.progress_page.visit() # Verify the basic a11y of the progress page self.progress_page.a11y_audit.check_for_accessibility_errors() # Verify that y-Axis labels are aria-hidden assert ['100%', 'true'] == self.progress_page.y_tick_label(0) assert ['0%', 'true'] == self.progress_page.y_tick_label(1) assert ['Pass 50%', 'true'] == self.progress_page.y_tick_label(2) # Verify x-Axis labels and sr-text self._check_tick_text(0, ['Homework 1 - Test Subsection 1 - 50% (1/2)'], 'HW 01') # Homeworks 2-10 are checked in the for loop below. self._check_tick_text( 10, ['Homework 11 Unreleased - 0% (?/?)', 'The lowest 2 Homework scores are dropped.'], 'HW 11' ) self._check_tick_text( 11, ['Homework 12 Unreleased - 0% (?/?)', 'The lowest 2 Homework scores are dropped.'], 'HW 12' ) self._check_tick_text(12, ['Homework Average = 5%'], 'HW Avg') self._check_tick_text(13, ['Lab 1 - Lab Subsection - 100% (1/1)'], 'Lab 01') # Labs 2-10 are checked in the for loop below. self._check_tick_text( 23, ['Lab 11 Unreleased - 0% (?/?)', 'The lowest 2 Lab scores are dropped.'], 'Lab 11' ) self._check_tick_text( 24, ['Lab 12 Unreleased - 0% (?/?)', 'The lowest 2 Lab scores are dropped.'], 'Lab 12' ) self._check_tick_text(25, ['Lab Average = 10%'], 'Lab Avg') self._check_tick_text(26, ['Midterm Exam = 0%'], 'Midterm') self._check_tick_text(27, ['Final Exam = 0%'], 'Final') self._check_tick_text( 28, ['Homework = 0.75% of a possible 15.00%', 'Lab = 1.50% of a possible 15.00%'], 'Total', False # The label "Total" should NOT be aria-hidden ) # The grading policy has 12 Homeworks and 12 Labs. Most of them are unpublished, # with no additional information. for i in range(1, 10): self._check_tick_text( i, ['Homework {index} Unreleased - 0% (?/?)'.format(index=i + 1)], 'HW 0{index}'.format(index=i + 1) if i < 9 else 'HW {index}'.format(index=i + 1) ) self._check_tick_text( i + 13, ['Lab {index} Unreleased - 0% (?/?)'.format(index=i + 1)], 'Lab 0{index}'.format(index=i + 1) if i < 9 else 'Lab {index}'.format(index=i + 1) ) # Verify the overall score. The first element in the array is the sr-only text, and the # second is the total text (including the sr-only text). assert ['Overall Score', 'Overall Score\n2%'] == self.progress_page.graph_overall_score() class ProgressPageA11yTest(ProgressPageBaseTest): """ Class to test the accessibility of the progress page. """ a11y = True def test_progress_page_a11y(self): """ Test the accessibility of the progress page. """ self.progress_page.a11y_audit.config.set_rules({ "ignore": [ 'aria-valid-attr', # TODO: LEARNER-6611 & LEARNER-6865 'region', # TODO: AC-932 ] }) self.progress_page.visit() self.progress_page.a11y_audit.check_for_accessibility_errors()