Merge pull request #5969 from edx/muhhshoaib/WL-124-front-end-tests-for-auto-enrollment
muhhshoaib/wl 124 add front end tests for autoenrollment
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.")
|
||||
3
common/test/data/uploads/auto_reg_enrollment.csv
Normal file
3
common/test/data/uploads/auto_reg_enrollment.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
a@a.com,aname,aname,PK
|
||||
b@a.com,bname,bname,PK
|
||||
c@a.com,cname,cname,PK
|
||||
|
@@ -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
|
||||
|
@@ -72,7 +72,8 @@
|
||||
"ENABLE_COMBINED_LOGIN_REGISTRATION": 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 **",
|
||||
|
||||
20
lms/static/coffee/fixtures/autoenrollment.html
Normal file
20
lms/static/coffee/fixtures/autoenrollment.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<div class="auto_enroll auto_enroll_csv">
|
||||
<h2> ${_("Register/Enroll Students")} </h2>
|
||||
|
||||
<p>
|
||||
${_("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.")}
|
||||
</p>
|
||||
|
||||
<form id="student-auto-enroll-form">
|
||||
<div class="customBrowseBtn">
|
||||
<input disabled="disabled" id="browseFile" placeholder="choose file"/>
|
||||
|
||||
<div class="file-browse btn btn-primary">
|
||||
<span class="browse"> Browse </span>
|
||||
<input class="file_field" id="browseBtn" name="students_list" type="file" accept=".csv"/>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" name="enrollment_signup_button">${_("Upload CSV")}</button>
|
||||
</form>
|
||||
<div class="results"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,73 @@
|
||||
describe 'AutoEnrollment', ->
|
||||
beforeEach ->
|
||||
loadFixtures 'coffee/fixtures/autoenrollment.html'
|
||||
@autoenrollment = new AutoEnrollmentViaCsv $('.auto_enroll_csv')
|
||||
|
||||
it 'binds to the enrollment_signup_button on click event', ->
|
||||
expect(@autoenrollment.$enrollment_signup_button).toHandle 'click'
|
||||
|
||||
it 'binds to the browse button on change event', ->
|
||||
expect(@autoenrollment.$browse_button).toHandle 'change'
|
||||
|
||||
it 'binds the ajax call and the result will be success', ->
|
||||
spyOn($, "ajax").andCallFake((params) =>
|
||||
params.success({row_errors: [], general_errors: [], warnings: []})
|
||||
{always: ->}
|
||||
)
|
||||
# mock the render_notification_view which returns the html (since we are only using the existing notification model)
|
||||
@autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").andCallFake =>
|
||||
return '<div><div class="message message-confirmation"><h3 class="message-title">Success</h3><div class="message-copy"><p>All accounts were created successfully.</p></div></div><div>'
|
||||
|
||||
submitCallback = jasmine.createSpy().andReturn()
|
||||
@autoenrollment.$student_enrollment_form.submit(submitCallback)
|
||||
@autoenrollment.$enrollment_signup_button.click()
|
||||
expect($('.results .message-copy').text()).toEqual('All accounts were created successfully.')
|
||||
expect(submitCallback).toHaveBeenCalled()
|
||||
|
||||
it 'binds the ajax call and the result will be error', ->
|
||||
spyOn($, "ajax").andCallFake((params) =>
|
||||
params.success({
|
||||
row_errors: [{
|
||||
'username': 'testuser1',
|
||||
'email': 'testemail1@email.com',
|
||||
'response': 'Username already exists'
|
||||
}],
|
||||
general_errors: [{
|
||||
'response': 'cannot read the line 2'
|
||||
}],
|
||||
warnings: []
|
||||
})
|
||||
{always: ->}
|
||||
)
|
||||
# mock the render_notification_view which returns the html (since we are only using the existing notification model)
|
||||
@autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").andCallFake =>
|
||||
return '<div><div class="message message-error"><h3 class="message-title">Errors</h3><div class="message-copy"><p>The following errors were generated:</p><ul class="list-summary summary-items"><li class="summary-item">cannot read the line 2</li><li class="summary-item">testuser1 (testemail1@email.com): (Username already exists)</li></ul></div></div></div>'
|
||||
|
||||
submitCallback = jasmine.createSpy().andReturn()
|
||||
@autoenrollment.$student_enrollment_form.submit(submitCallback)
|
||||
@autoenrollment.$enrollment_signup_button.click()
|
||||
expect($('.results .list-summary').text()).toEqual('cannot read the line 2testuser1 (testemail1@email.com): (Username already exists)');
|
||||
expect(submitCallback).toHaveBeenCalled()
|
||||
|
||||
it 'binds the ajax call and the result will be warnings', ->
|
||||
spyOn($, "ajax").andCallFake((params) =>
|
||||
params.success({
|
||||
row_errors: [],
|
||||
general_errors: [],
|
||||
warnings: [{
|
||||
'username': 'user1',
|
||||
'email': 'user1email',
|
||||
'response': 'email is in valid'
|
||||
}]
|
||||
})
|
||||
{always: ->}
|
||||
)
|
||||
# mock the render_notification_view which returns the html (since we are only using the existing notification model)
|
||||
@autoenrollment.render_notification_view = jasmine.createSpy("render_notification_view(type, title, message, details) spy").andCallFake =>
|
||||
return '<div><div class="message message-warning"><h3 class="message-title">Warnings</h3><div class="message-copy"><p>The following warnings were generated:</p><ul class="list-summary summary-items"><li class="summary-item">user1 (user1email): (email is in valid)</li></ul></div></div></div>'
|
||||
|
||||
submitCallback = jasmine.createSpy().andReturn()
|
||||
@autoenrollment.$student_enrollment_form.submit(submitCallback)
|
||||
@autoenrollment.$enrollment_signup_button.click()
|
||||
expect($('.results .list-summary').text()).toEqual('user1 (user1email): (email is in valid)')
|
||||
expect(submitCallback).toHaveBeenCalled()
|
||||
@@ -174,7 +174,7 @@ class AuthListWidget extends MemberListWidget
|
||||
else
|
||||
@reload_list()
|
||||
|
||||
class AutoEnrollmentViaCsv
|
||||
class @AutoEnrollmentViaCsv
|
||||
constructor: (@$container) ->
|
||||
# Wrapper for the AutoEnrollmentViaCsv subsection.
|
||||
# This object handles buttons, success and failure reporting,
|
||||
@@ -220,7 +220,6 @@ class AutoEnrollmentViaCsv
|
||||
@$results.empty()
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
result_from_server_is_success = true
|
||||
|
||||
if data_from_server.general_errors.length
|
||||
@@ -241,41 +240,35 @@ class AutoEnrollmentViaCsv
|
||||
warning['is_general_error'] = false
|
||||
warnings.push warning
|
||||
|
||||
render_response = (label, type, student_results) =>
|
||||
if type is 'success'
|
||||
task_res_section = $ '<div/>', class: 'message message-confirmation'
|
||||
message_title = $ '<h3/>', class: 'message-title', text: label
|
||||
task_res_section.append message_title
|
||||
@$results.append task_res_section
|
||||
return
|
||||
|
||||
if type is 'error'
|
||||
task_res_section = $ '<div/>', class: 'message message-error'
|
||||
if type is 'warning'
|
||||
task_res_section = $ '<div/>', class: 'message message-warning'
|
||||
|
||||
message_title = $ '<h3/>', class: 'message-title', text: label
|
||||
task_res_section. append message_title
|
||||
messages_copy = $ '<div/>', class: 'message-copy'
|
||||
task_res_section. append messages_copy
|
||||
messages_summary = $ '<ul/>', class: 'list-summary summary-items'
|
||||
messages_copy.append messages_summary
|
||||
|
||||
render_response = (title, message, type, student_results) =>
|
||||
details = []
|
||||
for student_result in student_results
|
||||
if student_result.is_general_error
|
||||
response_message = student_result.response
|
||||
details.push student_result.response
|
||||
else
|
||||
response_message = student_result.username + ' ('+ student_result.email + '): ' + ' (' + student_result.response + ')'
|
||||
messages_summary.append $ '<li/>', class: 'summary-item', text: response_message
|
||||
details.push response_message
|
||||
|
||||
@$results.append task_res_section
|
||||
@$results.append @render_notification_view type, title, message, details
|
||||
|
||||
if errors.length
|
||||
render_response gettext("The following errors were generated:"), 'error', errors
|
||||
render_response gettext('Errors'), gettext("The following errors were generated:"), 'error', errors
|
||||
if warnings.length
|
||||
render_response gettext("The following warnings were generated:"), 'warning', warnings
|
||||
render_response gettext('Warnings'), gettext("The following warnings were generated:"), 'warning', warnings
|
||||
if result_from_server_is_success
|
||||
render_response gettext("All accounts were created successfully."), 'success', []
|
||||
render_response gettext('Success'), gettext("All accounts were created successfully."), 'confirmation', []
|
||||
|
||||
render_notification_view: (type, title, message, details) ->
|
||||
notification_model = new NotificationModel()
|
||||
notification_model.set({
|
||||
'type': type,
|
||||
'title': title,
|
||||
'message': message,
|
||||
'details': details,
|
||||
});
|
||||
view = new NotificationView(model:notification_model);
|
||||
view.render()
|
||||
return view.$el.html()
|
||||
|
||||
class BetaTesterBulkAddition
|
||||
constructor: (@$container) ->
|
||||
|
||||
Reference in New Issue
Block a user