Commit 97b45cc2 by Muhammad Shoaib Committed by Afzal Wali

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.
parent ec74398d
......@@ -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.
......
# -*- 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.")
a@a.com,aname,aname,PK
b@a.com,bname,bname,PK
c@a.com,cname,cname,PK
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 **",
......
<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>
\ No newline at end of file
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()
\ No newline at end of file
......@@ -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) ->
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment