WL-124 added the jasmine tests for the autoenrollment csv
Added bokchoy tests and assets (csv files) for CSV auto reg and enrollment. Set the env flag "ALLOW_AUTOMATED_SIGNUPS": true in bok_choy.env.json Resolved quality issues. resolved cherry pick conflicts Improved bokchoy tests as per code review suggestions. added the BDD in the docstrings for all the test scenarios changed the bok choy test string Improved bokchoy tests as per further code review suggestions. Made a MembershipPageAutoEnrollSection a separate PageObject.
This commit is contained in:
committed by
Afzal Wali
parent
ec74398d74
commit
97b45cc2a7
@@ -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
|
||||
|
@@ -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 **",
|
||||
|
||||
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