def test_enable_entrance_exam_for_course(self): """ Test that entrance exam should be created after checking the 'enable entrance exam' checkbox. And also that the entrance exam is destroyed after deselecting the checkbox. """ self.settings_detail.require_entrance_exam(required=True) self.settings_detail.save_changes() # getting the course outline page. course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) course_outline_page.visit() # title with text 'Entrance Exam' should be present on page. self.assertTrue(element_has_text( page=course_outline_page, css_selector='span.section-title', text='Entrance Exam' )) # Delete the currently created entrance exam. self.settings_detail.visit() self.settings_detail.require_entrance_exam(required=False) self.settings_detail.save_changes() course_outline_page.visit() self.assertFalse(element_has_text( page=course_outline_page, css_selector='span.section-title', text='Entrance Exam' ))
def test_entrance_exam_has_unit_button(self): """ Test that entrance exam should be created after checking the 'enable entrance exam' checkbox. And user has option to add units only instead of any Subsection. """ self.settings_detail.require_entrance_exam(required=True) self.settings_detail.save_changes() # getting the course outline page. course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) course_outline_page.visit() course_outline_page.wait_for_ajax() # button with text 'New Unit' should be present. self.assertTrue(element_has_text( page=course_outline_page, css_selector='.add-item a.button-new', text='New Unit' )) # button with text 'New Subsection' should not be present. self.assertFalse(element_has_text( page=course_outline_page, css_selector='.add-item a.button-new', text='New Subsection' ))
def test_enable_entrance_exam_for_course(self): """ Test that entrance exam should be created after checking the 'enable entrance exam' checkbox. And also that the entrance exam is destroyed after deselecting the checkbox. """ self.settings_detail.require_entrance_exam(required=True) self.settings_detail.save_changes() # getting the course outline page. course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) course_outline_page.visit() # title with text 'Entrance Exam' should be present on page. self.assertTrue(element_has_text( page=course_outline_page, css_selector='span.section-title', text='Entrance Exam' )) # Delete the currently created entrance exam. self.settings_detail.visit() self.settings_detail.require_entrance_exam(required=False) self.settings_detail.save_changes() course_outline_page.visit() self.assertFalse(element_has_text( page=course_outline_page, css_selector='span.section-title', text='Entrance Exam' ))
def setUp(self): super(DiscussionPreviewTest, self).setUp() cop = CourseOutlinePage( self.browser, self.course_info["org"], self.course_info["number"], self.course_info["run"] ) cop.visit() self.unit = cop.section("Test Section").subsection("Test Subsection").expand_subsection().unit("Test Unit") self.unit.go_to()
class StudioSubsectionSettingsA11yTest(StudioCourseTest): """ Class to test accessibility on the subsection settings modals. """ def setUp(self): # pylint: disable=arguments-differ browser = os.environ.get('SELENIUM_BROWSER', 'firefox') # This test will fail if run using phantomjs < 2.0, due to an issue with bind() # See https://github.com/ariya/phantomjs/issues/10522 for details. # The course_outline uses this function, and as such will not fully load when run # under phantomjs 1.9.8. So, to prevent this test from timing out at course_outline.visit(), # force the use of firefox vs the standard a11y test usage of phantomjs 1.9.8. # TODO: remove this block once https://openedx.atlassian.net/browse/TE-1047 is resolved. if browser == 'phantomjs': browser = 'firefox' with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}): super(StudioSubsectionSettingsA11yTest, self).setUp(is_staff=True) self.course_outline = CourseOutlinePage(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) def populate_course_fixture(self, course_fixture): course_fixture.add_advanced_settings( {"enable_proctored_exams": { "value": "true" }}) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section 1').add_children( XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children( XBlockFixtureDesc( 'problem', 'Test Problem 1')))) def test_special_exams_menu_a11y(self): """ Given that I am a staff member And I am editing settings on the special exams menu Then that menu is accessible """ self.course_outline.visit() self.course_outline.open_subsection_settings_dialog() self.course_outline.select_advanced_tab() self.course_outline.a11y_audit.config.set_rules({ "ignore": [ 'section', # TODO: AC-491 ], }) # limit the scope of the audit to the special exams tab on the modal dialog self.course_outline.a11y_audit.config.set_scope( include=['section.edit-settings-timed-examination']) self.course_outline.a11y_audit.check_for_accessibility_errors()
def setUp(self): super(DiscussionPreviewTest, self).setUp() cop = CourseOutlinePage(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) cop.visit() self.unit = cop.section('Test Section').subsection( 'Test Subsection').expand_subsection().unit('Test Unit') self.unit.go_to()
class StudioSubsectionSettingsA11yTest(StudioCourseTest): """ Class to test accessibility on the subsection settings modals. """ def setUp(self): # pylint: disable=arguments-differ browser = os.environ.get('SELENIUM_BROWSER', 'firefox') # This test will fail if run using phantomjs < 2.0, due to an issue with bind() # See https://github.com/ariya/phantomjs/issues/10522 for details. # The course_outline uses this function, and as such will not fully load when run # under phantomjs 1.9.8. So, to prevent this test from timing out at course_outline.visit(), # force the use of firefox vs the standard a11y test usage of phantomjs 1.9.8. # TODO: remove this block once https://openedx.atlassian.net/browse/TE-1047 is resolved. if browser == 'phantomjs': browser = 'firefox' with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}): super(StudioSubsectionSettingsA11yTest, self).setUp(is_staff=True) self.course_outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) def populate_course_fixture(self, course_fixture): course_fixture.add_advanced_settings({ "enable_proctored_exams": {"value": "true"} }) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section 1').add_children( XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children( XBlockFixtureDesc('problem', 'Test Problem 1') ) ) ) def test_special_exams_menu_a11y(self): """ Given that I am a staff member And I am editing settings on the special exams menu Then that menu is accessible """ self.course_outline.visit() self.course_outline.open_subsection_settings_dialog() self.course_outline.select_advanced_tab() # limit the scope of the audit to the special exams tab on the modal dialog self.course_outline.a11y_audit.config.set_scope( include=['section.edit-settings-timed-examination'] ) self.course_outline.a11y_audit.check_for_accessibility_errors()
def setUp(self): super(DiscussionPreviewTest, self).setUp() cop = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) cop.visit() self.unit = cop.section('Test Section').subsection('Test Subsection').expand_subsection().unit('Test Unit') self.unit.go_to()
class CourseOutlineHelpTest(StudioCourseTest): """ Tests help links on course outline page. """ def setUp(self): # pylint: disable=arguments-differ super(CourseOutlineHelpTest, self).setUp() self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.course_outline_page.visit() @skip("This scenario depends upon TNL-5460") def test_course_outline_nav_help(self): """ Scenario: Help link in navigation bar is working on Course Outline page Given that I am on the Course Outline page And I want help about the process And I click the 'Help' in the navigation bar Then Help link should open. And help url should end with 'developing_course/course_outline.html' """ href = 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course' \ '/en/open-release-ficus.master/developing_course/course_outline.html' # Assert that help link is correct. assert_nav_help_link( test=self, page=self.course_outline_page, href=href ) def test_course_outline_side_bar_help(self): """ Scenario: Help link in sidebar links is working on Course Outline page Given that I am on the Course Outline page. And I want help about the process And I click the 'Learn more about the course outline' in the sidebar links Then Help link should open. And help url should end with 'developing_course/course_outline.html' """ href = 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course' \ '/en/open-release-ficus.master/developing_course/course_outline.html' # Assert that help link is correct. assert_side_bar_help_link( test=self, page=self.course_outline_page, href=href, help_text='Learn more about the course outline', index=0 )
def record_visit_outline(self): """ Produce a HAR for loading the course outline page. """ course_outline_page = CourseOutlinePage(self.browser, self.course_org, self.course_num, self.course_run) har_name = 'OutlinePage_{org}_{course}'.format(org=self.course_org, course=self.course_num) self.har_capturer.add_page(self.browser, har_name) course_outline_page.visit() self.har_capturer.save_har(self.browser, har_name)
def record_visit_outline(self): """ Produce a HAR for loading the course outline page. """ course_outline_page = CourseOutlinePage(self.browser, self.course_org, self.course_num, self.course_run) har_name = 'OutlinePage_{org}_{course}'.format( org=self.course_org, course=self.course_num ) self.har_capturer.add_page(self.browser, har_name) course_outline_page.visit() self.har_capturer.save_har(self.browser, har_name)
class CourseOutlineHelpTest(StudioCourseTest): """ Tests help links on course outline page. """ def setUp(self): # pylint: disable=arguments-differ super(CourseOutlineHelpTest, self).setUp() self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.course_outline_page.visit() @skip("This scenario depends upon TNL-5460") def test_course_outline_nav_help(self): """ Scenario: Help link in navigation bar is working on Course Outline page Given that I am on the Course Outline page And I want help about the process And I click the 'Help' in the navigation bar Then Help link should open. And help url should be correct """ expected_url = _get_expected_documentation_url('/developing_course/course_outline.html') # Assert that help link is correct. assert_nav_help_link( test=self, page=self.course_outline_page, href=expected_url ) def test_course_outline_side_bar_help(self): """ Scenario: Help link in sidebar links is working on Course Outline page Given that I am on the Course Outline page. And I want help about the process And I click the 'Learn more about the course outline' in the sidebar links Then Help link should open. And help url should be correct """ expected_url = _get_expected_documentation_url('/developing_course/course_outline.html') # Assert that help link is correct. assert_side_bar_help_link( test=self, page=self.course_outline_page, href=expected_url, help_text='Learn more about the course outline', index=0 )
class CourseOutlineHelpTest(StudioCourseTest): """ Tests help links on course outline page. """ def setUp(self): # pylint: disable=arguments-differ super(CourseOutlineHelpTest, self).setUp() self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.course_outline_page.visit() @skip("This scenario depends upon TNL-5460") def test_course_outline_nav_help(self): """ Scenario: Help link in navigation bar is working on Course Outline page Given that I am on the Course Outline page And I want help about the process And I click the 'Help' in the navigation bar Then Help link should open. And help url should be correct """ expected_url = _get_expected_documentation_url('/developing_course/course_outline.html') # Assert that help link is correct. assert_nav_help_link( test=self, page=self.course_outline_page, href=expected_url ) def test_course_outline_side_bar_help(self): """ Scenario: Help link in sidebar links is working on Course Outline page Given that I am on the Course Outline page. And I want help about the process And I click the 'Learn more about the course outline' in the sidebar links Then Help link should open. And help url should be correct """ expected_url = _get_expected_documentation_url('/developing_course/course_outline.html') # Assert that help link is correct. assert_side_bar_help_link( test=self, page=self.course_outline_page, href=expected_url, help_text='Learn more about the course outline', index=0 )
class ContainerBase(StudioCourseTest): """ Base class for tests that do operations on the container page. """ def setUp(self, is_staff=False): """ Create a unique identifier for the course used in this test. """ # Ensure that the superclass sets up super(ContainerBase, self).setUp(is_staff=is_staff) self.outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) def go_to_nested_container_page(self): """ Go to the nested container page. """ unit = self.go_to_unit_page() # The 0th entry is the unit page itself. container = unit.xblocks[1].go_to_container() return container def go_to_unit_page(self, section_name='Test Section', subsection_name='Test Subsection', unit_name='Test Unit'): """ Go to the test unit page. If make_draft is true, the unit page will be put into draft mode. """ self.outline.visit() subsection = self.outline.section(section_name).subsection(subsection_name) return subsection.expand_subsection().unit(unit_name).go_to() def do_action_and_verify(self, action, expected_ordering): """ Perform the supplied action and then verify the resulting ordering. """ container = self.go_to_nested_container_page() action(container) verify_ordering(self, container, expected_ordering) # Reload the page to see that the change was persisted. container = self.go_to_nested_container_page() verify_ordering(self, container, expected_ordering)
def test_create_course_with_existing_org_via_autocomplete(self): """ Scenario: Ensure that the course creation with an existing org should be successful. Given I have filled course creation form with an existing org and all required fields And I selected `Course Organization` input via autocomplete When I click 'Create' button Form validation should pass Then I see the course listing page with newly created course """ self.auth_page.visit() self.dashboard_page.visit() new_org = 'orgX2' self.assertFalse(self.dashboard_page.has_course( org=new_org, number=self.course_number, run=self.course_run )) self.assertTrue(self.dashboard_page.new_course_button.present) self.dashboard_page.click_new_course_button() self.assertTrue(self.dashboard_page.is_new_course_form_visible()) self.dashboard_page.fill_new_course_form( self.course_name, '', self.course_number, self.course_run ) self.dashboard_page.course_org_field.fill('org') self.dashboard_page.select_item_in_autocomplete_widget(new_org) self.assertTrue(self.dashboard_page.is_new_course_form_valid()) self.dashboard_page.submit_new_course_form() # Successful creation of course takes user to course outline page course_outline_page = CourseOutlinePage( self.browser, new_org, self.course_number, self.course_run ) course_outline_page.visit() course_outline_page.wait_for_page() # Go back to dashboard and verify newly created course exists there self.dashboard_page.visit() self.assertTrue(self.dashboard_page.has_course( org=new_org, number=self.course_number, run=self.course_run ))
def test_create_course_with_existing_org_via_autocomplete(self): """ Scenario: Ensure that the course creation with an existing org should be successful. Given I have filled course creation form with an existing org and all required fields And I selected `Course Organization` input via autocomplete When I click 'Create' button Form validation should pass Then I see the course listing page with newly created course """ self.auth_page.visit() self.dashboard_page.visit() new_org = 'orgX2' self.assertFalse(self.dashboard_page.has_course( org=new_org, number=self.course_number, run=self.course_run )) self.assertTrue(self.dashboard_page.new_course_button.present) self.dashboard_page.click_new_course_button() self.assertTrue(self.dashboard_page.is_new_course_form_visible()) self.dashboard_page.fill_new_course_form( self.course_name, '', self.course_number, self.course_run ) self.dashboard_page.course_org_field.fill('org') self.dashboard_page.select_item_in_autocomplete_widget(new_org) self.assertTrue(self.dashboard_page.is_new_course_form_valid()) self.dashboard_page.submit_new_course_form() # Successful creation of course takes user to course outline page course_outline_page = CourseOutlinePage( self.browser, new_org, self.course_number, self.course_run ) course_outline_page.visit() course_outline_page.wait_for_page() # Go back to dashboard and verify newly created course exists there self.dashboard_page.visit() self.assertTrue(self.dashboard_page.has_course( org=new_org, number=self.course_number, run=self.course_run ))
def test_create_course_with_existing_org(self): """ Scenario: Ensure that the course creation with an existing org should be successful. Given I have filled course creation form with an existing org and all required fields When I click 'Create' button Form validation should pass Then I see the course listing page with newly created course """ self.auth_page.visit() self.dashboard_page.visit() self.assertFalse(self.dashboard_page.has_course( org=self.course_org, number=self.course_number, run=self.course_run )) self.assertTrue(self.dashboard_page.new_course_button.present) self.dashboard_page.click_new_course_button() self.assertTrue(self.dashboard_page.is_new_course_form_visible()) self.dashboard_page.fill_new_course_form( self.course_name, self.course_org, self.course_number, self.course_run ) self.assertTrue(self.dashboard_page.is_new_course_form_valid()) self.dashboard_page.submit_new_course_form() # Successful creation of course takes user to course outline page course_outline_page = CourseOutlinePage( self.browser, self.course_org, self.course_number, self.course_run ) course_outline_page.visit() course_outline_page.wait_for_page() # Go back to dashboard and verify newly created course exists there self.dashboard_page.visit() self.assertTrue(self.dashboard_page.has_course( org=self.course_org, number=self.course_number, run=self.course_run )) # Click on the course listing and verify that the Studio course outline page opens. self.dashboard_page.click_course_run(self.course_run) course_outline_page.wait_for_page()
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 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 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 GroupConfigurationsTest(ContainerBase, SplitTestMixin): """ Tests that Group Configurations page works correctly with previously added configurations in Studio """ __test__ = True def setUp(self): super(GroupConfigurationsTest, self).setUp() self.page = GroupConfigurationsPage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) def _assert_fields(self, config, cid=None, name='', description='', groups=None): self.assertEqual(config.mode, 'details') if name: self.assertIn(name, config.name) if cid: self.assertEqual(cid, config.id) else: # To make sure that id is present on the page and it is not an empty. # We do not check the value of the id, because it's generated randomly and we cannot # predict this value self.assertTrue(config.id) # Expand the configuration config.toggle() if description: self.assertIn(description, config.description) if groups: allocation = int(math.floor(100 / len(groups))) self.assertEqual(groups, [group.name for group in config.groups]) for group in config.groups: self.assertEqual(str(allocation) + "%", group.allocation) # Collapse the configuration config.toggle() def _add_split_test_to_vertical(self, number, group_configuration_metadata=None): """ Add split test to vertical #`number`. If `group_configuration_metadata` is not None, use it to assign group configuration to split test. """ vertical = self.course_fixture.get_nested_xblocks(category="vertical")[number] if group_configuration_metadata: split_test = XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata=group_configuration_metadata) else: split_test = XBlockFixtureDesc('split_test', 'Test Content Experiment') self.course_fixture.create_xblock(vertical.locator, split_test) return split_test def populate_course_fixture(self, course_fixture): course_fixture.add_advanced_settings({ u"advanced_modules": {"value": ["split_test"]}, }) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit') ) ) ) def create_group_configuration_experiment(self, groups, associate_experiment): """ Creates a Group Configuration containing a list of groups. Optionally creates a Content Experiment and associates it with previous Group Configuration. Returns group configuration or (group configuration, experiment xblock) """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json(0, "Name", "Description.", groups), ], }, }) if associate_experiment: # Assign newly created group configuration to experiment vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] split_test = XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 0}) self.course_fixture.create_xblock(vertical.locator, split_test) # Go to the Group Configuration Page self.page.visit() config = self.page.experiment_group_configurations[0] if associate_experiment: return config, split_test return config def publish_unit_in_lms_and_view(self, courseware_page, publish=True): """ Given course outline page, publish first unit and view it in LMS when publish is false, it will only view """ self.outline_page.visit() self.outline_page.expand_all_subsections() section = self.outline_page.section_at(0) unit = section.subsection_at(0).unit_at(0).go_to() # I publish and view in LMS and it is rendered correctly if publish: unit.publish_action.click() unit.view_published_version() self.assertEqual(len(self.browser.window_handles), 2) courseware_page.wait_for_page() def get_select_options(self, page, selector): """ Get list of options of dropdown that is specified by selector on a given page. """ select_element = page.q(css=selector) self.assertTrue(select_element.is_present()) return [option.text for option in Select(select_element[0]).options] def test_no_group_configurations_added(self): """ Scenario: Ensure that message telling me to create a new group configuration is shown when group configurations were not added. Given I have a course without group configurations When I go to the Group Configuration page in Studio Then I see "You have not created any group configurations yet." message """ self.page.visit() self.assertTrue(self.page.experiment_group_sections_present) self.assertTrue(self.page.no_experiment_groups_message_is_present) self.assertIn( "You have not created any group configurations yet.", self.page.no_experiment_groups_message_text ) def test_group_configurations_have_correct_data(self): """ Scenario: Ensure that the group configuration is rendered correctly in expanded/collapsed mode. Given I have a course with 2 group configurations And I go to the Group Configuration page in Studio And I work with the first group configuration And I see `name`, `id` are visible and have correct values When I expand the first group configuration Then I see `description` and `groups` appear and also have correct values And I do the same checks for the second group configuration """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')] ), create_user_partition_json( 1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')] ), ], }, }) self.page.visit() config = self.page.experiment_group_configurations[0] # no groups when the the configuration is collapsed self.assertEqual(len(config.groups), 0) self._assert_fields( config, cid="0", name="Name of the Group Configuration", description="Description of the group configuration.", groups=["Group 0", "Group 1"] ) config = self.page.experiment_group_configurations[1] self._assert_fields( config, name="Name of second Group Configuration", description="Second group configuration.", groups=["Alpha", "Beta", "Gamma"] ) def test_can_create_and_edit_group_configuration(self): """ Scenario: Ensure that the group configuration can be created and edited correctly. Given I have a course without group configurations When I click button 'Create new Group Configuration' And I set new name and description, change name for the 2nd default group, add one new group And I click button 'Create' Then I see the new group configuration is added and has correct data When I edit the group group_configuration And I change the name and description, add new group, remove old one and change name for the Group A And I click button 'Save' Then I see the group configuration is saved successfully and has the new data """ self.page.visit() self.assertEqual(len(self.page.experiment_group_configurations), 0) # Create new group configuration self.page.create_experiment_group_configuration() config = self.page.experiment_group_configurations[0] config.name = "New Group Configuration Name" config.description = "New Description of the group configuration." config.groups[1].name = "New Group Name" # Add new group config.add_group() # Group C # Save the configuration self.assertEqual(config.get_text('.action-primary'), "Create") self.assertFalse(config.delete_button_is_present) config.save() self._assert_fields( config, name="New Group Configuration Name", description="New Description of the group configuration.", groups=["Group A", "New Group Name", "Group C"] ) # Edit the group configuration config.edit() # Update fields self.assertTrue(config.id) config.name = "Second Group Configuration Name" config.description = "Second Description of the group configuration." self.assertEqual(config.get_text('.action-primary'), "Save") # Add new group config.add_group() # Group D # Remove group with name "New Group Name" config.groups[1].remove() # Rename Group A config.groups[0].name = "First Group" # Save the configuration config.save() self._assert_fields( config, name="Second Group Configuration Name", description="Second Description of the group configuration.", groups=["First Group", "Group C", "Group D"] ) def test_focus_management_in_experiment_group_inputs(self): """ Scenario: Ensure that selecting the focus inputs in the groups list sets the .is-focused class on the fieldset Given I have a course with experiment group configurations When I click the name of the first group Then the fieldset wrapping the group names whould get class .is-focused When I click away from the first group Then the fieldset should not have class .is-focused anymore """ self.page.visit() self.page.create_experiment_group_configuration() config = self.page.experiment_group_configurations[0] group_a = config.groups[0] # Assert the fieldset doesn't have .is-focused class self.assertFalse(self.page.q(css="fieldset.groups-fields.is-focused").visible) # Click on the Group A input field self.page.q(css=group_a.prefix).click() # Assert the fieldset has .is-focused class applied self.assertTrue(self.page.q(css="fieldset.groups-fields.is-focused").visible) # Click away self.page.q(css=".page-header").click() # Assert the fieldset doesn't have .is-focused class self.assertFalse(self.page.q(css="fieldset.groups-fields.is-focused").visible) def test_use_group_configuration(self): """ Scenario: Ensure that the group configuration can be used by split_module correctly Given I have a course without group configurations When I create new group configuration And I set new name and add a new group, save the group configuration And I go to the unit page in Studio And I add new advanced module "Content Experiment" When I assign created group configuration to the module Then I see the module has correct groups """ self.page.visit() # Create new group configuration self.page.create_experiment_group_configuration() config = self.page.experiment_group_configurations[0] config.name = "New Group Configuration Name" # Add new group config.add_group() config.groups[2].name = "New group" # Save the configuration config.save() split_test = self._add_split_test_to_vertical(number=0) container = ContainerPage(self.browser, split_test.locator) container.visit() container.edit() component_editor = ComponentEditorView(self.browser, container.locator) component_editor.set_select_value_and_save('Group Configuration', 'New Group Configuration Name') self.verify_groups(container, ['Group A', 'Group B', 'New group'], []) def test_container_page_active_verticals_names_are_synced(self): """ Scenario: Ensure that the Content Experiment display synced vertical names and correct groups. Given I have a course with group configuration And I go to the Group Configuration page in Studio And I edit the name of the group configuration, add new group and remove old one And I change the name for the group "New group" to "Second Group" And I go to the Container page in Studio And I edit the Content Experiment Then I see the group configuration name is changed in `Group Configuration` dropdown And the group configuration name is changed on container page And I see the module has 2 active groups and one inactive And I see "Add missing groups" link exists When I click on "Add missing groups" link The I see the module has 3 active groups and one inactive """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group A'), Group("1", 'Group B'), Group("2", 'Group C')] ), ], }, }) # Add split test to vertical and assign newly created group configuration to it split_test = self._add_split_test_to_vertical(number=0, group_configuration_metadata={'user_partition_id': 0}) self.page.visit() config = self.page.experiment_group_configurations[0] config.edit() config.name = "Second Group Configuration Name" # `Group C` -> `Second Group` config.groups[2].name = "Second Group" # Add new group config.add_group() # Group D # Remove Group A config.groups[0].remove() # Save the configuration config.save() container = ContainerPage(self.browser, split_test.locator) container.visit() container.edit() component_editor = ComponentEditorView(self.browser, container.locator) self.assertEqual( "Second Group Configuration Name", component_editor.get_selected_option_text('Group Configuration') ) component_editor.cancel() self.assertIn( "Second Group Configuration Name", container.get_xblock_information_message() ) self.verify_groups( container, ['Group B', 'Second Group'], ['Group ID 0'], verify_missing_groups_not_present=False ) # Click the add button and verify that the groups were added on the page container.add_missing_groups() self.verify_groups(container, ['Group B', 'Second Group', 'Group D'], ['Group ID 0']) def test_can_cancel_creation_of_group_configuration(self): """ Scenario: Ensure that creation of the group configuration can be canceled correctly. Given I have a course without group configurations When I click button 'Create new Group Configuration' And I set new name and description, add 1 additional group And I click button 'Cancel' Then I see that there is no new group configurations in the course """ self.page.visit() self.assertEqual(len(self.page.experiment_group_configurations), 0) # Create new group configuration self.page.create_experiment_group_configuration() config = self.page.experiment_group_configurations[0] config.name = "Name of the Group Configuration" config.description = "Description of the group configuration." # Add new group config.add_group() # Group C # Cancel the configuration config.cancel() self.assertEqual(len(self.page.experiment_group_configurations), 0) def test_can_cancel_editing_of_group_configuration(self): """ Scenario: Ensure that editing of the group configuration can be canceled correctly. Given I have a course with group configuration When I go to the edit mode of the group configuration And I set new name and description, add 2 additional groups And I click button 'Cancel' Then I see that new changes were discarded """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')] ), create_user_partition_json( 1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')] ), ], }, }) self.page.visit() config = self.page.experiment_group_configurations[0] config.name = "New Group Configuration Name" config.description = "New Description of the group configuration." # Add 2 new groups config.add_group() # Group C config.add_group() # Group D # Cancel the configuration config.cancel() self._assert_fields( config, name="Name of the Group Configuration", description="Description of the group configuration.", groups=["Group 0", "Group 1"] ) def test_group_configuration_validation(self): """ Scenario: Ensure that validation of the group configuration works correctly. Given I have a course without group configurations And I create new group configuration with 2 default groups When I set only description and try to save Then I see error message "Group Configuration name is required." When I set a name And I delete the name of one of the groups and try to save Then I see error message "All groups must have a name" When I delete all the groups and try to save Then I see error message "There must be at least one group." When I add a group and try to save Then I see the group configuration is saved successfully """ def try_to_save_and_verify_error_message(message): # Try to save config.save() # Verify that configuration is still in editing mode self.assertEqual(config.mode, 'edit') # Verify error message self.assertEqual(message, config.validation_message) self.page.visit() # Create new group configuration self.page.create_experiment_group_configuration() # Leave empty required field config = self.page.experiment_group_configurations[0] config.description = "Description of the group configuration." try_to_save_and_verify_error_message("Group Configuration name is required.") # Set required field config.name = "Name of the Group Configuration" config.groups[1].name = '' try_to_save_and_verify_error_message("All groups must have a name.") config.groups[0].remove() config.groups[0].remove() try_to_save_and_verify_error_message("There must be at least one group.") config.add_group() # Save the configuration config.save() self._assert_fields( config, name="Name of the Group Configuration", description="Description of the group configuration.", groups=["Group A"] ) def test_group_configuration_empty_usage(self): """ Scenario: When group configuration is not used, ensure that the link to outline page works correctly. Given I have a course without group configurations And I create new group configuration with 2 default groups Then I see a link to the outline page When I click on the outline link Then I see the outline page """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")] ), ], }, }) # Go to the Group Configuration Page and click on outline anchor self.page.visit() config = self.page.experiment_group_configurations[0] config.toggle() config.click_outline_anchor() # Waiting for the page load and verify that we've landed on course outline page self.outline_page.wait_for_page() def test_group_configuration_non_empty_usage(self): """ Scenario: When group configuration is used, ensure that the links to units using a group configuration work correctly. Given I have a course without group configurations And I create new group configuration with 2 default groups And I create a unit and assign the newly created group configuration And open the Group Configuration page Then I see a link to the newly created unit When I click on the unit link Then I see correct unit page """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")] ), ], }, }) # Assign newly created group configuration to unit vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] self.course_fixture.create_xblock( vertical.locator, XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 0}) ) unit = CourseOutlineUnit(self.browser, vertical.locator) # Go to the Group Configuration Page and click unit anchor self.page.visit() config = self.page.experiment_group_configurations[0] config.toggle() usage = config.usages[0] config.click_unit_anchor() unit = ContainerPage(self.browser, vertical.locator) # Waiting for the page load and verify that we've landed on the unit page unit.wait_for_page() self.assertIn(unit.name, usage) def test_can_delete_unused_group_configuration(self): """ Scenario: Ensure that the user can delete unused group configuration. Given I have a course with 2 group configurations And I go to the Group Configuration page When I delete the Group Configuration with name "Configuration 1" Then I see that there is one Group Configuration When I edit the Group Configuration with name "Configuration 2" And I delete the Group Configuration with name "Configuration 2" Then I see that the are no Group Configurations """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Configuration 1', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')] ), create_user_partition_json( 1, 'Configuration 2', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')] ) ], }, }) self.page.visit() self.assertEqual(len(self.page.experiment_group_configurations), 2) config = self.page.experiment_group_configurations[1] # Delete first group configuration via detail view config.delete() self.assertEqual(len(self.page.experiment_group_configurations), 1) config = self.page.experiment_group_configurations[0] config.edit() self.assertFalse(config.delete_button_is_disabled) # Delete first group configuration via edit view config.delete() self.assertEqual(len(self.page.experiment_group_configurations), 0) def test_cannot_delete_used_group_configuration(self): """ Scenario: Ensure that the user cannot delete unused group configuration. Given I have a course with group configuration that is used in the Content Experiment When I go to the Group Configuration page Then I do not see delete button and I see a note about that When I edit the Group Configuration Then I do not see delete button and I see the note about that """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")] ) ], }, }) vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] self.course_fixture.create_xblock( vertical.locator, XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 0}) ) # Go to the Group Configuration Page and click unit anchor self.page.visit() config = self.page.experiment_group_configurations[0] self.assertTrue(config.delete_button_is_disabled) self.assertIn('Cannot delete when in use by an experiment', config.delete_note) config.edit() self.assertTrue(config.delete_button_is_disabled) self.assertIn('Cannot delete when in use by an experiment', config.delete_note) def test_easy_access_from_experiment(self): """ Scenario: When a Content Experiment uses a Group Configuration, ensure that the link to that Group Configuration works correctly. Given I have a course with two Group Configurations And Content Experiment is assigned to one Group Configuration Then I see a link to Group Configuration When I click on the Group Configuration link Then I see the Group Configurations page And I see that appropriate Group Configuration is expanded. """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")] ), create_user_partition_json( 1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')] ), ], }, }) # Assign newly created group configuration to unit vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] self.course_fixture.create_xblock( vertical.locator, XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 1}) ) unit = ContainerPage(self.browser, vertical.locator) unit.visit() experiment = unit.xblocks[0] group_configuration_link_name = experiment.group_configuration_link_name experiment.go_to_group_configuration_page() self.page.wait_for_page() # Appropriate Group Configuration is expanded. self.assertFalse(self.page.experiment_group_configurations[0].is_expanded) self.assertTrue(self.page.experiment_group_configurations[1].is_expanded) self.assertEqual( group_configuration_link_name, self.page.experiment_group_configurations[1].name ) def test_details_error_validation_message(self): """ Scenario: When a Content Experiment uses a Group Configuration, ensure that an error validation message appears if necessary. Given I have a course with a Group Configuration containing two Groups And a Content Experiment is assigned to that Group Configuration When I go to the Group Configuration Page Then I do not see a error icon and message in the Group Configuration details view. When I add a Group Then I see an error icon and message in the Group Configuration details view """ # Create group configuration and associated experiment config, _ = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B")], True) # Display details view config.toggle() # Check that error icon and message are not present self.assertFalse(config.details_error_icon_is_present) self.assertFalse(config.details_message_is_present) # Add a group config.toggle() config.edit() config.add_group() config.save() # Display details view config.toggle() # Check that error icon and message are present self.assertTrue(config.details_error_icon_is_present) self.assertTrue(config.details_message_is_present) self.assertIn( "This content experiment has issues that affect content visibility.", config.details_message_text ) def test_details_warning_validation_message(self): """ Scenario: When a Content Experiment uses a Group Configuration, ensure that a warning validation message appears if necessary. Given I have a course with a Group Configuration containing three Groups And a Content Experiment is assigned to that Group Configuration When I go to the Group Configuration Page Then I do not see a warning icon and message in the Group Configuration details view. When I remove a Group Then I see a warning icon and message in the Group Configuration details view """ # Create group configuration and associated experiment config, _ = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B"), Group("2", "Group C")], True) # Display details view config.toggle() # Check that warning icon and message are not present self.assertFalse(config.details_warning_icon_is_present) self.assertFalse(config.details_message_is_present) # Remove a group config.toggle() config.edit() config.groups[2].remove() config.save() # Display details view config.toggle() # Check that warning icon and message are present self.assertTrue(config.details_warning_icon_is_present) self.assertTrue(config.details_message_is_present) self.assertIn( "This content experiment has issues that affect content visibility.", config.details_message_text ) def test_edit_warning_message_empty_usage(self): """ Scenario: When a Group Configuration is not used, ensure that there are no warning icon and message. Given I have a course with a Group Configuration containing two Groups When I edit the Group Configuration Then I do not see a warning icon and message """ # Create a group configuration with no associated experiment and display edit view config = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B")], False) config.edit() # Check that warning icon and message are not present self.assertFalse(config.edit_warning_icon_is_present) self.assertFalse(config.edit_warning_message_is_present) def test_edit_warning_message_non_empty_usage(self): """ Scenario: When a Group Configuration is used, ensure that there are a warning icon and message. Given I have a course with a Group Configuration containing two Groups When I edit the Group Configuration Then I see a warning icon and message """ # Create a group configuration with an associated experiment and display edit view config, _ = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B")], True) config.edit() # Check that warning icon and message are present self.assertTrue(config.edit_warning_icon_is_present) self.assertTrue(config.edit_warning_message_is_present) self.assertIn( "This configuration is currently used in content experiments. If you make changes to the groups, you may need to edit those experiments.", config.edit_warning_message_text ) def publish_unit_and_verify_groups_in_lms(self, courseware_page, group_names, publish=True): """ Publish first unit in LMS and verify that Courseware page has given Groups """ self.publish_unit_in_lms_and_view(courseware_page, publish) self.assertEqual(u'split_test', courseware_page.xblock_component_type()) self.assertTrue(courseware_page.q(css=".split-test-select").is_present()) rendered_group_names = self.get_select_options(page=courseware_page, selector=".split-test-select") self.assertListEqual(group_names, rendered_group_names) def test_split_test_LMS_staff_view(self): """ Scenario: Ensure that split test is correctly rendered in LMS staff mode as it is and after inactive group removal. Given I have a course with group configurations and split test that assigned to first group configuration Then I publish split test and view it in LMS in staff view And it is rendered correctly Then I go to group configuration and delete group Then I publish split test and view it in LMS in staff view And it is rendered correctly Then I go to split test and delete inactive vertical Then I publish unit and view unit in LMS in staff view And it is rendered correctly """ config, split_test = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B"), Group("2", "Group C")], True) container = ContainerPage(self.browser, split_test.locator) # render in LMS correctly courseware_page = CoursewarePage(self.browser, self.course_id) self.publish_unit_and_verify_groups_in_lms(courseware_page, [u'Group A', u'Group B', u'Group C']) # I go to group configuration and delete group self.page.visit() self.page.q(css='.group-toggle').first.click() config.edit() config.groups[2].remove() config.save() self.page.q(css='.group-toggle').first.click() self._assert_fields(config, name="Name", description="Description", groups=["Group A", "Group B"]) self.browser.close() self.browser.switch_to_window(self.browser.window_handles[0]) # render in LMS to see how inactive vertical is rendered self.publish_unit_and_verify_groups_in_lms( courseware_page, [u'Group A', u'Group B', u'Group ID 2 (inactive)'], publish=False ) self.browser.close() self.browser.switch_to_window(self.browser.window_handles[0]) # I go to split test and delete inactive vertical container.visit() container.delete(0) # render in LMS again self.publish_unit_and_verify_groups_in_lms(courseware_page, [u'Group A', u'Group B'])
class CoursewareSearchTest(UniqueCourseTest): """ Test courseware search. """ USERNAME = '******' EMAIL = '*****@*****.**' STAFF_USERNAME = "******" STAFF_EMAIL = "*****@*****.**" HTML_CONTENT = """ Someday I'll wish upon a star And wake up where the clouds are far Behind me. Where troubles melt like lemon drops Away above the chimney tops That's where you'll find me. """ SEARCH_STRING = "chimney" EDITED_CHAPTER_NAME = "Section 2 - edited" EDITED_SEARCH_STRING = "edited" TEST_INDEX_FILENAME = "test_root/index_file.dat" shard = 5 def setUp(self): """ Create search page and course content to search """ # create test file in which index for this test will live with open(self.TEST_INDEX_FILENAME, "w+") as index_file: json.dump({}, index_file) self.addCleanup(remove_file, self.TEST_INDEX_FILENAME) super(CoursewareSearchTest, self).setUp() self.course_home_page = CourseHomePage(self.browser, self.course_id) self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) 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', 'Section 1').add_children( XBlockFixtureDesc('sequential', 'Subsection 1') ) ).add_children( XBlockFixtureDesc('chapter', 'Section 2').add_children( XBlockFixtureDesc('sequential', 'Subsection 2') ) ).install() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ LogoutPage(self.browser).visit() AutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit() def _studio_publish_content(self, section_index): """ Publish content on studio course page under specified section """ self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.studio_course_outline.visit() subsection = self.studio_course_outline.section_at(section_index).subsection_at(0) subsection.expand_subsection() unit = subsection.unit_at(0) unit.publish() def _studio_edit_chapter_name(self, section_index): """ Edit chapter name on studio course page under specified section """ self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.studio_course_outline.visit() section = self.studio_course_outline.section_at(section_index) section.change_name(self.EDITED_CHAPTER_NAME) def _studio_add_content(self, section_index): """ Add content on studio course page under specified section """ self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) # create a unit in course outline self.studio_course_outline.visit() subsection = self.studio_course_outline.section_at(section_index).subsection_at(0) subsection.expand_subsection() subsection.add_unit() # got to unit and create an HTML component and save (not publish) unit_page = ContainerPage(self.browser, None) unit_page.wait_for_page() add_html_component(unit_page, 0) unit_page.wait_for_element_presence('.edit-button', 'Edit button is visible') click_css(unit_page, '.edit-button', 0, require_notification=False) unit_page.wait_for_element_visibility('.modal-editor', 'Modal editor is visible') type_in_codemirror(unit_page, 0, self.HTML_CONTENT) click_css(unit_page, '.action-save', 0) def _studio_reindex(self): """ Reindex course content on studio course page """ self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.studio_course_outline.visit() self.studio_course_outline.start_reindex() self.studio_course_outline.wait_for_ajax() def _search_for_content(self, search_term): """ Login and search for specific content Arguments: search_term - term to be searched for Returns: (bool) True if search term is found in resulting content; False if not found """ self._auto_auth(self.USERNAME, self.EMAIL, False) self.course_home_page.visit() course_search_results_page = self.course_home_page.search_for_term(search_term) if len(course_search_results_page.search_results.html) > 0: search_string = course_search_results_page.search_results.html[0] else: search_string = "" return search_term in search_string # TODO: TNL-6546: Remove usages of sidebar search def _search_for_content_in_sidebar(self, search_term, perform_auto_auth=True): """ Login and search for specific content in the legacy sidebar search Arguments: search_term - term to be searched for perform_auto_auth - if False, skip auto_auth call. Returns: (bool) True if search term is found in resulting content; False if not found """ if perform_auto_auth: self._auto_auth(self.USERNAME, self.EMAIL, False) self.courseware_search_page = CoursewareSearchPage(self.browser, self.course_id) self.courseware_search_page.visit() self.courseware_search_page.search_for_term(search_term) return search_term in self.courseware_search_page.search_results.html[0] def test_search(self): """ Make sure that you can search for something. """ # Create content in studio without publishing. self._studio_add_content(0) # Do a search, there should be no results shown. self.assertFalse(self._search_for_content(self.SEARCH_STRING)) # Do a search in the legacy sidebar, there should be no results shown. self.assertFalse(self._search_for_content_in_sidebar(self.SEARCH_STRING, False)) # Publish in studio to trigger indexing. self._studio_publish_content(0) # Do the search again, this time we expect results. self.assertTrue(self._search_for_content(self.SEARCH_STRING)) # Do the search again in the legacy sidebar, this time we expect results. self.assertTrue(self._search_for_content_in_sidebar(self.SEARCH_STRING, False))
class SplitTestCoursewareSearchTest(ContainerBase): """ Test courseware search on Split Test Module. """ shard = 1 USERNAME = '******' EMAIL = '*****@*****.**' TEST_INDEX_FILENAME = "test_root/index_file.dat" def setUp(self, is_staff=True): """ Create search page and course content to search """ # create test file in which index for this test will live with open(self.TEST_INDEX_FILENAME, "w+") as index_file: json.dump({}, index_file) self.addCleanup(remove_file, self.TEST_INDEX_FILENAME) super(SplitTestCoursewareSearchTest, self).setUp(is_staff=is_staff) self.staff_user = self.user self.course_home_page = CourseHomePage(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._create_group_configuration() self._studio_reindex() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ LogoutPage(self.browser).visit() AutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit() def _studio_reindex(self): """ Reindex course content on studio course page """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], True) self.studio_course_outline.visit() self.studio_course_outline.start_reindex() self.studio_course_outline.wait_for_ajax() def _create_group_configuration(self): """ Create a group configuration for course """ # pylint: disable=protected-access self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Configuration A/B", "Content Group Partition.", [Group("0", "Group A"), Group("1", "Group B")] ) ] } }) def populate_course_fixture(self, course_fixture): """ Populate the children of the test course fixture. """ course_fixture.add_advanced_settings({ u"advanced_modules": {"value": ["split_test"]}, }) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc( 'html', 'VISIBLE TO A', data='<html>VISIBLE TO A</html>', metadata={"group_access": {0: [0]}} ), XBlockFixtureDesc( 'html', 'VISIBLE TO B', data='<html>VISIBLE TO B</html>', metadata={"group_access": {0: [1]}} ) ) ) ) ) def test_search_for_experiment_content_user_assigned_to_one_group(self): """ Test user can search for experiment content restricted to his group when assigned to just one experiment group """ self._auto_auth(self.USERNAME, self.EMAIL, False) self.course_home_page.visit() course_search_results_page = self.course_home_page.search_for_term("VISIBLE TO") assert "result-excerpt" in course_search_results_page.search_results.html[0]
class XBlockAcidBase(AcceptanceTest): """ Base class for tests that verify that XBlock integration is working correctly """ __test__ = False def setUp(self): """ Create a unique identifier for the course used in this test. """ # Ensure that the superclass sets up super(XBlockAcidBase, self).setUp() # Define a unique course identifier self.course_info = { "org": "test_org", "number": "course_" + self.unique_id[:5], "run": "test_" + self.unique_id, "display_name": "Test Course " + self.unique_id, } self.outline = CourseOutlinePage( self.browser, self.course_info["org"], self.course_info["number"], self.course_info["run"] ) self.course_id = "{org}.{number}.{run}".format(**self.course_info) self.setup_fixtures() self.auth_page = AutoAuthPage( self.browser, staff=False, username=self.user.get("username"), email=self.user.get("email"), password=self.user.get("password"), ) self.auth_page.visit() def validate_acid_block_preview(self, acid_block): """ Validate the Acid Block's preview """ self.assertTrue(acid_block.init_fn_passed) self.assertTrue(acid_block.resource_url_passed) self.assertTrue(acid_block.scope_passed("user_state")) self.assertTrue(acid_block.scope_passed("user_state_summary")) self.assertTrue(acid_block.scope_passed("preferences")) self.assertTrue(acid_block.scope_passed("user_info")) def test_acid_block_preview(self): """ Verify that all expected acid block tests pass in studio preview """ self.outline.visit() subsection = self.outline.section("Test Section").subsection("Test Subsection") unit = subsection.expand_subsection().unit("Test Unit").go_to() acid_block = AcidView(self.browser, unit.xblocks[0].preview_selector) self.validate_acid_block_preview(acid_block) def test_acid_block_editor(self): """ Verify that all expected acid block tests pass in studio editor """ self.outline.visit() subsection = self.outline.section("Test Section").subsection("Test Subsection") unit = subsection.expand_subsection().unit("Test Unit").go_to() acid_block = AcidView(self.browser, unit.xblocks[0].edit().editor_selector) self.assertTrue(acid_block.init_fn_passed) self.assertTrue(acid_block.resource_url_passed)
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 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 CMSVideoBaseTest(UniqueCourseTest): """ CMS Video Module Base Test Class """ def setUp(self): """ Initialization of pages and course fixture for tests """ super(CMSVideoBaseTest, self).setUp() self.video = VideoComponentPage(self.browser) # This will be initialized later self.unit_page = None self.outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) self.assets = [] self.metadata = None self.addCleanup(YouTubeStubConfig.reset) def _create_course_unit(self, youtube_stub_config=None, subtitles=False): """ Create a Studio Video Course Unit and Navigate to it. Arguments: youtube_stub_config (dict) subtitles (bool) """ if youtube_stub_config: YouTubeStubConfig.configure(youtube_stub_config) if subtitles: self.assets.append('subs_3_yD_cEKoCk.srt.sjson') self.navigate_to_course_unit() def _create_video(self): """ Create Xblock Video Component. """ self.video.create_video() video_xblocks = self.video.xblocks() # Total video xblock components count should be equals to 2 # Why 2? One video component is created by default for each test. Please see # test_studio_video_module.py:CMSVideoTest._create_course_unit # And we are creating second video component here. self.assertEqual(video_xblocks, 2) def _install_course_fixture(self): """ Prepare for tests by creating a course with a section, subsection, and unit. Performs the following: Create a course with a section, subsection, and unit Create a user and make that user a course author Log the user into studio """ if self.assets: self.course_fixture.add_asset(self.assets) # Create course with Video component self.course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc('video', 'Video', metadata=self.metadata) ) ) ) ).install() # Auto login and register the course AutoAuthPage( self.browser, staff=False, username=self.course_fixture.user.get('username'), email=self.course_fixture.user.get('email'), password=self.course_fixture.user.get('password') ).visit() def _navigate_to_course_unit_page(self): """ Open the course from the dashboard and expand the section and subsection and click on the Unit link The end result is the page where the user is editing the newly created unit """ # Visit Course Outline page self.outline.visit() # Visit Unit page self.unit_page = self.outline.section('Test Section').subsection('Test Subsection').expand_subsection().unit( 'Test Unit').go_to() self.video.wait_for_video_component_render() def navigate_to_course_unit(self): """ Install the course with required components and navigate to course unit page """ self._install_course_fixture() self._navigate_to_course_unit_page() def edit_component(self, xblock_index=1): """ Open component Edit Dialog for first component on page. Arguments: xblock_index: number starting from 1 (0th entry is the unit page itself) """ self.unit_page.xblocks[xblock_index].edit() EmptyPromise( lambda: self.video.q(css='div.basic_metadata_edit').visible, "Wait for the basic editor to be open", timeout=5 ).fulfill() def open_advanced_tab(self): """ Open components advanced tab. """ # The 0th entry is the unit page itself. self.unit_page.xblocks[1].open_advanced_tab() def open_basic_tab(self): """ Open components basic tab. """ # The 0th entry is the unit page itself. self.unit_page.xblocks[1].open_basic_tab() def save_unit_settings(self): """ Save component settings. """ # The 0th entry is the unit page itself. self.unit_page.xblocks[1].save_settings()
class XBlockAcidBase(AcceptanceTest): """ Base class for tests that verify that XBlock integration is working correctly """ __test__ = False def setUp(self): """ Create a unique identifier for the course used in this test. """ # Ensure that the superclass sets up super(XBlockAcidBase, self).setUp() # Define a unique course identifier self.course_info = { 'org': 'test_org', 'number': 'course_' + self.unique_id[:5], 'run': 'test_' + self.unique_id, 'display_name': 'Test Course ' + self.unique_id } self.outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.course_id = '{org}.{number}.{run}'.format(**self.course_info) self.setup_fixtures() self.auth_page = AutoAuthPage( self.browser, staff=False, username=self.user.get('username'), email=self.user.get('email'), password=self.user.get('password') ) self.auth_page.visit() def validate_acid_block_preview(self, acid_block): """ Validate the Acid Block's preview """ self.assertTrue(acid_block.init_fn_passed) self.assertTrue(acid_block.resource_url_passed) self.assertTrue(acid_block.scope_passed('user_state')) self.assertTrue(acid_block.scope_passed('user_state_summary')) self.assertTrue(acid_block.scope_passed('preferences')) self.assertTrue(acid_block.scope_passed('user_info')) def test_acid_block_preview(self): """ Verify that all expected acid block tests pass in studio preview """ self.outline.visit() subsection = self.outline.section('Test Section').subsection('Test Subsection') unit = subsection.expand_subsection().unit('Test Unit').go_to() acid_block = AcidView(self.browser, unit.xblocks[0].preview_selector) self.validate_acid_block_preview(acid_block) def test_acid_block_editor(self): """ Verify that all expected acid block tests pass in studio editor """ self.outline.visit() subsection = self.outline.section('Test Section').subsection('Test Subsection') unit = subsection.expand_subsection().unit('Test Unit').go_to() acid_block = AcidView(self.browser, unit.xblocks[0].edit().editor_selector) self.assertTrue(acid_block.init_fn_passed) self.assertTrue(acid_block.resource_url_passed)
class CoursewareSearchCohortTest(ContainerBase): """ Test courseware search. """ TEST_INDEX_FILENAME = "test_root/index_file.dat" def setUp(self, is_staff=True): """ Create search page and course content to search """ # create test file in which index for this test will live with open(self.TEST_INDEX_FILENAME, "w+") as index_file: json.dump({}, index_file) self.addCleanup(remove_file, self.TEST_INDEX_FILENAME) super(CoursewareSearchCohortTest, self).setUp(is_staff=is_staff) self.staff_user = self.user self.course_outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.content_group_a = "Content Group A" self.content_group_b = "Content Group B" # Create a student who will be in "Cohort A" self.cohort_a_student_username = "******" + str(uuid.uuid4().hex)[:12] self.cohort_a_student_email = self.cohort_a_student_username + "@example.com" StudioAutoAuthPage( self.browser, username=self.cohort_a_student_username, email=self.cohort_a_student_email, no_login=True ).visit() # Create a student who will be in "Cohort B" self.cohort_b_student_username = "******" + str(uuid.uuid4().hex)[:12] self.cohort_b_student_email = self.cohort_b_student_username + "@example.com" StudioAutoAuthPage( self.browser, username=self.cohort_b_student_username, email=self.cohort_b_student_email, no_login=True ).visit() # Create a student who will end up in the default cohort group self.cohort_default_student_username = "******" self.cohort_default_student_email = "*****@*****.**" StudioAutoAuthPage( self.browser, username=self.cohort_default_student_username, email=self.cohort_default_student_email, no_login=True ).visit() self.courseware_search_page = CoursewareSearchPage(self.browser, self.course_id) # Enable Cohorting and assign cohorts and content groups self._auto_auth(self.staff_user["username"], self.staff_user["email"], True) self.enable_cohorting(self.course_fixture) self.create_content_groups() self.link_html_to_content_groups_and_publish() self.create_cohorts_and_assign_students() self._studio_reindex() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ LogoutPage(self.browser).visit() StudioAutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit() def _studio_reindex(self): """ Reindex course content on studio course page """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], True) self.course_outline.visit() self.course_outline.start_reindex() self.course_outline.wait_for_ajax() def _goto_staff_page(self): """ Open staff page with assertion """ self.courseware_search_page.visit() staff_page = StaffPage(self.browser, self.course_id) self.assertEqual(staff_page.staff_view_mode, 'Staff') return staff_page def populate_course_fixture(self, course_fixture): """ Populate the children of the test course fixture. """ self.group_a_html = 'GROUPACONTENT' self.group_b_html = 'GROUPBCONTENT' self.group_a_and_b_html = 'GROUPAANDBCONTENT' self.visible_to_all_html = 'VISIBLETOALLCONTENT' course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc('html', self.group_a_html, data='<html>GROUPACONTENT</html>'), XBlockFixtureDesc('html', self.group_b_html, data='<html>GROUPBCONTENT</html>'), XBlockFixtureDesc('html', self.group_a_and_b_html, data='<html>GROUPAANDBCONTENT</html>'), XBlockFixtureDesc('html', self.visible_to_all_html, data='<html>VISIBLETOALLCONTENT</html>') ) ) ) ) def enable_cohorting(self, course_fixture): """ Enables cohorting for the current course. """ url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access data = json.dumps({'is_cohorted': True}) response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers) self.assertTrue(response.ok, "Failed to enable cohorts") def create_content_groups(self): """ Creates two content groups in Studio Group Configurations Settings. """ group_configurations_page = GroupConfigurationsPage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) group_configurations_page.visit() group_configurations_page.create_first_content_group() config = group_configurations_page.content_groups[0] config.name = self.content_group_a config.save() group_configurations_page.add_content_group() config = group_configurations_page.content_groups[1] config.name = self.content_group_b config.save() def link_html_to_content_groups_and_publish(self): """ Updates 3 of the 4 existing html to limit their visibility by content group. Publishes the modified units. """ container_page = self.go_to_unit_page() def set_visibility(html_block_index, content_group, second_content_group=None): """ Set visibility on html blocks to specified groups. """ html_block = container_page.xblocks[html_block_index] html_block.edit_visibility() if second_content_group: ComponentVisibilityEditorView(self.browser, html_block.locator).select_option( second_content_group, save=False ) ComponentVisibilityEditorView(self.browser, html_block.locator).select_option(content_group) set_visibility(1, self.content_group_a) set_visibility(2, self.content_group_b) set_visibility(3, self.content_group_a, self.content_group_b) set_visibility(4, 'All Students and Staff') # Does not work without this container_page.publish_action.click() def create_cohorts_and_assign_students(self): """ Adds 2 manual cohorts, linked to content groups, to the course. Each cohort is assigned one student. """ instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id) instructor_dashboard_page.visit() cohort_management_page = instructor_dashboard_page.select_cohort_management() def add_cohort_with_student(cohort_name, content_group, student): """ Create cohort and assign student to it. """ cohort_management_page.add_cohort(cohort_name, content_group=content_group) cohort_management_page.add_students_to_selected_cohort([student]) add_cohort_with_student("Cohort A", self.content_group_a, self.cohort_a_student_username) add_cohort_with_student("Cohort B", self.content_group_b, self.cohort_b_student_username) cohort_management_page.wait_for_ajax() def test_page_existence(self): """ Make sure that the page is accessible. """ self._auto_auth(self.cohort_default_student_username, self.cohort_default_student_email, False) self.courseware_search_page.visit() def test_cohorted_search_user_a_a_content(self): """ Test user can search content restricted to his cohort. """ self._auto_auth(self.cohort_a_student_username, self.cohort_a_student_email, False) self.courseware_search_page.visit() self.courseware_search_page.search_for_term(self.group_a_html) assert self.group_a_html in self.courseware_search_page.search_results.html[0] def test_cohorted_search_user_b_a_content(self): """ Test user can not search content restricted to his cohort. """ self._auto_auth(self.cohort_b_student_username, self.cohort_b_student_email, False) self.courseware_search_page.visit() self.courseware_search_page.search_for_term(self.group_a_html) assert self.group_a_html not in self.courseware_search_page.search_results.html[0] def test_cohorted_search_user_default_ab_content(self): """ Test user not enrolled in any cohorts can't see any of restricted content. """ self._auto_auth(self.cohort_default_student_username, self.cohort_default_student_email, False) self.courseware_search_page.visit() self.courseware_search_page.search_for_term(self.group_a_and_b_html) assert self.group_a_and_b_html not in self.courseware_search_page.search_results.html[0] def test_cohorted_search_user_default_all_content(self): """ Test user can search public content if cohorts used on course. """ self._auto_auth(self.cohort_default_student_username, self.cohort_default_student_email, False) self.courseware_search_page.visit() self.courseware_search_page.search_for_term(self.visible_to_all_html) assert self.visible_to_all_html in self.courseware_search_page.search_results.html[0] def test_cohorted_search_user_staff_all_content(self): """ Test staff user can search all public content if cohorts used on course. """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], False) self._goto_staff_page().set_staff_view_mode('Staff') self.courseware_search_page.search_for_term(self.visible_to_all_html) assert self.visible_to_all_html in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_a_and_b_html) assert self.group_a_and_b_html in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_a_html) assert self.group_a_html in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_b_html) assert self.group_b_html in self.courseware_search_page.search_results.html[0] def test_cohorted_search_user_staff_masquerade_student_content(self): """ Test staff user can search just student public content if selected from preview menu. """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], False) self._goto_staff_page().set_staff_view_mode('Student') self.courseware_search_page.search_for_term(self.visible_to_all_html) assert self.visible_to_all_html in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_a_and_b_html) assert self.group_a_and_b_html not in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_a_html) assert self.group_a_html not in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_b_html) assert self.group_b_html not in self.courseware_search_page.search_results.html[0] def test_cohorted_search_user_staff_masquerade_cohort_content(self): """ Test staff user can search cohort and public content if selected from preview menu. """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], False) self._goto_staff_page().set_staff_view_mode('Student in ' + self.content_group_a) self.courseware_search_page.search_for_term(self.visible_to_all_html) assert self.visible_to_all_html in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_a_and_b_html) assert self.group_a_and_b_html in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_a_html) assert self.group_a_html in self.courseware_search_page.search_results.html[0] self.courseware_search_page.clear_search() self.courseware_search_page.search_for_term(self.group_b_html) assert self.group_b_html not in self.courseware_search_page.search_results.html[0]
class CoursewareSearchCohortTest(ContainerBase, CohortTestMixin): """ Test courseware search. """ shard = 1 TEST_INDEX_FILENAME = "test_root/index_file.dat" def setUp(self, is_staff=True): """ Create search page and course content to search """ # create test file in which index for this test will live with open(self.TEST_INDEX_FILENAME, "w+") as index_file: json.dump({}, index_file) self.addCleanup(remove_file, self.TEST_INDEX_FILENAME) super(CoursewareSearchCohortTest, self).setUp(is_staff=is_staff) self.staff_user = self.user self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.content_group_a = "Content Group A" self.content_group_b = "Content Group B" # Create a student who will be in "Cohort A" self.cohort_a_student_username = "******" + str(uuid.uuid4().hex)[:12] self.cohort_a_student_email = self.cohort_a_student_username + "@example.com" AutoAuthPage( self.browser, username=self.cohort_a_student_username, email=self.cohort_a_student_email, no_login=True ).visit() # Create a student who will be in "Cohort B" self.cohort_b_student_username = "******" + str(uuid.uuid4().hex)[:12] self.cohort_b_student_email = self.cohort_b_student_username + "@example.com" AutoAuthPage( self.browser, username=self.cohort_b_student_username, email=self.cohort_b_student_email, no_login=True ).visit() # Create a student who will end up in the default cohort group self.cohort_default_student_username = "******" self.cohort_default_student_email = "*****@*****.**" AutoAuthPage( self.browser, username=self.cohort_default_student_username, email=self.cohort_default_student_email, no_login=True ).visit() self.course_home_page = CourseHomePage(self.browser, self.course_id) # Enable Cohorting and assign cohorts and content groups self._auto_auth(self.staff_user["username"], self.staff_user["email"], True) self.enable_cohorting(self.course_fixture) self.create_content_groups() self.link_html_to_content_groups_and_publish() self.create_cohorts_and_assign_students() self._studio_reindex() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ LogoutPage(self.browser).visit() AutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit() def _studio_reindex(self): """ Reindex course content on studio course page """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], True) self.studio_course_outline.visit() self.studio_course_outline.start_reindex() self.studio_course_outline.wait_for_ajax() def _goto_staff_page(self): """ Open staff page with assertion """ self.course_home_page.visit() self.course_home_page.resume_course_from_header() staff_page = StaffCoursewarePage(self.browser, self.course_id) self.assertEqual(staff_page.staff_view_mode, 'Staff') return staff_page def _search_for_term(self, term): """ Search for term in course and return results. """ self.course_home_page.visit() course_search_results_page = self.course_home_page.search_for_term(term) results = course_search_results_page.search_results.html return results[0] if len(results) > 0 else [] def populate_course_fixture(self, course_fixture): """ Populate the children of the test course fixture. """ self.group_a_html = 'GROUPACONTENT' self.group_b_html = 'GROUPBCONTENT' self.group_a_and_b_html = 'GROUPAANDBCONTENT' self.visible_to_all_html = 'VISIBLETOALLCONTENT' course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit').add_children( XBlockFixtureDesc('html', self.group_a_html, data='<html>GROUPACONTENT</html>'), XBlockFixtureDesc('html', self.group_b_html, data='<html>GROUPBCONTENT</html>'), XBlockFixtureDesc('html', self.group_a_and_b_html, data='<html>GROUPAANDBCONTENT</html>'), XBlockFixtureDesc('html', self.visible_to_all_html, data='<html>VISIBLETOALLCONTENT</html>') ) ) ) ) def create_content_groups(self): """ Creates two content groups in Studio Group Configurations Settings. """ group_configurations_page = GroupConfigurationsPage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) group_configurations_page.visit() group_configurations_page.create_first_content_group() config = group_configurations_page.content_groups[0] config.name = self.content_group_a config.save() group_configurations_page.add_content_group() config = group_configurations_page.content_groups[1] config.name = self.content_group_b config.save() def link_html_to_content_groups_and_publish(self): """ Updates 3 of the 4 existing html to limit their visibility by content group. Publishes the modified units. """ container_page = self.go_to_unit_page() def set_visibility(html_block_index, groups): """ Set visibility on html blocks to specified groups. """ html_block = container_page.xblocks[html_block_index] html_block.edit_visibility() visibility_dialog = XBlockVisibilityEditorView(self.browser, html_block.locator) visibility_dialog.select_groups_in_partition_scheme(visibility_dialog.CONTENT_GROUP_PARTITION, groups) set_visibility(1, [self.content_group_a]) set_visibility(2, [self.content_group_b]) set_visibility(3, [self.content_group_a, self.content_group_b]) container_page.publish() def create_cohorts_and_assign_students(self): """ Adds 2 manual cohorts, linked to content groups, to the course. Each cohort is assigned one student. """ instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id) instructor_dashboard_page.visit() cohort_management_page = instructor_dashboard_page.select_cohort_management() def add_cohort_with_student(cohort_name, content_group, student): """ Create cohort and assign student to it. """ cohort_management_page.add_cohort(cohort_name, content_group=content_group) cohort_management_page.add_students_to_selected_cohort([student]) add_cohort_with_student("Cohort A", self.content_group_a, self.cohort_a_student_username) add_cohort_with_student("Cohort B", self.content_group_b, self.cohort_b_student_username) cohort_management_page.wait_for_ajax() def test_cohorted_search_user_a_a_content(self): """ Test user can search content restricted to his cohort. """ self._auto_auth(self.cohort_a_student_username, self.cohort_a_student_email, False) search_results = self._search_for_term(self.group_a_html) assert self.group_a_html in search_results def test_cohorted_search_user_b_a_content(self): """ Test user can not search content restricted to his cohort. """ self._auto_auth(self.cohort_b_student_username, self.cohort_b_student_email, False) search_results = self._search_for_term(self.group_a_html) assert self.group_a_html not in search_results def test_cohorted_search_user_staff_all_content(self): """ Test staff user can search all public content if cohorts used on course. """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], False) self._goto_staff_page().set_staff_view_mode('Staff') search_results = self._search_for_term(self.visible_to_all_html) assert self.visible_to_all_html in search_results search_results = self._search_for_term(self.group_a_and_b_html) assert self.group_a_and_b_html in search_results search_results = self._search_for_term(self.group_a_html) assert self.group_a_html in search_results search_results = self._search_for_term(self.group_b_html) assert self.group_b_html in search_results def test_cohorted_search_user_staff_masquerade_student_content(self): """ Test staff user can search just student public content if selected from preview menu. NOTE: Although it would be wise to combine these masquerading tests into a single test due to expensive setup, doing so revealed a very low priority bug where searching seems to stick/cache the access of the first user who searches for future searches. """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], False) self._goto_staff_page().set_staff_view_mode('Learner') search_results = self._search_for_term(self.visible_to_all_html) assert self.visible_to_all_html in search_results search_results = self._search_for_term(self.group_a_and_b_html) assert self.group_a_and_b_html not in search_results search_results = self._search_for_term(self.group_a_html) assert self.group_a_html not in search_results search_results = self._search_for_term(self.group_b_html) assert self.group_b_html not in search_results def test_cohorted_search_user_staff_masquerade_cohort_content(self): """ Test staff user can search cohort and public content if selected from preview menu. """ self._auto_auth(self.staff_user["username"], self.staff_user["email"], False) self._goto_staff_page().set_staff_view_mode('Learner in ' + self.content_group_a) search_results = self._search_for_term(self.visible_to_all_html) assert self.visible_to_all_html in search_results search_results = self._search_for_term(self.group_a_and_b_html) assert self.group_a_and_b_html in search_results search_results = self._search_for_term(self.group_a_html) assert self.group_a_html in search_results search_results = self._search_for_term(self.group_b_html) assert self.group_b_html not in search_results
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 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 CoursewareSearchTest(UniqueCourseTest): """ Test courseware search. """ USERNAME = "******" EMAIL = "*****@*****.**" STAFF_USERNAME = "******" STAFF_EMAIL = "*****@*****.**" HTML_CONTENT = """ Someday I'll wish upon a star And wake up where the clouds are far Behind me. Where troubles melt like lemon drops Away above the chimney tops That's where you'll find me. """ SEARCH_STRING = "chimney" EDITED_CHAPTER_NAME = "Section 2 - edited" EDITED_SEARCH_STRING = "edited" TEST_INDEX_FILENAME = "test_root/index_file.dat" def setUp(self): """ Create search page and course content to search """ # create test file in which index for this test will live with open(self.TEST_INDEX_FILENAME, "w+") as index_file: json.dump({}, index_file) self.addCleanup(remove_file, self.TEST_INDEX_FILENAME) super(CoursewareSearchTest, self).setUp() self.courseware_search_page = CoursewareSearchPage(self.browser, self.course_id) self.course_outline = CourseOutlinePage( self.browser, self.course_info["org"], self.course_info["number"], self.course_info["run"] ) 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", "Section 1").add_children(XBlockFixtureDesc("sequential", "Subsection 1")) ).add_children( XBlockFixtureDesc("chapter", "Section 2").add_children(XBlockFixtureDesc("sequential", "Subsection 2")) ).install() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ LogoutPage(self.browser).visit() AutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit() def _studio_publish_content(self, section_index): """ Publish content on studio course page under specified section """ self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.course_outline.visit() subsection = self.course_outline.section_at(section_index).subsection_at(0) subsection.expand_subsection() unit = subsection.unit_at(0) unit.publish() def _studio_edit_chapter_name(self, section_index): """ Edit chapter name on studio course page under specified section """ self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.course_outline.visit() section = self.course_outline.section_at(section_index) section.change_name(self.EDITED_CHAPTER_NAME) def _studio_add_content(self, section_index): """ Add content on studio course page under specified section """ self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) # create a unit in course outline self.course_outline.visit() subsection = self.course_outline.section_at(section_index).subsection_at(0) subsection.expand_subsection() subsection.add_unit() # got to unit and create an HTML component and save (not publish) unit_page = ContainerPage(self.browser, None) unit_page.wait_for_page() add_html_component(unit_page, 0) unit_page.wait_for_element_presence(".edit-button", "Edit button is visible") click_css(unit_page, ".edit-button", 0, require_notification=False) unit_page.wait_for_element_visibility(".modal-editor", "Modal editor is visible") type_in_codemirror(unit_page, 0, self.HTML_CONTENT) click_css(unit_page, ".action-save", 0) def _studio_reindex(self): """ Reindex course content on studio course page """ self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.course_outline.visit() self.course_outline.start_reindex() self.course_outline.wait_for_ajax() def _search_for_content(self, search_term): """ Login and search for specific content Arguments: search_term - term to be searched for Returns: (bool) True if search term is found in resulting content; False if not found """ self._auto_auth(self.USERNAME, self.EMAIL, False) self.courseware_search_page.visit() self.courseware_search_page.search_for_term(search_term) return search_term in self.courseware_search_page.search_results.html[0] def test_page_existence(self): """ Make sure that the page is accessible. """ self._auto_auth(self.USERNAME, self.EMAIL, False) self.courseware_search_page.visit() def test_search(self): """ Make sure that you can search for something. """ # Create content in studio without publishing. self._studio_add_content(0) # Do a search, there should be no results shown. self.assertFalse(self._search_for_content(self.SEARCH_STRING)) # Publish in studio to trigger indexing. self._studio_publish_content(0) # Do the search again, this time we expect results. self.assertTrue(self._search_for_content(self.SEARCH_STRING)) def test_reindex(self): """ Make sure new content gets reindexed on button press. """ # Create content in studio without publishing. self._studio_add_content(1) # Do a search, there should be no results shown. self.assertFalse(self._search_for_content(self.EDITED_SEARCH_STRING)) # Publish in studio to trigger indexing, and edit chapter name afterwards. self._studio_publish_content(1) # Do a ReIndex from studio to ensure that our stuff is updated before the next stage of the test self._studio_reindex() # Search after publish, there should still be no results shown. self.assertFalse(self._search_for_content(self.EDITED_SEARCH_STRING)) self._studio_edit_chapter_name(1) # Do a ReIndex from studio to ensure that our stuff is updated before the next stage of the test self._studio_reindex() # Do the search again, this time we expect results. self.assertTrue(self._search_for_content(self.EDITED_SEARCH_STRING))
class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest, TestWithSearchIndexMixin): """ Test Library Content block in LMS """ def setUp(self): """ Install library with some content and a course using fixtures """ self._create_search_index() super(StudioLibraryContainerTest, self).setUp() # Also create a course: 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() self.outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.outline.visit() subsection = self.outline.section(SECTION_NAME).subsection(SUBSECTION_NAME) self.unit_page = subsection.expand_subsection().unit(UNIT_NAME).go_to() def tearDown(self): """ Tear down method: remove search index backing file """ self._cleanup_index_file() super(StudioLibraryContainerTest, self).tearDown() def populate_library_fixture(self, library_fixture): """ Populate the children of the test course fixture. """ library_fixture.add_children( XBlockFixtureDesc("html", "Html1"), XBlockFixtureDesc("html", "Html2"), XBlockFixtureDesc("html", "Html3"), ) def populate_course_fixture(self, course_fixture): """ Install a course with sections/problems, tabs, updates, and handouts """ library_content_metadata = { 'source_library_id': unicode(self.library_key), 'mode': 'random', 'max_count': 1, } course_fixture.add_children( XBlockFixtureDesc('chapter', SECTION_NAME).add_children( XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children( XBlockFixtureDesc('vertical', UNIT_NAME).add_children( XBlockFixtureDesc('library_content', "Library Content", metadata=library_content_metadata) ) ) ) ) def _get_library_xblock_wrapper(self, xblock): """ Wraps xblock into :class:`...pages.studio.library.StudioLibraryContainerXBlockWrapper` """ return StudioLibraryContainerXBlockWrapper.from_xblock_wrapper(xblock) @ddt.data(1, 2, 3) def test_can_edit_metadata(self, max_count): """ Scenario: Given I have a library, a course and library content xblock in a course When I go to studio unit page for library content block And I edit library content metadata and save it Then I can ensure that data is persisted """ library_name = self.library_info['display_name'] library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[1]) library_container.edit() edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator) edit_modal.library_name = library_name edit_modal.count = max_count library_container.save_settings() # saving settings # open edit window again to verify changes are persistent library_container.edit() edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator) self.assertEqual(edit_modal.library_name, library_name) self.assertEqual(edit_modal.count, max_count) def test_no_library_shows_library_not_configured(self): """ Scenario: Given I have a library, a course and library content xblock in a course When I go to studio unit page for library content block And I edit to select "No Library" Then I can see that library content block is misconfigured """ expected_text = 'A library has not yet been selected.' expected_action = 'Select a Library' library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[1]) # precondition check - the library block should be configured before we remove the library setting self.assertFalse(library_container.has_validation_not_configured_warning) library_container.edit() edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator) edit_modal.library_name = "No Library Selected" library_container.save_settings() self.assertTrue(library_container.has_validation_not_configured_warning) self.assertIn(expected_text, library_container.validation_not_configured_warning_text) self.assertIn(expected_action, library_container.validation_not_configured_warning_text) def test_out_of_date_message(self): """ Scenario: Given I have a library, a course and library content xblock in a course When I go to studio unit page for library content block Then I update the library being used Then I refresh the page Then I can see that library content block needs to be updated When I click on the update link Then I can see that the content no longer needs to be updated """ # Formerly flaky: see TE-745 expected_text = "This component is out of date. The library has new content." library_block = self._get_library_xblock_wrapper(self.unit_page.xblocks[1]) self.assertFalse(library_block.has_validation_warning) # Removed this assert until a summary message is added back to the author view (SOL-192) #self.assertIn("3 matching components", library_block.author_content) self.library_fixture.create_xblock(self.library_fixture.library_location, XBlockFixtureDesc("html", "Html4")) self.unit_page.visit() # Reload the page self.assertTrue(library_block.has_validation_warning) self.assertIn(expected_text, library_block.validation_warning_text) library_block.refresh_children() self.unit_page.wait_for_page() # Wait for the page to reload library_block = self._get_library_xblock_wrapper(self.unit_page.xblocks[1]) self.assertFalse(library_block.has_validation_message) # Removed this assert until a summary message is added back to the author view (SOL-192) #self.assertIn("4 matching components", library_block.author_content) def test_no_content_message(self): """ Scenario: Given I have a library, a course and library content xblock in a course When I go to studio unit page for library content block And I set Problem Type selector so that no libraries have matching content Then I can see that "No matching content" warning is shown When I set Problem Type selector so that there is matching content Then I can see that warning messages are not shown """ # Add a single "Dropdown" type problem to the library (which otherwise has only HTML blocks): self.library_fixture.create_xblock(self.library_fixture.library_location, XBlockFixtureDesc( "problem", "Dropdown", data=textwrap.dedent(""" <problem> <p>Dropdown</p> <optionresponse><optioninput label="Dropdown" options="('1', '2')" correct="'2'"></optioninput></optionresponse> </problem> """) )) expected_text = 'There are no matching problem types in the specified libraries. Select another problem type' library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[1]) # precondition check - assert library has children matching filter criteria self.assertFalse(library_container.has_validation_error) self.assertFalse(library_container.has_validation_warning) library_container.edit() edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator) self.assertEqual(edit_modal.capa_type, "Any Type") # precondition check edit_modal.capa_type = "Custom Evaluated Script" library_container.save_settings() self.assertTrue(library_container.has_validation_warning) self.assertIn(expected_text, library_container.validation_warning_text) library_container.edit() edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator) self.assertEqual(edit_modal.capa_type, "Custom Evaluated Script") # precondition check edit_modal.capa_type = "Dropdown" library_container.save_settings() # Library should contain single Dropdown problem, so now there should be no errors again self.assertFalse(library_container.has_validation_error) self.assertFalse(library_container.has_validation_warning) def test_not_enough_children_blocks(self): """ Scenario: Given I have a library, a course and library content xblock in a course When I go to studio unit page for library content block And I set Problem Type selector so "Any" Then I can see that "No matching content" warning is shown """ expected_tpl = "The specified library is configured to fetch {count} problems, " \ "but there are only {actual} matching problems." library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[1]) # precondition check - assert block is configured fine self.assertFalse(library_container.has_validation_error) self.assertFalse(library_container.has_validation_warning) library_container.edit() edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator) edit_modal.count = 50 library_container.save_settings() self.assertTrue(library_container.has_validation_warning) self.assertIn( expected_tpl.format(count=50, actual=len(self.library_fixture.children)), library_container.validation_warning_text ) def test_settings_overrides(self): """ Scenario: Given I have a library, a course and library content xblock in a course When I go to studio unit page for library content block And when I click the "View" link Then I can see a preview of the blocks drawn from the library. When I edit one of the blocks to change a setting such as "display_name", Then I can see the new setting is overriding the library version. When I subsequently click to refresh the content with the latest from the library, Then I can see that the overrided version of the setting is preserved. When I click to edit the block and reset the setting, then I can see that the setting's field defaults back to the library version. """ block_wrapper_unit_page = self._get_library_xblock_wrapper(self.unit_page.xblocks[0].children[0]) container_page = block_wrapper_unit_page.go_to_container() library_block = self._get_library_xblock_wrapper(container_page.xblocks[0]) self.assertFalse(library_block.has_validation_message) self.assertEqual(len(library_block.children), 3) block = library_block.children[0] self.assertIn(block.name, ("Html1", "Html2", "Html3")) name_default = block.name block.edit() new_display_name = "A new name for this HTML block" block.set_field_val("Display Name", new_display_name) block.save_settings() self.assertEqual(block.name, new_display_name) # Create a new block, causing a new library version: self.library_fixture.create_xblock(self.library_fixture.library_location, XBlockFixtureDesc("html", "Html4")) container_page.visit() # Reload self.assertTrue(library_block.has_validation_warning) library_block.refresh_children() container_page.wait_for_page() # Wait for the page to reload self.assertEqual(len(library_block.children), 4) self.assertEqual(block.name, new_display_name) # Reset: block.edit() block.reset_field_val("Display Name") block.save_settings() self.assertEqual(block.name, name_default) def test_cannot_manage(self): """ Scenario: Given I have a library, a course and library content xblock in a course When I go to studio unit page for library content block And when I click the "View" link Then I can see a preview of the blocks drawn from the library. And I do not see a duplicate button And I do not see a delete button """ block_wrapper_unit_page = self._get_library_xblock_wrapper(self.unit_page.xblocks[0].children[0]) container_page = block_wrapper_unit_page.go_to_container() for block in container_page.xblocks: self.assertFalse(block.has_duplicate_button) self.assertFalse(block.has_delete_button) self.assertFalse(block.has_edit_visibility_button)