diff --git a/common/test/acceptance/pages/lms/learner_profile.py b/common/test/acceptance/pages/lms/learner_profile.py
index 50d1a9dcb0..c32847ee52 100644
--- a/common/test/acceptance/pages/lms/learner_profile.py
+++ b/common/test/acceptance/pages/lms/learner_profile.py
@@ -5,6 +5,8 @@ from . import BASE_URL
from bok_choy.page_object import PageObject
from .fields import FieldsMixin
from bok_choy.promise import EmptyPromise
+from .instructor_dashboard import InstructorDashboardPage
+from selenium.webdriver import ActionChains
PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]'
@@ -165,3 +167,109 @@ class LearnerProfilePage(FieldsMixin, PageObject):
"""
self.wait_for_ajax()
return self.q(css='#u-field-message-account_privacy').visible
+
+ @property
+ def profile_has_default_image(self):
+ """
+ Return bool if image field has default photo or not.
+ """
+ self.wait_for_field('image')
+ default_links = self.q(css='.image-frame').attrs('src')
+ return 'default-profile' in default_links[0] if default_links else False
+
+ def mouse_hover(self, element):
+ """
+ Mouse over on given element.
+ """
+ mouse_hover_action = ActionChains(self.browser).move_to_element(element)
+ mouse_hover_action.perform()
+
+ def profile_has_image_with_public_access(self):
+ """
+ Check if image is present with remove/upload access.
+ """
+ self.wait_for_field('image')
+
+ self.mouse_hover(self.browser.find_element_by_css_selector('.image-wrapper'))
+ self.wait_for_element_visibility('.u-field-upload-button', "upload button is visible")
+ return self.q(css='.u-field-upload-button').visible
+
+ def profile_has_image_with_private_access(self):
+ """
+ Check if image is present with remove/upload access.
+ """
+ self.wait_for_field('image')
+ return self.q(css='.u-field-upload-button').visible
+
+ def upload_file(self, filename):
+ """
+ Helper method to upload an image file.
+ """
+ self.wait_for_element_visibility('.u-field-upload-button', "upload button is visible")
+ file_path = InstructorDashboardPage.get_asset_path(filename)
+
+ # make the elements visible.
+ self.browser.execute_script('$(".u-field-upload-button").css("opacity",1);')
+ self.browser.execute_script('$(".upload-button-input").css("opacity",1);')
+
+ self.wait_for_element_visibility('.upload-button-input', "upload button is visible")
+
+ self.browser.execute_script('$(".upload-submit").show();')
+
+ # First send_keys will initialize the jquery auto upload plugin.
+ self.q(css='.upload-button-input').results[0].send_keys(file_path)
+ self.q(css='.upload-submit').first.click()
+ self.q(css='.upload-button-input').results[0].send_keys(file_path)
+
+ self.wait_for_ajax()
+
+ def upload_correct_image_file(self, filename):
+ """
+ Selects the correct file and clicks the upload button.
+ """
+ self._upload_file(filename)
+
+ @property
+ def image_upload_success(self):
+ """
+ Returns the bool, if image is updated or not.
+ """
+ self.wait_for_field('image')
+ self.wait_for_ajax()
+
+ self.wait_for_element_visibility('.image-frame', "image box is visible")
+ image_link = self.q(css='.image-frame').attrs('src')
+ return 'default-profile' not in image_link[0]
+
+ @property
+ def profile_image_message(self):
+ """
+ Returns the text message for profile image.
+ """
+ self.wait_for_field('image')
+ self.wait_for_ajax()
+ return self.q(css='.message-banner p').text[0]
+
+ def remove_profile_image(self):
+ """
+ Removes the profile image.
+ """
+ self.wait_for_field('image')
+ self.wait_for_ajax()
+
+ self.wait_for_element_visibility('.image-wrapper', "remove button is visible")
+ self.browser.execute_script('$(".u-field-remove-button").css("opacity",1);')
+ self.mouse_hover(self.browser.find_element_by_css_selector('.image-wrapper'))
+
+ self.wait_for_element_visibility('.u-field-remove-button', "remove button is visible")
+ self.q(css='.u-field-remove-button').first.click()
+
+ self.mouse_hover(self.browser.find_element_by_css_selector('.image-wrapper'))
+ self.wait_for_element_visibility('.u-field-upload-button', "upload button is visible")
+ return True
+
+ @property
+ def remove_link_present(self):
+ self.wait_for_field('image')
+ self.mouse_hover(self.browser.find_element_by_css_selector('.image-wrapper'))
+ return self.q(css='.u-field-remove-button').visible
diff --git a/common/test/acceptance/tests/lms/test_learner_profile.py b/common/test/acceptance/tests/lms/test_learner_profile.py
index d9cc96f3bd..43be7ef37a 100644
--- a/common/test/acceptance/tests/lms/test_learner_profile.py
+++ b/common/test/acceptance/tests/lms/test_learner_profile.py
@@ -111,6 +111,13 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
self.assertEqual(profile_page.age_limit_message_present, message is not None)
self.assertIn(message, profile_page.profile_forced_private_message)
+ def assert_default_image_has_public_access(self, profile_page):
+ """
+ Assert that profile image has public access.
+ """
+ self.assertTrue(profile_page.profile_has_default_image)
+ self.assertTrue(profile_page.profile_has_image_with_public_access())
+
def test_dashboard_learner_profile_link(self):
"""
Scenario: Verify that my profile link is present on dashboard page and we can navigate to correct page.
@@ -320,6 +327,171 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
)
self.verify_profile_page_view_event(user_id, visibility=self.PRIVACY_PRIVATE)
+ def test_user_can_only_see_default_image_for_private_profile(self):
+ """
+ Scenario: Default profile image behaves correctly for under age user.
+
+ Given that I am on my profile page with private access
+ And I can see default image
+ When I move my cursor to the image
+ Then i cannot see the upload/remove image text
+ And i cannot upload/remove the image.
+ """
+ year_of_birth = datetime.now().year - 5
+ username, user_id = self.log_in_as_unique_user()
+ profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PRIVATE)
+
+ self.verify_profile_forced_private_message(
+ username,
+ year_of_birth,
+ message='You must be over 13 to share a full profile.'
+ )
+ self.assertTrue(profile_page.profile_has_default_image)
+ self.assertFalse(profile_page.profile_has_image_with_private_access())
+
+ def test_user_can_see_default_image_for_public_profile(self):
+ """
+ Scenario: Default profile image behaves correctly for public profile.
+
+ Given that I am on my profile page with public access
+ And I can see default image
+ When I move my cursor to the image
+ Then i can see the upload/remove image text
+ And i am able to upload new image
+ """
+ username, user_id = self.log_in_as_unique_user()
+ profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
+
+ self.assert_default_image_has_public_access(profile_page)
+
+ def test_user_can_upload_the_profile_image_with_success(self):
+ """
+ Scenario: Upload profile image works correctly.
+
+ Given that I am on my profile page with public access
+ And I can see default image
+ When I move my cursor to the image
+ Then i can see the upload/remove image text
+ When i upload new image via file uploader
+ Then i can see the changed image
+ And i can also see the latest image after reload.
+ """
+ username, user_id = self.log_in_as_unique_user()
+ profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
+
+ self.assert_default_image_has_public_access(profile_page)
+
+ profile_page.upload_file(filename='image.jpg')
+ self.assertTrue(profile_page.image_upload_success)
+ profile_page.visit()
+ self.assertTrue(profile_page.image_upload_success)
+
+ def test_user_can_see_error_for_exceeding_max_file_size_limit(self):
+ """
+ Scenario: Upload profile image does not work for > 1MB image file.
+
+ Given that I am on my profile page with public access
+ And I can see default image
+ When I move my cursor to the image
+ Then i can see the upload/remove image text
+ When i upload new > 1MB image via file uploader
+ Then i can see the error message for file size limit
+ And i can still see the default image after page reload.
+ """
+ username, user_id = self.log_in_as_unique_user()
+ profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
+
+ self.assert_default_image_has_public_access(profile_page)
+
+ profile_page.upload_file(filename='larger_image.jpg')
+ self.assertEqual(profile_page.profile_image_message, "Your image must be smaller than 1 MB in size.")
+ profile_page.visit()
+ self.assertTrue(profile_page.profile_has_default_image)
+
+ def test_user_can_see_error_for_file_size_below_the_min_limit(self):
+ """
+ Scenario: Upload profile image does not work for < 100 Bytes image file.
+
+ Given that I am on my profile page with public access
+ And I can see default image
+ When I move my cursor to the image
+ Then i can see the upload/remove image text
+ When i upload new < 100 Bytes image via file uploader
+ Then i can see the error message for minimum file size limit
+ And i can still see the default image after page reload.
+ """
+ username, user_id = self.log_in_as_unique_user()
+ profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
+
+ self.assert_default_image_has_public_access(profile_page)
+
+ profile_page.upload_file(filename='list-icon-visited.png')
+ self.assertEqual(profile_page.profile_image_message, "Your image must be at least 100 bytes in size.")
+ profile_page.visit()
+ self.assertTrue(profile_page.profile_has_default_image)
+
+ def test_user_can_see_error_for_wrong_file_type(self):
+ """
+ Scenario: Upload profile image does not work for wrong file types.
+
+ Given that I am on my profile page with public access
+ And I can see default image
+ When I move my cursor to the image
+ Then i can see the upload/remove image text
+ When i upload new csv file via file uploader
+ Then i can see the error message for wrong/unsupported file type
+ And i can still see the default image after page reload.
+ """
+ username, user_id = self.log_in_as_unique_user()
+ profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
+
+ self.assert_default_image_has_public_access(profile_page)
+
+ profile_page.upload_file(filename='cohort_users_only_username.csv')
+ self.assertEqual(profile_page.profile_image_message, "Unsupported file type.")
+ profile_page.visit()
+ self.assertTrue(profile_page.profile_has_default_image)
+
+ def test_user_can_remove_profile_image(self):
+ """
+ Scenario: Remove profile image works correctly.
+
+ Given that I am on my profile page with public access
+ And I can see default image
+ When I move my cursor to the image
+ Then i can see the upload/remove image text
+ When i click on the remove image link
+ Then i can see the default image
+ And i can still see the default image after page reload.
+ """
+ username, user_id = self.log_in_as_unique_user()
+ profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
+
+ self.assert_default_image_has_public_access(profile_page)
+
+ profile_page.upload_file(filename='image.jpg')
+ self.assertTrue(profile_page.image_upload_success)
+ self.assertTrue(profile_page.remove_profile_image())
+ self.assertTrue(profile_page.profile_has_default_image)
+ profile_page.visit()
+ self.assertTrue(profile_page.profile_has_default_image)
+
+ def test_user_cannot_remove_default_image(self):
+ """
+ Scenario: Remove profile image does not works for default images.
+
+ Given that I am on my profile page with public access
+ And I can see default image
+ When I move my cursor to the image
+ Then i can see only the upload image text
+ And i cannot see the remove image text
+ """
+ username, user_id = self.log_in_as_unique_user()
+ profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC)
+
+ self.assert_default_image_has_public_access(profile_page)
+ self.assertFalse(profile_page.remove_link_present)
+
class DifferentUserLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
"""
diff --git a/common/test/data/uploads/larger_image.jpg b/common/test/data/uploads/larger_image.jpg
new file mode 100644
index 0000000000..18a3e1c6f9
Binary files /dev/null and b/common/test/data/uploads/larger_image.jpg differ
diff --git a/common/test/data/uploads/list-icon-visited.png b/common/test/data/uploads/list-icon-visited.png
new file mode 100644
index 0000000000..a3704f3b98
Binary files /dev/null and b/common/test/data/uploads/list-icon-visited.png differ
diff --git a/lms/envs/bok_choy.py b/lms/envs/bok_choy.py
index 8cfd1e48d9..0070b1513f 100644
--- a/lms/envs/bok_choy.py
+++ b/lms/envs/bok_choy.py
@@ -131,6 +131,14 @@ MOCK_SEARCH_BACKING_FILE = (
import uuid
SECRET_KEY = uuid.uuid4().hex
+# Set dummy values for profile image settings.
+PROFILE_IMAGE_BACKEND = {
+ 'class': 'storages.backends.overwrite.OverwriteStorage',
+ 'options': {
+ 'location': os.path.join(MEDIA_ROOT, 'profile-images/'),
+ 'base_url': os.path.join(MEDIA_URL, 'profile-images/'),
+ },
+}
#####################################################################
# Lastly, see if the developer has any local overrides.
try:
diff --git a/lms/static/js/fixtures/student_profile/student_profile.html b/lms/static/js/fixtures/student_profile/student_profile.html
new file mode 100644
index 0000000000..40ae15e5ba
--- /dev/null
+++ b/lms/static/js/fixtures/student_profile/student_profile.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+ Loading
+
+
+
+
+
+
+ An error occurred. Please reload the page.
+
+