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()
Пример #5
0
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()
Пример #6
0
 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()
Пример #8
0
 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()
Пример #9
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 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)
Пример #12
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
        )
Пример #13
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
        )
Пример #14
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()
Пример #18
0
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)
Пример #19
0
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()
Пример #20
0
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)
Пример #21
0
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)
Пример #27
0
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)
Пример #28
0
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())
Пример #33
0
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)