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_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 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 test_create_course_with_existing_org_via_autocomplete(self): """ Scenario: Ensure that the course creation with an existing org should be successful. Given I have filled course creation form with an existing org and all required fields And I selected `Course Organization` input via autocomplete When I click 'Create' button Form validation should pass Then I see the course listing page with newly created course """ self.auth_page.visit() self.dashboard_page.visit() new_org = 'orgX2' self.assertFalse(self.dashboard_page.has_course( org=new_org, number=self.course_number, run=self.course_run )) self.assertTrue(self.dashboard_page.new_course_button.present) self.dashboard_page.click_new_course_button() self.assertTrue(self.dashboard_page.is_new_course_form_visible()) self.dashboard_page.fill_new_course_form( self.course_name, '', self.course_number, self.course_run ) self.dashboard_page.course_org_field.fill('org') self.dashboard_page.select_item_in_autocomplete_widget(new_org) self.assertTrue(self.dashboard_page.is_new_course_form_valid()) self.dashboard_page.submit_new_course_form() # Successful creation of course takes user to course outline page course_outline_page = CourseOutlinePage( self.browser, new_org, self.course_number, self.course_run ) course_outline_page.visit() course_outline_page.wait_for_page() # Go back to dashboard and verify newly created course exists there self.dashboard_page.visit() self.assertTrue(self.dashboard_page.has_course( org=new_org, number=self.course_number, run=self.course_run ))
def test_archived_course(self): """ Scenario: Ensure that an archived course displays in its own list and can be clicked on. """ self.dashboard_page.visit() self.assertTrue(self.dashboard_page.has_course( org=self.course_info['org'], number=self.course_info['number'], run=self.course_info['run'], archived=True )) # Click on the archived course and make sure that the Studio course outline appears. self.dashboard_page.click_course_run(self.course_info['run']) course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) course_outline_page.wait_for_page()
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 test_create_course_with_existing_org(self): """ Scenario: Ensure that the course creation with an existing org should be successful. Given I have filled course creation form with an existing org and all required fields When I click 'Create' button Form validation should pass Then I see the course listing page with newly created course """ self.auth_page.visit() self.dashboard_page.visit() self.assertFalse(self.dashboard_page.has_course( org=self.course_org, number=self.course_number, run=self.course_run )) self.assertTrue(self.dashboard_page.new_course_button.present) self.dashboard_page.click_new_course_button() self.assertTrue(self.dashboard_page.is_new_course_form_visible()) self.dashboard_page.fill_new_course_form( self.course_name, self.course_org, self.course_number, self.course_run ) self.assertTrue(self.dashboard_page.is_new_course_form_valid()) self.dashboard_page.submit_new_course_form() # Successful creation of course takes user to course outline page course_outline_page = CourseOutlinePage( self.browser, self.course_org, self.course_number, self.course_run ) course_outline_page.visit() course_outline_page.wait_for_page() # Go back to dashboard and verify newly created course exists there self.dashboard_page.visit() self.assertTrue(self.dashboard_page.has_course( org=self.course_org, number=self.course_number, run=self.course_run )) # Click on the course listing and verify that the Studio course outline page opens. self.dashboard_page.click_course_run(self.course_run) course_outline_page.wait_for_page()
class 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 BookmarksTest(BookmarksTestMixin): """ Tests to verify bookmarks functionality. """ def setUp(self): """ Initialize test setup. """ super(BookmarksTest, self).setUp() self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.bookmarks_page = BookmarksPage(self.browser, self.course_id) self.course_nav = CourseNavPage(self.browser) # Get session to be used for bookmarking units self.session = requests.Session() params = {'username': self.USERNAME, 'email': self.EMAIL, 'course_id': self.course_id} response = self.session.get(BASE_URL + "/auto_auth", params=params) self.assertTrue(response.ok, "Failed to get session") def _test_setup(self, num_chapters=2): """ Setup test settings. Arguments: num_chapters: number of chapters to create in course """ self.create_course_fixture(num_chapters) # Auto-auth register for the course. LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() def _bookmark_unit(self, location): """ Bookmark a unit Arguments: location (str): unit location """ _headers = { 'Content-type': 'application/json', 'X-CSRFToken': self.session.cookies['csrftoken'], } params = {'course_id': self.course_id} data = json.dumps({'usage_id': location}) response = self.session.post( BASE_URL + '/api/bookmarks/v1/bookmarks/', data=data, params=params, headers=_headers ) self.assertTrue(response.ok, "Failed to bookmark unit") def _bookmark_units(self, num_units): """ Bookmark first `num_units` units Arguments: num_units(int): Number of units to bookmarks """ xblocks = self.course_fixture.get_nested_xblocks(category="vertical") for index in range(num_units): self._bookmark_unit(xblocks[index].locator) def _breadcrumb(self, num_units, modified_name=None): """ Creates breadcrumbs for the first `num_units` Arguments: num_units(int): Number of units for which we want to create breadcrumbs Returns: list of breadcrumbs """ breadcrumbs = [] for index in range(num_units): breadcrumbs.append( [ 'TestSection{}'.format(index), 'TestSubsection{}'.format(index), modified_name if modified_name else 'TestVertical{}'.format(index) ] ) return breadcrumbs def _delete_section(self, index): """ Delete a section at index `index` """ # Logout and login as staff LogoutPage(self.browser).visit() StudioAutoAuthPage( self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True ).visit() # Visit course outline page in studio. self.course_outline_page.visit() self.course_outline_page.wait_for_page() self.course_outline_page.section_at(index).delete() # Logout and login as a student. LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() # Visit courseware as a student. self.courseware_page.visit() self.courseware_page.wait_for_page() def _toggle_bookmark_and_verify(self, bookmark_icon_state, bookmark_button_state, bookmarked_count): """ Bookmark/Un-Bookmark a unit and then verify """ self.assertTrue(self.courseware_page.bookmark_button_visible) self.courseware_page.click_bookmark_unit_button() self.assertEqual(self.courseware_page.bookmark_icon_visible, bookmark_icon_state) self.assertEqual(self.courseware_page.bookmark_button_state, bookmark_button_state) self.bookmarks_page.click_bookmarks_button() self.assertEqual(self.bookmarks_page.count(), bookmarked_count) def _verify_pagination_info( self, bookmark_count_on_current_page, header_text, previous_button_enabled, next_button_enabled, current_page_number, total_pages ): """ Verify pagination info """ self.assertEqual(self.bookmarks_page.count(), bookmark_count_on_current_page) self.assertEqual(self.bookmarks_page.get_pagination_header_text(), header_text) self.assertEqual(self.bookmarks_page.is_previous_page_button_enabled(), previous_button_enabled) self.assertEqual(self.bookmarks_page.is_next_page_button_enabled(), next_button_enabled) self.assertEqual(self.bookmarks_page.get_current_page_number(), current_page_number) self.assertEqual(self.bookmarks_page.get_total_pages, total_pages) def _navigate_to_bookmarks_list(self): """ Navigates and verifies the bookmarks list page. """ self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.results_header_text(), 'My Bookmarks') def _verify_breadcrumbs(self, num_units, modified_name=None): """ Verifies the breadcrumb trail. """ bookmarked_breadcrumbs = self.bookmarks_page.breadcrumbs() # Verify bookmarked breadcrumbs. breadcrumbs = self._breadcrumb(num_units=num_units, modified_name=modified_name) breadcrumbs.reverse() self.assertEqual(bookmarked_breadcrumbs, breadcrumbs) def update_and_publish_block_display_name(self, modified_name): """ Update and publish the block/unit display name. """ self.course_outline_page.visit() self.course_outline_page.wait_for_page() self.course_outline_page.expand_all_subsections() section = self.course_outline_page.section_at(0) container_page = section.subsection_at(0).unit_at(0).go_to() self.course_fixture._update_xblock(container_page.locator, { # pylint: disable=protected-access "metadata": { "display_name": modified_name } }) container_page.visit() container_page.wait_for_page() self.assertEqual(container_page.name, modified_name) container_page.publish_action.click() def test_bookmark_button(self): """ Scenario: Bookmark unit button toggles correctly Given that I am a registered user And I visit my courseware page For first 2 units I visit the unit And I can see the Bookmark button When I click on Bookmark button Then unit should be bookmarked Then I click again on the bookmark button And I should see a unit un-bookmarked """ self._test_setup() for index in range(2): self.course_nav.go_to_section('TestSection{}'.format(index), 'TestSubsection{}'.format(index)) self._toggle_bookmark_and_verify(True, 'bookmarked', 1) self.bookmarks_page.click_bookmarks_button(False) self._toggle_bookmark_and_verify(False, '', 0) def test_empty_bookmarks_list(self): """ Scenario: An empty bookmarks list is shown if there are no bookmarked units. Given that I am a registered user And I visit my courseware page And I can see the Bookmarks button When I click on Bookmarks button Then I should see an empty bookmarks list And empty bookmarks list content is correct """ self._test_setup() self.assertTrue(self.bookmarks_page.bookmarks_button_visible()) self.bookmarks_page.click_bookmarks_button() self.assertEqual(self.bookmarks_page.results_header_text(), 'My Bookmarks') self.assertEqual(self.bookmarks_page.empty_header_text(), 'You have not bookmarked any courseware pages yet.') empty_list_text = ("Use bookmarks to help you easily return to courseware pages. To bookmark a page, " "select Bookmark in the upper right corner of that page. To see a list of all your " "bookmarks, select Bookmarks in the upper left corner of any courseware page.") self.assertEqual(self.bookmarks_page.empty_list_text(), empty_list_text) def test_bookmarks_list(self): """ Scenario: A bookmarks list is shown if there are bookmarked units. Given that I am a registered user And I visit my courseware page And I have bookmarked 2 units When I click on Bookmarks button Then I should see a bookmarked list with 2 bookmark links And breadcrumb trail is correct for a bookmark When I click on bookmarked link Then I can navigate to correct bookmarked unit """ self._test_setup() self._bookmark_units(2) self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=2) self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 1-2 out of 2 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1 ) # get usage ids for units xblocks = self.course_fixture.get_nested_xblocks(category="vertical") xblock_usage_ids = [xblock.locator for xblock in xblocks] # Verify link navigation for index in range(2): self.bookmarks_page.click_bookmarked_block(index) self.courseware_page.wait_for_page() self.assertIn(self.courseware_page.active_usage_id(), xblock_usage_ids) self.courseware_page.visit().wait_for_page() self.bookmarks_page.click_bookmarks_button() def test_bookmark_shows_updated_breadcrumb_after_publish(self): """ Scenario: A bookmark breadcrumb trail is updated after publishing the changed display name. Given that I am a registered user And I visit my courseware page And I can see bookmarked unit Then I visit unit page in studio Then I change unit display_name And I publish the changes Then I visit my courseware page And I visit bookmarks list page When I see the bookmark Then I can see the breadcrumb trail with updated display_name. """ self._test_setup(num_chapters=1) self._bookmark_units(num_units=1) self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=1) LogoutPage(self.browser).visit() LmsAutoAuthPage( self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True ).visit() modified_name = "Updated name" self.update_and_publish_block_display_name(modified_name) LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=1, modified_name=modified_name) def test_unreachable_bookmark(self): """ Scenario: We should get a HTTP 404 for an unreachable bookmark. Given that I am a registered user And I visit my courseware page And I have bookmarked 2 units Then I delete a bookmarked unit Then I click on Bookmarks button And I should see a bookmarked list When I click on deleted bookmark Then I should navigated to 404 page """ self._test_setup(num_chapters=1) self._bookmark_units(1) self._delete_section(0) self._navigate_to_bookmarks_list() self._verify_pagination_info( bookmark_count_on_current_page=1, header_text='Showing 1 out of 1 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1 ) self.bookmarks_page.click_bookmarked_block(0) self.assertTrue(is_404_page(self.browser)) def test_page_size_limit(self): """ Scenario: We can't get bookmarks more than default page size. Given that I am a registered user And I visit my courseware page And I have bookmarked all the 11 units available Then I click on Bookmarks button And I should see a bookmarked list And bookmark list contains 10 bookmarked items """ self._test_setup(11) self._bookmark_units(11) self._navigate_to_bookmarks_list() self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 11 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2 ) def test_pagination_with_single_page(self): """ Scenario: Bookmarks list pagination is working as expected for single page Given that I am a registered user And I visit my courseware page And I have bookmarked all the 2 units available Then I click on Bookmarks button And I should see a bookmarked list with 2 bookmarked items And I should see paging header and footer with correct data And previous and next buttons are disabled """ self._test_setup(num_chapters=2) self._bookmark_units(num_units=2) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 1-2 out of 2 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1 ) def test_next_page_button(self): """ Scenario: Next button is working as expected for bookmarks list pagination Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available Then I click on Bookmarks button And I should see a bookmarked list of 10 items And I should see paging header and footer with correct info Then I click on next page button in footer And I should be navigated to second page And I should see a bookmarked list with 2 items And I should see paging header and footer with correct info """ self._test_setup(num_chapters=12) self._bookmark_units(num_units=12) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 12 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2 ) self.bookmarks_page.press_next_page_button() self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 11-12 out of 12 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2 ) def test_previous_page_button(self): """ Scenario: Previous button is working as expected for bookmarks list pagination Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available And I click on Bookmarks button Then I click on next page button in footer And I should be navigated to second page And I should see a bookmarked list with 2 items And I should see paging header and footer with correct info Then I click on previous page button And I should be navigated to first page And I should see paging header and footer with correct info """ self._test_setup(num_chapters=12) self._bookmark_units(num_units=12) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.bookmarks_page.press_next_page_button() self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 11-12 out of 12 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2 ) self.bookmarks_page.press_previous_page_button() self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 12 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2 ) def test_pagination_with_valid_page_number(self): """ Scenario: Bookmarks list pagination works as expected for valid page number Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available Then I click on Bookmarks button And I should see a bookmarked list And I should see total page value is 2 Then I enter 2 in the page number input And I should be navigated to page 2 """ self._test_setup(num_chapters=11) self._bookmark_units(num_units=11) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.get_total_pages, 2) self.bookmarks_page.go_to_page(2) self._verify_pagination_info( bookmark_count_on_current_page=1, header_text='Showing 11-11 out of 11 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2 ) def test_pagination_with_invalid_page_number(self): """ Scenario: Bookmarks list pagination works as expected for invalid page number Given that I am a registered user And I visit my courseware page And I have bookmarked all the 11 units available Then I click on Bookmarks button And I should see a bookmarked list And I should see total page value is 2 Then I enter 3 in the page number input And I should stay at page 1 """ self._test_setup(num_chapters=11) self._bookmark_units(num_units=11) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.get_total_pages, 2) self.bookmarks_page.go_to_page(3) self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 11 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2 ) def test_bookmarked_unit_accessed_event(self): """ Scenario: Bookmark events are emitted with correct data when we access/visit a bookmarked unit. Given that I am a registered user And I visit my courseware page And I have bookmarked a unit When I click on bookmarked unit Then `edx.course.bookmark.accessed` event is emitted """ self._test_setup(num_chapters=1) self.reset_event_tracking() # create expected event data xblocks = self.course_fixture.get_nested_xblocks(category="vertical") event_data = [ { 'event': { 'bookmark_id': '{},{}'.format(self.USERNAME, xblocks[0].locator), 'component_type': xblocks[0].category, 'component_usage_id': xblocks[0].locator, } } ] self._bookmark_units(num_units=1) self.bookmarks_page.click_bookmarks_button() self._verify_pagination_info( bookmark_count_on_current_page=1, header_text='Showing 1 out of 1 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1 ) self.bookmarks_page.click_bookmarked_block(0) self.verify_event_data('edx.bookmark.accessed', event_data)
class SignUpAndSignInTest(UniqueCourseTest): """ Test studio sign-up and sign-in """ 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 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() unique_number = uuid.uuid4().hex[:4] registration_dic = { "#email": "{}[email protected]".format(unique_number), "#name": "{}-name".format(unique_number), "#username": "******".format(unique_number), "#password": "******".format(unique_number), } # Register the user. self.sign_up_page.sign_up_user(registration_dic) home = HomePage(self.browser) home.wait_for_page() 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"]) home = HomePage(self.browser) home.wait_for_page() self.assertEqual(self.browser.current_url, home.url) 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("#login_error", "Login error is visible") # Change the password self.course_outline_sign_in_redirect_page.fill_field("input#password", "changed_password") # Login error should not be visible self.course_outline_sign_in_redirect_page.wait_for_element_invisibility( "#login_error", "Login error is not 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 BookmarksTest(BookmarksTestMixin): """ Tests to verify bookmarks functionality. """ def setUp(self): """ Initialize test setup. """ super(BookmarksTest, self).setUp() self.course_outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.bookmarks_page = BookmarksPage(self.browser, self.course_id) self.course_nav = CourseNavPage(self.browser) # Get session to be used for bookmarking units self.session = requests.Session() params = { 'username': self.USERNAME, 'email': self.EMAIL, 'course_id': self.course_id } response = self.session.get(BASE_URL + "/auto_auth", params=params) self.assertTrue(response.ok, "Failed to get session") def _test_setup(self, num_chapters=2): """ Setup test settings. Arguments: num_chapters: number of chapters to create in course """ self.create_course_fixture(num_chapters) # Auto-auth register for the course. LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() def _bookmark_unit(self, location): """ Bookmark a unit Arguments: location (str): unit location """ _headers = { 'Content-type': 'application/json', 'X-CSRFToken': self.session.cookies['csrftoken'], } params = {'course_id': self.course_id} data = json.dumps({'usage_id': location}) response = self.session.post(BASE_URL + '/api/bookmarks/v1/bookmarks/', data=data, params=params, headers=_headers) self.assertTrue(response.ok, "Failed to bookmark unit") def _bookmark_units(self, num_units): """ Bookmark first `num_units` units Arguments: num_units(int): Number of units to bookmarks """ xblocks = self.course_fixture.get_nested_xblocks(category="vertical") for index in range(num_units): self._bookmark_unit(xblocks[index].locator) def _breadcrumb(self, num_units, modified_name=None): """ Creates breadcrumbs for the first `num_units` Arguments: num_units(int): Number of units for which we want to create breadcrumbs Returns: list of breadcrumbs """ breadcrumbs = [] for index in range(num_units): breadcrumbs.append([ 'TestSection{}'.format(index), 'TestSubsection{}'.format(index), modified_name if modified_name else 'TestVertical{}'.format(index) ]) return breadcrumbs def _delete_section(self, index): """ Delete a section at index `index` """ # Logout and login as staff LogoutPage(self.browser).visit() StudioAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True).visit() # Visit course outline page in studio. self.course_outline_page.visit() self.course_outline_page.wait_for_page() self.course_outline_page.section_at(index).delete() # Logout and login as a student. LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() # Visit courseware as a student. self.courseware_page.visit() self.courseware_page.wait_for_page() def _toggle_bookmark_and_verify(self, bookmark_icon_state, bookmark_button_state, bookmarked_count): """ Bookmark/Un-Bookmark a unit and then verify """ self.assertTrue(self.courseware_page.bookmark_button_visible) self.courseware_page.click_bookmark_unit_button() self.assertEqual(self.courseware_page.bookmark_icon_visible, bookmark_icon_state) self.assertEqual(self.courseware_page.bookmark_button_state, bookmark_button_state) self.bookmarks_page.click_bookmarks_button() self.assertEqual(self.bookmarks_page.count(), bookmarked_count) def _verify_pagination_info(self, bookmark_count_on_current_page, header_text, previous_button_enabled, next_button_enabled, current_page_number, total_pages): """ Verify pagination info """ self.assertEqual(self.bookmarks_page.count(), bookmark_count_on_current_page) self.assertEqual(self.bookmarks_page.get_pagination_header_text(), header_text) self.assertEqual(self.bookmarks_page.is_previous_page_button_enabled(), previous_button_enabled) self.assertEqual(self.bookmarks_page.is_next_page_button_enabled(), next_button_enabled) self.assertEqual(self.bookmarks_page.get_current_page_number(), current_page_number) self.assertEqual(self.bookmarks_page.get_total_pages, total_pages) def _navigate_to_bookmarks_list(self): """ Navigates and verifies the bookmarks list page. """ self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.results_header_text(), 'My Bookmarks') def _verify_breadcrumbs(self, num_units, modified_name=None): """ Verifies the breadcrumb trail. """ bookmarked_breadcrumbs = self.bookmarks_page.breadcrumbs() # Verify bookmarked breadcrumbs. breadcrumbs = self._breadcrumb(num_units=num_units, modified_name=modified_name) breadcrumbs.reverse() self.assertEqual(bookmarked_breadcrumbs, breadcrumbs) def update_and_publish_block_display_name(self, modified_name): """ Update and publish the block/unit display name. """ self.course_outline_page.visit() self.course_outline_page.wait_for_page() self.course_outline_page.expand_all_subsections() section = self.course_outline_page.section_at(0) container_page = section.subsection_at(0).unit_at(0).go_to() self.course_fixture._update_xblock( container_page.locator, { # pylint: disable=protected-access "metadata": { "display_name": modified_name } }) container_page.visit() container_page.wait_for_page() self.assertEqual(container_page.name, modified_name) container_page.publish_action.click() def test_bookmark_button(self): """ Scenario: Bookmark unit button toggles correctly Given that I am a registered user And I visit my courseware page For first 2 units I visit the unit And I can see the Bookmark button When I click on Bookmark button Then unit should be bookmarked Then I click again on the bookmark button And I should see a unit un-bookmarked """ self._test_setup() for index in range(2): self.course_nav.go_to_section('TestSection{}'.format(index), 'TestSubsection{}'.format(index)) self._toggle_bookmark_and_verify(True, 'bookmarked', 1) self.bookmarks_page.click_bookmarks_button(False) self._toggle_bookmark_and_verify(False, '', 0) def test_empty_bookmarks_list(self): """ Scenario: An empty bookmarks list is shown if there are no bookmarked units. Given that I am a registered user And I visit my courseware page And I can see the Bookmarks button When I click on Bookmarks button Then I should see an empty bookmarks list And empty bookmarks list content is correct """ self._test_setup() self.assertTrue(self.bookmarks_page.bookmarks_button_visible()) self.bookmarks_page.click_bookmarks_button() self.assertEqual(self.bookmarks_page.results_header_text(), 'My Bookmarks') self.assertEqual(self.bookmarks_page.empty_header_text(), 'You have not bookmarked any courseware pages yet.') empty_list_text = ( "Use bookmarks to help you easily return to courseware pages. To bookmark a page, " "select Bookmark in the upper right corner of that page. To see a list of all your " "bookmarks, select Bookmarks in the upper left corner of any courseware page." ) self.assertEqual(self.bookmarks_page.empty_list_text(), empty_list_text) def test_bookmarks_list(self): """ Scenario: A bookmarks list is shown if there are bookmarked units. Given that I am a registered user And I visit my courseware page And I have bookmarked 2 units When I click on Bookmarks button Then I should see a bookmarked list with 2 bookmark links And breadcrumb trail is correct for a bookmark When I click on bookmarked link Then I can navigate to correct bookmarked unit """ self._test_setup() self._bookmark_units(2) self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=2) self._verify_pagination_info(bookmark_count_on_current_page=2, header_text='Showing 1-2 out of 2 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1) # get usage ids for units xblocks = self.course_fixture.get_nested_xblocks(category="vertical") xblock_usage_ids = [xblock.locator for xblock in xblocks] # Verify link navigation for index in range(2): self.bookmarks_page.click_bookmarked_block(index) self.courseware_page.wait_for_page() self.assertIn(self.courseware_page.active_usage_id(), xblock_usage_ids) self.courseware_page.visit().wait_for_page() self.bookmarks_page.click_bookmarks_button() def test_bookmark_shows_updated_breadcrumb_after_publish(self): """ Scenario: A bookmark breadcrumb trail is updated after publishing the changed display name. Given that I am a registered user And I visit my courseware page And I can see bookmarked unit Then I visit unit page in studio Then I change unit display_name And I publish the changes Then I visit my courseware page And I visit bookmarks list page When I see the bookmark Then I can see the breadcrumb trail with updated display_name. """ self._test_setup(num_chapters=1) self._bookmark_units(num_units=1) self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=1) LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True).visit() modified_name = "Updated name" self.update_and_publish_block_display_name(modified_name) LogoutPage(self.browser).visit() LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit() self.courseware_page.visit() self._navigate_to_bookmarks_list() self._verify_breadcrumbs(num_units=1, modified_name=modified_name) def test_unreachable_bookmark(self): """ Scenario: We should get a HTTP 404 for an unreachable bookmark. Given that I am a registered user And I visit my courseware page And I have bookmarked 2 units Then I delete a bookmarked unit Then I click on Bookmarks button And I should see a bookmarked list When I click on deleted bookmark Then I should navigated to 404 page """ self._test_setup(num_chapters=1) self._bookmark_units(1) self._delete_section(0) self._navigate_to_bookmarks_list() self._verify_pagination_info(bookmark_count_on_current_page=1, header_text='Showing 1 out of 1 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1) self.bookmarks_page.click_bookmarked_block(0) self.assertTrue(is_404_page(self.browser)) def test_page_size_limit(self): """ Scenario: We can't get bookmarks more than default page size. Given that I am a registered user And I visit my courseware page And I have bookmarked all the 11 units available Then I click on Bookmarks button And I should see a bookmarked list And bookmark list contains 10 bookmarked items """ self._test_setup(11) self._bookmark_units(11) self._navigate_to_bookmarks_list() self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 11 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2) def test_pagination_with_single_page(self): """ Scenario: Bookmarks list pagination is working as expected for single page Given that I am a registered user And I visit my courseware page And I have bookmarked all the 2 units available Then I click on Bookmarks button And I should see a bookmarked list with 2 bookmarked items And I should see paging header and footer with correct data And previous and next buttons are disabled """ self._test_setup(num_chapters=2) self._bookmark_units(num_units=2) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self._verify_pagination_info(bookmark_count_on_current_page=2, header_text='Showing 1-2 out of 2 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1) def test_next_page_button(self): """ Scenario: Next button is working as expected for bookmarks list pagination Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available Then I click on Bookmarks button And I should see a bookmarked list of 10 items And I should see paging header and footer with correct info Then I click on next page button in footer And I should be navigated to second page And I should see a bookmarked list with 2 items And I should see paging header and footer with correct info """ self._test_setup(num_chapters=12) self._bookmark_units(num_units=12) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 12 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2) self.bookmarks_page.press_next_page_button() self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 11-12 out of 12 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2) def test_previous_page_button(self): """ Scenario: Previous button is working as expected for bookmarks list pagination Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available And I click on Bookmarks button Then I click on next page button in footer And I should be navigated to second page And I should see a bookmarked list with 2 items And I should see paging header and footer with correct info Then I click on previous page button And I should be navigated to first page And I should see paging header and footer with correct info """ self._test_setup(num_chapters=12) self._bookmark_units(num_units=12) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.bookmarks_page.press_next_page_button() self._verify_pagination_info( bookmark_count_on_current_page=2, header_text='Showing 11-12 out of 12 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2) self.bookmarks_page.press_previous_page_button() self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 12 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2) def test_pagination_with_valid_page_number(self): """ Scenario: Bookmarks list pagination works as expected for valid page number Given that I am a registered user And I visit my courseware page And I have bookmarked all the 12 units available Then I click on Bookmarks button And I should see a bookmarked list And I should see total page value is 2 Then I enter 2 in the page number input And I should be navigated to page 2 """ self._test_setup(num_chapters=11) self._bookmark_units(num_units=11) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.get_total_pages, 2) self.bookmarks_page.go_to_page(2) self._verify_pagination_info( bookmark_count_on_current_page=1, header_text='Showing 11-11 out of 11 total', previous_button_enabled=True, next_button_enabled=False, current_page_number=2, total_pages=2) def test_pagination_with_invalid_page_number(self): """ Scenario: Bookmarks list pagination works as expected for invalid page number Given that I am a registered user And I visit my courseware page And I have bookmarked all the 11 units available Then I click on Bookmarks button And I should see a bookmarked list And I should see total page value is 2 Then I enter 3 in the page number input And I should stay at page 1 """ self._test_setup(num_chapters=11) self._bookmark_units(num_units=11) self.bookmarks_page.click_bookmarks_button() self.assertTrue(self.bookmarks_page.results_present()) self.assertEqual(self.bookmarks_page.get_total_pages, 2) self.bookmarks_page.go_to_page(3) self._verify_pagination_info( bookmark_count_on_current_page=10, header_text='Showing 1-10 out of 11 total', previous_button_enabled=False, next_button_enabled=True, current_page_number=1, total_pages=2) def test_bookmarked_unit_accessed_event(self): """ Scenario: Bookmark events are emitted with correct data when we access/visit a bookmarked unit. Given that I am a registered user And I visit my courseware page And I have bookmarked a unit When I click on bookmarked unit Then `edx.course.bookmark.accessed` event is emitted """ self._test_setup(num_chapters=1) self.reset_event_tracking() # create expected event data xblocks = self.course_fixture.get_nested_xblocks(category="vertical") event_data = [{ 'event': { 'bookmark_id': '{},{}'.format(self.USERNAME, xblocks[0].locator), 'component_type': xblocks[0].category, 'component_usage_id': xblocks[0].locator, } }] self._bookmark_units(num_units=1) self.bookmarks_page.click_bookmarks_button() self._verify_pagination_info(bookmark_count_on_current_page=1, header_text='Showing 1 out of 1 total', previous_button_enabled=False, next_button_enabled=False, current_page_number=1, total_pages=1) self.bookmarks_page.click_bookmarked_block(0) self.verify_event_data('edx.bookmark.accessed', event_data)
class 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 the path is "/signin_redirect_to_lms?next=/course/slashes%3AMITx%2B999%2BRobot_Super_Course" When I fill in and submit the LMS login 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)
class SignUpAndSignInTest(UniqueCourseTest): """ Test studio sign-up and sign-in """ 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 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() unique_number = uuid.uuid4().hex[:4] registration_dic = { '#email': '{}[email protected]'.format(unique_number), '#name': '{}-name'.format(unique_number), '#username': '******'.format(unique_number), '#password': '******'.format(unique_number), } # Register the user. self.sign_up_page.sign_up_user(registration_dic) home = HomePage(self.browser) home.wait_for_page() 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']) home = HomePage(self.browser) home.wait_for_page() self.assertEqual(self.browser.current_url, home.url) 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( '#login_error', 'Login error is visible') # Change the password self.course_outline_sign_in_redirect_page.fill_field( 'input#password', 'changed_password') # Login error should not be visible self.course_outline_sign_in_redirect_page.wait_for_element_invisibility( '#login_error', 'Login error is not 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 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 GroupConfigurationsTest(ContainerBase, SplitTestMixin): """ Tests that Group Configurations page works correctly with previously added configurations in Studio """ __test__ = True def setUp(self): super(GroupConfigurationsTest, self).setUp() self.page = GroupConfigurationsPage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) self.outline_page = CourseOutlinePage( self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] ) def _assert_fields(self, config, cid=None, name='', description='', groups=None): self.assertEqual(config.mode, 'details') if name: self.assertIn(name, config.name) if cid: self.assertEqual(cid, config.id) else: # To make sure that id is present on the page and it is not an empty. # We do not check the value of the id, because it's generated randomly and we cannot # predict this value self.assertTrue(config.id) # Expand the configuration config.toggle() if description: self.assertIn(description, config.description) if groups: allocation = int(math.floor(100 / len(groups))) self.assertEqual(groups, [group.name for group in config.groups]) for group in config.groups: self.assertEqual(str(allocation) + "%", group.allocation) # Collapse the configuration config.toggle() def _add_split_test_to_vertical(self, number, group_configuration_metadata=None): """ Add split test to vertical #`number`. If `group_configuration_metadata` is not None, use it to assign group configuration to split test. """ vertical = self.course_fixture.get_nested_xblocks(category="vertical")[number] if group_configuration_metadata: split_test = XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata=group_configuration_metadata) else: split_test = XBlockFixtureDesc('split_test', 'Test Content Experiment') self.course_fixture.create_xblock(vertical.locator, split_test) return split_test def populate_course_fixture(self, course_fixture): course_fixture.add_advanced_settings({ u"advanced_modules": {"value": ["split_test"]}, }) course_fixture.add_children( XBlockFixtureDesc('chapter', 'Test Section').add_children( XBlockFixtureDesc('sequential', 'Test Subsection').add_children( XBlockFixtureDesc('vertical', 'Test Unit') ) ) ) def create_group_configuration_experiment(self, groups, associate_experiment): """ Creates a Group Configuration containing a list of groups. Optionally creates a Content Experiment and associates it with previous Group Configuration. Returns group configuration or (group configuration, experiment xblock) """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json(0, "Name", "Description.", groups), ], }, }) if associate_experiment: # Assign newly created group configuration to experiment vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] split_test = XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 0}) self.course_fixture.create_xblock(vertical.locator, split_test) # Go to the Group Configuration Page self.page.visit() config = self.page.experiment_group_configurations[0] if associate_experiment: return config, split_test return config def publish_unit_in_lms_and_view(self, courseware_page, publish=True): """ Given course outline page, publish first unit and view it in LMS when publish is false, it will only view """ self.outline_page.visit() self.outline_page.expand_all_subsections() section = self.outline_page.section_at(0) unit = section.subsection_at(0).unit_at(0).go_to() # I publish and view in LMS and it is rendered correctly if publish: unit.publish_action.click() unit.view_published_version() self.assertEqual(len(self.browser.window_handles), 2) courseware_page.wait_for_page() def get_select_options(self, page, selector): """ Get list of options of dropdown that is specified by selector on a given page. """ select_element = page.q(css=selector) self.assertTrue(select_element.is_present()) return [option.text for option in Select(select_element[0]).options] def test_no_group_configurations_added(self): """ Scenario: Ensure that message telling me to create a new group configuration is shown when group configurations were not added. Given I have a course without group configurations When I go to the Group Configuration page in Studio Then I see "You have not created any group configurations yet." message """ self.page.visit() self.assertTrue(self.page.experiment_group_sections_present) self.assertTrue(self.page.no_experiment_groups_message_is_present) self.assertIn( "You have not created any group configurations yet.", self.page.no_experiment_groups_message_text ) def test_group_configurations_have_correct_data(self): """ Scenario: Ensure that the group configuration is rendered correctly in expanded/collapsed mode. Given I have a course with 2 group configurations And I go to the Group Configuration page in Studio And I work with the first group configuration And I see `name`, `id` are visible and have correct values When I expand the first group configuration Then I see `description` and `groups` appear and also have correct values And I do the same checks for the second group configuration """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')] ), create_user_partition_json( 1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')] ), ], }, }) self.page.visit() config = self.page.experiment_group_configurations[0] # no groups when the the configuration is collapsed self.assertEqual(len(config.groups), 0) self._assert_fields( config, cid="0", name="Name of the Group Configuration", description="Description of the group configuration.", groups=["Group 0", "Group 1"] ) config = self.page.experiment_group_configurations[1] self._assert_fields( config, name="Name of second Group Configuration", description="Second group configuration.", groups=["Alpha", "Beta", "Gamma"] ) def test_can_create_and_edit_group_configuration(self): """ Scenario: Ensure that the group configuration can be created and edited correctly. Given I have a course without group configurations When I click button 'Create new Group Configuration' And I set new name and description, change name for the 2nd default group, add one new group And I click button 'Create' Then I see the new group configuration is added and has correct data When I edit the group group_configuration And I change the name and description, add new group, remove old one and change name for the Group A And I click button 'Save' Then I see the group configuration is saved successfully and has the new data """ self.page.visit() self.assertEqual(len(self.page.experiment_group_configurations), 0) # Create new group configuration self.page.create_experiment_group_configuration() config = self.page.experiment_group_configurations[0] config.name = "New Group Configuration Name" config.description = "New Description of the group configuration." config.groups[1].name = "New Group Name" # Add new group config.add_group() # Group C # Save the configuration self.assertEqual(config.get_text('.action-primary'), "Create") self.assertFalse(config.delete_button_is_present) config.save() self._assert_fields( config, name="New Group Configuration Name", description="New Description of the group configuration.", groups=["Group A", "New Group Name", "Group C"] ) # Edit the group configuration config.edit() # Update fields self.assertTrue(config.id) config.name = "Second Group Configuration Name" config.description = "Second Description of the group configuration." self.assertEqual(config.get_text('.action-primary'), "Save") # Add new group config.add_group() # Group D # Remove group with name "New Group Name" config.groups[1].remove() # Rename Group A config.groups[0].name = "First Group" # Save the configuration config.save() self._assert_fields( config, name="Second Group Configuration Name", description="Second Description of the group configuration.", groups=["First Group", "Group C", "Group D"] ) def test_focus_management_in_experiment_group_inputs(self): """ Scenario: Ensure that selecting the focus inputs in the groups list sets the .is-focused class on the fieldset Given I have a course with experiment group configurations When I click the name of the first group Then the fieldset wrapping the group names whould get class .is-focused When I click away from the first group Then the fieldset should not have class .is-focused anymore """ self.page.visit() self.page.create_experiment_group_configuration() config = self.page.experiment_group_configurations[0] group_a = config.groups[0] # Assert the fieldset doesn't have .is-focused class self.assertFalse(self.page.q(css="fieldset.groups-fields.is-focused").visible) # Click on the Group A input field self.page.q(css=group_a.prefix).click() # Assert the fieldset has .is-focused class applied self.assertTrue(self.page.q(css="fieldset.groups-fields.is-focused").visible) # Click away self.page.q(css=".page-header").click() # Assert the fieldset doesn't have .is-focused class self.assertFalse(self.page.q(css="fieldset.groups-fields.is-focused").visible) def test_use_group_configuration(self): """ Scenario: Ensure that the group configuration can be used by split_module correctly Given I have a course without group configurations When I create new group configuration And I set new name and add a new group, save the group configuration And I go to the unit page in Studio And I add new advanced module "Content Experiment" When I assign created group configuration to the module Then I see the module has correct groups """ self.page.visit() # Create new group configuration self.page.create_experiment_group_configuration() config = self.page.experiment_group_configurations[0] config.name = "New Group Configuration Name" # Add new group config.add_group() config.groups[2].name = "New group" # Save the configuration config.save() split_test = self._add_split_test_to_vertical(number=0) container = ContainerPage(self.browser, split_test.locator) container.visit() container.edit() component_editor = ComponentEditorView(self.browser, container.locator) component_editor.set_select_value_and_save('Group Configuration', 'New Group Configuration Name') self.verify_groups(container, ['Group A', 'Group B', 'New group'], []) def test_container_page_active_verticals_names_are_synced(self): """ Scenario: Ensure that the Content Experiment display synced vertical names and correct groups. Given I have a course with group configuration And I go to the Group Configuration page in Studio And I edit the name of the group configuration, add new group and remove old one And I change the name for the group "New group" to "Second Group" And I go to the Container page in Studio And I edit the Content Experiment Then I see the group configuration name is changed in `Group Configuration` dropdown And the group configuration name is changed on container page And I see the module has 2 active groups and one inactive And I see "Add missing groups" link exists When I click on "Add missing groups" link The I see the module has 3 active groups and one inactive """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group A'), Group("1", 'Group B'), Group("2", 'Group C')] ), ], }, }) # Add split test to vertical and assign newly created group configuration to it split_test = self._add_split_test_to_vertical(number=0, group_configuration_metadata={'user_partition_id': 0}) self.page.visit() config = self.page.experiment_group_configurations[0] config.edit() config.name = "Second Group Configuration Name" # `Group C` -> `Second Group` config.groups[2].name = "Second Group" # Add new group config.add_group() # Group D # Remove Group A config.groups[0].remove() # Save the configuration config.save() container = ContainerPage(self.browser, split_test.locator) container.visit() container.edit() component_editor = ComponentEditorView(self.browser, container.locator) self.assertEqual( "Second Group Configuration Name", component_editor.get_selected_option_text('Group Configuration') ) component_editor.cancel() self.assertIn( "Second Group Configuration Name", container.get_xblock_information_message() ) self.verify_groups( container, ['Group B', 'Second Group'], ['Group ID 0'], verify_missing_groups_not_present=False ) # Click the add button and verify that the groups were added on the page container.add_missing_groups() self.verify_groups(container, ['Group B', 'Second Group', 'Group D'], ['Group ID 0']) def test_can_cancel_creation_of_group_configuration(self): """ Scenario: Ensure that creation of the group configuration can be canceled correctly. Given I have a course without group configurations When I click button 'Create new Group Configuration' And I set new name and description, add 1 additional group And I click button 'Cancel' Then I see that there is no new group configurations in the course """ self.page.visit() self.assertEqual(len(self.page.experiment_group_configurations), 0) # Create new group configuration self.page.create_experiment_group_configuration() config = self.page.experiment_group_configurations[0] config.name = "Name of the Group Configuration" config.description = "Description of the group configuration." # Add new group config.add_group() # Group C # Cancel the configuration config.cancel() self.assertEqual(len(self.page.experiment_group_configurations), 0) def test_can_cancel_editing_of_group_configuration(self): """ Scenario: Ensure that editing of the group configuration can be canceled correctly. Given I have a course with group configuration When I go to the edit mode of the group configuration And I set new name and description, add 2 additional groups And I click button 'Cancel' Then I see that new changes were discarded """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')] ), create_user_partition_json( 1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')] ), ], }, }) self.page.visit() config = self.page.experiment_group_configurations[0] config.name = "New Group Configuration Name" config.description = "New Description of the group configuration." # Add 2 new groups config.add_group() # Group C config.add_group() # Group D # Cancel the configuration config.cancel() self._assert_fields( config, name="Name of the Group Configuration", description="Description of the group configuration.", groups=["Group 0", "Group 1"] ) def test_group_configuration_validation(self): """ Scenario: Ensure that validation of the group configuration works correctly. Given I have a course without group configurations And I create new group configuration with 2 default groups When I set only description and try to save Then I see error message "Group Configuration name is required." When I set a name And I delete the name of one of the groups and try to save Then I see error message "All groups must have a name" When I delete all the groups and try to save Then I see error message "There must be at least one group." When I add a group and try to save Then I see the group configuration is saved successfully """ def try_to_save_and_verify_error_message(message): # Try to save config.save() # Verify that configuration is still in editing mode self.assertEqual(config.mode, 'edit') # Verify error message self.assertEqual(message, config.validation_message) self.page.visit() # Create new group configuration self.page.create_experiment_group_configuration() # Leave empty required field config = self.page.experiment_group_configurations[0] config.description = "Description of the group configuration." try_to_save_and_verify_error_message("Group Configuration name is required.") # Set required field config.name = "Name of the Group Configuration" config.groups[1].name = '' try_to_save_and_verify_error_message("All groups must have a name.") config.groups[0].remove() config.groups[0].remove() try_to_save_and_verify_error_message("There must be at least one group.") config.add_group() # Save the configuration config.save() self._assert_fields( config, name="Name of the Group Configuration", description="Description of the group configuration.", groups=["Group A"] ) def test_group_configuration_empty_usage(self): """ Scenario: When group configuration is not used, ensure that the link to outline page works correctly. Given I have a course without group configurations And I create new group configuration with 2 default groups Then I see a link to the outline page When I click on the outline link Then I see the outline page """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")] ), ], }, }) # Go to the Group Configuration Page and click on outline anchor self.page.visit() config = self.page.experiment_group_configurations[0] config.toggle() config.click_outline_anchor() # Waiting for the page load and verify that we've landed on course outline page self.outline_page.wait_for_page() def test_group_configuration_non_empty_usage(self): """ Scenario: When group configuration is used, ensure that the links to units using a group configuration work correctly. Given I have a course without group configurations And I create new group configuration with 2 default groups And I create a unit and assign the newly created group configuration And open the Group Configuration page Then I see a link to the newly created unit When I click on the unit link Then I see correct unit page """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")] ), ], }, }) # Assign newly created group configuration to unit vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] self.course_fixture.create_xblock( vertical.locator, XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 0}) ) unit = CourseOutlineUnit(self.browser, vertical.locator) # Go to the Group Configuration Page and click unit anchor self.page.visit() config = self.page.experiment_group_configurations[0] config.toggle() usage = config.usages[0] config.click_unit_anchor() unit = ContainerPage(self.browser, vertical.locator) # Waiting for the page load and verify that we've landed on the unit page unit.wait_for_page() self.assertIn(unit.name, usage) def test_can_delete_unused_group_configuration(self): """ Scenario: Ensure that the user can delete unused group configuration. Given I have a course with 2 group configurations And I go to the Group Configuration page When I delete the Group Configuration with name "Configuration 1" Then I see that there is one Group Configuration When I edit the Group Configuration with name "Configuration 2" And I delete the Group Configuration with name "Configuration 2" Then I see that the are no Group Configurations """ self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, 'Configuration 1', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')] ), create_user_partition_json( 1, 'Configuration 2', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')] ) ], }, }) self.page.visit() self.assertEqual(len(self.page.experiment_group_configurations), 2) config = self.page.experiment_group_configurations[1] # Delete first group configuration via detail view config.delete() self.assertEqual(len(self.page.experiment_group_configurations), 1) config = self.page.experiment_group_configurations[0] config.edit() self.assertFalse(config.delete_button_is_disabled) # Delete first group configuration via edit view config.delete() self.assertEqual(len(self.page.experiment_group_configurations), 0) def test_cannot_delete_used_group_configuration(self): """ Scenario: Ensure that the user cannot delete unused group configuration. Given I have a course with group configuration that is used in the Content Experiment When I go to the Group Configuration page Then I do not see delete button and I see a note about that When I edit the Group Configuration Then I do not see delete button and I see the note about that """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")] ) ], }, }) vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] self.course_fixture.create_xblock( vertical.locator, XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 0}) ) # Go to the Group Configuration Page and click unit anchor self.page.visit() config = self.page.experiment_group_configurations[0] self.assertTrue(config.delete_button_is_disabled) self.assertIn('Cannot delete when in use by an experiment', config.delete_note) config.edit() self.assertTrue(config.delete_button_is_disabled) self.assertIn('Cannot delete when in use by an experiment', config.delete_note) def test_easy_access_from_experiment(self): """ Scenario: When a Content Experiment uses a Group Configuration, ensure that the link to that Group Configuration works correctly. Given I have a course with two Group Configurations And Content Experiment is assigned to one Group Configuration Then I see a link to Group Configuration When I click on the Group Configuration link Then I see the Group Configurations page And I see that appropriate Group Configuration is expanded. """ # Create a new group configurations self.course_fixture._update_xblock(self.course_fixture._course_location, { "metadata": { u"user_partitions": [ create_user_partition_json( 0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")] ), create_user_partition_json( 1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')] ), ], }, }) # Assign newly created group configuration to unit vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] self.course_fixture.create_xblock( vertical.locator, XBlockFixtureDesc('split_test', 'Test Content Experiment', metadata={'user_partition_id': 1}) ) unit = ContainerPage(self.browser, vertical.locator) unit.visit() experiment = unit.xblocks[0] group_configuration_link_name = experiment.group_configuration_link_name experiment.go_to_group_configuration_page() self.page.wait_for_page() # Appropriate Group Configuration is expanded. self.assertFalse(self.page.experiment_group_configurations[0].is_expanded) self.assertTrue(self.page.experiment_group_configurations[1].is_expanded) self.assertEqual( group_configuration_link_name, self.page.experiment_group_configurations[1].name ) def test_details_error_validation_message(self): """ Scenario: When a Content Experiment uses a Group Configuration, ensure that an error validation message appears if necessary. Given I have a course with a Group Configuration containing two Groups And a Content Experiment is assigned to that Group Configuration When I go to the Group Configuration Page Then I do not see a error icon and message in the Group Configuration details view. When I add a Group Then I see an error icon and message in the Group Configuration details view """ # Create group configuration and associated experiment config, _ = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B")], True) # Display details view config.toggle() # Check that error icon and message are not present self.assertFalse(config.details_error_icon_is_present) self.assertFalse(config.details_message_is_present) # Add a group config.toggle() config.edit() config.add_group() config.save() # Display details view config.toggle() # Check that error icon and message are present self.assertTrue(config.details_error_icon_is_present) self.assertTrue(config.details_message_is_present) self.assertIn( "This content experiment has issues that affect content visibility.", config.details_message_text ) def test_details_warning_validation_message(self): """ Scenario: When a Content Experiment uses a Group Configuration, ensure that a warning validation message appears if necessary. Given I have a course with a Group Configuration containing three Groups And a Content Experiment is assigned to that Group Configuration When I go to the Group Configuration Page Then I do not see a warning icon and message in the Group Configuration details view. When I remove a Group Then I see a warning icon and message in the Group Configuration details view """ # Create group configuration and associated experiment config, _ = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B"), Group("2", "Group C")], True) # Display details view config.toggle() # Check that warning icon and message are not present self.assertFalse(config.details_warning_icon_is_present) self.assertFalse(config.details_message_is_present) # Remove a group config.toggle() config.edit() config.groups[2].remove() config.save() # Display details view config.toggle() # Check that warning icon and message are present self.assertTrue(config.details_warning_icon_is_present) self.assertTrue(config.details_message_is_present) self.assertIn( "This content experiment has issues that affect content visibility.", config.details_message_text ) def test_edit_warning_message_empty_usage(self): """ Scenario: When a Group Configuration is not used, ensure that there are no warning icon and message. Given I have a course with a Group Configuration containing two Groups When I edit the Group Configuration Then I do not see a warning icon and message """ # Create a group configuration with no associated experiment and display edit view config = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B")], False) config.edit() # Check that warning icon and message are not present self.assertFalse(config.edit_warning_icon_is_present) self.assertFalse(config.edit_warning_message_is_present) def test_edit_warning_message_non_empty_usage(self): """ Scenario: When a Group Configuration is used, ensure that there are a warning icon and message. Given I have a course with a Group Configuration containing two Groups When I edit the Group Configuration Then I see a warning icon and message """ # Create a group configuration with an associated experiment and display edit view config, _ = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B")], True) config.edit() # Check that warning icon and message are present self.assertTrue(config.edit_warning_icon_is_present) self.assertTrue(config.edit_warning_message_is_present) self.assertIn( "This configuration is currently used in content experiments. If you make changes to the groups, you may need to edit those experiments.", config.edit_warning_message_text ) def publish_unit_and_verify_groups_in_lms(self, courseware_page, group_names, publish=True): """ Publish first unit in LMS and verify that Courseware page has given Groups """ self.publish_unit_in_lms_and_view(courseware_page, publish) self.assertEqual(u'split_test', courseware_page.xblock_component_type()) self.assertTrue(courseware_page.q(css=".split-test-select").is_present()) rendered_group_names = self.get_select_options(page=courseware_page, selector=".split-test-select") self.assertListEqual(group_names, rendered_group_names) def test_split_test_LMS_staff_view(self): """ Scenario: Ensure that split test is correctly rendered in LMS staff mode as it is and after inactive group removal. Given I have a course with group configurations and split test that assigned to first group configuration Then I publish split test and view it in LMS in staff view And it is rendered correctly Then I go to group configuration and delete group Then I publish split test and view it in LMS in staff view And it is rendered correctly Then I go to split test and delete inactive vertical Then I publish unit and view unit in LMS in staff view And it is rendered correctly """ config, split_test = self.create_group_configuration_experiment([Group("0", "Group A"), Group("1", "Group B"), Group("2", "Group C")], True) container = ContainerPage(self.browser, split_test.locator) # render in LMS correctly courseware_page = CoursewarePage(self.browser, self.course_id) self.publish_unit_and_verify_groups_in_lms(courseware_page, [u'Group A', u'Group B', u'Group C']) # I go to group configuration and delete group self.page.visit() self.page.q(css='.group-toggle').first.click() config.edit() config.groups[2].remove() config.save() self.page.q(css='.group-toggle').first.click() self._assert_fields(config, name="Name", description="Description", groups=["Group A", "Group B"]) self.browser.close() self.browser.switch_to_window(self.browser.window_handles[0]) # render in LMS to see how inactive vertical is rendered self.publish_unit_and_verify_groups_in_lms( courseware_page, [u'Group A', u'Group B', u'Group ID 2 (inactive)'], publish=False ) self.browser.close() self.browser.switch_to_window(self.browser.window_handles[0]) # I go to split test and delete inactive vertical container.visit() container.delete(0) # render in LMS again self.publish_unit_and_verify_groups_in_lms(courseware_page, [u'Group A', u'Group B'])
class SignUpAndSignInTest(UniqueCourseTest): """ Test studio sign-up and sign-in """ shard = 21 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 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() unique_number = uuid.uuid4().hex[:4] registration_dic = { '#email': '{}[email protected]'.format(unique_number), '#name': '{}-name'.format(unique_number), '#username': '******'.format(unique_number), '#password': '******'.format(unique_number), } # Register the user. self.sign_up_page.sign_up_user(registration_dic) 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('#password_error', 'Password Error Message') self.assertIsNotNone(index_page.q(css='#password_error').text) # Make sure there is an error message 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']) home = HomePage(self.browser) home.wait_for_page() self.assertEqual(self.browser.current_url, home.url) 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( '#login_error', 'Login error is visible' ) # Change the password self.course_outline_sign_in_redirect_page.fill_field('input#password', 'changed_password') # Login error should not be visible self.course_outline_sign_in_redirect_page.wait_for_element_invisibility( '#login_error', 'Login error is not 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)