diff --git a/common/test/acceptance/pages/lms/instructor_dashboard.py b/common/test/acceptance/pages/lms/instructor_dashboard.py index abb4715858..6f703495d5 100644 --- a/common/test/acceptance/pages/lms/instructor_dashboard.py +++ b/common/test/acceptance/pages/lms/instructor_dashboard.py @@ -5,6 +5,7 @@ Instructor (2) dashboard page. from bok_choy.page_object import PageObject from .course_page import CoursePage +import os class InstructorDashboardPage(CoursePage): @@ -12,7 +13,6 @@ class InstructorDashboardPage(CoursePage): Instructor dashboard, where course staff can manage a course. """ url_path = "instructor" - def is_browser_on_page(self): return self.q(css='div.instructor-dashboard-wrapper-2').present @@ -31,10 +31,15 @@ class MembershipPage(PageObject): Membership section of the Instructor dashboard. """ url = None - def is_browser_on_page(self): return self.q(css='a[data-section=membership].active-section').present + def select_auto_enroll_section(self): + """ + returns the MembershipPageAutoEnrollSection + """ + return MembershipPageAutoEnrollSection(self.browser) + def _get_cohort_options(self): """ Returns the available options in the cohort dropdown, including the initial "Select a cohort group". @@ -154,6 +159,106 @@ class MembershipPage(PageObject): self.q(css="a.link-cross-reference[data-section=data_download]").first.click() +class MembershipPageAutoEnrollSection(PageObject): + """ + CSV Auto Enroll section of the Membership tab of the Instructor dashboard. + """ + url = None + + auto_enroll_browse_button_selector = '.auto_enroll_csv .file-browse input.file_field#browseBtn' + auto_enroll_upload_button_selector = '.auto_enroll_csv button[name="enrollment_signup_button"]' + NOTIFICATION_ERROR = 'error' + NOTIFICATION_WARNING = 'warning' + NOTIFICATION_SUCCESS = 'confirmation' + + def is_browser_on_page(self): + return self.q(css=self.auto_enroll_browse_button_selector).present + + def is_file_attachment_browse_button_visible(self): + """ + Returns True if the Auto-Enroll Browse button is present. + """ + return self.q(css=self.auto_enroll_browse_button_selector).is_present() + + def is_upload_button_visible(self): + """ + Returns True if the Auto-Enroll Upload button is present. + """ + return self.q(css=self.auto_enroll_upload_button_selector).is_present() + + def click_upload_file_button(self): + """ + Clicks the Auto-Enroll Upload Button. + """ + self.q(css=self.auto_enroll_upload_button_selector).click() + + def is_notification_displayed(self, section_type): + """ + Valid inputs for section_type: MembershipPageAutoEnrollSection.NOTIFICATION_SUCCESS / + MembershipPageAutoEnrollSection.NOTIFICATION_WARNING / + MembershipPageAutoEnrollSection.NOTIFICATION_ERROR + Returns True if a {section_type} notification is displayed. + """ + notification_selector = '.auto_enroll_csv .results .message-%s' % section_type + self.wait_for_element_presence(notification_selector, "%s Notification" % section_type.title()) + return self.q(css=notification_selector).is_present() + + def first_notification_message(self, section_type): + """ + Valid inputs for section_type: MembershipPageAutoEnrollSection.NOTIFICATION_WARNING / + MembershipPageAutoEnrollSection.NOTIFICATION_ERROR + Returns the first message from the list of messages in the {section_type} section. + """ + error_message_selector = '.auto_enroll_csv .results .message-%s li.summary-item' % section_type + self.wait_for_element_presence(error_message_selector, "%s message" % section_type.title()) + return self.q(css=error_message_selector).text[0] + + def get_asset_path(self, file_name): + """ + Returns the full path of the file to upload. + These files have been placed in edx-platform/common/test/data/uploads/ + """ + + # Separate the list of folders in the path reaching to the current file, + # e.g. '... common/test/acceptance/pages/lms/instructor_dashboard.py' will result in + # [..., 'common', 'test', 'acceptance', 'pages', 'lms', 'instructor_dashboard.py'] + folders_list_in_path = __file__.split(os.sep) + + # Get rid of the last 4 elements: 'acceptance', 'pages', 'lms', and 'instructor_dashboard.py' + # to point to the 'test' folder, a shared point in the path's tree. + folders_list_in_path = folders_list_in_path[:-4] + + # Append the folders in the asset's path + folders_list_in_path.extend(['data', 'uploads', file_name]) + + # Return the joined path of the required asset. + return os.sep.join(folders_list_in_path) + + def upload_correct_csv_file(self): + """ + Selects the correct file and clicks the upload button. + """ + correct_files_path = self.get_asset_path('auto_reg_enrollment.csv') + self.q(css=self.auto_enroll_browse_button_selector).results[0].send_keys(correct_files_path) + self.click_upload_file_button() + + def upload_csv_file_with_errors_warnings(self): + """ + Selects the file which will generate errors and warnings and clicks the upload button. + """ + errors_warnings_files_path = self.get_asset_path('auto_reg_enrollment_errors_warnings.csv') + self.q(css=self.auto_enroll_browse_button_selector).results[0].send_keys(errors_warnings_files_path) + self.click_upload_file_button() + + def upload_non_csv_file(self): + """ + Selects an image file and clicks the upload button. + """ + errors_warnings_files_path = self.get_asset_path('image.jpg') + self.q(css=self.auto_enroll_browse_button_selector).results[0].send_keys(errors_warnings_files_path) + self.click_upload_file_button() + + class DataDownloadPage(PageObject): """ Data Download section of the Instructor dashboard. diff --git a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py new file mode 100644 index 0000000000..a486d88f7b --- /dev/null +++ b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +""" +End-to-end tests for the LMS Instructor Dashboard. +""" + +from ..helpers import UniqueCourseTest +from ...pages.lms.auto_auth import AutoAuthPage +from ...pages.lms.instructor_dashboard import InstructorDashboardPage +from ...fixtures.course import CourseFixture + + +class AutoEnrollmentWithCSVTest(UniqueCourseTest): + """ + End-to-end tests for Auto-Registration and enrollment functionality via CSV file. + """ + + def setUp(self): + super(AutoEnrollmentWithCSVTest, self).setUp() + self.course_fixture = CourseFixture(**self.course_info).install() + + # login as an instructor + AutoAuthPage(self.browser, course_id=self.course_id, staff=True).visit() + + # go to the membership page on the instructor dashboard + instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id) + instructor_dashboard_page.visit() + self.auto_enroll_section = instructor_dashboard_page.select_membership().select_auto_enroll_section() + + def test_browse_and_upload_buttons_are_visible(self): + """ + Scenario: On the Membership tab of the Instructor Dashboard, Auto-Enroll Browse and Upload buttons are visible. + Given that I am on the Membership tab on the Instructor Dashboard + Then I see the 'REGISTER/ENROLL STUDENTS' section on the page with the 'Browse' and 'Upload' buttons + """ + self.assertTrue(self.auto_enroll_section.is_file_attachment_browse_button_visible()) + self.assertTrue(self.auto_enroll_section.is_upload_button_visible()) + + def test_clicking_file_upload_button_without_file_shows_error(self): + """ + Scenario: Clicking on the upload button without specifying a CSV file results in error. + Given that I am on the Membership tab on the Instructor Dashboard + When I click the Upload Button without specifying a CSV file + Then I should be shown an Error Notification + And The Notification message should read 'File is not attached.' + """ + self.auto_enroll_section.click_upload_file_button() + self.assertTrue(self.auto_enroll_section.is_notification_displayed(section_type=self.auto_enroll_section.NOTIFICATION_ERROR)) + self.assertEqual(self.auto_enroll_section.first_notification_message(section_type=self.auto_enroll_section.NOTIFICATION_ERROR), "File is not attached.") + + def test_uploading_correct_csv_file_results_in_success(self): + """ + Scenario: Uploading a CSV with correct data results in Success. + Given that I am on the Membership tab on the Instructor Dashboard + When I select a csv file with correct data and click the Upload Button + Then I should be shown a Success Notification. + """ + self.auto_enroll_section.upload_correct_csv_file() + self.assertTrue(self.auto_enroll_section.is_notification_displayed(section_type=self.auto_enroll_section.NOTIFICATION_SUCCESS)) + + def test_uploading_csv_file_with_bad_data_results_in_errors_and_warnings(self): + """ + Scenario: Uploading a CSV with incorrect data results in error and warnings. + Given that I am on the Membership tab on the Instructor Dashboard + When I select a csv file with incorrect data and click the Upload Button + Then I should be shown an Error Notification + And a corresponding Error Message. + And I should be shown a Warning Notification + And a corresponding Warning Message. + """ + self.auto_enroll_section.upload_csv_file_with_errors_warnings() + self.assertTrue(self.auto_enroll_section.is_notification_displayed(section_type=self.auto_enroll_section.NOTIFICATION_ERROR)) + self.assertEqual(self.auto_enroll_section.first_notification_message(section_type=self.auto_enroll_section.NOTIFICATION_ERROR), "Data in row #2 must have exactly four columns: email, username, full name, and country") + self.assertTrue(self.auto_enroll_section.is_notification_displayed(section_type=self.auto_enroll_section.NOTIFICATION_WARNING)) + self.assertEqual(self.auto_enroll_section.first_notification_message(section_type=self.auto_enroll_section.NOTIFICATION_WARNING), "ename (d@a.com): (An account with email d@a.com exists but the provided username ename is different. Enrolling anyway with d@a.com.)") + + def test_uploading_non_csv_file_results_in_error(self): + """ + Scenario: Uploading an image file for auto-enrollment results in error. + Given that I am on the Membership tab on the Instructor Dashboard + When I select an image file (a non-csv file) and click the Upload Button + Then I should be shown an Error Notification + And The Notification message should read 'Could not read uploaded file.' + """ + self.auto_enroll_section.upload_non_csv_file() + self.assertTrue(self.auto_enroll_section.is_notification_displayed(section_type=self.auto_enroll_section.NOTIFICATION_ERROR)) + self.assertEqual(self.auto_enroll_section.first_notification_message(section_type=self.auto_enroll_section.NOTIFICATION_ERROR), "Could not read uploaded file.") diff --git a/common/test/data/uploads/auto_reg_enrollment.csv b/common/test/data/uploads/auto_reg_enrollment.csv new file mode 100644 index 0000000000..1fcf16aa52 --- /dev/null +++ b/common/test/data/uploads/auto_reg_enrollment.csv @@ -0,0 +1,3 @@ +a@a.com,aname,aname,PK +b@a.com,bname,bname,PK +c@a.com,cname,cname,PK diff --git a/common/test/data/uploads/auto_reg_enrollment_errors_warnings.csv b/common/test/data/uploads/auto_reg_enrollment_errors_warnings.csv new file mode 100644 index 0000000000..49d35536f4 --- /dev/null +++ b/common/test/data/uploads/auto_reg_enrollment_errors_warnings.csv @@ -0,0 +1,4 @@ +d@a.com,dname,dname,PK +a@a.com,missing_data +d@a.com,ename,ename,PK +e@a.com,dname,dname,PK diff --git a/lms/envs/bok_choy.env.json b/lms/envs/bok_choy.env.json index fe5d509386..bfaff9e7f5 100644 --- a/lms/envs/bok_choy.env.json +++ b/lms/envs/bok_choy.env.json @@ -71,7 +71,8 @@ "ENABLE_THIRD_PARTY_AUTH": true, "PREVIEW_LMS_BASE": "localhost:8003", "SUBDOMAIN_BRANDING": false, - "SUBDOMAIN_COURSE_LISTINGS": false + "SUBDOMAIN_COURSE_LISTINGS": false, + "ALLOW_AUTOMATED_SIGNUPS": true }, "FEEDBACK_SUBMISSION_EMAIL": "", "GITHUB_REPO_ROOT": "** OVERRIDDEN **", diff --git a/lms/static/coffee/fixtures/autoenrollment.html b/lms/static/coffee/fixtures/autoenrollment.html new file mode 100644 index 0000000000..2e010a1533 --- /dev/null +++ b/lms/static/coffee/fixtures/autoenrollment.html @@ -0,0 +1,20 @@ +
+ ${_("To register and enroll a list of users in this course, choose a CSV file that contains the following columns in this exact order: email, username, name, and country. Please include one student per row and do not include any headers, footers, or blank lines.")} +
+ + + +