Files
edx-platform/common/test/acceptance/pages/studio/users.py
Ben Patterson 02800dcd12 Bok-choy on chrome: fix sequence for adding user to team.
TE-849

This passes fine in firefox; however in chrome, the click is occurring before
the page is ready; and then the click itself is not specific enough. Also
refactored the add button method so it more-clearly returns the expected
end-state.
2015-05-06 11:56:48 -04:00

247 lines
8.6 KiB
Python

"""
Page classes to test either the Course Team page or the Library Team page.
"""
from bok_choy.promise import EmptyPromise
from bok_choy.page_object import PageObject
from ...tests.helpers import disable_animations
from .course_page import CoursePage
from . import BASE_URL
def wait_for_ajax_or_reload(browser):
"""
Wait for all ajax requests to finish, OR for the page to reload.
Normal wait_for_ajax() chokes on occasion if the pages reloads,
giving "WebDriverException: Message: u'jQuery is not defined'"
"""
def _is_ajax_finished():
""" Wait for jQuery to finish all AJAX calls, if it is present. """
return browser.execute_script("return typeof(jQuery) == 'undefined' || jQuery.active == 0")
EmptyPromise(_is_ajax_finished, "Finished waiting for ajax requests.").fulfill()
class UsersPageMixin(PageObject):
""" Common functionality for course/library team pages """
new_user_form_selector = '.form-create.create-user .user-email-input'
def url(self):
"""
URL to this page - override in subclass
"""
raise NotImplementedError
def is_browser_on_page(self):
"""
Returns True iff the browser has loaded the page.
"""
return self.q(css='body.view-team').present
@property
def users(self):
"""
Return a list of users listed on this page.
"""
return self.q(css='.user-list .user-item').map(
lambda el: UserWrapper(self.browser, el.get_attribute('data-email'))
).results
@property
def usernames(self):
"""
Returns a list of user names for users listed on this page
"""
return [user.name for user in self.users]
@property
def has_add_button(self):
"""
Is the "New Team Member" button present?
"""
return self.q(css='.create-user-button').present
def click_add_button(self):
"""
Click on the "New Team Member" button
"""
self.q(css='.create-user-button').first.click()
self.wait_for(lambda: self.new_user_form_visible, "Add user form is visible")
@property
def new_user_form_visible(self):
""" Is the new user form visible? """
return self.q(css='.form-create.create-user .user-email-input').visible
def set_new_user_email(self, email):
""" Set the value of the "New User Email Address" field. """
self.q(css='.form-create.create-user .user-email-input').fill(email)
def click_submit_new_user_form(self):
""" Submit the "New User" form """
self.q(css='.form-create.create-user .action-primary').click()
wait_for_ajax_or_reload(self.browser)
def get_user(self, email):
""" Gets user wrapper by email """
target_users = [user for user in self.users if user.email == email]
assert len(target_users) == 1
return target_users[0]
def add_user_to_course(self, email):
""" Adds user to a course/library """
self.wait_for_element_visibility('.create-user-button', "Add team member button is available")
self.click_add_button()
self.set_new_user_email(email)
self.click_submit_new_user_form()
def delete_user_from_course(self, email):
""" Deletes user from course/library """
target_user = self.get_user(email)
target_user.click_delete()
def modal_dialog_visible(self, dialog_type):
""" Checks if modal dialog of specified class is displayed """
return self.q(css='.prompt.{dialog_type}'.format(dialog_type=dialog_type)).visible
def modal_dialog_text(self, dialog_type):
""" Gets modal dialog text """
return self.q(css='.prompt.{dialog_type} .message'.format(dialog_type=dialog_type)).text[0]
def wait_until_ready(self):
"""
When the page first loads, there is a loading indicator and most
functionality is not yet available. This waits for that loading to
finish.
Always call this before using the page. It also disables animations
for improved test reliability.
"""
self.wait_for_element_invisibility(
'.ui-loading',
'Wait for the page to complete its initial loading and rendering via Backbone'
)
disable_animations(self)
class LibraryUsersPage(UsersPageMixin):
"""
Library Team page in Studio
"""
def __init__(self, browser, locator):
super(LibraryUsersPage, self).__init__(browser)
self.locator = locator
@property
def url(self):
"""
URL to the "User Access" page for the given library.
"""
return "{}/library/{}/team/".format(BASE_URL, unicode(self.locator))
class CourseTeamPage(CoursePage, UsersPageMixin):
"""
Course Team page in Studio.
"""
url_path = "course_team"
class UserWrapper(PageObject):
"""
A PageObject representing a wrapper around a user listed on the course/library team page.
"""
url = None
COMPONENT_BUTTONS = {
'basic_tab': '.editor-tabs li.inner_tab_wrap:nth-child(1) > a',
'advanced_tab': '.editor-tabs li.inner_tab_wrap:nth-child(2) > a',
'save_settings': '.action-save',
}
def __init__(self, browser, email):
super(UserWrapper, self).__init__(browser)
self.email = email
self.selector = '.user-list .user-item[data-email="{}"]'.format(self.email)
def is_browser_on_page(self):
"""
Sanity check that our wrapper element is on the page.
"""
return self.q(css=self.selector).present
def _bounded_selector(self, selector):
"""
Return `selector`, but limited to this particular user entry's context
"""
return '{} {}'.format(self.selector, selector)
@property
def name(self):
""" Get this user's username, as displayed. """
return self.q(css=self._bounded_selector('.user-username')).text[0]
@property
def role_label(self):
""" Get this user's role, as displayed. """
return self.q(css=self._bounded_selector('.flag-role .value')).text[0]
@property
def is_current_user(self):
""" Does the UI indicate that this is the current user? """
return self.q(css=self._bounded_selector('.flag-role .msg-you')).present
@property
def can_promote(self):
""" Can this user be promoted to a more powerful role? """
return self.q(css=self._bounded_selector('.add-admin-role')).present
@property
def promote_button_text(self):
""" What does the promote user button say? """
return self.q(css=self._bounded_selector('.add-admin-role')).text[0]
def click_promote(self):
""" Click on the button to promote this user to the more powerful role """
self.q(css=self._bounded_selector('.add-admin-role')).click()
wait_for_ajax_or_reload(self.browser)
@property
def can_demote(self):
""" Can this user be demoted to a less powerful role? """
return self.q(css=self._bounded_selector('.remove-admin-role')).present
@property
def demote_button_text(self):
""" What does the demote user button say? """
return self.q(css=self._bounded_selector('.remove-admin-role')).text[0]
def click_demote(self):
""" Click on the button to demote this user to the less powerful role """
self.q(css=self._bounded_selector('.remove-admin-role')).click()
wait_for_ajax_or_reload(self.browser)
@property
def can_delete(self):
""" Can this user be deleted? """
return self.q(css=self._bounded_selector('.action-delete:not(.is-disabled) .remove-user')).present
def click_delete(self):
""" Click the button to delete this user. """
disable_animations(self)
self.q(css=self._bounded_selector('.remove-user')).click()
# We can't use confirm_prompt because its wait_for_ajax is flaky when the page is expected to reload.
self.wait_for_element_visibility('.prompt', 'Prompt is visible')
self.wait_for_element_visibility('.prompt .action-primary', 'Confirmation button is visible')
self.q(css='.prompt .action-primary').click()
wait_for_ajax_or_reload(self.browser)
@property
def has_no_change_warning(self):
""" Does this have a warning in place of the promote/demote buttons? """
return self.q(css=self._bounded_selector('.notoggleforyou')).present
@property
def no_change_warning_text(self):
""" Text of the warning seen in place of the promote/demote buttons. """
return self.q(css=self._bounded_selector('.notoggleforyou')).text[0]