Merge pull request #11080 from edx/saleem-latif/SOL-1529
SOL-1529: Allow PMs to Invalidate Certificates
This commit is contained in:
@@ -1006,6 +1006,16 @@ class CertificatesPage(PageObject):
|
||||
)
|
||||
self.wait_for_element_visibility('#add-exception', 'Add Exception button is visible')
|
||||
|
||||
def wait_for_certificate_invalidations_section(self): # pylint: disable=invalid-name
|
||||
"""
|
||||
Wait for certificate invalidations section to be rendered on page
|
||||
"""
|
||||
self.wait_for_element_visibility(
|
||||
'div.certificate-invalidation-container',
|
||||
'Certificate invalidations section is visible.'
|
||||
)
|
||||
self.wait_for_element_visibility('#invalidate-certificate', 'Invalidate Certificate button is visible')
|
||||
|
||||
def refresh(self):
|
||||
"""
|
||||
Refresh Certificates Page and wait for the page to load completely.
|
||||
@@ -1064,6 +1074,42 @@ class CertificatesPage(PageObject):
|
||||
"""
|
||||
self.get_selector('#add-exception').click()
|
||||
|
||||
def add_certificate_invalidation(self, student, notes):
|
||||
"""
|
||||
Add certificate invalidation for 'student'.
|
||||
"""
|
||||
self.wait_for_element_visibility('#invalidate-certificate', 'Invalidate Certificate button is visible')
|
||||
|
||||
self.get_selector('#certificate-invalidation-user').fill(student)
|
||||
self.get_selector('#certificate-invalidation-notes').fill(notes)
|
||||
self.get_selector('#invalidate-certificate').click()
|
||||
|
||||
self.wait_for_ajax()
|
||||
self.wait_for(
|
||||
lambda: student in self.get_selector('div.invalidation-history table tr:last-child td').text,
|
||||
description='Certificate invalidation added to list.'
|
||||
)
|
||||
|
||||
def remove_first_certificate_invalidation(self):
|
||||
"""
|
||||
Remove certificate invalidation from the invalidation list.
|
||||
"""
|
||||
self.wait_for_element_visibility('#invalidate-certificate', 'Invalidate Certificate button is visible')
|
||||
self.get_selector('div.invalidation-history table tr td .re-validate-certificate').first.click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
def fill_certificate_invalidation_user_name_field(self, student): # pylint: disable=invalid-name
|
||||
"""
|
||||
Fill username/email field with given text
|
||||
"""
|
||||
self.get_selector('#certificate-invalidation-user').fill(student)
|
||||
|
||||
def click_invalidate_certificate_button(self):
|
||||
"""
|
||||
Click 'Invalidate Certificate' button in 'certificates invalidations' section
|
||||
"""
|
||||
self.get_selector('#invalidate-certificate').click()
|
||||
|
||||
@property
|
||||
def generate_certificates_button(self):
|
||||
"""
|
||||
@@ -1111,4 +1157,18 @@ class CertificatesPage(PageObject):
|
||||
"""
|
||||
Returns the Message (error/success) in "Certificate Exceptions" section.
|
||||
"""
|
||||
return self.get_selector('div.message')
|
||||
return self.get_selector('.certificate-exception-container div.message')
|
||||
|
||||
@property
|
||||
def last_certificate_invalidation(self):
|
||||
"""
|
||||
Returns last certificate invalidation from "Certificate Invalidations" section.
|
||||
"""
|
||||
return self.get_selector('div.certificate-invalidation-container table tr:last-child td')
|
||||
|
||||
@property
|
||||
def certificate_invalidation_message(self): # pylint: disable=invalid-name
|
||||
"""
|
||||
Returns the message (error/success) in "Certificate Invalidation" section.
|
||||
"""
|
||||
return self.get_selector('.certificate-invalidation-container div.message')
|
||||
|
||||
@@ -845,3 +845,202 @@ class CertificatesTest(BaseInstructorDashboardTest):
|
||||
' below to send the certificate.',
|
||||
self.certificates_section.message.text
|
||||
)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class CertificateInvalidationTest(BaseInstructorDashboardTest):
|
||||
"""
|
||||
Tests for Certificates functionality on instructor dashboard.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CertificateInvalidationTest, cls).setUpClass()
|
||||
|
||||
# Create course fixture once each test run
|
||||
CourseFixture(
|
||||
org='test_org',
|
||||
number='335535897951379478207964576572017930000',
|
||||
run='test_run',
|
||||
display_name='Test Course 335535897951379478207964576572017930000',
|
||||
).install()
|
||||
|
||||
def setUp(self):
|
||||
super(CertificateInvalidationTest, self).setUp()
|
||||
# set same course number as we have in fixture json
|
||||
self.course_info['number'] = "335535897951379478207964576572017930000"
|
||||
|
||||
# we have created a user with this id in fixture, and created a generated certificate for it.
|
||||
self.student_id = "99"
|
||||
self.student_name = "testcert"
|
||||
self.student_email = "cert@example.com"
|
||||
|
||||
# Enroll above test user in the course
|
||||
AutoAuthPage(
|
||||
self.browser,
|
||||
username=self.student_name,
|
||||
email=self.student_email,
|
||||
course_id=self.course_id,
|
||||
).visit()
|
||||
|
||||
self.test_certificate_config = {
|
||||
'id': 1,
|
||||
'name': 'Certificate name',
|
||||
'description': 'Certificate description',
|
||||
'course_title': 'Course title override',
|
||||
'signatories': [],
|
||||
'version': 1,
|
||||
'is_active': True
|
||||
}
|
||||
|
||||
self.cert_fixture = CertificateConfigFixture(self.course_id, self.test_certificate_config)
|
||||
self.cert_fixture.install()
|
||||
self.user_name, self.user_id = self.log_in_as_instructor()
|
||||
self.instructor_dashboard_page = self.visit_instructor_dashboard()
|
||||
self.certificates_section = self.instructor_dashboard_page.select_certificates()
|
||||
|
||||
disable_animations(self.certificates_section)
|
||||
|
||||
def test_instructor_can_invalidate_certificate(self):
|
||||
"""
|
||||
Scenario: On the Certificates tab of the Instructor Dashboard, Instructor can add a certificate
|
||||
invalidation to invalidation list.
|
||||
|
||||
Given that I am on the Certificates tab on the Instructor Dashboard
|
||||
When I fill in student username and notes fields and click 'Add Exception' button
|
||||
Then new certificate exception should be visible in certificate exceptions list
|
||||
"""
|
||||
notes = 'Test Notes'
|
||||
# Add a student to certificate invalidation list
|
||||
self.certificates_section.add_certificate_invalidation(self.student_name, notes)
|
||||
self.assertIn(self.student_name, self.certificates_section.last_certificate_invalidation.text)
|
||||
self.assertIn(notes, self.certificates_section.last_certificate_invalidation.text)
|
||||
|
||||
# Validate success message
|
||||
self.assertIn(
|
||||
"Certificate has been successfully invalidated for {user}.".format(user=self.student_name),
|
||||
self.certificates_section.certificate_invalidation_message.text
|
||||
)
|
||||
|
||||
# Verify that added invalidations are also synced with backend
|
||||
# Revisit Page
|
||||
self.certificates_section.refresh()
|
||||
|
||||
# wait for the certificate invalidations section to render
|
||||
self.certificates_section.wait_for_certificate_invalidations_section()
|
||||
|
||||
# validate certificate invalidation is visible in certificate invalidation list
|
||||
self.assertIn(self.student_name, self.certificates_section.last_certificate_invalidation.text)
|
||||
self.assertIn(notes, self.certificates_section.last_certificate_invalidation.text)
|
||||
|
||||
def test_instructor_can_re_validate_certificate(self):
|
||||
"""
|
||||
Scenario: On the Certificates tab of the Instructor Dashboard, Instructor can re-validate certificate.
|
||||
|
||||
Given that I am on the certificates tab on the Instructor Dashboard
|
||||
AND there is a certificate invalidation in certificate invalidation table
|
||||
When I click "Remove from Invalidation Table" button
|
||||
Then certificate is re-validated and removed from certificate invalidation table.
|
||||
"""
|
||||
notes = 'Test Notes'
|
||||
# Add a student to certificate invalidation list
|
||||
self.certificates_section.add_certificate_invalidation(self.student_name, notes)
|
||||
self.assertIn(self.student_name, self.certificates_section.last_certificate_invalidation.text)
|
||||
self.assertIn(notes, self.certificates_section.last_certificate_invalidation.text)
|
||||
|
||||
# Verify that added invalidations are also synced with backend
|
||||
# Revisit Page
|
||||
self.certificates_section.refresh()
|
||||
|
||||
# wait for the certificate invalidations section to render
|
||||
self.certificates_section.wait_for_certificate_invalidations_section()
|
||||
|
||||
# click "Remove from Invalidation Table" button next to certificate invalidation
|
||||
self.certificates_section.remove_first_certificate_invalidation()
|
||||
|
||||
# validate certificate invalidation is removed from the list
|
||||
self.assertNotIn(self.student_name, self.certificates_section.last_certificate_invalidation.text)
|
||||
self.assertNotIn(notes, self.certificates_section.last_certificate_invalidation.text)
|
||||
|
||||
self.assertIn(
|
||||
"The certificate for this learner has been re-validated and the system is "
|
||||
"re-running the grade for this learner.",
|
||||
self.certificates_section.certificate_invalidation_message.text
|
||||
)
|
||||
|
||||
def test_error_on_empty_user_name_or_email(self):
|
||||
"""
|
||||
Scenario: On the Certificates tab of the Instructor Dashboard, Instructor should see error message if he clicks
|
||||
"Invalidate Certificate" button without entering student username or email.
|
||||
|
||||
Given that I am on the certificates tab on the Instructor Dashboard
|
||||
When I click "Invalidate Certificate" button without entering student username/email.
|
||||
Then I see following error message
|
||||
"Student username/email field is required and can not be empty."
|
||||
"Kindly fill in username/email and then press "Invalidate Certificate" button."
|
||||
"""
|
||||
# Click "Invalidate Certificate" with empty student username/email field
|
||||
self.certificates_section.fill_certificate_invalidation_user_name_field("")
|
||||
self.certificates_section.click_invalidate_certificate_button()
|
||||
self.certificates_section.wait_for_ajax()
|
||||
|
||||
self.assertIn(
|
||||
u'Student username/email field is required and can not be empty. '
|
||||
u'Kindly fill in username/email and then press "Invalidate Certificate" button.',
|
||||
self.certificates_section.certificate_invalidation_message.text
|
||||
)
|
||||
|
||||
def test_error_on_invalid_user(self):
|
||||
"""
|
||||
Scenario: On the Certificates tab of the Instructor Dashboard, Instructor should see error message if
|
||||
the student entered for certificate invalidation does not exist.
|
||||
|
||||
Given that I am on the certificates tab on the Instructor Dashboard
|
||||
When I click "Invalidate Certificate"
|
||||
AND the username entered does not exist in the system
|
||||
Then I see following error message
|
||||
"Student username/email field is required and can not be empty."
|
||||
"Kindly fill in username/email and then press "Invalidate Certificate" button."
|
||||
"""
|
||||
invalid_user = "invalid_test_user"
|
||||
# Click "Invalidate Certificate" with invalid student username/email
|
||||
self.certificates_section.fill_certificate_invalidation_user_name_field(invalid_user)
|
||||
self.certificates_section.click_invalidate_certificate_button()
|
||||
self.certificates_section.wait_for_ajax()
|
||||
|
||||
self.assertIn(
|
||||
u"{user} does not exist in the LMS. Please check your spelling and retry.".format(user=invalid_user),
|
||||
self.certificates_section.certificate_invalidation_message.text
|
||||
)
|
||||
|
||||
def test_user_not_enrolled_error(self):
|
||||
"""
|
||||
Scenario: On the Certificates tab of the Instructor Dashboard, Instructor should see error message if
|
||||
the student entered for certificate invalidation is not enrolled in the course.
|
||||
|
||||
Given that I am on the certificates tab on the Instructor Dashboard
|
||||
When I click "Invalidate Certificate"
|
||||
AND the username entered is not enrolled in the current course
|
||||
Then I see following error message
|
||||
"{user} is not enrolled in this course. Please check your spelling and retry."
|
||||
"""
|
||||
new_user = 'test_user_{uuid}'.format(uuid=self.unique_id[6:12])
|
||||
new_email = 'test_user_{uuid}@example.com'.format(uuid=self.unique_id[6:12])
|
||||
# Create a new user who is not enrolled in the course
|
||||
AutoAuthPage(self.browser, username=new_user, email=new_email).visit()
|
||||
# Login as instructor and visit Certificate Section of Instructor Dashboard
|
||||
self.user_name, self.user_id = self.log_in_as_instructor()
|
||||
self.instructor_dashboard_page.visit()
|
||||
self.certificates_section = self.instructor_dashboard_page.select_certificates()
|
||||
|
||||
# Click 'Invalidate Certificate' button with not enrolled student
|
||||
self.certificates_section.wait_for_certificate_invalidations_section()
|
||||
|
||||
self.certificates_section.fill_certificate_invalidation_user_name_field(new_user)
|
||||
self.certificates_section.click_invalidate_certificate_button()
|
||||
self.certificates_section.wait_for_ajax()
|
||||
|
||||
self.assertIn(
|
||||
u"{user} is not enrolled in this course. Please check your spelling and retry.".format(user=new_user),
|
||||
self.certificates_section.certificate_invalidation_message.text
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user