274 lines
9.1 KiB
Python
274 lines
9.1 KiB
Python
"""
|
|
Course Advanced Settings page
|
|
"""
|
|
|
|
|
|
import six
|
|
from bok_choy.promise import EmptyPromise
|
|
|
|
from common.test.acceptance.pages.studio.course_page import CoursePage
|
|
from common.test.acceptance.pages.studio.utils import (
|
|
get_codemirror_value,
|
|
press_the_notification_button,
|
|
type_in_codemirror
|
|
)
|
|
|
|
KEY_CSS = '.key h3.title'
|
|
UNDO_BUTTON_SELECTOR = ".action-item .action-undo"
|
|
MANUAL_BUTTON_SELECTOR = ".action-item .action-cancel"
|
|
MODAL_SELECTOR = ".validation-error-modal-content"
|
|
ERROR_ITEM_NAME_SELECTOR = ".error-item-title strong"
|
|
ERROR_ITEM_CONTENT_SELECTOR = ".error-item-message"
|
|
SETTINGS_NAME_SELECTOR = ".is-not-editable"
|
|
CONFIRMATION_MESSAGE_SELECTOR = "#alert-confirmation-title"
|
|
DEPRECATED_SETTINGS_SELECTOR = ".field-group.course-advanced-policy-list-item.is-deprecated"
|
|
DEPRECATED_SETTINGS_BUTTON_SELECTOR = ".deprecated-settings-label"
|
|
|
|
|
|
class AdvancedSettingsPage(CoursePage):
|
|
"""
|
|
Course Advanced Settings page.
|
|
"""
|
|
|
|
url_path = "settings/advanced"
|
|
|
|
def is_browser_on_page(self):
|
|
def _is_finished_loading():
|
|
return len(self.q(css='.course-advanced-policy-list-item')) > 0
|
|
|
|
EmptyPromise(_is_finished_loading, 'Finished rendering the advanced policy items.').fulfill()
|
|
return self.q(css='body.advanced').present
|
|
|
|
@property
|
|
def key_names(self):
|
|
"""
|
|
Returns a list of key names of all settings.
|
|
"""
|
|
return self.q(css=KEY_CSS).text
|
|
|
|
@property
|
|
def deprecated_settings_button_text(self):
|
|
"""
|
|
Returns text for deprecated settings button
|
|
"""
|
|
return self.q(css=DEPRECATED_SETTINGS_BUTTON_SELECTOR).text[0]
|
|
|
|
def is_deprecated_setting_visible(self):
|
|
"""
|
|
Returns true if deprecated settings are visible
|
|
"""
|
|
return self.q(css=DEPRECATED_SETTINGS_SELECTOR).visible
|
|
|
|
def toggle_deprecated_settings(self):
|
|
"""
|
|
Show deprecated Settings
|
|
"""
|
|
button_text = self.deprecated_settings_button_text
|
|
self.q(css=DEPRECATED_SETTINGS_BUTTON_SELECTOR).click()
|
|
if button_text == 'Show Deprecated Settings':
|
|
self.wait_for_element_presence(DEPRECATED_SETTINGS_SELECTOR, 'Deprecated Settings are present')
|
|
else:
|
|
self.wait_for_element_absence(DEPRECATED_SETTINGS_SELECTOR, 'Deprecated Settings are not present')
|
|
|
|
def wait_for_modal_load(self):
|
|
"""
|
|
Wait for validation response from the server, and make sure that
|
|
the validation error modal pops up.
|
|
|
|
This method should only be called when it is guaranteed that there're
|
|
validation errors in the settings changes.
|
|
"""
|
|
self.wait_for_ajax()
|
|
self.wait_for_element_presence(MODAL_SELECTOR, 'Validation Modal is present')
|
|
|
|
def refresh_and_wait_for_load(self):
|
|
"""
|
|
Refresh the page and wait for all resources to load.
|
|
"""
|
|
self.browser.refresh()
|
|
self.wait_for_page()
|
|
|
|
@property
|
|
def confirmation_message(self):
|
|
"""
|
|
Returns the text of confirmation message which appears after saving the settings
|
|
"""
|
|
self.wait_for_element_visibility(CONFIRMATION_MESSAGE_SELECTOR, 'Confirmation message is visible')
|
|
return self.q(css=CONFIRMATION_MESSAGE_SELECTOR).text[0]
|
|
|
|
def coordinates_for_scrolling(self, coordinates_for):
|
|
"""
|
|
Get the x and y coordinates of elements
|
|
"""
|
|
cordinates_dict = self.browser.find_element_by_css_selector(coordinates_for)
|
|
location = cordinates_dict.location
|
|
for key, val in six.iteritems(location):
|
|
if key == 'x':
|
|
x_axis = val
|
|
elif key == 'y':
|
|
y_axis = val
|
|
return x_axis, y_axis
|
|
|
|
def undo_changes_via_modal(self):
|
|
"""
|
|
Trigger clicking event of the undo changes button in the modal.
|
|
Wait for the undoing process to load via ajax call.
|
|
Before that Scroll so the button is clickable on all browsers
|
|
"""
|
|
self.browser.execute_script("window.scrollTo" + str(self.coordinates_for_scrolling(UNDO_BUTTON_SELECTOR)))
|
|
self.q(css=UNDO_BUTTON_SELECTOR).click()
|
|
self.wait_for_ajax()
|
|
|
|
def trigger_manual_changes(self):
|
|
"""
|
|
Trigger click event of the manual changes button in the modal.
|
|
No need to wait for any ajax.
|
|
Before that Scroll so the button is clickable on all browsers
|
|
"""
|
|
self.browser.execute_script("window.scrollTo" + str(self.coordinates_for_scrolling(MANUAL_BUTTON_SELECTOR)))
|
|
self.q(css=MANUAL_BUTTON_SELECTOR).click()
|
|
|
|
def is_validation_modal_present(self):
|
|
"""
|
|
Checks if the validation modal is present.
|
|
"""
|
|
return self.q(css=MODAL_SELECTOR).present
|
|
|
|
def get_error_item_names(self):
|
|
"""
|
|
Returns a list of display names of all invalid settings.
|
|
"""
|
|
return self.q(css=ERROR_ITEM_NAME_SELECTOR).text
|
|
|
|
def get_error_item_messages(self):
|
|
"""
|
|
Returns a list of error messages of all invalid settings.
|
|
"""
|
|
return self.q(css=ERROR_ITEM_CONTENT_SELECTOR).text
|
|
|
|
def _get_index_of(self, expected_key):
|
|
"""
|
|
Returns the index of expected key
|
|
"""
|
|
for i, element in enumerate(self.q(css=KEY_CSS)):
|
|
# Sometimes get stale reference if I hold on to the array of elements
|
|
key = self.q(css=KEY_CSS).nth(i).text[0]
|
|
if key == expected_key:
|
|
return i
|
|
|
|
return -1
|
|
|
|
def save(self):
|
|
press_the_notification_button(self, "Save")
|
|
|
|
def cancel(self):
|
|
press_the_notification_button(self, "Cancel")
|
|
|
|
def set(self, key, new_value):
|
|
index = self._get_index_of(key)
|
|
type_in_codemirror(self, index, new_value)
|
|
self.save()
|
|
|
|
def get(self, key):
|
|
index = self._get_index_of(key)
|
|
return get_codemirror_value(self, index)
|
|
|
|
def set_values(self, key_value_map):
|
|
"""
|
|
Make multiple settings changes and save them.
|
|
"""
|
|
for key, value in six.iteritems(key_value_map):
|
|
index = self._get_index_of(key)
|
|
type_in_codemirror(self, index, value)
|
|
|
|
self.save()
|
|
|
|
def get_values(self, key_list):
|
|
"""
|
|
Get a key-value dictionary of all keys in the given list.
|
|
"""
|
|
result_map = {}
|
|
|
|
for key in key_list:
|
|
index = self._get_index_of(key)
|
|
val = get_codemirror_value(self, index)
|
|
result_map[key] = val
|
|
|
|
return result_map
|
|
|
|
@property
|
|
def displayed_settings_names(self):
|
|
"""
|
|
Returns all settings displayed on the advanced settings page/screen/modal/whatever
|
|
We call it 'name', but it's really whatever is embedded in the 'id' element for each field
|
|
"""
|
|
query = self.q(css=SETTINGS_NAME_SELECTOR)
|
|
return query.attrs('id')
|
|
|
|
@property
|
|
def expected_settings_names(self):
|
|
"""
|
|
Returns a list of settings expected to be displayed on the Advanced Settings screen
|
|
Should match the list of settings found in cms/djangoapps/models/settings/course_metadata.py
|
|
If a new setting is added to the metadata list, this test will fail and you must update it.
|
|
Basically this guards against accidental exposure of a field on the Advanced Settings screen
|
|
"""
|
|
return [
|
|
'advanced_modules',
|
|
'allow_anonymous',
|
|
'allow_anonymous_to_peers',
|
|
'allow_public_wiki_access',
|
|
'cert_html_view_overrides',
|
|
'cert_name_long',
|
|
'cert_name_short',
|
|
'certificates_display_behavior',
|
|
'course_image',
|
|
'banner_image',
|
|
'video_thumbnail_image',
|
|
'cosmetic_display_price',
|
|
'advertised_start',
|
|
'announcement',
|
|
'display_name',
|
|
'is_new',
|
|
'issue_badges',
|
|
'max_student_enrollments_allowed',
|
|
'no_grade',
|
|
'display_coursenumber',
|
|
'display_organization',
|
|
'catalog_visibility',
|
|
'days_early_for_beta',
|
|
'disable_progress_graph',
|
|
'discussion_blackouts',
|
|
'discussion_sort_alpha',
|
|
'discussion_topics',
|
|
'due',
|
|
'due_date_display_format',
|
|
'edxnotes',
|
|
'use_latex_compiler',
|
|
'video_speed_optimizations',
|
|
'enrollment_domain',
|
|
'html_textbooks',
|
|
'invitation_only',
|
|
'lti_passports',
|
|
'matlab_api_key',
|
|
'max_attempts',
|
|
'mobile_available',
|
|
'rerandomize',
|
|
'remote_gradebook',
|
|
'showanswer',
|
|
'show_calculator',
|
|
'show_reset_button',
|
|
'static_asset_path',
|
|
'teams_configuration',
|
|
'social_sharing_url',
|
|
'video_bumper',
|
|
'enable_proctored_exams',
|
|
'allow_proctoring_opt_out',
|
|
'enable_timed_exams',
|
|
'enable_subsection_gating',
|
|
'learning_info',
|
|
'instructor_info',
|
|
'ccx_connector',
|
|
'enable_ccx',
|
|
]
|