774 lines
32 KiB
Python
774 lines
32 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
End-to-end tests for the LMS.
|
||
"""
|
||
|
||
import json
|
||
from nose.plugins.attrib import attr
|
||
from datetime import datetime, timedelta
|
||
import ddt
|
||
|
||
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.overview import CourseOutlinePage
|
||
from ...pages.lms.courseware import CoursewarePage, CoursewareSequentialTabPage
|
||
from ...pages.lms.course_nav import CourseNavPage
|
||
from ...pages.lms.problem import ProblemPage
|
||
from ...pages.common.logout import LogoutPage
|
||
from ...pages.lms.track_selection import TrackSelectionPage
|
||
from ...pages.lms.pay_and_verify import PaymentAndVerificationFlow, FakePaymentPage
|
||
from ...pages.lms.dashboard import DashboardPage
|
||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
|
||
|
||
|
||
class CoursewareTest(UniqueCourseTest):
|
||
"""
|
||
Test courseware.
|
||
"""
|
||
USERNAME = "STUDENT_TESTER"
|
||
EMAIL = "student101@example.com"
|
||
|
||
def setUp(self):
|
||
super(CoursewareTest, self).setUp()
|
||
|
||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||
self.course_nav = CourseNavPage(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
|
||
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', 'Test Section 1').add_children(
|
||
XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children(
|
||
XBlockFixtureDesc('problem', 'Test Problem 1')
|
||
)
|
||
),
|
||
XBlockFixtureDesc('chapter', 'Test Section 2').add_children(
|
||
XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children(
|
||
XBlockFixtureDesc('problem', 'Test Problem 2')
|
||
)
|
||
)
|
||
).install()
|
||
|
||
# Auto-auth register for the course.
|
||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||
|
||
def _goto_problem_page(self):
|
||
"""
|
||
Open problem page with assertion.
|
||
"""
|
||
self.courseware_page.visit()
|
||
self.problem_page = ProblemPage(self.browser) # pylint: disable=attribute-defined-outside-init
|
||
self.assertEqual(self.problem_page.problem_name, 'Test Problem 1')
|
||
|
||
def _create_breadcrumb(self, index):
|
||
""" Create breadcrumb """
|
||
return ['Test Section {}'.format(index), 'Test Subsection {}'.format(index), 'Test Problem {}'.format(index)]
|
||
|
||
def _auto_auth(self, username, email, staff):
|
||
"""
|
||
Logout and login with given credentials.
|
||
"""
|
||
AutoAuthPage(self.browser, username=username, email=email,
|
||
course_id=self.course_id, staff=staff).visit()
|
||
|
||
def test_courseware(self):
|
||
"""
|
||
Test courseware if recent visited subsection become unpublished.
|
||
"""
|
||
|
||
# Visit problem page as a student.
|
||
self._goto_problem_page()
|
||
|
||
# Logout and login as a staff user.
|
||
LogoutPage(self.browser).visit()
|
||
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
|
||
|
||
# Visit course outline page in studio.
|
||
self.course_outline.visit()
|
||
|
||
# Set release date for subsection in future.
|
||
self.course_outline.change_problem_release_date()
|
||
|
||
# Logout and login as a student.
|
||
LogoutPage(self.browser).visit()
|
||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||
|
||
# Visit courseware as a student.
|
||
self.courseware_page.visit()
|
||
# Problem name should be "Test Problem 2".
|
||
self.assertEqual(self.problem_page.problem_name, 'Test Problem 2')
|
||
|
||
def test_course_tree_breadcrumb(self):
|
||
"""
|
||
Scenario: Correct course tree breadcrumb is shown.
|
||
|
||
Given that I am a registered user
|
||
And I visit my courseware page
|
||
Then I should see correct course tree breadcrumb
|
||
"""
|
||
self.courseware_page.visit()
|
||
|
||
xblocks = self.course_fix.get_nested_xblocks(category="problem")
|
||
for index in range(1, len(xblocks) + 1):
|
||
self.course_nav.go_to_section('Test Section {}'.format(index), 'Test Subsection {}'.format(index))
|
||
courseware_page_breadcrumb = self.courseware_page.breadcrumb
|
||
expected_breadcrumb = self._create_breadcrumb(index) # pylint: disable=no-member
|
||
self.assertEqual(courseware_page_breadcrumb, expected_breadcrumb)
|
||
|
||
|
||
@ddt.ddt
|
||
class ProctoredExamTest(UniqueCourseTest):
|
||
"""
|
||
Test courseware.
|
||
"""
|
||
USERNAME = "STUDENT_TESTER"
|
||
EMAIL = "student101@example.com"
|
||
|
||
def setUp(self):
|
||
super(ProctoredExamTest, self).setUp()
|
||
|
||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||
|
||
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_advanced_settings({
|
||
"enable_proctored_exams": {"value": "true"}
|
||
})
|
||
|
||
course_fix.add_children(
|
||
XBlockFixtureDesc('chapter', 'Test Section 1').add_children(
|
||
XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children(
|
||
XBlockFixtureDesc('problem', 'Test Problem 1')
|
||
)
|
||
)
|
||
).install()
|
||
|
||
self.track_selection_page = TrackSelectionPage(self.browser, self.course_id)
|
||
self.payment_and_verification_flow = PaymentAndVerificationFlow(self.browser, self.course_id)
|
||
self.immediate_verification_page = PaymentAndVerificationFlow(
|
||
self.browser, self.course_id, entry_point='verify-now'
|
||
)
|
||
self.upgrade_page = PaymentAndVerificationFlow(self.browser, self.course_id, entry_point='upgrade')
|
||
self.fake_payment_page = FakePaymentPage(self.browser, self.course_id)
|
||
self.dashboard_page = DashboardPage(self.browser)
|
||
self.problem_page = ProblemPage(self.browser)
|
||
|
||
# Add a verified mode to the course
|
||
ModeCreationPage(
|
||
self.browser, self.course_id, mode_slug=u'verified', mode_display_name=u'Verified Certificate',
|
||
min_price=10, suggested_prices='10,20'
|
||
).visit()
|
||
|
||
# Auto-auth register for the course.
|
||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||
|
||
def _auto_auth(self, username, email, staff):
|
||
"""
|
||
Logout and login with given credentials.
|
||
"""
|
||
AutoAuthPage(self.browser, username=username, email=email,
|
||
course_id=self.course_id, staff=staff).visit()
|
||
|
||
def _login_as_a_verified_user(self):
|
||
"""
|
||
login as a verififed user
|
||
"""
|
||
|
||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||
|
||
# the track selection page cannot be visited. see the other tests to see if any prereq is there.
|
||
# Navigate to the track selection page
|
||
self.track_selection_page.visit()
|
||
|
||
# Enter the payment and verification flow by choosing to enroll as verified
|
||
self.track_selection_page.enroll('verified')
|
||
|
||
# Proceed to the fake payment page
|
||
self.payment_and_verification_flow.proceed_to_payment()
|
||
|
||
# Submit payment
|
||
self.fake_payment_page.submit_payment()
|
||
|
||
def test_can_create_proctored_exam_in_studio(self):
|
||
"""
|
||
Given that I am a staff member
|
||
When I visit the course outline page in studio.
|
||
And open the subsection edit dialog
|
||
Then I can view all settings related to Proctored and timed exams
|
||
"""
|
||
LogoutPage(self.browser).visit()
|
||
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
|
||
self.course_outline.visit()
|
||
|
||
self.course_outline.open_subsection_settings_dialog()
|
||
self.assertTrue(self.course_outline.proctoring_items_are_displayed())
|
||
|
||
def test_proctored_exam_flow(self):
|
||
"""
|
||
Given that I am a staff member on the exam settings section
|
||
select advanced settings tab
|
||
When I Make the exam proctored.
|
||
And I login as a verified student.
|
||
And visit the courseware as a verified student.
|
||
Then I can see an option to take the exam as a proctored exam.
|
||
"""
|
||
LogoutPage(self.browser).visit()
|
||
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
|
||
self.course_outline.visit()
|
||
self.course_outline.open_subsection_settings_dialog()
|
||
|
||
self.course_outline.select_advanced_tab()
|
||
self.course_outline.make_exam_proctored()
|
||
|
||
LogoutPage(self.browser).visit()
|
||
self._login_as_a_verified_user()
|
||
|
||
self.courseware_page.visit()
|
||
self.assertTrue(self.courseware_page.can_start_proctored_exam)
|
||
|
||
@ddt.data(True, False)
|
||
def test_timed_exam_flow(self, hide_after_due):
|
||
"""
|
||
Given that I am a staff member on the exam settings section
|
||
select advanced settings tab
|
||
When I Make the exam timed.
|
||
And I login as a verified student.
|
||
And visit the courseware as a verified student.
|
||
And I start the timed exam
|
||
Then I am taken to the exam with a timer bar showing
|
||
When I finish the exam
|
||
Then I see the exam submitted dialog in place of the exam
|
||
When I log back into studio as a staff member
|
||
And change the problem's due date to be in the past
|
||
And log back in as the original verified student
|
||
Then I see the exam or message in accordance with the hide_after_due setting
|
||
"""
|
||
LogoutPage(self.browser).visit()
|
||
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
|
||
self.course_outline.visit()
|
||
self.course_outline.open_subsection_settings_dialog()
|
||
|
||
self.course_outline.select_advanced_tab()
|
||
self.course_outline.make_exam_timed(hide_after_due=hide_after_due)
|
||
|
||
LogoutPage(self.browser).visit()
|
||
self._login_as_a_verified_user()
|
||
self.courseware_page.visit()
|
||
|
||
self.courseware_page.start_timed_exam()
|
||
self.assertTrue(self.courseware_page.is_timer_bar_present)
|
||
|
||
self.courseware_page.stop_timed_exam()
|
||
self.assertTrue(self.courseware_page.has_submitted_exam_message())
|
||
|
||
LogoutPage(self.browser).visit()
|
||
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
|
||
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()
|
||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||
self.courseware_page.visit()
|
||
self.assertEqual(self.courseware_page.has_submitted_exam_message(), hide_after_due)
|
||
|
||
def test_field_visiblity_with_all_exam_types(self):
|
||
"""
|
||
Given that I am a staff member
|
||
And I have visited the course outline page in studio.
|
||
And the subsection edit dialog is open
|
||
select advanced settings tab
|
||
For each of None, Timed, Proctored, and Practice exam types
|
||
The time allotted, review rules, and hide after due fields have proper visibility
|
||
None: False, False, False
|
||
Timed: True, False, True
|
||
Proctored: True, True, False
|
||
Practice: True, False, False
|
||
"""
|
||
LogoutPage(self.browser).visit()
|
||
self._auto_auth("STAFF_TESTER", "staff101@example.com", True)
|
||
self.course_outline.visit()
|
||
|
||
self.course_outline.open_subsection_settings_dialog()
|
||
self.course_outline.select_advanced_tab()
|
||
|
||
self.course_outline.select_none_exam()
|
||
self.assertFalse(self.course_outline.time_allotted_field_visible())
|
||
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
|
||
self.assertFalse(self.course_outline.hide_after_due_field_visible())
|
||
|
||
self.course_outline.select_timed_exam()
|
||
self.assertTrue(self.course_outline.time_allotted_field_visible())
|
||
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
|
||
self.assertTrue(self.course_outline.hide_after_due_field_visible())
|
||
|
||
self.course_outline.select_proctored_exam()
|
||
self.assertTrue(self.course_outline.time_allotted_field_visible())
|
||
self.assertTrue(self.course_outline.exam_review_rules_field_visible())
|
||
self.assertFalse(self.course_outline.hide_after_due_field_visible())
|
||
|
||
self.course_outline.select_practice_exam()
|
||
self.assertTrue(self.course_outline.time_allotted_field_visible())
|
||
self.assertFalse(self.course_outline.exam_review_rules_field_visible())
|
||
self.assertFalse(self.course_outline.hide_after_due_field_visible())
|
||
|
||
|
||
class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin):
|
||
"""
|
||
Test courseware with multiple verticals
|
||
"""
|
||
USERNAME = "STUDENT_TESTER"
|
||
EMAIL = "student101@example.com"
|
||
|
||
def setUp(self):
|
||
super(CoursewareMultipleVerticalsTest, self).setUp()
|
||
|
||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||
|
||
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', 'Test Section 1').add_children(
|
||
XBlockFixtureDesc('sequential', 'Test Subsection 1,1').add_children(
|
||
XBlockFixtureDesc('problem', 'Test Problem 1', data='<problem>problem 1 dummy body</problem>'),
|
||
XBlockFixtureDesc('html', 'html 1', data="<html>html 1 dummy body</html>"),
|
||
XBlockFixtureDesc('problem', 'Test Problem 2', data="<problem>problem 2 dummy body</problem>"),
|
||
XBlockFixtureDesc('html', 'html 2', data="<html>html 2 dummy body</html>"),
|
||
),
|
||
XBlockFixtureDesc('sequential', 'Test Subsection 1,2').add_children(
|
||
XBlockFixtureDesc('problem', 'Test Problem 3', data='<problem>problem 3 dummy body</problem>'),
|
||
),
|
||
XBlockFixtureDesc(
|
||
'sequential', 'Test HIDDEN Subsection', metadata={'visible_to_staff_only': True}
|
||
).add_children(
|
||
XBlockFixtureDesc('problem', 'Test HIDDEN Problem', data='<problem>hidden problem</problem>'),
|
||
),
|
||
),
|
||
XBlockFixtureDesc('chapter', 'Test Section 2').add_children(
|
||
XBlockFixtureDesc('sequential', 'Test Subsection 2,1').add_children(
|
||
XBlockFixtureDesc('problem', 'Test Problem 4', data='<problem>problem 4 dummy body</problem>'),
|
||
),
|
||
),
|
||
XBlockFixtureDesc('chapter', 'Test HIDDEN Section', metadata={'visible_to_staff_only': True}).add_children(
|
||
XBlockFixtureDesc('sequential', 'Test HIDDEN Subsection'),
|
||
),
|
||
).install()
|
||
|
||
# Auto-auth register for the course.
|
||
AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL,
|
||
course_id=self.course_id, staff=False).visit()
|
||
self.courseware_page.visit()
|
||
self.course_nav = CourseNavPage(self.browser)
|
||
|
||
def test_navigation_buttons(self):
|
||
# start in first section
|
||
self.assert_navigation_state('Test Section 1', 'Test Subsection 1,1', 0, next_enabled=True, prev_enabled=False)
|
||
|
||
# next takes us to next tab in sequential
|
||
self.courseware_page.click_next_button_on_top()
|
||
self.assert_navigation_state('Test Section 1', 'Test Subsection 1,1', 1, next_enabled=True, prev_enabled=True)
|
||
|
||
# go to last sequential position
|
||
self.courseware_page.go_to_sequential_position(4)
|
||
self.assert_navigation_state('Test Section 1', 'Test Subsection 1,1', 3, next_enabled=True, prev_enabled=True)
|
||
|
||
# next takes us to next sequential
|
||
self.courseware_page.click_next_button_on_bottom()
|
||
self.assert_navigation_state('Test Section 1', 'Test Subsection 1,2', 0, next_enabled=True, prev_enabled=True)
|
||
|
||
# next takes us to next chapter
|
||
self.courseware_page.click_next_button_on_top()
|
||
self.assert_navigation_state('Test Section 2', 'Test Subsection 2,1', 0, next_enabled=False, prev_enabled=True)
|
||
|
||
# previous takes us to previous chapter
|
||
self.courseware_page.click_previous_button_on_top()
|
||
self.assert_navigation_state('Test Section 1', 'Test Subsection 1,2', 0, next_enabled=True, prev_enabled=True)
|
||
|
||
# previous takes us to last tab in previous sequential
|
||
self.courseware_page.click_previous_button_on_bottom()
|
||
self.assert_navigation_state('Test Section 1', 'Test Subsection 1,1', 3, next_enabled=True, prev_enabled=True)
|
||
|
||
# previous takes us to previous tab in sequential
|
||
self.courseware_page.click_previous_button_on_bottom()
|
||
self.assert_navigation_state('Test Section 1', 'Test Subsection 1,1', 2, next_enabled=True, prev_enabled=True)
|
||
|
||
# test UI events emitted by navigation
|
||
filter_sequence_ui_event = lambda event: event.get('name', '').startswith('edx.ui.lms.sequence.')
|
||
|
||
sequence_ui_events = self.wait_for_events(event_filter=filter_sequence_ui_event, timeout=2)
|
||
legacy_events = [ev for ev in sequence_ui_events if ev['event_type'] in {'seq_next', 'seq_prev', 'seq_goto'}]
|
||
nonlegacy_events = [ev for ev in sequence_ui_events if ev not in legacy_events]
|
||
|
||
self.assertTrue(all('old' in json.loads(ev['event']) for ev in legacy_events))
|
||
self.assertTrue(all('new' in json.loads(ev['event']) for ev in legacy_events))
|
||
self.assertFalse(any('old' in json.loads(ev['event']) for ev in nonlegacy_events))
|
||
self.assertFalse(any('new' in json.loads(ev['event']) for ev in nonlegacy_events))
|
||
|
||
self.assert_events_match(
|
||
[
|
||
{
|
||
'event_type': 'seq_next',
|
||
'event': {
|
||
'old': 1,
|
||
'new': 2,
|
||
'current_tab': 1,
|
||
'tab_count': 4,
|
||
'widget_placement': 'top',
|
||
}
|
||
},
|
||
{
|
||
'event_type': 'seq_goto',
|
||
'event': {
|
||
'old': 2,
|
||
'new': 4,
|
||
'current_tab': 2,
|
||
'target_tab': 4,
|
||
'tab_count': 4,
|
||
'widget_placement': 'top',
|
||
}
|
||
},
|
||
{
|
||
'event_type': 'edx.ui.lms.sequence.next_selected',
|
||
'event': {
|
||
'current_tab': 4,
|
||
'tab_count': 4,
|
||
'widget_placement': 'bottom',
|
||
}
|
||
},
|
||
{
|
||
'event_type': 'edx.ui.lms.sequence.next_selected',
|
||
'event': {
|
||
'current_tab': 1,
|
||
'tab_count': 1,
|
||
'widget_placement': 'top',
|
||
}
|
||
},
|
||
{
|
||
'event_type': 'edx.ui.lms.sequence.previous_selected',
|
||
'event': {
|
||
'current_tab': 1,
|
||
'tab_count': 1,
|
||
'widget_placement': 'top',
|
||
}
|
||
},
|
||
{
|
||
'event_type': 'edx.ui.lms.sequence.previous_selected',
|
||
'event': {
|
||
'current_tab': 1,
|
||
'tab_count': 1,
|
||
'widget_placement': 'bottom',
|
||
}
|
||
},
|
||
{
|
||
'event_type': 'seq_prev',
|
||
'event': {
|
||
'old': 4,
|
||
'new': 3,
|
||
'current_tab': 4,
|
||
'tab_count': 4,
|
||
'widget_placement': 'bottom',
|
||
}
|
||
},
|
||
],
|
||
sequence_ui_events
|
||
)
|
||
|
||
def test_accordion_events(self):
|
||
self.course_nav.go_to_section('Test Section 1', 'Test Subsection 1,2')
|
||
|
||
self.course_nav.go_to_section('Test Section 2', 'Test Subsection 2,1')
|
||
|
||
# test UI events emitted by navigating via the course outline
|
||
filter_outline_ui_event = lambda event: event.get('name', '') == 'edx.ui.lms.outline.selected'
|
||
|
||
outline_ui_events = self.wait_for_events(event_filter=filter_outline_ui_event, timeout=2)
|
||
|
||
# note: target_url is tested in unit tests, as the url changes here with every test (it includes GUIDs).
|
||
self.assert_events_match(
|
||
[
|
||
{
|
||
'event_type': 'edx.ui.lms.outline.selected',
|
||
'name': 'edx.ui.lms.outline.selected',
|
||
'event': {
|
||
'target_name': 'Test Subsection 1,2 ',
|
||
'widget_placement': 'accordion',
|
||
}
|
||
},
|
||
{
|
||
'event_type': 'edx.ui.lms.outline.selected',
|
||
'name': 'edx.ui.lms.outline.selected',
|
||
'event': {
|
||
'target_name': 'Test Subsection 2,1 ',
|
||
'widget_placement': 'accordion',
|
||
}
|
||
},
|
||
],
|
||
outline_ui_events
|
||
)
|
||
|
||
def assert_navigation_state(
|
||
self, section_title, subsection_title, subsection_position, next_enabled, prev_enabled
|
||
):
|
||
"""
|
||
Verifies that the navigation state is as expected.
|
||
"""
|
||
self.assertTrue(self.course_nav.is_on_section(section_title, subsection_title))
|
||
self.assertEquals(self.courseware_page.sequential_position, subsection_position)
|
||
self.assertEquals(self.courseware_page.is_next_button_enabled, next_enabled)
|
||
self.assertEquals(self.courseware_page.is_previous_button_enabled, prev_enabled)
|
||
|
||
def test_tab_position(self):
|
||
# test that using the position in the url direct to correct tab in courseware
|
||
self.course_nav.go_to_section('Test Section 1', 'Test Subsection 1,1')
|
||
subsection_url = self.course_nav.active_subsection_url
|
||
url_part_list = subsection_url.split('/')
|
||
self.assertEqual(len(url_part_list), 9)
|
||
|
||
course_id = url_part_list[4]
|
||
chapter_id = url_part_list[-3]
|
||
subsection_id = url_part_list[-2]
|
||
problem1_page = CoursewareSequentialTabPage(
|
||
self.browser,
|
||
course_id=course_id,
|
||
chapter=chapter_id,
|
||
subsection=subsection_id,
|
||
position=1
|
||
).visit()
|
||
self.assertIn('problem 1 dummy body', problem1_page.get_selected_tab_content())
|
||
|
||
html1_page = CoursewareSequentialTabPage(
|
||
self.browser,
|
||
course_id=course_id,
|
||
chapter=chapter_id,
|
||
subsection=subsection_id,
|
||
position=2
|
||
).visit()
|
||
self.assertIn('html 1 dummy body', html1_page.get_selected_tab_content())
|
||
|
||
problem2_page = CoursewareSequentialTabPage(
|
||
self.browser,
|
||
course_id=course_id,
|
||
chapter=chapter_id,
|
||
subsection=subsection_id,
|
||
position=3
|
||
).visit()
|
||
self.assertIn('problem 2 dummy body', problem2_page.get_selected_tab_content())
|
||
|
||
html2_page = CoursewareSequentialTabPage(
|
||
self.browser,
|
||
course_id=course_id,
|
||
chapter=chapter_id,
|
||
subsection=subsection_id,
|
||
position=4
|
||
).visit()
|
||
self.assertIn('html 2 dummy body', html2_page.get_selected_tab_content())
|
||
|
||
@attr('a11y')
|
||
def test_courseware_a11y(self):
|
||
"""
|
||
Run accessibility audit for the problem type.
|
||
"""
|
||
self.course_nav.go_to_section('Test Section 1', 'Test Subsection 1,1')
|
||
# Set the scope to the sequence navigation
|
||
self.courseware_page.a11y_audit.config.set_scope(
|
||
include=['div.sequence-nav'])
|
||
self.courseware_page.a11y_audit.check_for_accessibility_errors()
|
||
|
||
|
||
class ProblemStateOnNavigationTest(UniqueCourseTest):
|
||
"""
|
||
Test courseware with problems in multiple verticals
|
||
"""
|
||
USERNAME = "STUDENT_TESTER"
|
||
EMAIL = "student101@example.com"
|
||
|
||
problem1_name = 'MULTIPLE CHOICE TEST PROBLEM 1'
|
||
problem2_name = 'MULTIPLE CHOICE TEST PROBLEM 2'
|
||
|
||
def setUp(self):
|
||
super(ProblemStateOnNavigationTest, self).setUp()
|
||
|
||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||
|
||
# Install a course with section, tabs and multiple choice problems.
|
||
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', 'Test Section 1').add_children(
|
||
XBlockFixtureDesc('sequential', 'Test Subsection 1,1').add_children(
|
||
self.create_multiple_choice_problem(self.problem1_name),
|
||
self.create_multiple_choice_problem(self.problem2_name),
|
||
),
|
||
),
|
||
).install()
|
||
|
||
# Auto-auth register for the course.
|
||
AutoAuthPage(
|
||
self.browser, username=self.USERNAME, email=self.EMAIL,
|
||
course_id=self.course_id, staff=False
|
||
).visit()
|
||
|
||
self.courseware_page.visit()
|
||
self.problem_page = ProblemPage(self.browser)
|
||
|
||
def create_multiple_choice_problem(self, 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 go_to_tab_and_assert_problem(self, position, problem_name):
|
||
"""
|
||
Go to sequential tab and assert that we are on problem whose name is given as a parameter.
|
||
Args:
|
||
position: Position of the sequential tab
|
||
problem_name: Name of the problem
|
||
"""
|
||
self.courseware_page.go_to_sequential_position(position)
|
||
self.problem_page.wait_for_element_presence(
|
||
self.problem_page.CSS_PROBLEM_HEADER,
|
||
'wait for problem header'
|
||
)
|
||
self.assertEqual(self.problem_page.problem_name, problem_name)
|
||
|
||
def test_perform_problem_check_and_navigate(self):
|
||
"""
|
||
Scenario:
|
||
I go to sequential position 1
|
||
Facing problem1, I select 'choice_1'
|
||
Then I click check button
|
||
Then I go to sequential position 2
|
||
Then I came back to sequential position 1 again
|
||
Facing problem1, I observe the problem1 content is not
|
||
outdated before and after sequence navigation
|
||
"""
|
||
# Go to sequential position 1 and assert that we are on problem 1.
|
||
self.go_to_tab_and_assert_problem(1, self.problem1_name)
|
||
|
||
# Update problem 1's content state by clicking check button.
|
||
self.problem_page.click_choice('choice_choice_1')
|
||
self.problem_page.click_check()
|
||
self.problem_page.wait_for_expected_status('label.choicegroup_incorrect', 'incorrect')
|
||
|
||
# Save problem 1's content state as we're about to switch units in the sequence.
|
||
problem1_content_before_switch = self.problem_page.problem_content
|
||
|
||
# Go to sequential position 2 and assert that we are on problem 2.
|
||
self.go_to_tab_and_assert_problem(2, self.problem2_name)
|
||
|
||
# Come back to our original unit in the sequence and assert that the content hasn't changed.
|
||
self.go_to_tab_and_assert_problem(1, self.problem1_name)
|
||
problem1_content_after_coming_back = self.problem_page.problem_content
|
||
self.assertEqual(problem1_content_before_switch, problem1_content_after_coming_back)
|
||
|
||
def test_perform_problem_save_and_navigate(self):
|
||
"""
|
||
Scenario:
|
||
I go to sequential position 1
|
||
Facing problem1, I select 'choice_1'
|
||
Then I click save button
|
||
Then I go to sequential position 2
|
||
Then I came back to sequential position 1 again
|
||
Facing problem1, I observe the problem1 content is not
|
||
outdated before and after sequence navigation
|
||
"""
|
||
# Go to sequential position 1 and assert that we are on problem 1.
|
||
self.go_to_tab_and_assert_problem(1, self.problem1_name)
|
||
|
||
# Update problem 1's content state by clicking save button.
|
||
self.problem_page.click_choice('choice_choice_1')
|
||
self.problem_page.click_save()
|
||
self.problem_page.wait_for_expected_status('div.capa_alert', 'saved')
|
||
|
||
# Save problem 1's content state as we're about to switch units in the sequence.
|
||
problem1_content_before_switch = self.problem_page.problem_content
|
||
|
||
# Go to sequential position 2 and assert that we are on problem 2.
|
||
self.go_to_tab_and_assert_problem(2, self.problem2_name)
|
||
|
||
# Come back to our original unit in the sequence and assert that the content hasn't changed.
|
||
self.go_to_tab_and_assert_problem(1, self.problem1_name)
|
||
problem1_content_after_coming_back = self.problem_page.problem_content
|
||
self.assertIn(problem1_content_after_coming_back, problem1_content_before_switch)
|
||
|
||
def test_perform_problem_reset_and_navigate(self):
|
||
"""
|
||
Scenario:
|
||
I go to sequential position 1
|
||
Facing problem1, I select 'choice_1'
|
||
Then perform the action – check and reset
|
||
Then I go to sequential position 2
|
||
Then I came back to sequential position 1 again
|
||
Facing problem1, I observe the problem1 content is not
|
||
outdated before and after sequence navigation
|
||
"""
|
||
# Go to sequential position 1 and assert that we are on problem 1.
|
||
self.go_to_tab_and_assert_problem(1, self.problem1_name)
|
||
|
||
# Update problem 1's content state – by performing reset operation.
|
||
self.problem_page.click_choice('choice_choice_1')
|
||
self.problem_page.click_check()
|
||
self.problem_page.wait_for_expected_status('label.choicegroup_incorrect', 'incorrect')
|
||
self.problem_page.click_reset()
|
||
self.problem_page.wait_for_expected_status('span.unanswered', 'unanswered')
|
||
|
||
# Save problem 1's content state as we're about to switch units in the sequence.
|
||
problem1_content_before_switch = self.problem_page.problem_content
|
||
|
||
# Go to sequential position 2 and assert that we are on problem 2.
|
||
self.go_to_tab_and_assert_problem(2, self.problem2_name)
|
||
|
||
# Come back to our original unit in the sequence and assert that the content hasn't changed.
|
||
self.go_to_tab_and_assert_problem(1, self.problem1_name)
|
||
problem1_content_after_coming_back = self.problem_page.problem_content
|
||
self.assertEqual(problem1_content_before_switch, problem1_content_after_coming_back)
|