Files
edx-platform/common/test/acceptance/pages/lms/teams.py
Clinton Blackburn 74f9858b77 Optimized all imports in common.test.acceptance
- Removed unused imports
- Ran isort to properly order/style imports
2017-05-28 00:59:22 -04:00

601 lines
22 KiB
Python

# -*- coding: utf-8 -*-
"""
Teams pages.
"""
from common.test.acceptance.pages.common.paging import PaginatedUIMixin
from common.test.acceptance.pages.common.utils import click_css, confirm_prompt
from common.test.acceptance.pages.lms.course_page import CoursePage
from common.test.acceptance.pages.lms.discussion import InlineDiscussionPage
from common.test.acceptance.pages.lms.fields import FieldsMixin
TOPIC_CARD_CSS = 'div.wrapper-card-core'
CARD_TITLE_CSS = 'h3.card-title'
MY_TEAMS_BUTTON_CSS = '.nav-item[data-index="0"]'
BROWSE_BUTTON_CSS = '.nav-item[data-index="1"]'
TEAMS_LINK_CSS = '.action-view'
TEAMS_HEADER_CSS = '.teams-header'
CREATE_TEAM_LINK_CSS = '.create-team'
class TeamCardsMixin(object):
"""Provides common operations on the team card component."""
def _bounded_selector(self, css):
"""Bind the CSS to a particular tabpanel (e.g. My Teams or Browse)."""
return '{tabpanel_id} {css}'.format(tabpanel_id=getattr(self, 'tabpanel_id', ''), css=css)
def view_first_team(self):
"""Click the 'view' button of the first team card on the page."""
self.q(css=self._bounded_selector('a.action-view')).first.click()
@property
def team_cards(self):
"""Get all the team cards on the page."""
return self.q(css=self._bounded_selector('.team-card'))
@property
def team_names(self):
"""Return the names of each team on the page."""
return self.q(css=self._bounded_selector('h3.card-title')).map(lambda e: e.text).results
@property
def team_descriptions(self):
"""Return the names of each team on the page."""
return self.q(css=self._bounded_selector('p.card-description')).map(lambda e: e.text).results
@property
def team_memberships(self):
"""Return the team memberships text for each card on the page."""
return self.q(css=self._bounded_selector('.member-count')).map(lambda e: e.text).results
class BreadcrumbsMixin(object):
"""Provides common operations on teams page breadcrumb links."""
@property
def header_page_breadcrumbs(self):
"""Get the page breadcrumb text displayed by the page header"""
return self.q(css='.page-header .breadcrumbs')[0].text
def click_all_topics(self):
""" Click on the "All Topics" breadcrumb """
self.q(css='a.nav-item').filter(text='All Topics')[0].click()
def click_specific_topic(self, topic):
""" Click on the breadcrumb for a specific topic """
self.q(css='a.nav-item').filter(text=topic)[0].click()
class TeamsPage(CoursePage, BreadcrumbsMixin):
"""
Teams page/tab.
"""
url_path = "teams"
def is_browser_on_page(self):
""" Checks if teams page is being viewed """
return self.q(css='body.view-teams').present
def get_body_text(self):
""" Returns the current dummy text. This will be changed once there is more content on the page. """
main_page_content_css = '.page-content-main'
self.wait_for(
lambda: len(self.q(css=main_page_content_css).text) == 1,
description="Body text is present"
)
return self.q(css=main_page_content_css).text[0]
def active_tab(self):
""" Get the active tab. """
return self.q(css='.is-active').attrs('data-url')[0]
def browse_topics(self):
""" View the Browse tab of the Teams page. """
self.q(css=BROWSE_BUTTON_CSS).click()
def verify_team_count_in_first_topic(self, expected_count):
"""
Verify that the team count on the first topic card in the topic list is correct
(browse topics page).
"""
self.wait_for(
lambda: self.q(css='.team-count')[0].text == "0 Teams" if expected_count == 0 else "1 Team",
description="Team count text on topic is wrong"
)
def verify_topic_team_count(self, expected_count):
""" Verify the number of teams listed on the topic page (browse teams within topic). """
self.wait_for(
lambda: len(self.q(css='.team-card')) == expected_count,
description="Expected number of teams is wrong"
)
def verify_my_team_count(self, expected_count):
""" Verify the number of teams on 'My Team'. """
# Click to "My Team" and verify that it contains the expected number of teams.
self.q(css=MY_TEAMS_BUTTON_CSS).click()
self.wait_for_ajax()
self.wait_for(
lambda: len(self.q(css='.team-card')) == expected_count,
description="Expected number of teams is wrong"
)
def click_all_topics(self):
""" Click on the "All Topics" breadcrumb """
self.q(css='a.nav-item').filter(text='All Topics')[0].click()
def click_specific_topic(self, topic):
""" Click on the breadcrumb for a specific topic """
self.q(css='a.nav-item').filter(text=topic)[0].click()
@property
def warning_message(self):
"""Return the text of the team warning message."""
return self.q(css='.warning').results[0].text
class MyTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin):
"""
The 'My Teams' tab of the Teams page.
"""
url_path = "teams/#my-teams"
tabpanel_id = '#tabpanel-my-teams'
def is_browser_on_page(self):
"""Check if the "My Teams" tab is being viewed."""
button_classes = self.q(css=MY_TEAMS_BUTTON_CSS).attrs('class')
if len(button_classes) == 0:
return False
return 'is-active' in button_classes[0]
class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
"""
The 'Browse' tab of the Teams page.
"""
url_path = "teams/#browse"
def is_browser_on_page(self):
"""Check if the Browse tab is being viewed."""
# First off, you need to make sure that you're on the Teams page.
if not self.q(css='.teams-main').visible:
return False
button_classes = self.q(css=BROWSE_BUTTON_CSS).attrs('class')
if len(button_classes) == 0:
return False
return 'is-active' in button_classes[0]
@property
def topic_cards(self):
"""Return a list of the topic cards present on the page."""
return self.q(css=TOPIC_CARD_CSS).results
@property
def topic_names(self):
"""Return a list of the topic names present on the page."""
return self.q(css='#tabpanel-browse ' + CARD_TITLE_CSS).map(lambda e: e.text).results
@property
def topic_descriptions(self):
"""Return a list of the topic descriptions present on the page."""
return self.q(css='p.card-description').map(lambda e: e.text).results
def browse_teams_for_topic(self, topic_name):
"""
Show the teams list for `topic_name`.
"""
self.q(css=TEAMS_LINK_CSS).filter(
text='View Teams in the {topic_name} Topic'.format(topic_name=topic_name)
)[0].click()
self.wait_for_ajax()
def sort_topics_by(self, sort_order):
"""Sort the list of topics by the given `sort_order`."""
self.q(
css='#paging-header-select option[value={sort_order}]'.format(sort_order=sort_order)
).click()
self.wait_for_ajax()
class BaseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin, BreadcrumbsMixin):
"""
The paginated UI for browsing teams within a Topic on the Teams
page.
"""
def __init__(self, browser, course_id, topic):
"""
Note that `topic` is a dict representation of a topic following
the same convention as a course module's topic.
"""
super(BaseTeamsPage, self).__init__(browser, course_id)
self.browser = browser
self.course_id = course_id
self.topic = topic
def is_browser_on_page(self):
"""Check if we're on a teams list page for a particular topic."""
has_correct_url = self.url.endswith(self.url_path)
teams_list_view_present = self.q(css='.teams-main').present
return has_correct_url and teams_list_view_present
@property
def header_name(self):
"""Get the topic name displayed by the page header"""
return self.q(css=TEAMS_HEADER_CSS + ' .page-title')[0].text
@property
def header_description(self):
"""Get the topic description displayed by the page header"""
return self.q(css=TEAMS_HEADER_CSS + ' .page-description')[0].text
@property
def sort_order(self):
"""Return the current sort order on the page."""
return self.q(
css='#paging-header-select option'
).filter(
lambda e: e.is_selected()
).results[0].text.strip()
@property
def team_names(self):
"""Get all the team names on the page."""
return self.q(css=CARD_TITLE_CSS).map(lambda e: e.text).results
def click_create_team_link(self):
""" Click on create team link."""
query = self.q(css=CREATE_TEAM_LINK_CSS)
if query.present:
query.first.click()
# This will bring you to the team management page
team_management_page = TeamManagementPage(self.browser, self.course_id, self.topic)
team_management_page.wait_for_page()
def click_search_team_link(self):
""" Click on create team link."""
query = self.q(css='.search-team-descriptions')
if query.present:
query.first.click()
self.wait_for_ajax()
def click_browse_all_teams_link(self):
""" Click on browse team link."""
query = self.q(css='.browse-teams')
if query.present:
query.first.click()
self.wait_for_ajax()
def sort_teams_by(self, sort_order):
"""Sort the list of teams by the given `sort_order`."""
self.q(
css='#paging-header-select option[value={sort_order}]'.format(sort_order=sort_order)
).click()
self.wait_for_ajax()
@property
def _showing_search_results(self):
"""
Returns true if showing search results.
"""
return self.header_description.startswith(u"Showing results for")
def search(self, string):
"""
Searches for the specified string, and returns a SearchTeamsPage
representing the search results page.
"""
self.q(css='.search-field').first.fill(string)
self.q(css='.action-search').first.click()
self.wait_for_ajax()
self.wait_for(
lambda: self._showing_search_results,
description="Showing search results"
)
page = SearchTeamsPage(self.browser, self.course_id, self.topic)
page.wait_for_page()
return page
class BrowseTeamsPage(BaseTeamsPage):
"""
The paginated UI for browsing teams within a Topic on the Teams
page.
"""
def __init__(self, browser, course_id, topic):
super(BrowseTeamsPage, self).__init__(browser, course_id, topic)
self.url_path = "teams/#topics/{topic_id}".format(topic_id=self.topic['id'])
class SearchTeamsPage(BaseTeamsPage):
"""
The paginated UI for showing team search results.
page.
"""
def __init__(self, browser, course_id, topic):
super(SearchTeamsPage, self).__init__(browser, course_id, topic)
self.url_path = "teams/#topics/{topic_id}/search".format(topic_id=self.topic['id'])
class TeamManagementPage(CoursePage, FieldsMixin, BreadcrumbsMixin):
"""
Team page for creation, editing, and deletion.
"""
def __init__(self, browser, course_id, topic):
"""
Set up `self.url_path` on instantiation, since it dynamically
reflects the current topic. Note that `topic` is a dict
representation of a topic following the same convention as a
course module's topic.
"""
super(TeamManagementPage, self).__init__(browser, course_id)
self.topic = topic
self.url_path = "teams/#topics/{topic_id}/create-team".format(topic_id=self.topic['id'])
def is_browser_on_page(self):
"""Check if we're on the create team page for a particular topic."""
fields_css = '.team-edit-fields'
button_sr_css = '.action.action-primary > .sr'
return self.q(css=fields_css).present and self.q(css=button_sr_css).visible
@property
def header_page_name(self):
"""Get the page name displayed by the page header"""
return self.q(css='.page-header .page-title')[0].text
@property
def header_page_description(self):
"""Get the page description displayed by the page header"""
return self.q(css='.page-header .page-description')[0].text
@property
def validation_message_text(self):
"""Get the error message text"""
return self.q(css='.create-team.wrapper-msg .copy')[0].text
def create_team(self, name='Team Name', description='Team description.'):
"""Create a new team"""
self.value_for_text_field(field_id='name', value=name, press_enter=False)
self.set_value_for_textarea_field(
field_id='description',
value=description
)
self.submit_form()
def submit_form(self):
"""Click on create team button"""
self.q(css='.create-team .action-primary').first.click()
self.wait_for_ajax()
def cancel_team(self):
"""Click on cancel team button"""
self.q(css='.create-team .action-cancel').first.click()
self.wait_for_ajax()
@property
def delete_team_button(self):
"""Returns the 'delete team' button."""
return self.q(css='.action-delete').first
def click_membership_button(self):
"""Clicks the 'edit membership' button"""
self.q(css='.action-edit-members').first.click()
self.wait_for_ajax()
@property
def membership_button_present(self):
"""Checks if the edit membership button is present"""
return self.q(css='.action-edit-members').present
class EditMembershipPage(CoursePage):
"""
Staff or discussion-privileged user page to remove troublesome or inactive
students from a team
"""
def __init__(self, browser, course_id, team):
"""
Set up `self.url_path` on instantiation, since it dynamically
reflects the current team.
"""
super(EditMembershipPage, self).__init__(browser, course_id)
self.team = team
self.url_path = "teams/#teams/{topic_id}/{team_id}/edit-team/manage-members".format(
topic_id=self.team['topic_id'], team_id=self.team['id']
)
def is_browser_on_page(self):
"""Check if we're on the team membership page for a particular team."""
self.wait_for_ajax()
if self.q(css='.edit-members').present:
return True
empty_query = self.q(css='.teams-main>.page-content>p').first
return (
len(empty_query.results) > 0 and
empty_query[0].text == "This team does not have any members."
)
@property
def team_members(self):
"""Returns the number of team members shown on the page."""
return len(self.q(css='.team-member'))
def click_first_remove(self):
"""Clicks the remove link on the first member listed."""
self.q(css='.action-remove-member').first.click()
def confirm_delete_membership_dialog(self):
"""Click 'delete' on the warning dialog."""
confirm_prompt(self, require_notification=False)
self.wait_for_ajax()
def cancel_delete_membership_dialog(self):
"""Click 'delete' on the warning dialog."""
confirm_prompt(self, cancel=True)
class TeamPage(CoursePage, PaginatedUIMixin, BreadcrumbsMixin):
"""
The page for a specific Team within the Teams tab
"""
def __init__(self, browser, course_id, team=None):
"""
Set up `self.url_path` on instantiation, since it dynamically
reflects the current team.
"""
super(TeamPage, self).__init__(browser, course_id)
self.team = team
if self.team:
self.url_path = "teams/#teams/{topic_id}/{team_id}".format(
topic_id=self.team['topic_id'], team_id=self.team['id']
)
def is_browser_on_page(self):
"""Check if we're on the teams list page for a particular team."""
self.wait_for_ajax()
if self.team:
if not self.url.endswith(self.url_path):
return False
return self.q(css='.teams-main .team-members').visible
@property
def discussion_id(self):
"""Get the id of the discussion module on the page"""
return self.q(css='div.discussion-module').attrs('data-discussion-id')[0]
@property
def discussion_page(self):
"""Get the discussion as a bok_choy page object"""
if not hasattr(self, '_discussion_page'):
# pylint: disable=attribute-defined-outside-init
self._discussion_page = InlineDiscussionPage(self.browser, self.discussion_id)
return self._discussion_page
@property
def team_name(self):
"""Get the team's name as displayed in the page header"""
return self.q(css='.page-header .page-title')[0].text
@property
def team_description(self):
"""Get the team's description as displayed in the page header"""
return self.q(css=TEAMS_HEADER_CSS + ' .page-description')[0].text
@property
def team_members_present(self):
"""Verifies that team members are present"""
return self.q(css='.page-content-secondary .team-members .team-member').present
@property
def team_capacity_text(self):
"""Returns team capacity text"""
return self.q(css='.page-content-secondary .team-capacity :last-child').text[0]
@property
def team_location(self):
""" Returns team location/country. """
return self.q(css='.page-content-secondary .team-country :last-child').text[0]
@property
def team_language(self):
""" Returns team location/country. """
return self.q(css='.page-content-secondary .team-language :last-child').text[0]
@property
def team_user_membership_text(self):
"""Returns the team membership text"""
query = self.q(css='.page-content-secondary > .team-user-membership-status')
return query.text[0] if query.present else ''
@property
def team_leave_link_present(self):
"""Verifies that team leave link is present"""
return self.q(css='.leave-team-link').present
def click_leave_team_link(self, remaining_members=0, cancel=False):
""" Click on Leave Team link"""
leave_team_css = '.leave-team-link'
self.wait_for_element_visibility(leave_team_css, 'Leave Team link is visible.')
click_css(self, leave_team_css, require_notification=False)
confirm_prompt(self, cancel, require_notification=False)
if cancel is False:
self.wait_for(
lambda: self.join_team_button_present,
description="Join Team button did not become present"
)
self.wait_for_capacity_text(remaining_members)
@property
def team_members(self):
"""Returns the number of team members in this team"""
return len(self.q(css='.page-content-secondary .team-member'))
def click_first_profile_image(self):
"""Clicks on first team member's profile image"""
self.q(css='.page-content-secondary .members-info .team-member').first.click()
@property
def first_member_username(self):
"""Returns the username of team member"""
return self.q(css='.page-content-secondary .tooltip-custom').text[0]
def click_join_team_button(self, total_members=1):
""" Click on Join Team button"""
self.q(css='.join-team .action-primary').first.click()
self.wait_for(
lambda: not self.join_team_button_present,
description="Join Team button did not go away"
)
self.wait_for_capacity_text(total_members)
def wait_for_capacity_text(self, num_members, max_size=10):
""" Wait for the team capacity text to be correct. """
self.wait_for(
lambda: self.team_capacity_text == self.format_capacity_text(num_members, max_size),
description="Team capacity text is not correct"
)
def format_capacity_text(self, num_members, max_size):
""" Helper method to format the expected team capacity text. """
return '{num_members} / {max_size} {members_text}'.format(
num_members=num_members,
max_size=max_size,
members_text='Member' if num_members == max_size else 'Members'
)
@property
def join_team_message(self):
""" Returns join team message """
self.wait_for_ajax()
return self.q(css='.join-team .join-team-message').text[0]
@property
def join_team_button_present(self):
""" Returns True if Join Team button is present else False """
return self.q(css='.join-team .action-primary').present
@property
def join_team_message_present(self):
""" Returns True if Join Team message is present else False """
return self.q(css='.join-team .join-team-message').present
@property
def new_post_button_present(self):
""" Returns True if New Post button is present else False """
return self.q(css='.discussion-module .new-post-btn').visible
@property
def edit_team_button_present(self):
""" Returns True if Edit Team button is present else False """
return self.q(css='.form-actions .action-edit-team').present
def click_edit_team_button(self):
""" Click on Edit Team button"""
self.q(css='.form-actions .action-edit-team').first.click()