class StaffViewTest(UniqueCourseTest): """ Tests that verify the staff view. """ USERNAME = "******" EMAIL = "*****@*****.**" def setUp(self): super(StaffViewTest, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) # Install a course with sections/problems, tabs, updates, and handouts self.course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) self.populate_course_fixture(self.course_fixture) self.course_fixture.install() # Auto-auth register for the course. # Do this as global staff so that you will see the Staff View AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True).visit() def _goto_staff_page(self): """ Open staff page with assertion """ self.courseware_page.visit() staff_page = StaffCoursewarePage(self.browser, self.course_id) self.assertEqual(staff_page.staff_view_mode, 'Staff') return staff_page
class StaffViewTest(UniqueCourseTest): """ Tests that verify the staff view. """ USERNAME = "******" EMAIL = "*****@*****.**" def setUp(self): super(StaffViewTest, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) # Install a course with sections/problems, tabs, updates, and handouts self.course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) self.populate_course_fixture(self.course_fixture) # pylint: disable=no-member self.course_fixture.install() # Auto-auth register for the course. # Do this as global staff so that you will see the Staff View AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True).visit() def _goto_staff_page(self): """ Open staff page with assertion """ self.courseware_page.visit() staff_page = StaffPage(self.browser, self.course_id) self.assertEqual(staff_page.staff_view_mode, 'Staff') return staff_page
class TooltipTest(UniqueCourseTest): """ Tests that tooltips are displayed """ def setUp(self): """ Initialize pages and install a course fixture. """ super(TooltipTest, self).setUp() self.course_home_page = CourseHomePage(self.browser, self.course_id) self.tab_nav = TabNavPage(self.browser) 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('static_tab', 'Test Static Tab'), XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc( 'sequential', 'Test Subsection').add_children( XBlockFixtureDesc( 'problem', 'Test Problem 1', data=load_data_str('multiple_choice.xml')), XBlockFixtureDesc( 'problem', 'Test Problem 2', data=load_data_str('formula_problem.xml')), XBlockFixtureDesc('html', 'Test HTML'), ))).install() self.courseware_page = CoursewarePage(self.browser, self.course_id) # Auto-auth register for the course AutoAuthPage(self.browser, course_id=self.course_id).visit() def test_tooltip(self): """ Verify that tooltips are displayed when you hover over the sequence nav bar. """ self.courseware_page.visit() self.courseware_page.verify_tooltips_displayed()
def test_course_updated_with_entrance_exam(self): """ Given that I visit an empty course before import I should not see a section named 'Section' or 'Entrance Exam' When I visit the import page And I upload a course that has an entrance exam section named 'Entrance Exam' And I visit the course outline page again The section named 'Entrance Exam' should now be available When I visit the LMS Course Home page Then I should see a section named 'Section' or 'Entrance Exam' When I switch the view mode to student view Then I should only see a section named 'Entrance Exam' When I visit the courseware page Then a message regarding the 'Entrance Exam' """ self.landing_page.visit() # Should not exist yet. self.assertRaises(IndexError, self.landing_page.section, "Section") self.assertRaises(IndexError, self.landing_page.section, "Entrance Exam") self.import_page.visit() self.import_page.wait_for_choose_file_click_handler() self.import_page.upload_tarball(self.tarball_name) self.import_page.wait_for_upload() self.landing_page.visit() # There should be two sections. 'Entrance Exam' and 'Section' on the landing page. self.landing_page.section("Entrance Exam") self.landing_page.section("Section") self.landing_page.view_live() course_home = CourseHomePage(self.browser, self.course_id) course_home.visit() self.assertEqual(course_home.outline.num_sections, 2) course_home.preview.set_staff_view_mode('Learner') self.assertEqual(course_home.outline.num_sections, 1) courseware = CoursewarePage(self.browser, self.course_id) courseware.visit() StaffCoursewarePage(self.browser, self.course_id).set_staff_view_mode('Learner') self.assertIn( "To access course materials, you must score", courseware.entrance_exam_message_selector.text[0] )
class TooltipTest(UniqueCourseTest): """ Tests that tooltips are displayed """ def setUp(self): """ Initialize pages and install a course fixture. """ super(TooltipTest, self).setUp() self.course_home_page = CourseHomePage(self.browser, self.course_id) self.tab_nav = TabNavPage(self.browser) 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('static_tab', 'Test Static Tab'), XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('problem', 'Test Problem 1', data=load_data_str('multiple_choice.xml')), XBlockFixtureDesc('problem', 'Test Problem 2', data=load_data_str('formula_problem.xml')), XBlockFixtureDesc('html', 'Test HTML'), ) ) ).install() self.courseware_page = CoursewarePage(self.browser, self.course_id) # Auto-auth register for the course AutoAuthPage(self.browser, course_id=self.course_id).visit() def test_tooltip(self): """ Verify that tooltips are displayed when you hover over the sequence nav bar. """ self.courseware_page.visit() self.courseware_page.verify_tooltips_displayed()
def test_course_updated_with_entrance_exam(self): """ Given that I visit an empty course before import I should not see a section named 'Section' or 'Entrance Exam' When I visit the import page And I upload a course that has an entrance exam section named 'Entrance Exam' And I visit the course outline page again The section named 'Entrance Exam' should now be available When I visit the LMS Course Home page Then I should see a section named 'Section' or 'Entrance Exam' When I switch the view mode to student view Then I should only see a section named 'Entrance Exam' When I visit the courseware page Then a message regarding the 'Entrance Exam' """ self.landing_page.visit() # Should not exist yet. self.assertRaises(IndexError, self.landing_page.section, "Section") self.assertRaises(IndexError, self.landing_page.section, "Entrance Exam") self.import_page.visit() self.import_page.upload_tarball(self.tarball_name) self.import_page.wait_for_upload() self.landing_page.visit() # There should be two sections. 'Entrance Exam' and 'Section' on the landing page. self.landing_page.section("Entrance Exam") self.landing_page.section("Section") self.landing_page.view_live() course_home = CourseHomePage(self.browser, self.course_id) course_home.visit() self.assertEqual(course_home.outline.num_sections, 2) course_home.preview.set_staff_view_mode('Learner') self.assertEqual(course_home.outline.num_sections, 1) courseware = CoursewarePage(self.browser, self.course_id) courseware.visit() StaffCoursewarePage(self.browser, self.course_id).set_staff_view_mode('Learner') self.assertIn( "To access course materials, you must score", courseware.entrance_exam_message_selector.text[0] )
def test_entrance_exam_section_2(self): """ Scenario: Any course that is enabled for an entrance exam, should have entrance exam chapter at course page. Given that I am on the course page When I view the course that has an entrance exam Then there should be an "Entrance Exam" chapter.' """ courseware_page = CoursewarePage(self.browser, self.course_id) entrance_exam_link_selector = '.accordion .course-navigation .chapter .group-heading' # visit course page and make sure there is not entrance exam chapter. courseware_page.visit() courseware_page.wait_for_page() self.assertFalse(element_has_text( page=courseware_page, css_selector=entrance_exam_link_selector, text='Entrance Exam' )) # Logout and login as a staff. LogoutPage(self.browser).visit() AutoAuthPage(self.browser, course_id=self.course_id, staff=True).visit() # visit course settings page and set/enabled entrance exam for that course. self.settings_page.visit() self.settings_page.require_entrance_exam() self.settings_page.save_changes() # Logout and login as a student. LogoutPage(self.browser).visit() AutoAuthPage(self.browser, course_id=self.course_id, staff=False).visit() # visit course info page and make sure there is an "Entrance Exam" section. courseware_page.visit() courseware_page.wait_for_page() self.assertTrue(element_has_text( page=courseware_page, css_selector=entrance_exam_link_selector, text='Entrance Exam' ))
def test_entrance_exam_section_2(self): """ Scenario: Any course that is enabled for an entrance exam, should have entrance exam chapter at course page. Given that I am on the course page When I view the course that has an entrance exam Then there should be an "Entrance Exam" chapter.' """ courseware_page = CoursewarePage(self.browser, self.course_id) entrance_exam_link_selector = '.accordion .course-navigation .chapter .group-heading' # visit course page and make sure there is not entrance exam chapter. courseware_page.visit() courseware_page.wait_for_page() self.assertFalse( element_has_text(page=courseware_page, css_selector=entrance_exam_link_selector, text='Entrance Exam')) # Logout and login as a staff. LogoutPage(self.browser).visit() AutoAuthPage(self.browser, course_id=self.course_id, staff=True).visit() # visit course settings page and set/enabled entrance exam for that course. self.settings_page.visit() self.settings_page.require_entrance_exam() self.settings_page.save_changes() # Logout and login as a student. LogoutPage(self.browser).visit() AutoAuthPage(self.browser, course_id=self.course_id, staff=False).visit() # visit course info page and make sure there is an "Entrance Exam" section. courseware_page.visit() courseware_page.wait_for_page() self.assertTrue( element_has_text(page=courseware_page, css_selector=entrance_exam_link_selector, text='Entrance Exam'))
class BookmarksTest(BookmarksTestMixin): """ Tests to verify bookmarks functionality. """ def setUp(self): """ Initialize test setup. """ super(BookmarksTest, self).setUp() self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.bookmarks_page = BookmarksPage(self.browser, self.course_id) self.course_nav = CourseNavPage(self.browser) # Get session to be used for bookmarking units self.session = requests.Session() params = {'username': self.USERNAME, 'email': self.EMAIL, 'course_id': self.course_id} response = self.session.get(BASE_URL + "/auto_auth", params=params) self.assertTrue(response.ok, "Failed to get session") def _test_setup(self, num_chapters=2): """ Setup test settings. Arguments: num_chapters: number of chapters to create in course """ self.create_course_fixture(num_chapters) # Auto-auth register for the course. LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() def _bookmark_unit(self, location): """ Bookmark a unit Arguments: location (str): unit location """ _headers = { 'Content-type': 'application/json', 'X-CSRFToken': self.session.cookies['csrftoken'], } params = {'course_id': self.course_id} data = json.dumps({'usage_id': location}) response = self.session.post( BASE_URL + '/api/bookmarks/v1/bookmarks/', data=data, params=params, headers=_headers ) self.assertTrue(response.ok, "Failed to bookmark unit") def _bookmark_units(self, num_units): """ Bookmark first `num_units` units Arguments: num_units(int): Number of units to bookmarks """ xblocks = self.course_fixture.get_nested_xblocks(category="vertical") for index in range(num_units): self._bookmark_unit(xblocks[index].locator) def _breadcrumb(self, num_units, modified_name=None): """ Creates breadcrumbs for the first `num_units` Arguments: num_units(int): Number of units for which we want to create breadcrumbs Returns: list of breadcrumbs """ breadcrumbs = [] for index in range(num_units): breadcrumbs.append( [ 'TestSection{}'.format(index), 'TestSubsection{}'.format(index), modified_name if modified_name else 'TestVertical{}'.format(index) ] ) return breadcrumbs def _delete_section(self, index): """ Delete a section at index `index` """ # Logout and login as staff LogoutPage(self.browser).visit() StudioAutoAuthPage( self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True ).visit() # Visit course outline page in studio. self.course_outline_page.visit() self.course_outline_page.wait_for_page() self.course_outline_page.section_at(index).delete() # Logout and login as a student. LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() # Visit courseware as a student. self.courseware_page.visit() self.courseware_page.wait_for_page() def _toggle_bookmark_and_verify(self, bookmark_icon_state, bookmark_button_state, bookmarked_count): """ Bookmark/Un-Bookmark a unit and then verify """ self.assertTrue(self.courseware_page.bookmark_button_visible) self.courseware_page.click_bookmark_unit_button() self.assertEqual(self.courseware_page.bookmark_icon_visible, bookmark_icon_state) self.assertEqual(self.courseware_page.bookmark_button_state, bookmark_button_state) self.bookmarks_page.click_bookmarks_button() self.assertEqual(self.bookmarks_page.count(), bookmarked_count) def _verify_pagination_info( self, bookmark_count_on_current_page, header_text, previous_button_enabled, next_button_enabled, current_page_number, total_pages ): """ Verify pagination info """ self.assertEqual(self.bookmarks_page.count(), bookmark_count_on_current_page) self.assertEqual(self.bookmarks_page.get_pagination_header_text(), header_text) self.assertEqual(self.bookmarks_page.is_previous_page_button_enabled(), previous_button_enabled) self.assertEqual(self.bookmarks_page.is_next_page_button_enabled(), next_button_enabled) self.assertEqual(self.bookmarks_page.get_current_page_number(), current_page_number) self.assertEqual(self.bookmarks_page.get_total_pages, total_pages) def _navigate_to_bookmarks_list(self): """ Navigates and verifies the bookmarks list page. """ self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.results_header_text(), 'My Bookmarks') def _verify_breadcrumbs(self, num_units, modified_name=None): """ Verifies the breadcrumb trail. """ bookmarked_breadcrumbs = self.bookmarks_page.breadcrumbs() # Verify bookmarked breadcrumbs. breadcrumbs = self._breadcrumb(num_units=num_units, modified_name=modified_name) breadcrumbs.reverse() self.assertEqual(bookmarked_breadcrumbs, breadcrumbs) def update_and_publish_block_display_name(self, modified_name): """ Update and publish the block/unit display name. """ self.course_outline_page.visit() self.course_outline_page.wait_for_page() self.course_outline_page.expand_all_subsections() section = self.course_outline_page.section_at(0) container_page = section.subsection_at(0).unit_at(0).go_to() self.course_fixture._update_xblock(container_page.locator, { # pylint: disable=protected-access "metadata": { "display_name": modified_name } }) container_page.visit() container_page.wait_for_page() self.assertEqual(container_page.name, modified_name) container_page.publish_action.click() def test_bookmark_button(self): """ Scenario: Bookmark unit button toggles correctly Given that I am a registered user And I visit my courseware page For first 2 units I visit the unit And I can see the Bookmark button When I click on Bookmark button Then unit should be bookmarked Then I click again on the bookmark button And I should see a unit un-bookmarked """ self._test_setup() for index in range(2): self.course_nav.go_to_section('TestSection{}'.format(index), 'TestSubsection{}'.format(index)) self._toggle_bookmark_and_verify(True, 'bookmarked', 1) self.bookmarks_page.click_bookmarks_button(False) self._toggle_bookmark_and_verify(False, '', 0) def test_empty_bookmarks_list(self): """ Scenario: An empty bookmarks list is shown if there are no bookmarked units. Given that I am a registered user And I visit my courseware page And I can see the Bookmarks button When I click on Bookmarks button Then I should see an empty bookmarks list And empty bookmarks list content is correct """ self._test_setup() self.assertTrue(self.bookmarks_page.bookmarks_button_visible()) self.bookmarks_page.click_bookmarks_button() self.assertEqual(self.bookmarks_page.results_header_text(), 'My Bookmarks') self.assertEqual(self.bookmarks_page.empty_header_text(), 'You have not bookmarked any courseware pages yet.') empty_list_text = ("Use bookmarks to help you easily return to courseware pages. To bookmark a page, " "select Bookmark in the upper right corner of that page. To see a list of all your " "bookmarks, select Bookmarks in the upper left corner of any courseware page.") self.assertEqual(self.bookmarks_page.empty_list_text(), empty_list_text) def test_bookmarks_list(self): """ Scenario: A bookmarks list is shown if there are bookmarked units. Given that I am a registered user And I visit my courseware page And I have bookmarked 2 units When I click on Bookmarks button Then I should see a bookmarked list with 2 bookmark links And breadcrumb trail is correct for a bookmark When I click on bookmarked link Then I can navigate to correct bookmarked unit """ self._test_setup() self._bookmark_units(2) self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=2) self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 1-2 out of 2 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1 ) # get usage ids for units xblocks = self.course_fixture.get_nested_xblocks(category="vertical") xblock_usage_ids = [xblock.locator for xblock in xblocks] # Verify link navigation for index in range(2): self.bookmarks_page.click_bookmarked_block(index) self.courseware_page.wait_for_page() self.assertIn(self.courseware_page.active_usage_id(), xblock_usage_ids) self.courseware_page.visit().wait_for_page() self.bookmarks_page.click_bookmarks_button() def test_bookmark_shows_updated_breadcrumb_after_publish(self): """ Scenario: A bookmark breadcrumb trail is updated after publishing the changed display name. Given that I am a registered user And I visit my courseware page And I can see bookmarked unit Then I visit unit page in studio Then I change unit display_name And I publish the changes Then I visit my courseware page And I visit bookmarks list page When I see the bookmark Then I can see the breadcrumb trail with updated display_name. """ self._test_setup(num_chapters=1) self._bookmark_units(num_units=1) self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=1) LogoutPage(self.browser).visit() LmsAutoAuthPage( self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True ).visit() modified_name = "Updated name" self.update_and_publish_block_display_name(modified_name) LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=1, modified_name=modified_name) def test_unreachable_bookmark(self): """ Scenario: We should get a HTTP 404 for an unreachable bookmark. Given that I am a registered user And I visit my courseware page And I have bookmarked 2 units Then I delete a bookmarked unit Then I click on Bookmarks button And I should see a bookmarked list When I click on deleted bookmark Then I should navigated to 404 page """ self._test_setup(num_chapters=1) self._bookmark_units(1) self._delete_section(0) self._navigate_to_bookmarks_list() self._verify_pagination_info( bookmark_count_on_current_page=1, header_text='Showing 1 out of 1 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1 ) self.bookmarks_page.click_bookmarked_block(0) self.assertTrue(is_404_page(self.browser)) def test_page_size_limit(self): """ Scenario: We can't get bookmarks more than default page size. Given that I am a registered user And I visit my courseware page And I have bookmarked all the 11 units available Then I click on Bookmarks button And I should see a bookmarked list And bookmark list contains 10 bookmarked items """ self._test_setup(11) self._bookmark_units(11) self._navigate_to_bookmarks_list() self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 11 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2 ) def test_pagination_with_single_page(self): """ Scenario: Bookmarks list pagination is working as expected for single page Given that I am a registered user And I visit my courseware page And I have bookmarked all the 2 units available Then I click on Bookmarks button And I should see a bookmarked list with 2 bookmarked items And I should see paging header and footer with correct data And previous and next buttons are disabled """ self._test_setup(num_chapters=2) self._bookmark_units(num_units=2) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 1-2 out of 2 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1 ) def test_next_page_button(self): """ Scenario: Next button is working as expected for bookmarks list pagination Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available Then I click on Bookmarks button And I should see a bookmarked list of 10 items And I should see paging header and footer with correct info Then I click on next page button in footer And I should be navigated to second page And I should see a bookmarked list with 2 items And I should see paging header and footer with correct info """ self._test_setup(num_chapters=12) self._bookmark_units(num_units=12) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 12 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2 ) self.bookmarks_page.press_next_page_button() self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 11-12 out of 12 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2 ) def test_previous_page_button(self): """ Scenario: Previous button is working as expected for bookmarks list pagination Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available And I click on Bookmarks button Then I click on next page button in footer And I should be navigated to second page And I should see a bookmarked list with 2 items And I should see paging header and footer with correct info Then I click on previous page button And I should be navigated to first page And I should see paging header and footer with correct info """ self._test_setup(num_chapters=12) self._bookmark_units(num_units=12) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.bookmarks_page.press_next_page_button() self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 11-12 out of 12 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2 ) self.bookmarks_page.press_previous_page_button() self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 12 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2 ) def test_pagination_with_valid_page_number(self): """ Scenario: Bookmarks list pagination works as expected for valid page number Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available Then I click on Bookmarks button And I should see a bookmarked list And I should see total page value is 2 Then I enter 2 in the page number input And I should be navigated to page 2 """ self._test_setup(num_chapters=11) self._bookmark_units(num_units=11) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.get_total_pages, 2) self.bookmarks_page.go_to_page(2) self._verify_pagination_info( bookmark_count_on_current_page=1, header_text='Showing 11-11 out of 11 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2 ) def test_pagination_with_invalid_page_number(self): """ Scenario: Bookmarks list pagination works as expected for invalid page number Given that I am a registered user And I visit my courseware page And I have bookmarked all the 11 units available Then I click on Bookmarks button And I should see a bookmarked list And I should see total page value is 2 Then I enter 3 in the page number input And I should stay at page 1 """ self._test_setup(num_chapters=11) self._bookmark_units(num_units=11) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.get_total_pages, 2) self.bookmarks_page.go_to_page(3) self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 11 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2 ) def test_bookmarked_unit_accessed_event(self): """ Scenario: Bookmark events are emitted with correct data when we access/visit a bookmarked unit. Given that I am a registered user And I visit my courseware page And I have bookmarked a unit When I click on bookmarked unit Then `edx.course.bookmark.accessed` event is emitted """ self._test_setup(num_chapters=1) self.reset_event_tracking() # create expected event data xblocks = self.course_fixture.get_nested_xblocks(category="vertical") event_data = [ { 'event': { 'bookmark_id': '{},{}'.format(self.USERNAME, xblocks[0].locator), 'component_type': xblocks[0].category, 'component_usage_id': xblocks[0].locator, } } ] self._bookmark_units(num_units=1) self.bookmarks_page.click_bookmarks_button() self._verify_pagination_info( bookmark_count_on_current_page=1, header_text='Showing 1 out of 1 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1 ) self.bookmarks_page.click_bookmarked_block(0) self.verify_event_data('edx.bookmark.accessed', event_data)
class DragAndDropXblockWithMixinsTest(UniqueCourseTest): """ Test Suite to verify various behaviors of DragAndDrop Xblock on the LMS. """ def setUp(self): super(DragAndDropXblockWithMixinsTest, self).setUp() self.username = "******".format(uuid=self.unique_id[0:8]) self.email = "{username}@example.com".format(username=self.username) self.password = "******" self.courseware_page = CoursewarePage(self.browser, self.course_id) # Install a course with a hierarchy and problems self.course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'], start_date=datetime.now() + timedelta(days=10) ) self.browser.set_window_size(1024, 1024) def setup_sequential(self, metadata): """ Setup a sequential with DnD problem, alongwith the metadata provided. This method will allow to customize the sequential, such as changing the due date for individual tests. """ problem = self.get_problem() sequential = self.get_sequential(metadata=metadata) self.course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( sequential.add_children(problem) ) ).install() # Auto-auth register for the course. AutoAuthPage( self.browser, username=self.username, email=self.email, password=self.password, course_id=self.course_id, staff=True ).visit() def format_date(self, date_value): """ Get the date in isoformat as this is required format to add date data in the sequential. """ return date_value.isoformat() def get_problem(self): """ Creating a DnD problem with assessment mode """ return XBlockFixtureDesc('drag-and-drop-v2', 'DnD', metadata={'mode': "assessment"}) def get_sequential(self, metadata=None): return XBlockFixtureDesc('sequential', 'Test Subsection', metadata=metadata) @ddt.data( (datetime.now(), True), (datetime.now() - timedelta(days=1), True), (datetime.now() + timedelta(days=1), False) ) @ddt.unpack def test_submit_button_status_with_due_date(self, due_date, is_button_disabled): """ Scenario: Test that DnD submit button will be enabled if section is not past due. Given I have a sequential in instructor-paced course And a DnD problem with assessment mode is present in the sequential When I visit the problem Then the submit button should be present And button should be disabled as some item needs to be on a zone When I drag an item to a zone Then submit button will be enabled if due date has not passed, else disabled """ problem_page = DragAndDropPage(self.browser) self.setup_sequential(metadata={'due': self.format_date(due_date)}) self.courseware_page.visit() self.assertTrue(problem_page.is_submit_button_present()) self.assertTrue(problem_page.is_submit_disabled()) problem_page.drag_item_to_zone(0, 'middle') self.assertEqual(is_button_disabled, problem_page.is_submit_disabled()) def test_submit_button_when_pacing_change_self_paced(self): """ Scenario: For a self-paced course, the submit button of DnD problems will be be enabled, regardless of the subsection due date. Given a DnD problem in a subsection with past due date And the course is instructor-paced Then the submit button will remain disabled after initial drag When the pacing is changed to self-paced Then the submit button is not disabled anymore """ problem_page = DragAndDropPage(self.browser) self.setup_sequential(metadata={'due': self.format_date(datetime.now())}) self.courseware_page.visit() problem_page.drag_item_to_zone(0, 'middle') self.assertTrue(problem_page.is_submit_disabled()) self.course_fixture.add_course_details({'self_paced': True}) self.course_fixture.configure_course() self.courseware_page.visit() self.assertFalse(problem_page.is_submit_disabled())
class GatingTest(UniqueCourseTest): """ Test gating feature in LMS. """ STAFF_USERNAME = "******" STAFF_EMAIL = "*****@*****.**" STUDENT_USERNAME = "******" STUDENT_EMAIL = "*****@*****.**" def setUp(self): super(GatingTest, self).setUp() self.logout_page = LogoutPage(self.browser) self.course_home_page = CourseHomePage(self.browser, self.course_id) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) xml = dedent(""" <problem> <p>What is height of eiffel tower without the antenna?.</p> <multiplechoiceresponse> <choicegroup label="What is height of eiffel tower without the antenna?" type="MultipleChoice"> <choice correct="false">324 meters<choicehint>Antenna is 24 meters high</choicehint></choice> <choice correct="true">300 meters</choice> <choice correct="false">224 meters</choice> <choice correct="false">400 meters</choice> </choicegroup> </multiplechoiceresponse> </problem> """) self.problem1 = XBlockFixtureDesc('problem', 'HEIGHT OF EIFFEL TOWER', data=xml) # Install a course with sections/problems course_fixture = CourseFixture(self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name']) course_fixture.add_advanced_settings( {"enable_subsection_gating": { "value": "true" }}) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section 1').add_children( XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children( self.problem1), XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children( XBlockFixtureDesc( 'problem', 'Test Problem 2')))).install() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ self.logout_page.visit() AutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit() def _setup_prereq(self): """ Make the first subsection a prerequisite """ # Login as staff self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) # Make the first subsection a prerequisite self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(0) self.studio_course_outline.select_advanced_tab( desired_item='gated_content') self.studio_course_outline.make_gating_prerequisite() def _setup_gated_subsection(self): """ Gate the second subsection on the first subsection """ # Login as staff self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) # Gate the second subsection based on the score achieved in the first subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(1) self.studio_course_outline.select_advanced_tab( desired_item='gated_content') self.studio_course_outline.add_prerequisite_to_subsection("80") def _fulfill_prerequisite(self): """ Fulfill the prerequisite needed to see gated content """ problem_page = ProblemPage(self.browser) self.assertEqual(problem_page.wait_for_page().problem_name, 'HEIGHT OF EIFFEL TOWER') problem_page.click_choice('choice_1') problem_page.click_submit() def test_subsection_gating_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 Gating And update those settings to gate a subsection """ self._setup_prereq() # Assert settings are displayed correctly for a prerequisite subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(0) self.studio_course_outline.select_advanced_tab( desired_item='gated_content') self.assertTrue(self.studio_course_outline. gating_prerequisite_checkbox_is_visible()) self.assertTrue(self.studio_course_outline. gating_prerequisite_checkbox_is_checked()) self.assertFalse(self.studio_course_outline. gating_prerequisites_dropdown_is_visible()) self.assertFalse(self.studio_course_outline. gating_prerequisite_min_score_is_visible()) self._setup_gated_subsection() # Assert settings are displayed correctly for a gated subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(1) self.studio_course_outline.select_advanced_tab( desired_item='gated_content') self.assertTrue(self.studio_course_outline. gating_prerequisite_checkbox_is_visible()) self.assertTrue(self.studio_course_outline. gating_prerequisites_dropdown_is_visible()) self.assertTrue(self.studio_course_outline. gating_prerequisite_min_score_is_visible()) def test_gated_subsection_in_lms_for_student(self): """ Given that I am a student When I visit the LMS Courseware Then I can see a gated subsection The gated subsection should have a lock icon and be in the format: "<Subsection Title> (Prerequisite Required)" When I fulfill the gating Prerequisite Then I can see the gated subsection Now the gated subsection should have an unlock icon and screen readers should read the section as: "<Subsection Title> Unlocked" """ self._setup_prereq() self._setup_gated_subsection() self._auto_auth(self.STUDENT_USERNAME, self.STUDENT_EMAIL, False) self.course_home_page.visit() self.assertEqual(self.course_home_page.outline.num_subsections, 2) # Fulfill prerequisite and verify that gated subsection is shown self.courseware_page.visit() self._fulfill_prerequisite() self.course_home_page.visit() self.assertEqual(self.course_home_page.outline.num_subsections, 2) def test_gated_subsection_in_lms_for_staff(self): """ Given that I am a staff member When I visit the LMS Courseware Then I can see all gated subsections Displayed along with notification banners Then if I masquerade as a student Then I can see a gated subsection The gated subsection should have a lock icon and be in the format: "<Subsection Title> (Prerequisite Required)" """ self._setup_prereq() self._setup_gated_subsection() self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.course_home_page.visit() self.assertEqual(self.course_home_page.preview.staff_view_mode, 'Staff') self.assertEqual(self.course_home_page.outline.num_subsections, 2) # Click on gated section and check for banner self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 2') self.courseware_page.wait_for_page() self.assertTrue(self.courseware_page.has_banner()) self.course_home_page.visit() self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 1') self.courseware_page.wait_for_page() self.course_home_page.visit() self.course_home_page.preview.set_staff_view_mode('Learner') self.course_home_page.wait_for_page() self.assertEqual(self.course_home_page.outline.num_subsections, 2) self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 1') self.courseware_page.wait_for_page() # banner displayed informing section is a prereq self.assertTrue(self.courseware_page.has_banner())
class VideoBaseTest(UniqueCourseTest): """ Base class for tests of the Video Player Sets up the course and provides helper functions for the Video tests. """ def setUp(self): """ Initialization of pages and course fixture for video tests """ super(VideoBaseTest, self).setUp() self.longMessage = True self.video = VideoPage(self.browser) self.tab_nav = TabNavPage(self.browser) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.course_info_page = CourseInfoPage(self.browser, self.course_id) self.auth_page = AutoAuthPage(self.browser, course_id=self.course_id) self.course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) self.metadata = None self.assets = [] self.contents_of_verticals = None self.youtube_configuration = {} self.user_info = {} # reset youtube stub server self.addCleanup(YouTubeStubConfig.reset) def navigate_to_video(self): """ Prepare the course and get to the video and render it """ self._install_course_fixture() self._navigate_to_courseware_video_and_render() def navigate_to_video_no_render(self): """ Prepare the course and get to the video unit however do not wait for it to render, because the has been an error. """ self._install_course_fixture() self._navigate_to_courseware_video_no_render() def _install_course_fixture(self): """ Install the course fixture that has been defined """ if self.assets: self.course_fixture.add_asset(self.assets) chapter_sequential = XBlockFixtureDesc('sequential', 'Test Section') chapter_sequential.add_children(*self._add_course_verticals()) chapter = XBlockFixtureDesc('chapter', 'Test Chapter').add_children(chapter_sequential) self.course_fixture.add_children(chapter) self.course_fixture.install() if len(self.youtube_configuration) > 0: YouTubeStubConfig.configure(self.youtube_configuration) def _add_course_verticals(self): """ Create XBlockFixtureDesc verticals :return: a list of XBlockFixtureDesc """ xblock_verticals = [] _contents_of_verticals = self.contents_of_verticals # Video tests require at least one vertical with a single video. if not _contents_of_verticals: _contents_of_verticals = [[{'display_name': 'Video', 'metadata': self.metadata}]] for vertical_index, vertical in enumerate(_contents_of_verticals): xblock_verticals.append(self._create_single_vertical(vertical, vertical_index)) return xblock_verticals def _create_single_vertical(self, vertical_contents, vertical_index): """ Create a single course vertical of type XBlockFixtureDesc with category `vertical`. A single course vertical can contain single or multiple video modules. :param vertical_contents: a list of items for the vertical to contain :param vertical_index: index for the vertical display name :return: XBlockFixtureDesc """ xblock_course_vertical = XBlockFixtureDesc('vertical', u'Test Vertical-{0}'.format(vertical_index)) for video in vertical_contents: xblock_course_vertical.add_children( XBlockFixtureDesc('video', video['display_name'], metadata=video.get('metadata'))) return xblock_course_vertical def _navigate_to_courseware_video(self): """ Register for the course and navigate to the video unit """ self.auth_page.visit() self.user_info = self.auth_page.user_info self.courseware_page.visit() def _navigate_to_courseware_video_and_render(self): """ Wait for the video player to render """ self._navigate_to_courseware_video() self.video.wait_for_video_player_render() def _navigate_to_courseware_video_no_render(self): """ Wait for the video Xmodule but not for rendering """ self._navigate_to_courseware_video() self.video.wait_for_video_class() def metadata_for_mode(self, player_mode, additional_data=None): """ Create a dictionary for video player configuration according to `player_mode` :param player_mode (str): Video player mode :param additional_data (dict): Optional additional metadata. :return: dict """ metadata = {} youtube_ids = { 'youtube_id_1_0': '', 'youtube_id_0_75': '', 'youtube_id_1_25': '', 'youtube_id_1_5': '', } if player_mode == 'html5': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HTML5_SOURCES }) if player_mode == 'youtube_html5': metadata.update({ 'html5_sources': HTML5_SOURCES, }) if player_mode == 'youtube_html5_unsupported_video': metadata.update({ 'html5_sources': HTML5_SOURCES_INCORRECT }) if player_mode == 'html5_unsupported_video': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HTML5_SOURCES_INCORRECT }) if player_mode == 'hls': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HLS_SOURCES, }) if player_mode == 'html5_and_hls': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HTML5_SOURCES + HLS_SOURCES, }) if additional_data: metadata.update(additional_data) return metadata def go_to_sequential_position(self, position): """ Navigate to sequential specified by `video_display_name` """ self.courseware_page.go_to_sequential_position(position) self.video.wait_for_video_player_render()
class BookmarksTest(BookmarksTestMixin): """ Tests to verify bookmarks functionality. """ def setUp(self): """ Initialize test setup. """ super(BookmarksTest, self).setUp() self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.bookmarks_page = BookmarksPage(self.browser, self.course_id) self.course_nav = CourseNavPage(self.browser) # Get session to be used for bookmarking units self.session = requests.Session() params = { 'username': self.USERNAME, 'email': self.EMAIL, 'course_id': self.course_id } response = self.session.get(BASE_URL + "/auto_auth", params=params) self.assertTrue(response.ok, "Failed to get session") def _test_setup(self, num_chapters=2): """ Setup test settings. Arguments: num_chapters: number of chapters to create in course """ self.create_course_fixture(num_chapters) # Auto-auth register for the course. LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() def _bookmark_unit(self, location): """ Bookmark a unit Arguments: location (str): unit location """ _headers = { 'Content-type': 'application/json', 'X-CSRFToken': self.session.cookies['csrftoken'], } params = {'course_id': self.course_id} data = json.dumps({'usage_id': location}) response = self.session.post(BASE_URL + '/api/bookmarks/v1/bookmarks/', data=data, params=params, headers=_headers) self.assertTrue(response.ok, "Failed to bookmark unit") def _bookmark_units(self, num_units): """ Bookmark first `num_units` units Arguments: num_units(int): Number of units to bookmarks """ xblocks = self.course_fixture.get_nested_xblocks(category="vertical") for index in range(num_units): self._bookmark_unit(xblocks[index].locator) def _breadcrumb(self, num_units, modified_name=None): """ Creates breadcrumbs for the first `num_units` Arguments: num_units(int): Number of units for which we want to create breadcrumbs Returns: list of breadcrumbs """ breadcrumbs = [] for index in range(num_units): breadcrumbs.append([ 'TestSection{}'.format(index), 'TestSubsection{}'.format(index), modified_name if modified_name else 'TestVertical{}'.format(index) ]) return breadcrumbs def _delete_section(self, index): """ Delete a section at index `index` """ # Logout and login as staff LogoutPage(self.browser).visit() StudioAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True).visit() # Visit course outline page in studio. self.course_outline_page.visit() self.course_outline_page.wait_for_page() self.course_outline_page.section_at(index).delete() # Logout and login as a student. LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() # Visit courseware as a student. self.courseware_page.visit() self.courseware_page.wait_for_page() def _toggle_bookmark_and_verify(self, bookmark_icon_state, bookmark_button_state, bookmarked_count): """ Bookmark/Un-Bookmark a unit and then verify """ self.assertTrue(self.courseware_page.bookmark_button_visible) self.courseware_page.click_bookmark_unit_button() self.assertEqual(self.courseware_page.bookmark_icon_visible, bookmark_icon_state) self.assertEqual(self.courseware_page.bookmark_button_state, bookmark_button_state) self.bookmarks_page.click_bookmarks_button() self.assertEqual(self.bookmarks_page.count(), bookmarked_count) def _verify_pagination_info(self, bookmark_count_on_current_page, header_text, previous_button_enabled, next_button_enabled, current_page_number, total_pages): """ Verify pagination info """ self.assertEqual(self.bookmarks_page.count(), bookmark_count_on_current_page) self.assertEqual(self.bookmarks_page.get_pagination_header_text(), header_text) self.assertEqual(self.bookmarks_page.is_previous_page_button_enabled(), previous_button_enabled) self.assertEqual(self.bookmarks_page.is_next_page_button_enabled(), next_button_enabled) self.assertEqual(self.bookmarks_page.get_current_page_number(), current_page_number) self.assertEqual(self.bookmarks_page.get_total_pages, total_pages) def _navigate_to_bookmarks_list(self): """ Navigates and verifies the bookmarks list page. """ self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.results_header_text(), 'My Bookmarks') def _verify_breadcrumbs(self, num_units, modified_name=None): """ Verifies the breadcrumb trail. """ bookmarked_breadcrumbs = self.bookmarks_page.breadcrumbs() # Verify bookmarked breadcrumbs. breadcrumbs = self._breadcrumb(num_units=num_units, modified_name=modified_name) breadcrumbs.reverse() self.assertEqual(bookmarked_breadcrumbs, breadcrumbs) def update_and_publish_block_display_name(self, modified_name): """ Update and publish the block/unit display name. """ self.course_outline_page.visit() self.course_outline_page.wait_for_page() self.course_outline_page.expand_all_subsections() section = self.course_outline_page.section_at(0) container_page = section.subsection_at(0).unit_at(0).go_to() self.course_fixture._update_xblock( container_page.locator, { # pylint: disable=protected-access "metadata": { "display_name": modified_name } }) container_page.visit() container_page.wait_for_page() self.assertEqual(container_page.name, modified_name) container_page.publish_action.click() def test_bookmark_button(self): """ Scenario: Bookmark unit button toggles correctly Given that I am a registered user And I visit my courseware page For first 2 units I visit the unit And I can see the Bookmark button When I click on Bookmark button Then unit should be bookmarked Then I click again on the bookmark button And I should see a unit un-bookmarked """ self._test_setup() for index in range(2): self.course_nav.go_to_section('TestSection{}'.format(index), 'TestSubsection{}'.format(index)) self._toggle_bookmark_and_verify(True, 'bookmarked', 1) self.bookmarks_page.click_bookmarks_button(False) self._toggle_bookmark_and_verify(False, '', 0) def test_empty_bookmarks_list(self): """ Scenario: An empty bookmarks list is shown if there are no bookmarked units. Given that I am a registered user And I visit my courseware page And I can see the Bookmarks button When I click on Bookmarks button Then I should see an empty bookmarks list And empty bookmarks list content is correct """ self._test_setup() self.assertTrue(self.bookmarks_page.bookmarks_button_visible()) self.bookmarks_page.click_bookmarks_button() self.assertEqual(self.bookmarks_page.results_header_text(), 'My Bookmarks') self.assertEqual(self.bookmarks_page.empty_header_text(), 'You have not bookmarked any courseware pages yet.') empty_list_text = ( "Use bookmarks to help you easily return to courseware pages. To bookmark a page, " "select Bookmark in the upper right corner of that page. To see a list of all your " "bookmarks, select Bookmarks in the upper left corner of any courseware page." ) self.assertEqual(self.bookmarks_page.empty_list_text(), empty_list_text) def test_bookmarks_list(self): """ Scenario: A bookmarks list is shown if there are bookmarked units. Given that I am a registered user And I visit my courseware page And I have bookmarked 2 units When I click on Bookmarks button Then I should see a bookmarked list with 2 bookmark links And breadcrumb trail is correct for a bookmark When I click on bookmarked link Then I can navigate to correct bookmarked unit """ self._test_setup() self._bookmark_units(2) self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=2) self._verify_pagination_info(bookmark_count_on_current_page=2, header_text='Showing 1-2 out of 2 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1) # get usage ids for units xblocks = self.course_fixture.get_nested_xblocks(category="vertical") xblock_usage_ids = [xblock.locator for xblock in xblocks] # Verify link navigation for index in range(2): self.bookmarks_page.click_bookmarked_block(index) self.courseware_page.wait_for_page() self.assertIn(self.courseware_page.active_usage_id(), xblock_usage_ids) self.courseware_page.visit().wait_for_page() self.bookmarks_page.click_bookmarks_button() def test_bookmark_shows_updated_breadcrumb_after_publish(self): """ Scenario: A bookmark breadcrumb trail is updated after publishing the changed display name. Given that I am a registered user And I visit my courseware page And I can see bookmarked unit Then I visit unit page in studio Then I change unit display_name And I publish the changes Then I visit my courseware page And I visit bookmarks list page When I see the bookmark Then I can see the breadcrumb trail with updated display_name. """ self._test_setup(num_chapters=1) self._bookmark_units(num_units=1) self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=1) LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True).visit() modified_name = "Updated name" self.update_and_publish_block_display_name(modified_name) LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=1, modified_name=modified_name) def test_unreachable_bookmark(self): """ Scenario: We should get a HTTP 404 for an unreachable bookmark. Given that I am a registered user And I visit my courseware page And I have bookmarked 2 units Then I delete a bookmarked unit Then I click on Bookmarks button And I should see a bookmarked list When I click on deleted bookmark Then I should navigated to 404 page """ self._test_setup(num_chapters=1) self._bookmark_units(1) self._delete_section(0) self._navigate_to_bookmarks_list() self._verify_pagination_info(bookmark_count_on_current_page=1, header_text='Showing 1 out of 1 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1) self.bookmarks_page.click_bookmarked_block(0) self.assertTrue(is_404_page(self.browser)) def test_page_size_limit(self): """ Scenario: We can't get bookmarks more than default page size. Given that I am a registered user And I visit my courseware page And I have bookmarked all the 11 units available Then I click on Bookmarks button And I should see a bookmarked list And bookmark list contains 10 bookmarked items """ self._test_setup(11) self._bookmark_units(11) self._navigate_to_bookmarks_list() self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 11 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2) def test_pagination_with_single_page(self): """ Scenario: Bookmarks list pagination is working as expected for single page Given that I am a registered user And I visit my courseware page And I have bookmarked all the 2 units available Then I click on Bookmarks button And I should see a bookmarked list with 2 bookmarked items And I should see paging header and footer with correct data And previous and next buttons are disabled """ self._test_setup(num_chapters=2) self._bookmark_units(num_units=2) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self._verify_pagination_info(bookmark_count_on_current_page=2, header_text='Showing 1-2 out of 2 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1) def test_next_page_button(self): """ Scenario: Next button is working as expected for bookmarks list pagination Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available Then I click on Bookmarks button And I should see a bookmarked list of 10 items And I should see paging header and footer with correct info Then I click on next page button in footer And I should be navigated to second page And I should see a bookmarked list with 2 items And I should see paging header and footer with correct info """ self._test_setup(num_chapters=12) self._bookmark_units(num_units=12) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 12 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2) self.bookmarks_page.press_next_page_button() self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 11-12 out of 12 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2) def test_previous_page_button(self): """ Scenario: Previous button is working as expected for bookmarks list pagination Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available And I click on Bookmarks button Then I click on next page button in footer And I should be navigated to second page And I should see a bookmarked list with 2 items And I should see paging header and footer with correct info Then I click on previous page button And I should be navigated to first page And I should see paging header and footer with correct info """ self._test_setup(num_chapters=12) self._bookmark_units(num_units=12) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.bookmarks_page.press_next_page_button() self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 11-12 out of 12 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2) self.bookmarks_page.press_previous_page_button() self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 12 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2) def test_pagination_with_valid_page_number(self): """ Scenario: Bookmarks list pagination works as expected for valid page number Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available Then I click on Bookmarks button And I should see a bookmarked list And I should see total page value is 2 Then I enter 2 in the page number input And I should be navigated to page 2 """ self._test_setup(num_chapters=11) self._bookmark_units(num_units=11) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.get_total_pages, 2) self.bookmarks_page.go_to_page(2) self._verify_pagination_info( bookmark_count_on_current_page=1, header_text='Showing 11-11 out of 11 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2) def test_pagination_with_invalid_page_number(self): """ Scenario: Bookmarks list pagination works as expected for invalid page number Given that I am a registered user And I visit my courseware page And I have bookmarked all the 11 units available Then I click on Bookmarks button And I should see a bookmarked list And I should see total page value is 2 Then I enter 3 in the page number input And I should stay at page 1 """ self._test_setup(num_chapters=11) self._bookmark_units(num_units=11) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.get_total_pages, 2) self.bookmarks_page.go_to_page(3) self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 11 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2) def test_bookmarked_unit_accessed_event(self): """ Scenario: Bookmark events are emitted with correct data when we access/visit a bookmarked unit. Given that I am a registered user And I visit my courseware page And I have bookmarked a unit When I click on bookmarked unit Then `edx.course.bookmark.accessed` event is emitted """ self._test_setup(num_chapters=1) self.reset_event_tracking() # create expected event data xblocks = self.course_fixture.get_nested_xblocks(category="vertical") event_data = [{ 'event': { 'bookmark_id': '{},{}'.format(self.USERNAME, xblocks[0].locator), 'component_type': xblocks[0].category, 'component_usage_id': xblocks[0].locator, } }] self._bookmark_units(num_units=1) self.bookmarks_page.click_bookmarks_button() self._verify_pagination_info(bookmark_count_on_current_page=1, header_text='Showing 1 out of 1 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1) self.bookmarks_page.click_bookmarked_block(0) self.verify_event_data('edx.bookmark.accessed', event_data)
class LibraryContentTestBase(UniqueCourseTest): """ Base class for library content block tests """ USERNAME = "******" EMAIL = "*****@*****.**" STAFF_USERNAME = "******" STAFF_EMAIL = "*****@*****.**" def populate_library_fixture(self, library_fixture): """ To be overwritten by subclassed tests. Used to install a library to run tests on. """ def setUp(self): """ Set up library, course and library content XBlock """ super(LibraryContentTestBase, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.library_fixture = LibraryFixture('test_org', self.unique_id, 'Test Library {}'.format(self.unique_id)) self.populate_library_fixture(self.library_fixture) self.library_fixture.install() self.library_info = self.library_fixture.library_info self.library_key = self.library_fixture.library_key # Install a course with library content xblock self.course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) library_content_metadata = { 'source_library_id': unicode(self.library_key), 'mode': 'random', 'max_count': 1, } self.lib_block = XBlockFixtureDesc('library_content', "Library Content", metadata=library_content_metadata) self.course_fixture.add_children( XBlockFixtureDesc('chapter', SECTION_NAME).add_children( XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children( XBlockFixtureDesc('vertical', UNIT_NAME).add_children( self.lib_block ) ) ) ) self.course_fixture.install() def _change_library_content_settings(self, count=1, capa_type=None): """ Performs library block refresh in Studio, configuring it to show {count} children """ unit_page = self._go_to_unit_page(True) library_container_block = StudioLibraryContainerXBlockWrapper.from_xblock_wrapper(unit_page.xblocks[1]) library_container_block.edit() editor = StudioLibraryContentEditor(self.browser, library_container_block.locator) editor.count = count if capa_type is not None: editor.capa_type = capa_type editor.save() self._go_to_unit_page(change_login=False) unit_page.wait_for_page() unit_page.publish_action.click() unit_page.wait_for_ajax() self.assertIn("Published and Live", unit_page.publish_title) @property def library_xblocks_texts(self): """ Gets texts of all xblocks in library """ return frozenset(child.data for child in self.library_fixture.children) def _go_to_unit_page(self, change_login=True): """ Open unit page in Studio """ if change_login: LogoutPage(self.browser).visit() self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.studio_course_outline.visit() subsection = self.studio_course_outline.section(SECTION_NAME).subsection(SUBSECTION_NAME) return subsection.expand_subsection().unit(UNIT_NAME).go_to() def _goto_library_block_page(self, block_id=None): """ Open library page in LMS """ self.courseware_page.visit() paragraphs = self.courseware_page.q(css='.course-content p').results if not paragraphs: course_home_page = CourseHomePage(self.browser, self.course_id) course_home_page.visit() course_home_page.outline.go_to_section_by_index(0, 0) block_id = block_id if block_id is not None else self.lib_block.locator #pylint: disable=attribute-defined-outside-init self.library_content_page = LibraryContentXBlockWrapper(self.browser, block_id) self.library_content_page.wait_for_page() 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()
class CoursewareTest(UniqueCourseTest): """ Test courseware. """ USERNAME = "******" EMAIL = "*****@*****.**" 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", "*****@*****.**", 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)
class ProctoredExamTest(UniqueCourseTest): """ Test courseware. """ USERNAME = "******" EMAIL = "*****@*****.**" 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", "*****@*****.**", 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", "*****@*****.**", 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) def _setup_and_take_timed_exam(self, hide_after_due=False): """ Helper to perform the common action "set up a timed exam as staff, then take it as student" """ LogoutPage(self.browser).visit() self._auto_auth("STAFF_TESTER", "*****@*****.**", 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() @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 """ self._setup_and_take_timed_exam(hide_after_due) LogoutPage(self.browser).visit() self._auto_auth("STAFF_TESTER", "*****@*****.**", 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_masquerade_visibility_override(self): """ Given that a timed exam problem exists in the course And a student has taken that exam And that exam is hidden to the student And I am a staff user masquerading as the student Then I should be able to see the exam content """ self._setup_and_take_timed_exam() LogoutPage(self.browser).visit() self._auto_auth("STAFF_TESTER", "*****@*****.**", True) self.courseware_page.visit() staff_page = StaffPage(self.browser, self.course_id) self.assertEqual(staff_page.staff_view_mode, 'Staff') staff_page.set_staff_view_mode_specific_student(self.USERNAME) self.assertFalse(self.courseware_page.has_submitted_exam_message()) 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 and review rules fields have proper visibility None: False, False Timed: True, False Proctored: True, True Practice: True, False """ LogoutPage(self.browser).visit() self._auto_auth("STAFF_TESTER", "*****@*****.**", 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.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.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.course_outline.select_practice_exam() self.assertTrue(self.course_outline.time_allotted_field_visible()) self.assertFalse(self.course_outline.exam_review_rules_field_visible())
class ProblemStateOnNavigationTest(UniqueCourseTest): """ Test courseware with problems in multiple verticals """ USERNAME = "******" EMAIL = "*****@*****.**" 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)
class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin): """ Test courseware with multiple verticals """ USERNAME = "******" EMAIL = "*****@*****.**" 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_outline_selected_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_selected_events = lambda event: event.get('name', '') == 'edx.ui.lms.outline.selected' selected_events = self.wait_for_events(event_filter=filter_selected_events, 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', } }, ], selected_events ) def test_link_clicked_events(self): """ Given that I am a user in the courseware When I navigate via the left-hand nav Then a link clicked event is logged """ 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') filter_link_clicked = lambda event: event.get('name', '') == 'edx.ui.lms.link_clicked' link_clicked_events = self.wait_for_events(event_filter=filter_link_clicked, timeout=2) self.assertEqual(len(link_clicked_events), 2) 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 CoursewareTest(UniqueCourseTest): """ Test courseware. """ USERNAME = "******" EMAIL = "*****@*****.**" 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", "*****@*****.**", 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)
class ProctoredExamTest(UniqueCourseTest): """ Test courseware. """ USERNAME = "******" EMAIL = "*****@*****.**" 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", "*****@*****.**", 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", "*****@*****.**", 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) def _setup_and_take_timed_exam(self, hide_after_due=False): """ Helper to perform the common action "set up a timed exam as staff, then take it as student" """ LogoutPage(self.browser).visit() self._auto_auth("STAFF_TESTER", "*****@*****.**", 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() @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 """ self._setup_and_take_timed_exam(hide_after_due) LogoutPage(self.browser).visit() self._auto_auth("STAFF_TESTER", "*****@*****.**", 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_masquerade_visibility_override(self): """ Given that a timed exam problem exists in the course And a student has taken that exam And that exam is hidden to the student And I am a staff user masquerading as the student Then I should be able to see the exam content """ self._setup_and_take_timed_exam() LogoutPage(self.browser).visit() self._auto_auth("STAFF_TESTER", "*****@*****.**", True) self.courseware_page.visit() staff_page = StaffPage(self.browser, self.course_id) self.assertEqual(staff_page.staff_view_mode, 'Staff') staff_page.set_staff_view_mode_specific_student(self.USERNAME) self.assertFalse(self.courseware_page.has_submitted_exam_message()) 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 and review rules fields have proper visibility None: False, False Timed: True, False Proctored: True, True Practice: True, False """ LogoutPage(self.browser).visit() self._auto_auth("STAFF_TESTER", "*****@*****.**", 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.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.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.course_outline.select_practice_exam() self.assertTrue(self.course_outline.time_allotted_field_visible()) self.assertFalse(self.course_outline.exam_review_rules_field_visible())
class VideoBaseTest(UniqueCourseTest): """ Base class for tests of the Video Player Sets up the course and provides helper functions for the Video tests. """ def setUp(self): """ Initialization of pages and course fixture for video tests """ super(VideoBaseTest, self).setUp() self.longMessage = True self.video = VideoPage(self.browser) self.tab_nav = TabNavPage(self.browser) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.course_info_page = CourseInfoPage(self.browser, self.course_id) self.auth_page = AutoAuthPage(self.browser, course_id=self.course_id) self.course_fixture = CourseFixture(self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name']) self.metadata = None self.assets = [] self.contents_of_verticals = None self.youtube_configuration = {} self.user_info = {} # reset youtube stub server self.addCleanup(YouTubeStubConfig.reset) def navigate_to_video(self): """ Prepare the course and get to the video and render it """ self._install_course_fixture() self._navigate_to_courseware_video_and_render() def navigate_to_video_no_render(self): """ Prepare the course and get to the video unit however do not wait for it to render, because the has been an error. """ self._install_course_fixture() self._navigate_to_courseware_video_no_render() def _install_course_fixture(self): """ Install the course fixture that has been defined """ if self.assets: self.course_fixture.add_asset(self.assets) chapter_sequential = XBlockFixtureDesc('sequential', 'Test Section') chapter_sequential.add_children(*self._add_course_verticals()) chapter = XBlockFixtureDesc( 'chapter', 'Test Chapter').add_children(chapter_sequential) self.course_fixture.add_children(chapter) self.course_fixture.install() if len(self.youtube_configuration) > 0: YouTubeStubConfig.configure(self.youtube_configuration) def _add_course_verticals(self): """ Create XBlockFixtureDesc verticals :return: a list of XBlockFixtureDesc """ xblock_verticals = [] _contents_of_verticals = self.contents_of_verticals # Video tests require at least one vertical with a single video. if not _contents_of_verticals: _contents_of_verticals = [[{ 'display_name': 'Video', 'metadata': self.metadata }]] for vertical_index, vertical in enumerate(_contents_of_verticals): xblock_verticals.append( self._create_single_vertical(vertical, vertical_index)) return xblock_verticals def _create_single_vertical(self, vertical_contents, vertical_index): """ Create a single course vertical of type XBlockFixtureDesc with category `vertical`. A single course vertical can contain single or multiple video modules. :param vertical_contents: a list of items for the vertical to contain :param vertical_index: index for the vertical display name :return: XBlockFixtureDesc """ xblock_course_vertical = XBlockFixtureDesc( 'vertical', u'Test Vertical-{0}'.format(vertical_index)) for video in vertical_contents: xblock_course_vertical.add_children( XBlockFixtureDesc('video', video['display_name'], metadata=video.get('metadata'))) return xblock_course_vertical def _navigate_to_courseware_video(self): """ Register for the course and navigate to the video unit """ self.auth_page.visit() self.user_info = self.auth_page.user_info self.courseware_page.visit() def _navigate_to_courseware_video_and_render(self): """ Wait for the video player to render """ self._navigate_to_courseware_video() self.video.wait_for_video_player_render() def _navigate_to_courseware_video_no_render(self): """ Wait for the video Xmodule but not for rendering """ self._navigate_to_courseware_video() self.video.wait_for_video_class() def metadata_for_mode(self, player_mode, additional_data=None): """ Create a dictionary for video player configuration according to `player_mode` :param player_mode (str): Video player mode :param additional_data (dict): Optional additional metadata. :return: dict """ metadata = {} youtube_ids = { 'youtube_id_1_0': '', 'youtube_id_0_75': '', 'youtube_id_1_25': '', 'youtube_id_1_5': '', } if player_mode == 'html5': metadata.update(youtube_ids) metadata.update({'html5_sources': HTML5_SOURCES}) if player_mode == 'youtube_html5': metadata.update({ 'html5_sources': HTML5_SOURCES, }) if player_mode == 'youtube_html5_unsupported_video': metadata.update({'html5_sources': HTML5_SOURCES_INCORRECT}) if player_mode == 'html5_unsupported_video': metadata.update(youtube_ids) metadata.update({'html5_sources': HTML5_SOURCES_INCORRECT}) if player_mode == 'hls': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HLS_SOURCES, }) if player_mode == 'html5_and_hls': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HTML5_SOURCES + HLS_SOURCES, }) if additional_data: metadata.update(additional_data) return metadata def go_to_sequential_position(self, position): """ Navigate to sequential specified by `video_display_name` """ self.courseware_page.go_to_sequential_position(position) self.video.wait_for_video_player_render()
class BookmarksTestMixin(EventsTestMixin, UniqueCourseTest): """ Mixin with helper methods for testing Bookmarks. """ USERNAME = "******" EMAIL = "*****@*****.**" def setUp(self): super(BookmarksTestMixin, self).setUp() self.studio_course_outline_page = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.course_home_page = CourseHomePage(self.browser, self.course_id) self.bookmarks_page = BookmarksPage(self.browser, self.course_id) # Get session to be used for bookmarking units self.session = requests.Session() params = { 'username': self.USERNAME, 'email': self.EMAIL, 'course_id': self.course_id } response = self.session.get(BASE_URL + "/auto_auth", params=params) self.assertTrue(response.ok, "Failed to get session") def setup_test(self, num_chapters=2): """ Setup test settings. Arguments: num_chapters: number of chapters to create in course """ self.create_course_fixture(num_chapters) # Auto-auth register for the course. AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() def create_course_fixture(self, num_chapters): """ Create course fixture Arguments: num_chapters: number of chapters to create """ self.course_fixture = CourseFixture( # pylint: disable=attribute-defined-outside-init self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name']) xblocks = [] for index in range(num_chapters): xblocks += [ XBlockFixtureDesc( 'chapter', 'TestSection{}'.format(index)).add_children( XBlockFixtureDesc( 'sequential', 'TestSubsection{}'.format(index)).add_children( XBlockFixtureDesc( 'vertical', 'TestVertical{}'.format(index)))) ] self.course_fixture.add_children(*xblocks).install() def verify_event_data(self, event_type, event_data): """ Verify emitted event data. Arguments: event_type: expected event type event_data: expected event data """ actual_events = self.wait_for_events( event_filter={'event_type': event_type}, number_of_matches=1) self.assert_events_match(event_data, actual_events) def _bookmark_unit(self, location): """ Bookmark a unit Arguments: location (str): unit location """ _headers = { 'Content-type': 'application/json', 'X-CSRFToken': self.session.cookies['csrftoken'], } params = {'course_id': self.course_id} data = json.dumps({'usage_id': location}) response = self.session.post(BASE_URL + '/api/bookmarks/v1/bookmarks/', data=data, params=params, headers=_headers) self.assertTrue(response.ok, "Failed to bookmark unit") def bookmark_units(self, num_units): """ Bookmark first `num_units` units Arguments: num_units(int): Number of units to bookmarks """ xblocks = self.course_fixture.get_nested_xblocks(category="vertical") for index in range(num_units): self._bookmark_unit(xblocks[index].locator)
class AnnotatableProblemTest(UniqueCourseTest): """ Tests for annotation components. """ USERNAME = "******" EMAIL = "*****@*****.**" DATA_TEMPLATE = dedent("""\ <annotatable> <instructions>Instruction text</instructions> <p>{}</p> </annotatable> """) ANNOTATION_TEMPLATE = dedent("""\ Before {0}. <annotation title="region {0}" body="Comment {0}" highlight="yellow" problem="{0}"> Region Contents {0} </annotation> After {0}. """) PROBLEM_TEMPLATE = dedent("""\ <problem max_attempts="1" weight=""> <annotationresponse> <annotationinput> <title>Question {number}</title> <text>Region Contents {number}</text> <comment>What number is this region?</comment> <comment_prompt>Type your response below:</comment_prompt> <tag_prompt>What number is this region?</tag_prompt> <options> {options} </options> </annotationinput> </annotationresponse> <solution> This problem is checking region {number} </solution> </problem> """) OPTION_TEMPLATE = """<option choice="{correctness}">{number}</option>""" def setUp(self): super(AnnotatableProblemTest, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) # Install a course with two annotations and two annotations problems. course_fix = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) self.annotation_count = 2 course_fix.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Annotation Vertical').add_children( XBlockFixtureDesc('annotatable', 'Test Annotation Module', data=self.DATA_TEMPLATE.format("\n".join( self.ANNOTATION_TEMPLATE.format(i) for i in xrange(self.annotation_count) ))), XBlockFixtureDesc('problem', 'Test Annotation Problem 0', data=self.PROBLEM_TEMPLATE.format(number=0, options="\n".join( self.OPTION_TEMPLATE.format( number=k, correctness=_correctness(k, 0)) for k in xrange(self.annotation_count) ))), XBlockFixtureDesc('problem', 'Test Annotation Problem 1', data=self.PROBLEM_TEMPLATE.format(number=1, options="\n".join( self.OPTION_TEMPLATE.format( number=k, correctness=_correctness(k, 1)) for k in xrange(self.annotation_count) ))) ) ) ) ).install() # Auto-auth register for the course. AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=False).visit() def _goto_annotation_component_page(self): """ Open annotation component page with assertion. """ self.courseware_page.visit() annotation_component_page = AnnotationComponentPage(self.browser) self.assertEqual( annotation_component_page.component_name, 'Test Annotation Module'.format() ) return annotation_component_page def test_annotation_component(self): """ Test annotation components links to annotation problems. """ annotation_component_page = self._goto_annotation_component_page() # This will avoid scrolling related problems on different browsers and instead directly jump on the problem disable_animations(annotation_component_page) for i in xrange(self.annotation_count): annotation_component_page.click_reply_annotation(i) self.assertTrue(annotation_component_page.check_scroll_to_problem()) annotation_component_page.answer_problem() self.assertTrue(annotation_component_page.check_feedback()) annotation_component_page.click_return_to_annotation() self.assertTrue(annotation_component_page.check_scroll_to_annotation())
class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin): """ Test courseware with multiple verticals """ USERNAME = "******" EMAIL = "*****@*****.**" 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_outline_selected_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_selected_events = lambda event: event.get( 'name', '') == 'edx.ui.lms.outline.selected' selected_events = self.wait_for_events( event_filter=filter_selected_events, 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', } }, ], selected_events) def test_link_clicked_events(self): """ Given that I am a user in the courseware When I navigate via the left-hand nav Then a link clicked event is logged """ 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') filter_link_clicked = lambda event: event.get( 'name', '') == 'edx.ui.lms.link_clicked' link_clicked_events = self.wait_for_events( event_filter=filter_link_clicked, timeout=2) self.assertEqual(len(link_clicked_events), 2) 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 ConditionalTest(UniqueCourseTest): """ Test the conditional module in the lms. """ shard = 23 def setUp(self): super(ConditionalTest, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) AutoAuthPage( self.browser, course_id=self.course_id, staff=False ).visit() def install_course_fixture(self, block_type='problem'): """ Install a course fixture """ course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'], ) vertical = XBlockFixtureDesc('vertical', 'Test Unit') # populate the course fixture with the right conditional modules course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( vertical ) ) ) course_fixture.install() # Construct conditional block source_block = None conditional_attr = None conditional_value = None if block_type == 'problem': problem_factory = StringResponseXMLFactory() problem_xml = problem_factory.build_xml( question_text='The answer is "correct string"', case_sensitive=False, answer='correct string', ), problem = XBlockFixtureDesc('problem', 'Test Problem', data=problem_xml[0]) source_block = problem conditional_attr = 'attempted' conditional_value = 'True' elif block_type == 'poll': poll = XBlockFixtureDesc( 'poll_question', 'Conditional Poll', question='Is this a good poll?', answers=[ {'id': 'yes', 'text': POLL_ANSWER}, {'id': 'no', 'text': 'Of course not!'} ], ) conditional_attr = 'poll_answer' conditional_value = 'yes' source_block = poll else: raise NotImplementedError() course_fixture.create_xblock(vertical.locator, source_block) # create conditional conditional = XBlockFixtureDesc( 'conditional', 'Test Conditional', sources_list=[source_block.locator], conditional_attr=conditional_attr, conditional_value=conditional_value ) result_block = XBlockFixtureDesc( 'html', 'Conditional Contents', data='<html><div class="hidden-contents">Hidden Contents</p></html>' ) course_fixture.create_xblock(vertical.locator, conditional) course_fixture.create_xblock(conditional.locator, result_block) def test_conditional_hides_content(self): self.install_course_fixture() self.courseware_page.visit() conditional_page = ConditionalPage(self.browser) self.assertFalse(conditional_page.is_content_visible()) def test_conditional_displays_content(self): self.install_course_fixture() self.courseware_page.visit() # Answer the problem problem_page = ProblemPage(self.browser) problem_page.fill_answer('correct string') problem_page.click_submit() # The conditional does not update on its own, so we need to reload the page. self.courseware_page.visit() # Verify that we can see the content. conditional_page = ConditionalPage(self.browser) self.assertTrue(conditional_page.is_content_visible()) def test_conditional_handles_polls(self): self.install_course_fixture(block_type='poll') self.courseware_page.visit() # Fill in the conditional page poll conditional_page = ConditionalPage(self.browser) conditional_page.fill_in_poll() # The conditional does not update on its own, so we need to reload the page. self.courseware_page.visit() self.assertTrue(conditional_page.is_content_visible())
class ContentLicenseTest(StudioCourseTest): """ Tests for course-level licensing (that is, setting the license, for an entire course's content, to All Rights Reserved or Creative Commons) """ def setUp(self): # pylint: disable=arguments-differ super(ContentLicenseTest, self).setUp() self.outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.settings_page = SettingsPage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.lms_courseware = CoursewarePage( self.browser, self.course_id, ) self.settings_page.visit() def test_empty_license(self): """ When I visit the Studio settings page, I see that the course license is "All Rights Reserved" by default. Then I visit the LMS courseware page, and I see that the default course license is displayed. """ self.assertEqual(self.settings_page.course_license, "All Rights Reserved") self.lms_courseware.visit() self.assertEqual(self.lms_courseware.course_license, "© All Rights Reserved") def test_arr_license(self): """ When I visit the Studio settings page, and I set the course license to "All Rights Reserved", and I refresh the page, I see that the course license is "All Rights Reserved". Then I visit the LMS courseware page, and I see that the course license is "All Rights Reserved". """ self.settings_page.course_license = "All Rights Reserved" self.settings_page.save_changes() self.settings_page.refresh_and_wait_for_load() self.assertEqual(self.settings_page.course_license, "All Rights Reserved") self.lms_courseware.visit() self.assertEqual(self.lms_courseware.course_license, "© All Rights Reserved") def test_cc_license(self): """ When I visit the Studio settings page, and I set the course license to "Creative Commons", and I refresh the page, I see that the course license is "Creative Commons". Then I visit the LMS courseware page, and I see that the course license is "Some Rights Reserved". """ self.settings_page.course_license = "Creative Commons" self.settings_page.save_changes() self.settings_page.refresh_and_wait_for_load() self.assertEqual(self.settings_page.course_license, "Creative Commons") self.lms_courseware.visit() # The course_license text will include a bunch of screen reader text to explain # the selected options self.assertIn("Some Rights Reserved", self.lms_courseware.course_license)
class ProblemStateOnNavigationTest(UniqueCourseTest): """ Test courseware with problems in multiple verticals """ USERNAME = "******" EMAIL = "*****@*****.**" 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)
class BookmarksTestMixin(EventsTestMixin, UniqueCourseTest): """ Mixin with helper methods for testing Bookmarks. """ USERNAME = "******" EMAIL = "*****@*****.**" def setUp(self): super(BookmarksTestMixin, self).setUp() self.studio_course_outline_page = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.course_home_page = CourseHomePage(self.browser, self.course_id) self.bookmarks_page = BookmarksPage(self.browser, self.course_id) # Get session to be used for bookmarking units self.session = requests.Session() params = {'username': self.USERNAME, 'email': self.EMAIL, 'course_id': self.course_id} response = self.session.get(BASE_URL + "/auto_auth", params=params) self.assertTrue(response.ok, "Failed to get session") def setup_test(self, num_chapters=2): """ Setup test settings. Arguments: num_chapters: number of chapters to create in course """ self.create_course_fixture(num_chapters) # Auto-auth register for the course. AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() def create_course_fixture(self, num_chapters): """ Create course fixture Arguments: num_chapters: number of chapters to create """ self.course_fixture = CourseFixture( # pylint: disable=attribute-defined-outside-init self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) xblocks = [] for index in range(num_chapters): xblocks += [ XBlockFixtureDesc('chapter', 'TestSection{}'.format(index)).add_children( XBlockFixtureDesc('sequential', 'TestSubsection{}'.format(index)).add_children( XBlockFixtureDesc('vertical', 'TestVertical{}'.format(index)) ) ) ] self.course_fixture.add_children(*xblocks).install() def verify_event_data(self, event_type, event_data): """ Verify emitted event data. Arguments: event_type: expected event type event_data: expected event data """ actual_events = self.wait_for_events(event_filter={'event_type': event_type}, number_of_matches=1) self.assert_events_match(event_data, actual_events) def _bookmark_unit(self, location): """ Bookmark a unit Arguments: location (str): unit location """ _headers = { 'Content-type': 'application/json', 'X-CSRFToken': self.session.cookies['csrftoken'], } params = {'course_id': self.course_id} data = json.dumps({'usage_id': location}) response = self.session.post( BASE_URL + '/api/bookmarks/v1/bookmarks/', data=data, params=params, headers=_headers ) self.assertTrue(response.ok, "Failed to bookmark unit") def bookmark_units(self, num_units): """ Bookmark first `num_units` units Arguments: num_units(int): Number of units to bookmarks """ xblocks = self.course_fixture.get_nested_xblocks(category="vertical") for index in range(num_units): self._bookmark_unit(xblocks[index].locator)
class GatingTest(UniqueCourseTest): """ Test gating feature in LMS. """ STAFF_USERNAME = "******" STAFF_EMAIL = "*****@*****.**" STUDENT_USERNAME = "******" STUDENT_EMAIL = "*****@*****.**" def setUp(self): super(GatingTest, self).setUp() self.logout_page = LogoutPage(self.browser) self.course_home_page = CourseHomePage(self.browser, self.course_id) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) xml = dedent(""" <problem> <p>What is height of eiffel tower without the antenna?.</p> <multiplechoiceresponse> <choicegroup label="What is height of eiffel tower without the antenna?" type="MultipleChoice"> <choice correct="false">324 meters<choicehint>Antenna is 24 meters high</choicehint></choice> <choice correct="true">300 meters</choice> <choice correct="false">224 meters</choice> <choice correct="false">400 meters</choice> </choicegroup> </multiplechoiceresponse> </problem> """) self.problem1 = XBlockFixtureDesc('problem', 'HEIGHT OF EIFFEL TOWER', data=xml) # Install a course with sections/problems course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) course_fixture.add_advanced_settings({ "enable_subsection_gating": {"value": "true"} }) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section 1').add_children( XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children( self.problem1 ), XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children( XBlockFixtureDesc('problem', 'Test Problem 2') ) ) ).install() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ self.logout_page.visit() AutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit() def _setup_prereq(self): """ Make the first subsection a prerequisite """ # Login as staff self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) # Make the first subsection a prerequisite self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(0) self.studio_course_outline.select_advanced_tab(desired_item='gated_content') self.studio_course_outline.make_gating_prerequisite() def _setup_gated_subsection(self): """ Gate the second subsection on the first subsection """ # Login as staff self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) # Gate the second subsection based on the score achieved in the first subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(1) self.studio_course_outline.select_advanced_tab(desired_item='gated_content') self.studio_course_outline.add_prerequisite_to_subsection("80") def _fulfill_prerequisite(self): """ Fulfill the prerequisite needed to see gated content """ problem_page = ProblemPage(self.browser) self.assertEqual(problem_page.wait_for_page().problem_name, 'HEIGHT OF EIFFEL TOWER') problem_page.click_choice('choice_1') problem_page.click_submit() def test_subsection_gating_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 Gating And update those settings to gate a subsection """ self._setup_prereq() # Assert settings are displayed correctly for a prerequisite subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(0) self.studio_course_outline.select_advanced_tab(desired_item='gated_content') self.assertTrue(self.studio_course_outline.gating_prerequisite_checkbox_is_visible()) self.assertTrue(self.studio_course_outline.gating_prerequisite_checkbox_is_checked()) self.assertFalse(self.studio_course_outline.gating_prerequisites_dropdown_is_visible()) self.assertFalse(self.studio_course_outline.gating_prerequisite_min_score_is_visible()) self._setup_gated_subsection() # Assert settings are displayed correctly for a gated subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(1) self.studio_course_outline.select_advanced_tab(desired_item='gated_content') self.assertTrue(self.studio_course_outline.gating_prerequisite_checkbox_is_visible()) self.assertTrue(self.studio_course_outline.gating_prerequisites_dropdown_is_visible()) self.assertTrue(self.studio_course_outline.gating_prerequisite_min_score_is_visible()) def test_gated_subsection_in_lms_for_student(self): """ Given that I am a student When I visit the LMS Courseware Then I cannot see a gated subsection When I fulfill the gating Prerequisite Then I can see the gated subsection """ self._setup_prereq() self._setup_gated_subsection() self._auto_auth(self.STUDENT_USERNAME, self.STUDENT_EMAIL, False) self.course_home_page.visit() self.assertEqual(self.course_home_page.outline.num_subsections, 1) # Fulfill prerequisite and verify that gated subsection is shown self.courseware_page.visit() self._fulfill_prerequisite() self.course_home_page.visit() self.assertEqual(self.course_home_page.outline.num_subsections, 2) def test_gated_subsection_in_lms_for_staff(self): """ Given that I am a staff member When I visit the LMS Courseware Then I can see all gated subsections Displayed along with notification banners Then if I masquerade as a student Then I cannot see a gated subsection When I fufill the gating prerequisite Then I can see the gated subsection (without a banner) """ self._setup_prereq() self._setup_gated_subsection() # Fulfill prerequisites for specific student self._auto_auth(self.STUDENT_USERNAME, self.STUDENT_EMAIL, False) self.courseware_page.visit() self._fulfill_prerequisite() self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.course_home_page.visit() self.assertEqual(self.course_home_page.preview.staff_view_mode, 'Staff') self.assertEqual(self.course_home_page.outline.num_subsections, 2) # Click on gated section and check for banner self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 2') self.courseware_page.wait_for_page() self.assertTrue(self.courseware_page.has_banner()) self.course_home_page.visit() self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 1') self.courseware_page.wait_for_page() self.course_home_page.visit() self.course_home_page.preview.set_staff_view_mode('Learner') self.assertEqual(self.course_home_page.outline.num_subsections, 1) self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 1') self.courseware_page.wait_for_page() self.assertFalse(self.courseware_page.has_banner()) self.course_home_page.visit() self.course_home_page.preview.set_staff_view_mode_specific_student(self.STUDENT_USERNAME) self.assertEqual(self.course_home_page.outline.num_subsections, 2) self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 2') self.courseware_page.wait_for_page() self.assertFalse(self.courseware_page.has_banner())
class VideoLicenseTest(StudioCourseTest): """ Tests for video module-level licensing (that is, setting the license, for a specific video module, to All Rights Reserved or Creative Commons) """ def setUp(self): # pylint: disable=arguments-differ super(VideoLicenseTest, self).setUp() self.lms_courseware = CoursewarePage( self.browser, self.course_id, ) self.studio_course_outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) # used by StudioCourseTest.setUp() def populate_course_fixture(self, course_fixture): """ Create a course with a single chapter. That chapter has a single section. That section has a single vertical. That vertical has a single video element. """ video_block = XBlockFixtureDesc('video', "Test Video") vertical = XBlockFixtureDesc('vertical', "Test Vertical") vertical.add_children(video_block) sequential = XBlockFixtureDesc('sequential', "Test Section") sequential.add_children(vertical) chapter = XBlockFixtureDesc('chapter', "Test Chapter") chapter.add_children(sequential) self.course_fixture.add_children(chapter) def test_empty_license(self): """ When I visit the LMS courseware, I can see that the video is present but it has no license displayed by default. """ self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license = self.lms_courseware.q(css=".vert .xblock.xmodule_VideoModule .xblock-license") self.assertFalse(video_license.is_present()) def test_arr_license(self): """ When I edit a video element in Studio, I can set an "All Rights Reserved" license on that video element. When I visit the LMS courseware, I can see that the video is present and that it has "All Rights Reserved" displayed for the license. """ self.studio_course_outline.visit() subsection = self.studio_course_outline.section_at(0).subsection_at(0) subsection.expand_subsection() unit = subsection.unit_at(0) container_page = unit.go_to() container_page.edit() video = [xb for xb in container_page.xblocks if xb.name == "Test Video"][0] video.open_advanced_tab() video.set_license('all-rights-reserved') video.save_settings() container_page.publish_action.click() self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license_css = ".vert .xblock.xmodule_VideoModule .xblock-license" self.lms_courseware.wait_for_element_presence( video_license_css, "Video module license block is present" ) video_license = self.lms_courseware.q(css=video_license_css) self.assertEqual(video_license.text[0], "© All Rights Reserved") def test_cc_license(self): """ When I edit a video element in Studio, I can set a "Creative Commons" license on that video element. When I visit the LMS courseware, I can see that the video is present and that it has "Some Rights Reserved" displayed for the license. """ self.studio_course_outline.visit() subsection = self.studio_course_outline.section_at(0).subsection_at(0) subsection.expand_subsection() unit = subsection.unit_at(0) container_page = unit.go_to() container_page.edit() video = [xb for xb in container_page.xblocks if xb.name == "Test Video"][0] video.open_advanced_tab() video.set_license('creative-commons') video.save_settings() container_page.publish_action.click() self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license_css = ".vert .xblock.xmodule_VideoModule .xblock-license" self.lms_courseware.wait_for_element_presence( video_license_css, "Video module license block is present" ) video_license = self.lms_courseware.q(css=video_license_css) self.assertIn("Some Rights Reserved", video_license.text[0])
class CrowdsourcehinterProblemTest(UniqueCourseTest): """ Test scenario for the hinter. """ USERNAME = "******" EMAIL = "*****@*****.**" def setUp(self): super(CrowdsourcehinterProblemTest, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) # 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'] ) problem_data = dedent(''' <problem> <p>A text input problem accepts a line of text from the student, and evaluates the input for correctness based on an expected answer.</p> <p>The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.</p> <p>Which US state has Lansing as its capital?</p> <stringresponse answer="Michigan" type="ci" > <textline label="Which US state has Lansing as its capital?" size="20"/> </stringresponse> <solution> <div class="detailed-solution"> <p>Explanation</p> <p>Lansing is the capital of Michigan, although it is not Michigan's largest city, or even the seat of the county in which it resides.</p> </div> </solution> </problem> ''') children = XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc('problem', 'text input problem', data=problem_data), XBlockFixtureDesc('crowdsourcehinter', 'test crowdsourcehinter') ) ) ) course_fix.add_children(children).install() # Auto-auth register for the course. AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=False).visit() def _goto_csh_problem_page(self): """ Visit the page courseware page containing the hinter """ self.courseware_page.visit() csh_problem_page = CrowdsourcehinterProblemPage(self.browser) self.assertGreater(len(self.browser.find_elements_by_class_name('crowdsourcehinter_block')), 0) return csh_problem_page def test_student_hint_workflow(self): """ Test the basic workflow of a student recieving hints. The student should submit an incorrect answer and receive a hint (in this case no hint since none are set), be able to rate that hint, see a different UX after submitting a correct answer, and be capable of contributing a new hint to the system. """ csh_problem_page = self._goto_csh_problem_page() csh_problem_page.submit_text_answer("michigann") csh_problem_page.wait_for_ajax() self.assertEqual(csh_problem_page.get_hint_text()[0], u"Hint: Sorry, there are no hints for this answer.") self.assertGreater(len(self.browser.find_elements_by_class_name('csh_rate_hint')), 0) csh_problem_page.rate_hint() csh_problem_page.wait_for_ajax() csh_problem_page.submit_text_answer("michigan") csh_problem_page.wait_for_ajax() self.assertGreater(len(self.browser.find_elements_by_id('show_hint_rating_ux')), 0) csh_problem_page.submit_new_hint("new hint text")
class ContentLicenseTest(StudioCourseTest): """ Tests for course-level licensing (that is, setting the license, for an entire course's content, to All Rights Reserved or Creative Commons) """ def setUp(self): # pylint: disable=arguments-differ super(ContentLicenseTest, self).setUp() self.outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.settings_page = SettingsPage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.lms_courseware = CoursewarePage( self.browser, self.course_id, ) self.settings_page.visit() def test_empty_license(self): """ When I visit the Studio settings page, I see that the course license is "All Rights Reserved" by default. Then I visit the LMS courseware page, and I see that the default course license is displayed. """ self.assertEqual(self.settings_page.course_license, "All Rights Reserved") self.lms_courseware.visit() self.assertEqual(self.lms_courseware.course_license, "© All Rights Reserved") def test_arr_license(self): """ When I visit the Studio settings page, and I set the course license to "All Rights Reserved", and I refresh the page, I see that the course license is "All Rights Reserved". Then I visit the LMS courseware page, and I see that the course license is "All Rights Reserved". """ self.settings_page.course_license = "All Rights Reserved" self.settings_page.save_changes() self.settings_page.refresh_and_wait_for_load() self.assertEqual(self.settings_page.course_license, "All Rights Reserved") self.lms_courseware.visit() self.assertEqual(self.lms_courseware.course_license, "© All Rights Reserved") def test_cc_license(self): """ When I visit the Studio settings page, and I set the course license to "Creative Commons", and I refresh the page, I see that the course license is "Creative Commons". Then I visit the LMS courseware page, and I see that the course license is "Some Rights Reserved". """ self.settings_page.course_license = "Creative Commons" self.settings_page.save_changes() self.settings_page.refresh_and_wait_for_load() self.assertEqual(self.settings_page.course_license, "Creative Commons") self.lms_courseware.visit() # The course_license text will include a bunch of screen reader text to explain # the selected options self.assertIn("Some Rights Reserved", self.lms_courseware.course_license)
class ConditionalTest(UniqueCourseTest): """ Test the conditional module in the lms. """ shard = 23 def setUp(self): super(ConditionalTest, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) AutoAuthPage(self.browser, course_id=self.course_id, staff=False).visit() def install_course_fixture(self, block_type='problem'): """ Install a course fixture """ course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'], ) vertical = XBlockFixtureDesc('vertical', 'Test Unit') # populate the course fixture with the right conditional modules course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children(vertical))) course_fixture.install() # Construct conditional block source_block = None conditional_attr = None conditional_value = None if block_type == 'problem': problem_factory = StringResponseXMLFactory() problem_xml = problem_factory.build_xml( question_text='The answer is "correct string"', case_sensitive=False, answer='correct string', ), problem = XBlockFixtureDesc('problem', 'Test Problem', data=problem_xml[0]) source_block = problem conditional_attr = 'attempted' conditional_value = 'True' elif block_type == 'poll': poll = XBlockFixtureDesc( 'poll_question', 'Conditional Poll', question='Is this a good poll?', answers=[{ 'id': 'yes', 'text': POLL_ANSWER }, { 'id': 'no', 'text': 'Of course not!' }], ) conditional_attr = 'poll_answer' conditional_value = 'yes' source_block = poll else: raise NotImplementedError() course_fixture.create_xblock(vertical.locator, source_block) # create conditional conditional = XBlockFixtureDesc('conditional', 'Test Conditional', sources_list=[source_block.locator], conditional_attr=conditional_attr, conditional_value=conditional_value) result_block = XBlockFixtureDesc( 'html', 'Conditional Contents', data='<html><div class="hidden-contents">Hidden Contents</p></html>' ) course_fixture.create_xblock(vertical.locator, conditional) course_fixture.create_xblock(conditional.locator, result_block) def test_conditional_hides_content(self): self.install_course_fixture() self.courseware_page.visit() conditional_page = ConditionalPage(self.browser) self.assertFalse(conditional_page.is_content_visible()) def test_conditional_displays_content(self): self.install_course_fixture() self.courseware_page.visit() # Answer the problem problem_page = ProblemPage(self.browser) problem_page.fill_answer('correct string') problem_page.click_submit() # The conditional does not update on its own, so we need to reload the page. self.courseware_page.visit() # Verify that we can see the content. conditional_page = ConditionalPage(self.browser) self.assertTrue(conditional_page.is_content_visible()) def test_conditional_handles_polls(self): self.install_course_fixture(block_type='poll') self.courseware_page.visit() # Fill in the conditional page poll conditional_page = ConditionalPage(self.browser) conditional_page.fill_in_poll() # The conditional does not update on its own, so we need to reload the page. self.courseware_page.visit() self.assertTrue(conditional_page.is_content_visible())
class AnnotatableProblemTest(UniqueCourseTest): """ Tests for annotation components. """ USERNAME = "******" EMAIL = "*****@*****.**" DATA_TEMPLATE = dedent(u"""\ <annotatable> <instructions>Instruction text</instructions> <p>{}</p> </annotatable> """) ANNOTATION_TEMPLATE = dedent(u"""\ Before {0}. <annotation title="region {0}" body="Comment {0}" highlight="yellow" problem="{0}"> Region Contents {0} </annotation> After {0}. """) PROBLEM_TEMPLATE = dedent(u"""\ <problem max_attempts="1" weight=""> <annotationresponse> <annotationinput> <title>Question {number}</title> <text>Region Contents {number}</text> <comment>What number is this region?</comment> <comment_prompt>Type your response below:</comment_prompt> <tag_prompt>What number is this region?</tag_prompt> <options> {options} </options> </annotationinput> </annotationresponse> <solution> This problem is checking region {number} </solution> </problem> """) OPTION_TEMPLATE = u"""<option choice="{correctness}">{number}</option>""" def setUp(self): super(AnnotatableProblemTest, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) # Install a course with two annotations and two annotations problems. course_fix = CourseFixture(self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name']) self.annotation_count = 2 course_fix.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection'). add_children( XBlockFixtureDesc( 'vertical', 'Test Annotation Vertical').add_children( XBlockFixtureDesc( 'annotatable', 'Test Annotation Module', data=self.DATA_TEMPLATE.format("\n".join( self.ANNOTATION_TEMPLATE.format(i) for i in range(self.annotation_count)))), XBlockFixtureDesc( 'problem', 'Test Annotation Problem 0', data=self.PROBLEM_TEMPLATE.format( number=0, options="\n".join( self.OPTION_TEMPLATE.format( number=k, correctness=_correctness(k, 0)) for k in range(self.annotation_count)))), XBlockFixtureDesc( 'problem', 'Test Annotation Problem 1', data=self.PROBLEM_TEMPLATE.format( number=1, options="\n".join( self.OPTION_TEMPLATE.format( number=k, correctness=_correctness(k, 1)) for k in range(self.annotation_count) ))))))).install() # Auto-auth register for the course. AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=False).visit() def _goto_annotation_component_page(self): """ Open annotation component page with assertion. """ self.courseware_page.visit() annotation_component_page = AnnotationComponentPage(self.browser) self.assertEqual(annotation_component_page.component_name, 'Test Annotation Module'.format()) return annotation_component_page def test_annotation_component(self): """ Test annotation components links to annotation problems. """ annotation_component_page = self._goto_annotation_component_page() # This will avoid scrolling related problems on different browsers and instead directly jump on the problem disable_animations(annotation_component_page) for i in range(self.annotation_count): annotation_component_page.click_reply_annotation(i) self.assertTrue( annotation_component_page.check_scroll_to_problem()) annotation_component_page.answer_problem() self.assertTrue(annotation_component_page.check_feedback()) annotation_component_page.click_return_to_annotation() self.assertTrue( annotation_component_page.check_scroll_to_annotation())
class CrowdsourcehinterProblemTest(UniqueCourseTest): """ Test scenario for the hinter. """ shard = 21 USERNAME = "******" EMAIL = "*****@*****.**" def setUp(self): super(CrowdsourcehinterProblemTest, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) # 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']) problem_data = dedent(''' <problem> <p>A text input problem accepts a line of text from the student, and evaluates the input for correctness based on an expected answer.</p> <p>The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.</p> <p>Which US state has Lansing as its capital?</p> <stringresponse answer="Michigan" type="ci" > <textline label="Which US state has Lansing as its capital?" size="20"/> </stringresponse> <solution> <div class="detailed-solution"> <p>Explanation</p> <p>Lansing is the capital of Michigan, although it is not Michigan's largest city, or even the seat of the county in which it resides.</p> </div> </solution> </problem> ''') children = XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc('problem', 'text input problem', data=problem_data), XBlockFixtureDesc('crowdsourcehinter', 'test crowdsourcehinter')))) course_fix.add_children(children).install() # Auto-auth register for the course. AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=False).visit() def _goto_csh_problem_page(self): """ Visit the page courseware page containing the hinter """ self.courseware_page.visit() csh_problem_page = CrowdsourcehinterProblemPage(self.browser) self.assertGreater( len( self.browser.find_elements_by_class_name( 'crowdsourcehinter_block')), 0) return csh_problem_page def test_student_hint_workflow(self): """ Test the basic workflow of a student recieving hints. The student should submit an incorrect answer and receive a hint (in this case no hint since none are set), be able to rate that hint, see a different UX after submitting a correct answer, and be capable of contributing a new hint to the system. """ csh_problem_page = self._goto_csh_problem_page() csh_problem_page.submit_text_answer("michigann") csh_problem_page.wait_for_ajax() self.assertEqual(csh_problem_page.get_hint_text()[0], u"Hint: Sorry, there are no hints for this answer.") self.assertGreater( len(self.browser.find_elements_by_class_name('csh_rate_hint')), 0) csh_problem_page.rate_hint() csh_problem_page.wait_for_ajax() csh_problem_page.submit_text_answer("michigan") csh_problem_page.wait_for_ajax() self.assertGreater( len(self.browser.find_elements_by_id('show_hint_rating_ux')), 0) csh_problem_page.submit_new_hint("new hint text")
class LibraryContentTestBase(UniqueCourseTest): """ Base class for library content block tests """ USERNAME = "******" EMAIL = "*****@*****.**" STAFF_USERNAME = "******" STAFF_EMAIL = "*****@*****.**" shard = 10 def populate_library_fixture(self, library_fixture): """ To be overwritten by subclassed tests. Used to install a library to run tests on. """ def setUp(self): """ Set up library, course and library content XBlock """ super(LibraryContentTestBase, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) self.library_fixture = LibraryFixture( 'test_org', self.unique_id, u'Test Library {}'.format(self.unique_id)) self.populate_library_fixture(self.library_fixture) self.library_fixture.install() self.library_info = self.library_fixture.library_info self.library_key = self.library_fixture.library_key # Install a course with library content xblock self.course_fixture = CourseFixture(self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name']) library_content_metadata = { 'source_library_id': six.text_type(self.library_key), 'mode': 'random', 'max_count': 1, } self.lib_block = XBlockFixtureDesc('library_content', "Library Content", metadata=library_content_metadata) self.course_fixture.add_children( XBlockFixtureDesc('chapter', SECTION_NAME).add_children( XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children( XBlockFixtureDesc('vertical', UNIT_NAME).add_children( self.lib_block)))) self.course_fixture.install() def _change_library_content_settings(self, count=1, capa_type=None): """ Performs library block refresh in Studio, configuring it to show {count} children """ unit_page = self._go_to_unit_page(True) library_container_block = StudioLibraryContainerXBlockWrapper.from_xblock_wrapper( unit_page.xblocks[1]) library_container_block.edit() editor = StudioLibraryContentEditor(self.browser, library_container_block.locator) editor.count = count if capa_type is not None: editor.capa_type = capa_type editor.save() self._go_to_unit_page(change_login=False) unit_page.wait_for_page() unit_page.publish() self.assertIn("Published and Live", unit_page.publish_title) @property def library_xblocks_texts(self): """ Gets texts of all xblocks in library """ return frozenset(child.data for child in self.library_fixture.children) def _go_to_unit_page(self, change_login=True): """ Open unit page in Studio """ if change_login: LogoutPage(self.browser).visit() self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.studio_course_outline.visit() subsection = self.studio_course_outline.section( SECTION_NAME).subsection(SUBSECTION_NAME) return subsection.expand_subsection().unit(UNIT_NAME).go_to() def _goto_library_block_page(self, block_id=None): """ Open library page in LMS """ self.courseware_page.visit() paragraphs = self.courseware_page.q(css='.course-content p').results if not paragraphs: course_home_page = CourseHomePage(self.browser, self.course_id) course_home_page.visit() course_home_page.outline.go_to_section_by_index(0, 0) block_id = block_id if block_id is not None else self.lib_block.locator #pylint: disable=attribute-defined-outside-init self.library_content_page = LibraryContentXBlockWrapper( self.browser, block_id) self.library_content_page.wait_for_page() 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()
class VideoLicenseTest(StudioCourseTest): """ Tests for video module-level licensing (that is, setting the license, for a specific video module, to All Rights Reserved or Creative Commons) """ def setUp(self): # pylint: disable=arguments-differ super(VideoLicenseTest, self).setUp() self.lms_courseware = CoursewarePage( self.browser, self.course_id, ) self.studio_course_outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) # used by StudioCourseTest.setUp() def populate_course_fixture(self, course_fixture): """ Create a course with a single chapter. That chapter has a single section. That section has a single vertical. That vertical has a single video element. """ video_block = XBlockFixtureDesc('video', "Test Video") vertical = XBlockFixtureDesc('vertical', "Test Vertical") vertical.add_children(video_block) sequential = XBlockFixtureDesc('sequential', "Test Section") sequential.add_children(vertical) chapter = XBlockFixtureDesc('chapter', "Test Chapter") chapter.add_children(sequential) self.course_fixture.add_children(chapter) def test_empty_license(self): """ When I visit the LMS courseware, I can see that the video is present but it has no license displayed by default. """ self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license = self.lms_courseware.q(css=".vert .xblock.xmodule_VideoModule .xblock-license") self.assertFalse(video_license.is_present()) def test_arr_license(self): """ When I edit a video element in Studio, I can set an "All Rights Reserved" license on that video element. When I visit the LMS courseware, I can see that the video is present and that it has "All Rights Reserved" displayed for the license. """ self.studio_course_outline.visit() subsection = self.studio_course_outline.section_at(0).subsection_at(0) subsection.expand_subsection() unit = subsection.unit_at(0) container_page = unit.go_to() container_page.edit() video = [xb for xb in container_page.xblocks if xb.name == "Test Video"][0] video.open_advanced_tab() video.set_license('all-rights-reserved') video.save_settings() container_page.publish_action.click() self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license_css = ".vert .xblock.xmodule_VideoModule .xblock-license" self.lms_courseware.wait_for_element_presence( video_license_css, "Video module license block is present" ) video_license = self.lms_courseware.q(css=video_license_css) self.assertEqual(video_license.text[0], "© All Rights Reserved") def test_cc_license(self): """ When I edit a video element in Studio, I can set a "Creative Commons" license on that video element. When I visit the LMS courseware, I can see that the video is present and that it has "Some Rights Reserved" displayed for the license. """ self.studio_course_outline.visit() subsection = self.studio_course_outline.section_at(0).subsection_at(0) subsection.expand_subsection() unit = subsection.unit_at(0) container_page = unit.go_to() container_page.edit() video = [xb for xb in container_page.xblocks if xb.name == "Test Video"][0] video.open_advanced_tab() video.set_license('creative-commons') video.save_settings() container_page.publish_action.click() self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license_css = ".vert .xblock.xmodule_VideoModule .xblock-license" self.lms_courseware.wait_for_element_presence( video_license_css, "Video module license block is present" ) video_license = self.lms_courseware.q(css=video_license_css) self.assertIn("Some Rights Reserved", video_license.text[0])