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()
def setUp(self): super(DiscussionPreviewTest, self).setUp() cop = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) cop.visit() self.unit = cop.section('Test Section').subsection('Test Subsection').expand_subsection().unit('Test Unit') self.unit.go_to()
class CourseOutlineHelpTest(StudioCourseTest): """ Tests help links on course outline page. """ def setUp(self): # pylint: disable=arguments-differ super(CourseOutlineHelpTest, self).setUp() self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.course_outline_page.visit() @skip("This scenario depends upon TNL-5460") def test_course_outline_nav_help(self): """ Scenario: Help link in navigation bar is working on Course Outline page Given that I am on the Course Outline page And I want help about the process And I click the 'Help' in the navigation bar Then Help link should open. And help url should end with 'developing_course/course_outline.html' """ href = 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course' \ '/en/open-release-ficus.master/developing_course/course_outline.html' # Assert that help link is correct. assert_nav_help_link( test=self, page=self.course_outline_page, href=href ) def test_course_outline_side_bar_help(self): """ Scenario: Help link in sidebar links is working on Course Outline page Given that I am on the Course Outline page. And I want help about the process And I click the 'Learn more about the course outline' in the sidebar links Then Help link should open. And help url should end with 'developing_course/course_outline.html' """ href = 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course' \ '/en/open-release-ficus.master/developing_course/course_outline.html' # Assert that help link is correct. assert_side_bar_help_link( test=self, page=self.course_outline_page, href=href, help_text='Learn more about the course outline', index=0 )
def record_visit_outline(self): """ Produce a HAR for loading the course outline page. """ course_outline_page = CourseOutlinePage(self.browser, self.course_org, self.course_num, self.course_run) har_name = 'OutlinePage_{org}_{course}'.format( org=self.course_org, course=self.course_num ) self.har_capturer.add_page(self.browser, har_name) course_outline_page.visit() self.har_capturer.save_har(self.browser, har_name)
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 )
def record_visit_unit(self, section_title, subsection_title, unit_title): """ Produce a HAR for loading a unit page. """ course_outline_page = CourseOutlinePage(self.browser, self.course_org, self.course_num, self.course_run).visit() course_outline_unit = course_outline_page.section(section_title).subsection(subsection_title).expand_subsection().unit(unit_title) har_name = 'UnitPage_{org}_{course}'.format( org=self.course_org, course=self.course_num ) self.har_capturer.add_page(self.browser, har_name) course_outline_unit.go_to() self.har_capturer.save_har(self.browser, har_name)
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 setUp(self): # pylint: disable=arguments-differ super(SignUpAndSignInTest, self).setUp() self.sign_up_page = SignupPage(self.browser) self.login_page = LoginPage(self.browser) self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.course_outline_sign_in_redirect_page = CourseOutlineSignInRedirectPage( 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.user = None
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 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.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 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 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 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 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 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()
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 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 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 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"}, 'enable_proctored_exams': {"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') ), XBlockFixtureDesc('sequential', 'Test Subsection 3').add_children( XBlockFixtureDesc('problem', 'Test Problem 3') ), ) ).install()
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 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_no_error_appears_for_long_course_name(self): """ Scenario: Ensure that the course creation with 66 characters long course name is successful. Given I have filled course creation form with 66 characters long course name. And I have filled remaining form within the allowed characters length. When I click 'Create' button Form validation should pass Then I see the course listing page with newly created course """ course_name = ''.join( random.choice(string.ascii_uppercase) for _ in range(66)) 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.dashboard_page.click_new_course_button() self.assertTrue(self.dashboard_page.is_new_course_form_visible()) self.dashboard_page.fill_new_course_form(course_name, self.course_org, self.course_number, self.course_run) 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() self.dashboard_page.visit() # Assert that course is present on dashboard self.assertTrue( self.dashboard_page.has_course(org=self.course_org, number=self.course_number, run=self.course_run))
def test_course_rerun(self): """ Scenario: Courses can be rerun Given I have a course with a section, subsesction, vertical, and html component with content 'Test Content' When I visit the course rerun page And I type 'test_rerun' in the course run field And I click Create Rerun And I visit the course listing page And I wait for all courses to finish processing And I click on the course with run 'test_rerun' Then I see a rerun notification on the course outline page And when I click 'Dismiss' on the notification Then I do not see a rerun notification And when I expand the subsection and click on the unit And I click 'View Live Version' Then I see one html component with the content 'Test Content' """ course_info = (self.course_info['org'], self.course_info['number'], self.course_info['run']) updated_course_info = course_info[0] + "+" + course_info[1] + "+" + course_info[2] self.dashboard_page.visit() self.dashboard_page.scroll_to_course(course_info[1]) self.dashboard_page.create_rerun(updated_course_info) rerun_page = CourseRerunPage(self.browser, *course_info) rerun_page.wait_for_page() course_run = 'test_rerun_' + str(random.randrange(1000000, 9999999)) rerun_page.course_run = course_run rerun_page.create_rerun() def finished_processing(): self.dashboard_page.visit() return not self.dashboard_page.has_processing_courses EmptyPromise(finished_processing, "Rerun finished processing", try_interval=5, timeout=60).fulfill() assert course_run in self.dashboard_page.course_runs self.dashboard_page.click_course_run(course_run) outline_page = CourseOutlinePage(self.browser, *course_info) outline_page.wait_for_page() self.assertTrue(outline_page.has_rerun_notification) outline_page.dismiss_rerun_notification() EmptyPromise(lambda: not outline_page.has_rerun_notification, "Rerun notification dismissed").fulfill() subsection = outline_page.section(self.SECTION_NAME).subsection(self.SUBSECITON_NAME) subsection.expand_subsection() unit_page = subsection.unit(self.UNIT_NAME).go_to() unit_page.view_published_version() courseware = CoursewarePage(self.browser, self.course_id) courseware.wait_for_page() self.assertEqual(courseware.num_xblock_components, 1) self.assertEqual(courseware.xblock_component_html_content(), self.COMPONENT_CONTENT)
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.courseware_search_page = CoursewareSearchPage( self.browser, self.course_id) self.course_navigation_page = CourseNavPage(self.browser) self.course_outline = CourseOutlinePage(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) self._create_group_configuration() self._studio_reindex()
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'] )
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.addCleanup(YouTubeStubConfig.reset)
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'] )
class StudioSubsectionSettingsA11yTest(StudioCourseTest): """ Class to test accessibility on the subsection settings modals. """ def setUp(self): # pylint: disable=arguments-differ browser = os.environ.get('SELENIUM_BROWSER', 'firefox') # This test will fail if run using phantomjs < 2.0, due to an issue with bind() # See https://github.com/ariya/phantomjs/issues/10522 for details. # The course_outline uses this function, and as such will not fully load when run # under phantomjs 1.9.8. So, to prevent this test from timing out at course_outline.visit(), # force the use of firefox vs the standard a11y test usage of phantomjs 1.9.8. # TODO: remove this block once https://openedx.atlassian.net/browse/TE-1047 is resolved. if browser == 'phantomjs': browser = 'firefox' with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}): super(StudioSubsectionSettingsA11yTest, self).setUp(is_staff=True) self.course_outline = CourseOutlinePage(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) def populate_course_fixture(self, course_fixture): course_fixture.add_advanced_settings( {"enable_proctored_exams": { "value": "true" }}) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section 1').add_children( XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children( XBlockFixtureDesc( 'problem', 'Test Problem 1')))) def test_special_exams_menu_a11y(self): """ Given that I am a staff member And I am editing settings on the special exams menu Then that menu is accessible """ self.course_outline.visit() self.course_outline.open_subsection_settings_dialog() self.course_outline.select_advanced_tab() self.course_outline.a11y_audit.config.set_rules({ "ignore": [ 'section', # TODO: AC-491 ], }) # limit the scope of the audit to the special exams tab on the modal dialog self.course_outline.a11y_audit.config.set_scope( include=['section.edit-settings-timed-examination']) self.course_outline.a11y_audit.check_for_accessibility_errors()
def setUp(self): """ 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")
class StudioSubsectionSettingsA11yTest(StudioCourseTest): """ Class to test accessibility on the subsection settings modals. """ def setUp(self): # pylint: disable=arguments-differ browser = os.environ.get('SELENIUM_BROWSER', 'firefox') # This test will fail if run using phantomjs < 2.0, due to an issue with bind() # See https://github.com/ariya/phantomjs/issues/10522 for details. # The course_outline uses this function, and as such will not fully load when run # under phantomjs 1.9.8. So, to prevent this test from timing out at course_outline.visit(), # force the use of firefox vs the standard a11y test usage of phantomjs 1.9.8. # TODO: remove this block once https://openedx.atlassian.net/browse/TE-1047 is resolved. if browser == 'phantomjs': browser = 'firefox' with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}): super(StudioSubsectionSettingsA11yTest, self).setUp(is_staff=True) self.course_outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) def populate_course_fixture(self, course_fixture): course_fixture.add_advanced_settings({ "enable_proctored_exams": {"value": "true"} }) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section 1').add_children( XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children( XBlockFixtureDesc('problem', 'Test Problem 1') ) ) ) def test_special_exams_menu_a11y(self): """ Given that I am a staff member And I am editing settings on the special exams menu Then that menu is accessible """ self.course_outline.visit() self.course_outline.open_subsection_settings_dialog() self.course_outline.select_advanced_tab() # limit the scope of the audit to the special exams tab on the modal dialog self.course_outline.a11y_audit.config.set_scope( include=['section.edit-settings-timed-examination'] ) self.course_outline.a11y_audit.check_for_accessibility_errors()
def setUp(self): super(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 setUp(self): # pylint: disable=arguments-differ super(ContentLicenseTest, self).setUp() self.outline_page = CourseOutlinePage(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) self.settings_page = SettingsPage(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) self.lms_courseware = CoursewarePage( self.browser, self.course_id, ) self.settings_page.visit()
def setUp(self): """ Set up library, course and library content XBlock """ super(LibraryContentTestBase, 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'] ) 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, 'has_score': False } 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 test_course_rerun(self): """ Scenario: Courses can be rerun Given I have a course with a section, subsesction, vertical, and html component with content 'Test Content' When I visit the course rerun page And I type 'test_rerun' in the course run field And I click Create Rerun And I visit the course listing page And I wait for all courses to finish processing And I click on the course with run 'test_rerun' Then I see a rerun notification on the course outline page And when I click 'Dismiss' on the notification Then I do not see a rerun notification And when I expand the subsection and click on the unit And I click 'View Live Version' Then I see one html component with the content 'Test Content' """ course_info = (self.course_info['org'], self.course_info['number'], self.course_info['run']) updated_course_info = course_info[0] + "+" + course_info[1] + "+" + course_info[2] self.dashboard_page.visit() self.dashboard_page.scroll_to_course(course_info[1]) self.dashboard_page.create_rerun(updated_course_info) rerun_page = CourseRerunPage(self.browser, *course_info) rerun_page.wait_for_page() course_run = 'test_rerun_' + str(random.randrange(1000000, 9999999)) rerun_page.course_run = course_run rerun_page.create_rerun() def finished_processing(): self.dashboard_page.visit() return not self.dashboard_page.has_processing_courses EmptyPromise(finished_processing, "Rerun finished processing", try_interval=5, timeout=60).fulfill() assert_in(course_run, self.dashboard_page.course_runs) self.dashboard_page.click_course_run(course_run) outline_page = CourseOutlinePage(self.browser, *course_info) outline_page.wait_for_page() self.assertTrue(outline_page.has_rerun_notification) outline_page.dismiss_rerun_notification() EmptyPromise(lambda: not outline_page.has_rerun_notification, "Rerun notification dismissed").fulfill() subsection = outline_page.section(self.SECTION_NAME).subsection(self.SUBSECITON_NAME) subsection.expand_subsection() unit_page = subsection.unit(self.UNIT_NAME).go_to() unit_page.view_published_version() courseware = CoursewarePage(self.browser, self.course_id) courseware.wait_for_page() self.assertEqual(courseware.num_xblock_components, 1) self.assertEqual(courseware.xblock_component_html_content(), self.COMPONENT_CONTENT)
def setUp(self): """ Set up library, course and library content XBlock """ super(LibraryContentTestBase, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.library_fixture = LibraryFixture('test_org', self.unique_id, u'Test Library {}'.format(self.unique_id)) self.populate_library_fixture(self.library_fixture) self.library_fixture.install() self.library_info = self.library_fixture.library_info self.library_key = self.library_fixture.library_key # Install a course with library content xblock self.course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) library_content_metadata = { 'source_library_id': 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 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 setUp(self): """ Set up library, course and library content XBlock """ super(LibraryContentTestBase, 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"] ) 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, "has_score": False, } 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 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()
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 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()
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()
class GatingTest(UniqueCourseTest): """ Test gating feature in LMS. """ STAFF_USERNAME = "******" STAFF_EMAIL = "*****@*****.**" STUDENT_USERNAME = "******" STUDENT_EMAIL = "*****@*****.**" def setUp(self): super(GatingTest, self).setUp() self.logout_page = LogoutPage(self.browser) self.course_home_page = CourseHomePage(self.browser, self.course_id) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) xml = dedent(""" <problem> <p>What is height of eiffel tower without the antenna?.</p> <multiplechoiceresponse> <choicegroup label="What is height of eiffel tower without the antenna?" type="MultipleChoice"> <choice correct="false">324 meters<choicehint>Antenna is 24 meters high</choicehint></choice> <choice correct="true">300 meters</choice> <choice correct="false">224 meters</choice> <choice correct="false">400 meters</choice> </choicegroup> </multiplechoiceresponse> </problem> """) self.problem1 = XBlockFixtureDesc('problem', 'HEIGHT OF EIFFEL TOWER', data=xml) # Install a course with sections/problems course_fixture = CourseFixture(self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name']) course_fixture.add_advanced_settings( {"enable_subsection_gating": { "value": "true" }}) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section 1').add_children( XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children( self.problem1), XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children( XBlockFixtureDesc( 'problem', 'Test Problem 2')))).install() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ self.logout_page.visit() AutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit() def _setup_prereq(self): """ Make the first subsection a prerequisite """ # Login as staff self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) # Make the first subsection a prerequisite self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(0) self.studio_course_outline.select_advanced_tab( desired_item='gated_content') self.studio_course_outline.make_gating_prerequisite() def _setup_gated_subsection(self): """ Gate the second subsection on the first subsection """ # Login as staff self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) # Gate the second subsection based on the score achieved in the first subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(1) self.studio_course_outline.select_advanced_tab( desired_item='gated_content') self.studio_course_outline.add_prerequisite_to_subsection("80") def _fulfill_prerequisite(self): """ Fulfill the prerequisite needed to see gated content """ problem_page = ProblemPage(self.browser) self.assertEqual(problem_page.wait_for_page().problem_name, 'HEIGHT OF EIFFEL TOWER') problem_page.click_choice('choice_1') problem_page.click_submit() def test_subsection_gating_in_studio(self): """ Given that I am a staff member When I visit the course outline page in studio. And open the subsection edit dialog Then I can view all settings related to Gating And update those settings to gate a subsection """ self._setup_prereq() # Assert settings are displayed correctly for a prerequisite subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(0) self.studio_course_outline.select_advanced_tab( desired_item='gated_content') self.assertTrue(self.studio_course_outline. gating_prerequisite_checkbox_is_visible()) self.assertTrue(self.studio_course_outline. gating_prerequisite_checkbox_is_checked()) self.assertFalse(self.studio_course_outline. gating_prerequisites_dropdown_is_visible()) self.assertFalse(self.studio_course_outline. gating_prerequisite_min_score_is_visible()) self._setup_gated_subsection() # Assert settings are displayed correctly for a gated subsection self.studio_course_outline.visit() self.studio_course_outline.open_subsection_settings_dialog(1) self.studio_course_outline.select_advanced_tab( desired_item='gated_content') self.assertTrue(self.studio_course_outline. gating_prerequisite_checkbox_is_visible()) self.assertTrue(self.studio_course_outline. gating_prerequisites_dropdown_is_visible()) self.assertTrue(self.studio_course_outline. gating_prerequisite_min_score_is_visible()) def test_gated_subsection_in_lms_for_student(self): """ Given that I am a student When I visit the LMS Courseware Then I can see a gated subsection The gated subsection should have a lock icon and be in the format: "<Subsection Title> (Prerequisite Required)" When I fulfill the gating Prerequisite Then I can see the gated subsection Now the gated subsection should have an unlock icon and screen readers should read the section as: "<Subsection Title> Unlocked" """ self._setup_prereq() self._setup_gated_subsection() self._auto_auth(self.STUDENT_USERNAME, self.STUDENT_EMAIL, False) self.course_home_page.visit() self.assertEqual(self.course_home_page.outline.num_subsections, 2) # Fulfill prerequisite and verify that gated subsection is shown self.courseware_page.visit() self._fulfill_prerequisite() self.course_home_page.visit() self.assertEqual(self.course_home_page.outline.num_subsections, 2) def test_gated_subsection_in_lms_for_staff(self): """ Given that I am a staff member When I visit the LMS Courseware Then I can see all gated subsections Displayed along with notification banners Then if I masquerade as a student Then I can see a gated subsection The gated subsection should have a lock icon and be in the format: "<Subsection Title> (Prerequisite Required)" """ self._setup_prereq() self._setup_gated_subsection() self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.course_home_page.visit() self.assertEqual(self.course_home_page.preview.staff_view_mode, 'Staff') self.assertEqual(self.course_home_page.outline.num_subsections, 2) # Click on gated section and check for banner self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 2') self.courseware_page.wait_for_page() self.assertTrue(self.courseware_page.has_banner()) self.course_home_page.visit() self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 1') self.courseware_page.wait_for_page() self.course_home_page.visit() self.course_home_page.preview.set_staff_view_mode('Learner') self.course_home_page.wait_for_page() self.assertEqual(self.course_home_page.outline.num_subsections, 2) self.course_home_page.outline.go_to_section('Test Section 1', 'Test Subsection 1') self.courseware_page.wait_for_page() # banner displayed informing section is a prereq self.assertTrue(self.courseware_page.has_banner())
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()
class ContentGroupConfigurationTest(StudioCourseTest): """ Tests for content groups in the Group Configurations Page. There are tests for the experiment groups in test_studio_split_test. """ def setUp(self): super(ContentGroupConfigurationTest, self).setUp() self.group_configurations_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 populate_course_fixture(self, course_fixture): """ Populates test course with chapter, sequential, and 1 problems. The problem is visible only to Group "alpha". """ course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit') ) ) ) def create_and_verify_content_group(self, name, existing_groups): """ Creates a new content group and verifies that it was properly created. """ self.assertEqual(existing_groups, len(self.group_configurations_page.content_groups)) if existing_groups == 0: self.group_configurations_page.create_first_content_group() else: self.group_configurations_page.add_content_group() config = self.group_configurations_page.content_groups[existing_groups] config.name = name # Save the content group self.assertEqual(config.get_text('.action-primary'), "Create") self.assertFalse(config.delete_button_is_present) config.save() self.assertIn(name, config.name) return config def test_no_content_groups_by_default(self): """ Scenario: Ensure that message telling me to create a new content group is shown when no content groups exist. Given I have a course without content groups When I go to the Group Configuration page in Studio Then I see "You have not created any content groups yet." message """ self.group_configurations_page.visit() self.assertTrue(self.group_configurations_page.no_content_groups_message_is_present) self.assertIn( "You have not created any content groups yet.", self.group_configurations_page.no_content_groups_message_text ) def test_can_create_and_edit_content_groups(self): """ Scenario: Ensure that the content groups can be created and edited correctly. Given I have a course without content groups When I click button 'Add your first Content Group' And I set new the name and click the button 'Create' Then I see the new content is added and has correct data And I click 'New Content Group' button And I set the name and click the button 'Create' Then I see the second content group is added and has correct data When I edit the second content group And I change the name and click the button 'Save' Then I see the second content group is saved successfully and has the new name """ self.group_configurations_page.visit() self.create_and_verify_content_group("New Content Group", 0) second_config = self.create_and_verify_content_group("Second Content Group", 1) # Edit the second content group second_config.edit() second_config.name = "Updated Second Content Group" self.assertEqual(second_config.get_text('.action-primary'), "Save") second_config.save() self.assertIn("Updated Second Content Group", second_config.name) def test_cannot_delete_used_content_group(self): """ Scenario: Ensure that the user cannot delete used content group. Given I have a course with 1 Content Group And I go to the Group Configuration page When I try to delete the Content Group with name "New Content Group" Then I see the delete button is disabled. """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Configuration alpha,', 'Content Group Partition', [Group("0", 'alpha')], scheme="cohort" ) ], }, }) problem_data = dedent(""" <problem markdown="Simple Problem" max_attempts="" weight=""> <p>Choose Yes.</p> <choiceresponse> <checkboxgroup> <choice correct="true">Yes</choice> </checkboxgroup> </choiceresponse> </problem> """) vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] self.course_fixture.create_xblock( vertical.locator, XBlockFixtureDesc('problem', "VISIBLE TO ALPHA", data=problem_data, metadata={"group_access": {0: [0]}}), ) self.group_configurations_page.visit() config = self.group_configurations_page.content_groups[0] self.assertTrue(config.delete_button_is_disabled) def test_can_delete_unused_content_group(self): """ Scenario: Ensure that the user can delete unused content group. Given I have a course with 1 Content Group And I go to the Group Configuration page When I delete the Content Group with name "New Content Group" Then I see that there is no Content Group When I refresh the page Then I see that the content group has been deleted """ self.group_configurations_page.visit() config = self.create_and_verify_content_group("New Content Group", 0) self.assertTrue(config.delete_button_is_present) self.assertEqual(len(self.group_configurations_page.content_groups), 1) # Delete content group config.delete() self.assertEqual(len(self.group_configurations_page.content_groups), 0) self.group_configurations_page.visit() self.assertEqual(len(self.group_configurations_page.content_groups), 0) def test_must_supply_name(self): """ Scenario: Ensure that validation of the content group works correctly. Given I have a course without content groups And I create new content group without specifying a name click the button 'Create' Then I see error message "Content Group name is required." When I set a name and click the button 'Create' Then I see the content group is saved successfully """ self.group_configurations_page.visit() self.group_configurations_page.create_first_content_group() config = self.group_configurations_page.content_groups[0] config.save() self.assertEqual(config.mode, 'edit') self.assertEqual("Group name is required", config.validation_message) config.name = "Content Group Name" config.save() self.assertIn("Content Group Name", config.name) def test_can_cancel_creation_of_content_group(self): """ Scenario: Ensure that creation of a content group can be canceled correctly. Given I have a course without content groups When I click button 'Add your first Content Group' And I set new the name and click the button 'Cancel' Then I see that there is no content groups in the course """ self.group_configurations_page.visit() self.group_configurations_page.create_first_content_group() config = self.group_configurations_page.content_groups[0] config.name = "Content Group" config.cancel() self.assertEqual(0, len(self.group_configurations_page.content_groups)) def test_content_group_empty_usage(self): """ Scenario: When content group is not used, ensure that the link to outline page works correctly. Given I have a course without content group And I create new content group Then I see a link to the outline page When I click on the outline link Then I see the outline page """ self.group_configurations_page.visit() config = self.create_and_verify_content_group("New Content Group", 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()
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, 'has_score': False } 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, True), (2, False), (3, True), ) @ddt.unpack def test_can_edit_metadata(self, max_count, scored): """ 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 edit_modal.scored = scored 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) self.assertEqual(edit_modal.scored, scored) 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)
class XBlockAcidBase(AcceptanceTest): """ Base class for tests that verify that XBlock integration is working correctly """ shard = 21 __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 GroupConfigurationsTest(ContainerBase, SplitTestMixin): """ Tests that Group Configurations page works correctly with previously added configurations in Studio """ __test__ = True shard = 15 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() 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 = XBlockEditorView(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 = XBlockEditorView(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 SignUpAndSignInTest(UniqueCourseTest): """ Test studio sign-up and sign-in """ shard = 21 def setUp(self): super(SignUpAndSignInTest, self).setUp() self.sign_up_page = SignupPage(self.browser) self.login_page = LoginPage(self.browser) self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.course_outline_sign_in_redirect_page = CourseOutlineSignInRedirectPage( 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.user = None def install_course_fixture(self): """ Install a course fixture """ self.course_fixture.install() self.user = self.course_fixture.user def test_sign_up_from_home(self): """ Scenario: Sign up from the homepage Given I visit the Studio homepage When I click the link with the text "Sign Up" And I fill in the registration form And I press the Create My Account button on the registration form Then I should see an email verification prompt """ index_page = IndexPage(self.browser) index_page.visit() index_page.click_sign_up() # Register the user. unique_number = uuid.uuid4().hex[:4] self.sign_up_page.sign_up_user( '{}[email protected]'.format(unique_number), '{}-name'.format(unique_number), '{}-username'.format(unique_number), '{}-password'.format(unique_number), ) home = HomePage(self.browser) home.wait_for_page() def test_sign_up_with_bad_password(self): """ Scenario: Sign up from the homepage Given I visit the Studio homepage When I click the link with the text "Sign Up" And I fill in the registration form When I enter an insufficient password and focus out I should see an error message """ index_page = IndexPage(self.browser) index_page.visit() index_page.click_sign_up() password_input = self.sign_up_page.input_password('a') # Arbitrary short password that will fail password_input.send_keys(Keys.TAB) # Focus out of the element index_page.wait_for_element_visibility('#register-password-validation-error', 'Password Error Message') self.assertIsNotNone(index_page.q(css='#register-password-validation-error-msg')) # Error message should exist def test_login_with_valid_redirect(self): """ Scenario: Login with a valid redirect Given I have opened a new course in Studio And I am not logged in And I visit the url "/course/slashes:MITx+999+Robot_Super_Course" And I should see that the path is "/signin?next=/course/slashes%3AMITx%2B999%2BRobot_Super_Course" When I fill in and submit the signin form Then I should see that the path is "/course/slashes:MITx+999+Robot_Super_Course" """ self.install_course_fixture() # Get the url, browser should land here after sign in. course_url = self.course_outline_sign_in_redirect_page.url self.course_outline_sign_in_redirect_page.visit() # Login self.course_outline_sign_in_redirect_page.login(self.user['email'], self.user['password']) self.course_outline_page.wait_for_page() # Verify that correct course is displayed after sign in. self.assertEqual(self.browser.current_url, course_url) def test_login_with_invalid_redirect(self): """ Scenario: Login with an invalid redirect Given I have opened a new course in Studio And I am not logged in And I visit the url "/signin?next=http://www.google.com/" When I fill in and submit the signin form Then I should see that the path is "/home/" """ self.install_course_fixture() # Visit course self.course_outline_sign_in_redirect_page.visit() # Change redirect url self.browser.get(self.browser.current_url.split('=')[0] + '=http://www.google.com') # Login self.course_outline_sign_in_redirect_page.login(self.user['email'], self.user['password']) # Verify that we land in LMS instead of the invalid redirect url self.assertEqual(self.browser.current_url, LMS_URL + "/dashboard") def test_login_with_mistyped_credentials(self): """ Given I have opened a new course in Studio And I am not logged in And I visit the Studio homepage When I click the link with the text "Sign In" Then I should see that the path is "/signin" And I should not see a login error message And I fill in and submit the signin form incorrectly Then I should see a login error message And I edit the password field Then I should not see a login error message And I submit the signin form And I wait for "2" seconds Then I should see that the path is "/course/slashes:MITx+999+Robot_Super_Course" """ self.install_course_fixture() self.course_outline_sign_in_redirect_page.visit() # Verify login_error is not present self.course_outline_sign_in_redirect_page.wait_for_element_absence( '#login_error', 'Login error not be present' ) # Login with wrong credentials self.course_outline_sign_in_redirect_page.login( self.user['email'], 'wrong_password', expect_success=False ) # Verify that login error is shown self.course_outline_sign_in_redirect_page.wait_for_element_visibility( ".js-form-errors.status.submission-error", 'Login error is visible' ) # Login with correct credentials self.course_outline_sign_in_redirect_page.login(self.user['email'], self.user['password']) self.course_outline_page.wait_for_page() # Verify that correct course is displayed after sign in. self.assertEqual(self.browser.current_url, self.course_outline_page.url)
class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest, TestWithSearchIndexMixin): """ Test Library Content block in LMS """ shard = 17 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': six.text_type(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_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_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)
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 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 LibraryContentTestBase(UniqueCourseTest): """ Base class for library content block tests """ USERNAME = "******" EMAIL = "*****@*****.**" STAFF_USERNAME = "******" STAFF_EMAIL = "*****@*****.**" shard = 10 def populate_library_fixture(self, library_fixture): """ To be overwritten by subclassed tests. Used to install a library to run tests on. """ def setUp(self): """ Set up library, course and library content XBlock """ super(LibraryContentTestBase, self).setUp() self.courseware_page = CoursewarePage(self.browser, self.course_id) self.studio_course_outline = StudioCourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) self.library_fixture = LibraryFixture( 'test_org', self.unique_id, u'Test Library {}'.format(self.unique_id)) self.populate_library_fixture(self.library_fixture) self.library_fixture.install() self.library_info = self.library_fixture.library_info self.library_key = self.library_fixture.library_key # Install a course with library content xblock self.course_fixture = CourseFixture(self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name']) library_content_metadata = { 'source_library_id': six.text_type(self.library_key), 'mode': 'random', 'max_count': 1, } self.lib_block = XBlockFixtureDesc('library_content', "Library Content", metadata=library_content_metadata) self.course_fixture.add_children( XBlockFixtureDesc('chapter', SECTION_NAME).add_children( XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children( XBlockFixtureDesc('vertical', UNIT_NAME).add_children( self.lib_block)))) self.course_fixture.install() def _change_library_content_settings(self, count=1, capa_type=None): """ Performs library block refresh in Studio, configuring it to show {count} children """ unit_page = self._go_to_unit_page(True) library_container_block = StudioLibraryContainerXBlockWrapper.from_xblock_wrapper( unit_page.xblocks[1]) library_container_block.edit() editor = StudioLibraryContentEditor(self.browser, library_container_block.locator) editor.count = count if capa_type is not None: editor.capa_type = capa_type editor.save() self._go_to_unit_page(change_login=False) unit_page.wait_for_page() unit_page.publish() self.assertIn("Published and Live", unit_page.publish_title) @property def library_xblocks_texts(self): """ Gets texts of all xblocks in library """ return frozenset(child.data for child in self.library_fixture.children) def _go_to_unit_page(self, change_login=True): """ Open unit page in Studio """ if change_login: LogoutPage(self.browser).visit() self._auto_auth(self.STAFF_USERNAME, self.STAFF_EMAIL, True) self.studio_course_outline.visit() subsection = self.studio_course_outline.section( SECTION_NAME).subsection(SUBSECTION_NAME) return subsection.expand_subsection().unit(UNIT_NAME).go_to() def _goto_library_block_page(self, block_id=None): """ Open library page in LMS """ self.courseware_page.visit() paragraphs = self.courseware_page.q(css='.course-content p').results if not paragraphs: course_home_page = CourseHomePage(self.browser, self.course_id) course_home_page.visit() course_home_page.outline.go_to_section_by_index(0, 0) block_id = block_id if block_id is not None else self.lib_block.locator #pylint: disable=attribute-defined-outside-init self.library_content_page = LibraryContentXBlockWrapper( self.browser, block_id) self.library_content_page.wait_for_page() def _auto_auth(self, username, email, staff): """ Logout and login with given credentials. """ AutoAuthPage(self.browser, username=username, email=email, course_id=self.course_id, staff=staff).visit()
class VideoLicenseTest(StudioCourseTest): """ Tests for video module-level licensing (that is, setting the license, for a specific video module, to All Rights Reserved or Creative Commons) """ def setUp(self): # pylint: disable=arguments-differ super(VideoLicenseTest, self).setUp() self.lms_courseware = CoursewarePage( self.browser, self.course_id, ) self.studio_course_outline = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) # used by StudioCourseTest.setUp() def populate_course_fixture(self, course_fixture): """ Create a course with a single chapter. That chapter has a single section. That section has a single vertical. That vertical has a single video element. """ video_block = XBlockFixtureDesc('video', "Test Video") vertical = XBlockFixtureDesc('vertical', "Test Vertical") vertical.add_children(video_block) sequential = XBlockFixtureDesc('sequential', "Test Section") sequential.add_children(vertical) chapter = XBlockFixtureDesc('chapter', "Test Chapter") chapter.add_children(sequential) self.course_fixture.add_children(chapter) def test_empty_license(self): """ When I visit the LMS courseware, I can see that the video is present but it has no license displayed by default. """ self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license = self.lms_courseware.q(css=".vert .xblock.xmodule_VideoModule .xblock-license") self.assertFalse(video_license.is_present()) def test_arr_license(self): """ When I edit a video element in Studio, I can set an "All Rights Reserved" license on that video element. When I visit the LMS courseware, I can see that the video is present and that it has "All Rights Reserved" displayed for the license. """ self.studio_course_outline.visit() subsection = self.studio_course_outline.section_at(0).subsection_at(0) subsection.expand_subsection() unit = subsection.unit_at(0) container_page = unit.go_to() container_page.edit() video = [xb for xb in container_page.xblocks if xb.name == "Test Video"][0] video.open_advanced_tab() video.set_license('all-rights-reserved') video.save_settings() container_page.publish_action.click() self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license_css = ".vert .xblock.xmodule_VideoModule .xblock-license" self.lms_courseware.wait_for_element_presence( video_license_css, "Video module license block is present" ) video_license = self.lms_courseware.q(css=video_license_css) self.assertEqual(video_license.text[0], "© All Rights Reserved") def test_cc_license(self): """ When I edit a video element in Studio, I can set a "Creative Commons" license on that video element. When I visit the LMS courseware, I can see that the video is present and that it has "Some Rights Reserved" displayed for the license. """ self.studio_course_outline.visit() subsection = self.studio_course_outline.section_at(0).subsection_at(0) subsection.expand_subsection() unit = subsection.unit_at(0) container_page = unit.go_to() container_page.edit() video = [xb for xb in container_page.xblocks if xb.name == "Test Video"][0] video.open_advanced_tab() video.set_license('creative-commons') video.save_settings() container_page.publish_action.click() self.lms_courseware.visit() video = self.lms_courseware.q(css=".vert .xblock .video") self.assertTrue(video.is_present()) video_license_css = ".vert .xblock.xmodule_VideoModule .xblock-license" self.lms_courseware.wait_for_element_presence( video_license_css, "Video module license block is present" ) video_license = self.lms_courseware.q(css=video_license_css) self.assertIn("Some Rights Reserved", video_license.text[0])
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.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" 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.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.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 SplitTestCoursewareSearchTest(ContainerBase): """ Test courseware search on Split Test Module. """ 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.courseware_search_page = CoursewareSearchPage( self.browser, self.course_id) self.course_navigation_page = CourseNavPage(self.browser) self.course_outline = CourseOutlinePage(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() 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 _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_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_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.courseware_search_page.visit() self.courseware_search_page.search_for_term("VISIBLE TO") assert "1 result" in self.courseware_search_page.search_results.html[0]