Merge pull request #9520 from edx/efischer/teams-instructor-toolbar
Teams Instructor Tools bar
This commit is contained in:
62
common/test/acceptance/pages/common/utils.py
Normal file
62
common/test/acceptance/pages/common/utils.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Utility methods common to Studio and the LMS.
|
||||
"""
|
||||
from bok_choy.promise import EmptyPromise
|
||||
from ...tests.helpers import disable_animations
|
||||
|
||||
|
||||
def wait_for_notification(page):
|
||||
"""
|
||||
Waits for the "mini-notification" to appear and disappear on the given page (subclass of PageObject).
|
||||
"""
|
||||
def _is_saving():
|
||||
"""Whether or not the notification is currently showing."""
|
||||
return page.q(css='.wrapper-notification-mini.is-shown').present
|
||||
|
||||
def _is_saving_done():
|
||||
"""Whether or not the notification is finished showing."""
|
||||
return page.q(css='.wrapper-notification-mini.is-hiding').present
|
||||
|
||||
EmptyPromise(_is_saving, 'Notification should have been shown.', timeout=60).fulfill()
|
||||
EmptyPromise(_is_saving_done, 'Notification should have been hidden.', timeout=60).fulfill()
|
||||
|
||||
|
||||
def click_css(page, css, source_index=0, require_notification=True):
|
||||
"""
|
||||
Click the button/link with the given css and index on the specified page (subclass of PageObject).
|
||||
|
||||
Will only consider elements that are displayed and have a height and width greater than zero.
|
||||
|
||||
If require_notification is False (default value is True), the method will return immediately.
|
||||
Otherwise, it will wait for the "mini-notification" to appear and disappear.
|
||||
"""
|
||||
def _is_visible(element):
|
||||
"""Is the given element visible?"""
|
||||
# Only make the call to size once (instead of once for the height and once for the width)
|
||||
# because otherwise you will trigger a extra query on a remote element.
|
||||
return element.is_displayed() and all(size > 0 for size in element.size.itervalues())
|
||||
|
||||
# Disable all animations for faster testing with more reliable synchronization
|
||||
disable_animations(page)
|
||||
# Click on the element in the browser
|
||||
page.q(css=css).filter(_is_visible).nth(source_index).click()
|
||||
|
||||
if require_notification:
|
||||
wait_for_notification(page)
|
||||
|
||||
# Some buttons trigger ajax posts
|
||||
# (e.g. .add-missing-groups-button as configured in split_test_author_view.js)
|
||||
# so after you click anything wait for the ajax call to finish
|
||||
page.wait_for_ajax()
|
||||
|
||||
|
||||
def confirm_prompt(page, cancel=False, require_notification=None):
|
||||
"""
|
||||
Ensures that a modal prompt and confirmation button are visible, then clicks the button. The prompt is canceled iff
|
||||
cancel is True.
|
||||
"""
|
||||
page.wait_for_element_visibility('.prompt', 'Prompt is visible')
|
||||
confirmation_button_css = '.prompt .action-' + ('secondary' if cancel else 'primary')
|
||||
page.wait_for_element_visibility(confirmation_button_css, 'Confirmation button is visible')
|
||||
require_notification = (not cancel) if require_notification is None else require_notification
|
||||
click_css(page, confirmation_button_css, require_notification=require_notification)
|
||||
@@ -6,7 +6,7 @@ Teams pages.
|
||||
from .course_page import CoursePage
|
||||
from .discussion import InlineDiscussionPage
|
||||
from ..common.paging import PaginatedUIMixin
|
||||
from ...pages.studio.utils import confirm_prompt
|
||||
from ...pages.common.utils import confirm_prompt
|
||||
|
||||
from .fields import FieldsMixin
|
||||
|
||||
@@ -43,7 +43,24 @@ class TeamCardsMixin(object):
|
||||
return self.q(css='p.card-description').map(lambda e: e.text).results
|
||||
|
||||
|
||||
class TeamsPage(CoursePage):
|
||||
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.
|
||||
"""
|
||||
@@ -92,7 +109,7 @@ class TeamsPage(CoursePage):
|
||||
|
||||
# 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"
|
||||
@@ -173,7 +190,7 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
|
||||
self.wait_for_ajax()
|
||||
|
||||
|
||||
class BaseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin):
|
||||
class BaseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin, BreadcrumbsMixin):
|
||||
"""
|
||||
The paginated UI for browsing teams within a Topic on the Teams
|
||||
page.
|
||||
@@ -211,6 +228,11 @@ class BaseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin):
|
||||
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)
|
||||
@@ -282,9 +304,9 @@ class SearchTeamsPage(BaseTeamsPage):
|
||||
self.url_path = "teams/#topics/{topic_id}/search".format(topic_id=self.topic['id'])
|
||||
|
||||
|
||||
class CreateOrEditTeamPage(CoursePage, FieldsMixin):
|
||||
class TeamManagementPage(CoursePage, FieldsMixin, BreadcrumbsMixin):
|
||||
"""
|
||||
Create team page.
|
||||
Team page for creation, editing, and deletion.
|
||||
"""
|
||||
def __init__(self, browser, course_id, topic):
|
||||
"""
|
||||
@@ -293,15 +315,13 @@ class CreateOrEditTeamPage(CoursePage, FieldsMixin):
|
||||
representation of a topic following the same convention as a
|
||||
course module's topic.
|
||||
"""
|
||||
super(CreateOrEditTeamPage, self).__init__(browser, course_id)
|
||||
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."""
|
||||
has_correct_url = self.url.endswith(self.url_path)
|
||||
teams_create_view_present = self.q(css='.team-edit-fields').present
|
||||
return has_correct_url and teams_create_view_present
|
||||
return self.q(css='.team-edit-fields').present
|
||||
|
||||
@property
|
||||
def header_page_name(self):
|
||||
@@ -313,11 +333,6 @@ class CreateOrEditTeamPage(CoursePage, FieldsMixin):
|
||||
"""Get the page description displayed by the page header"""
|
||||
return self.q(css='.page-header .page-description')[0].text
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def validation_message_text(self):
|
||||
"""Get the error message text"""
|
||||
@@ -333,8 +348,70 @@ class CreateOrEditTeamPage(CoursePage, FieldsMixin):
|
||||
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
|
||||
|
||||
class TeamPage(CoursePage, PaginatedUIMixin):
|
||||
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
|
||||
"""
|
||||
@@ -483,11 +560,6 @@ class TeamPage(CoursePage, PaginatedUIMixin):
|
||||
""" Returns True if New Post button is present else False """
|
||||
return self.q(css='.discussion-module .new-post-btn').present
|
||||
|
||||
def click_all_topics_breadcrumb(self):
|
||||
"""Navigate to the 'All Topics' page."""
|
||||
self.q(css='.breadcrumbs a').results[0].click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
@property
|
||||
def edit_team_button_present(self):
|
||||
""" Returns True if Edit Team button is present else False """
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from bok_choy.page_object import PageObject
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from utils import click_css
|
||||
from ..common.utils import click_css
|
||||
from selenium.webdriver.support.ui import Select
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ from bok_choy.page_object import PageObject
|
||||
from bok_choy.promise import Promise, EmptyPromise
|
||||
from . import BASE_URL
|
||||
|
||||
from .utils import click_css, confirm_prompt, type_in_codemirror
|
||||
from ..common.utils import click_css, confirm_prompt
|
||||
|
||||
from .utils import type_in_codemirror
|
||||
|
||||
|
||||
class ContainerPage(PageObject):
|
||||
|
||||
@@ -9,7 +9,8 @@ import os
|
||||
import re
|
||||
import requests
|
||||
|
||||
from .utils import click_css
|
||||
from ..common.utils import click_css
|
||||
|
||||
from .library import LibraryPage
|
||||
from .course_page import CoursePage
|
||||
from . import BASE_URL
|
||||
|
||||
@@ -9,7 +9,9 @@ from .component_editor import ComponentEditorView
|
||||
from .container import XBlockWrapper
|
||||
from ...pages.studio.users import UsersPageMixin
|
||||
from ...pages.studio.pagination import PaginatedMixin
|
||||
from .utils import confirm_prompt, wait_for_notification
|
||||
|
||||
from ..common.utils import confirm_prompt, wait_for_notification
|
||||
|
||||
from . import BASE_URL
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ from bok_choy.promise import EmptyPromise
|
||||
from selenium.webdriver.support.ui import Select
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
from ..common.utils import click_css, confirm_prompt
|
||||
|
||||
from .course_page import CoursePage
|
||||
from .container import ContainerPage
|
||||
from .utils import set_input_value_and_save, set_input_value, click_css, confirm_prompt
|
||||
from .utils import set_input_value_and_save, set_input_value
|
||||
|
||||
|
||||
class CourseOutlineItem(object):
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
Course Group Configurations page.
|
||||
"""
|
||||
from bok_choy.promise import EmptyPromise
|
||||
from ..common.utils import confirm_prompt
|
||||
from .course_page import CoursePage
|
||||
from .utils import confirm_prompt
|
||||
|
||||
|
||||
class GroupConfigurationsPage(CoursePage):
|
||||
|
||||
@@ -4,8 +4,8 @@ Course Textbooks page.
|
||||
|
||||
import requests
|
||||
from path import Path as path
|
||||
from ..common.utils import click_css
|
||||
from .course_page import CoursePage
|
||||
from .utils import click_css
|
||||
|
||||
|
||||
class TextbooksPage(CoursePage):
|
||||
|
||||
@@ -3,52 +3,9 @@ Utility methods useful for Studio page tests.
|
||||
"""
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from bok_choy.promise import EmptyPromise
|
||||
from bok_choy.javascript import js_defined
|
||||
|
||||
from ...tests.helpers import disable_animations
|
||||
|
||||
|
||||
def click_css(page, css, source_index=0, require_notification=True):
|
||||
"""
|
||||
Click the button/link with the given css and index on the specified page (subclass of PageObject).
|
||||
|
||||
Will only consider elements that are displayed and have a height and width greater than zero.
|
||||
|
||||
If require_notification is False (default value is True), the method will return immediately.
|
||||
Otherwise, it will wait for the "mini-notification" to appear and disappear.
|
||||
"""
|
||||
def _is_visible(el):
|
||||
# Only make the call to size once (instead of once for the height and once for the width)
|
||||
# because otherwise you will trigger a extra query on a remote element.
|
||||
return el.is_displayed() and all(size > 0 for size in el.size.itervalues())
|
||||
|
||||
# Disable all animations for faster testing with more reliable synchronization
|
||||
disable_animations(page)
|
||||
# Click on the element in the browser
|
||||
page.q(css=css).filter(lambda el: _is_visible(el)).nth(source_index).click()
|
||||
|
||||
if require_notification:
|
||||
wait_for_notification(page)
|
||||
|
||||
# Some buttons trigger ajax posts
|
||||
# (e.g. .add-missing-groups-button as configured in split_test_author_view.js)
|
||||
# so after you click anything wait for the ajax call to finish
|
||||
page.wait_for_ajax()
|
||||
|
||||
|
||||
def wait_for_notification(page):
|
||||
"""
|
||||
Waits for the "mini-notification" to appear and disappear on the given page (subclass of PageObject).
|
||||
"""
|
||||
def _is_saving():
|
||||
return page.q(css='.wrapper-notification-mini.is-shown').present
|
||||
|
||||
def _is_saving_done():
|
||||
return page.q(css='.wrapper-notification-mini.is-hiding').present
|
||||
|
||||
EmptyPromise(_is_saving, 'Notification should have been shown.', timeout=60).fulfill()
|
||||
EmptyPromise(_is_saving_done, 'Notification should have been hidden.', timeout=60).fulfill()
|
||||
from ..common.utils import click_css, wait_for_notification
|
||||
|
||||
|
||||
@js_defined('window.jQuery')
|
||||
@@ -177,18 +134,6 @@ def get_codemirror_value(page, index=0, find_prefix="$"):
|
||||
)
|
||||
|
||||
|
||||
def confirm_prompt(page, cancel=False, require_notification=None):
|
||||
"""
|
||||
Ensures that a modal prompt and confirmation button are visible, then clicks the button. The prompt is canceled iff
|
||||
cancel is True.
|
||||
"""
|
||||
page.wait_for_element_visibility('.prompt', 'Prompt is visible')
|
||||
confirmation_button_css = '.prompt .action-' + ('secondary' if cancel else 'primary')
|
||||
page.wait_for_element_visibility(confirmation_button_css, 'Confirmation button is visible')
|
||||
require_notification = (not cancel) if require_notification is None else require_notification
|
||||
click_css(page, confirmation_button_css, require_notification=require_notification)
|
||||
|
||||
|
||||
def set_input_value(page, css, value):
|
||||
"""
|
||||
Sets the text field with the given label (display name) to the specified value.
|
||||
|
||||
@@ -8,8 +8,8 @@ from bok_choy.promise import EmptyPromise, Promise
|
||||
from bok_choy.javascript import wait_for_js, js_defined
|
||||
from ....tests.helpers import YouTubeStubConfig
|
||||
from ...lms.video.video import VideoPage
|
||||
from ...common.utils import wait_for_notification
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from ..utils import wait_for_notification
|
||||
|
||||
|
||||
CLASS_SELECTORS = {
|
||||
|
||||
@@ -7,7 +7,8 @@ from nose.plugins.attrib import attr
|
||||
|
||||
from ..helpers import UniqueCourseTest, remove_file
|
||||
from ...pages.common.logout import LogoutPage
|
||||
from ...pages.studio.utils import add_html_component, click_css, type_in_codemirror
|
||||
from ...pages.common.utils import click_css
|
||||
from ...pages.studio.utils import add_html_component, type_in_codemirror
|
||||
from ...pages.studio.auto_auth import AutoAuthPage
|
||||
from ...pages.studio.overview import CourseOutlinePage
|
||||
from ...pages.studio.container import ContainerPage
|
||||
|
||||
@@ -7,7 +7,8 @@ import json
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
from ..helpers import generate_course_key
|
||||
from ...pages.common.logout import LogoutPage
|
||||
from ...pages.studio.utils import add_html_component, click_css, type_in_codemirror
|
||||
from ...pages.common.utils import click_css
|
||||
from ...pages.studio.utils import add_html_component, type_in_codemirror
|
||||
from ...pages.studio.auto_auth import AutoAuthPage
|
||||
from ...pages.studio.overview import CourseOutlinePage
|
||||
from ...pages.studio.container import ContainerPage
|
||||
|
||||
@@ -22,7 +22,16 @@ from ...pages.lms.auto_auth import AutoAuthPage
|
||||
from ...pages.lms.course_info import CourseInfoPage
|
||||
from ...pages.lms.learner_profile import LearnerProfilePage
|
||||
from ...pages.lms.tab_nav import TabNavPage
|
||||
from ...pages.lms.teams import TeamsPage, MyTeamsPage, BrowseTopicsPage, BrowseTeamsPage, CreateOrEditTeamPage, TeamPage
|
||||
from ...pages.lms.teams import (
|
||||
TeamsPage,
|
||||
MyTeamsPage,
|
||||
BrowseTopicsPage,
|
||||
BrowseTeamsPage,
|
||||
TeamManagementPage,
|
||||
EditMembershipPage,
|
||||
TeamPage
|
||||
)
|
||||
from ...pages.common.utils import confirm_prompt
|
||||
|
||||
|
||||
TOPICS_PER_PAGE = 12
|
||||
@@ -199,7 +208,7 @@ class TeamsTabTest(TeamsTabBase):
|
||||
@ddt.data(
|
||||
'topics/{topic_id}',
|
||||
'topics/{topic_id}/search',
|
||||
'topics/{topic_id}/{team_id}/edit-team',
|
||||
'teams/{topic_id}/{team_id}/edit-team',
|
||||
'teams/{topic_id}/{team_id}'
|
||||
)
|
||||
def test_unauthorized_error_message(self, route):
|
||||
@@ -209,10 +218,10 @@ class TeamsTabTest(TeamsTabBase):
|
||||
"""
|
||||
topics = self.create_topics(1)
|
||||
topic = topics[0]
|
||||
self.set_team_configuration({
|
||||
u'max_team_size': 10,
|
||||
u'topics': topics
|
||||
})
|
||||
self.set_team_configuration(
|
||||
{u'max_team_size': 10, u'topics': topics},
|
||||
global_staff=True
|
||||
)
|
||||
team = self.create_teams(topic, 1)[0]
|
||||
self.teams_page.visit()
|
||||
self.browser.delete_cookie('sessionid')
|
||||
@@ -384,7 +393,7 @@ class BrowseTopicsTest(TeamsTabBase):
|
||||
browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, topic)
|
||||
self.assertTrue(browse_teams_page.is_browser_on_page())
|
||||
browse_teams_page.click_create_team_link()
|
||||
create_team_page = CreateOrEditTeamPage(self.browser, self.course_id, topic)
|
||||
create_team_page = TeamManagementPage(self.browser, self.course_id, topic)
|
||||
create_team_page.value_for_text_field(field_id='name', value='Team Name', press_enter=False)
|
||||
create_team_page.value_for_textarea_field(
|
||||
field_id='description',
|
||||
@@ -393,8 +402,9 @@ class BrowseTopicsTest(TeamsTabBase):
|
||||
create_team_page.submit_form()
|
||||
team_page = TeamPage(self.browser, self.course_id)
|
||||
self.assertTrue(team_page.is_browser_on_page)
|
||||
team_page.click_all_topics_breadcrumb()
|
||||
team_page.click_all_topics()
|
||||
self.assertTrue(self.topics_page.is_browser_on_page())
|
||||
self.topics_page.wait_for_ajax()
|
||||
self.assertEqual(topic_name, self.topics_page.topic_names[0])
|
||||
|
||||
def test_list_topics(self):
|
||||
@@ -834,21 +844,25 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
|
||||
@attr('shard_5')
|
||||
class TeamFormActions(TeamsTabBase):
|
||||
"""
|
||||
Base class for create & edit team.
|
||||
Base class for create, edit, and delete team.
|
||||
"""
|
||||
TEAM_DESCRIPTION = 'The Avengers are a fictional team of superheroes.'
|
||||
|
||||
topic = {'name': 'Example Topic', 'id': 'example_topic', 'description': 'Description'}
|
||||
TEAMS_NAME = 'Avengers'
|
||||
|
||||
def setUp(self):
|
||||
super(TeamFormActions, self).setUp()
|
||||
self.team_management_page = TeamManagementPage(self.browser, self.course_id, self.topic)
|
||||
|
||||
def verify_page_header(self, title, description, breadcrumbs):
|
||||
"""
|
||||
Verify that the page header correctly reflects the
|
||||
create team header, description and breadcrumb.
|
||||
"""
|
||||
self.assertEqual(self.create_or_edit_team_page.header_page_name, title)
|
||||
self.assertEqual(self.create_or_edit_team_page.header_page_description, description)
|
||||
self.assertEqual(self.create_or_edit_team_page.header_page_breadcrumbs, breadcrumbs)
|
||||
self.assertEqual(self.team_management_page.header_page_name, title)
|
||||
self.assertEqual(self.team_management_page.header_page_description, description)
|
||||
self.assertEqual(self.team_management_page.header_page_breadcrumbs, breadcrumbs)
|
||||
|
||||
def verify_and_navigate_to_create_team_page(self):
|
||||
"""Navigates to the create team page and verifies."""
|
||||
@@ -868,7 +882,7 @@ class TeamFormActions(TeamsTabBase):
|
||||
|
||||
self.team_page.click_edit_team_button()
|
||||
|
||||
self.create_or_edit_team_page.wait_for_page()
|
||||
self.team_management_page.wait_for_page()
|
||||
|
||||
# Edit page header.
|
||||
self.verify_page_header(
|
||||
@@ -891,33 +905,37 @@ class TeamFormActions(TeamsTabBase):
|
||||
|
||||
def fill_create_or_edit_form(self):
|
||||
"""Fill the create/edit team form fields with appropriate values."""
|
||||
self.create_or_edit_team_page.value_for_text_field(field_id='name', value=self.TEAMS_NAME, press_enter=False)
|
||||
self.create_or_edit_team_page.value_for_textarea_field(
|
||||
self.team_management_page.value_for_text_field(
|
||||
field_id='name',
|
||||
value=self.TEAMS_NAME,
|
||||
press_enter=False
|
||||
)
|
||||
self.team_management_page.value_for_textarea_field(
|
||||
field_id='description',
|
||||
value=self.TEAM_DESCRIPTION
|
||||
)
|
||||
self.create_or_edit_team_page.value_for_dropdown_field(field_id='language', value='English')
|
||||
self.create_or_edit_team_page.value_for_dropdown_field(field_id='country', value='Pakistan')
|
||||
self.team_management_page.value_for_dropdown_field(field_id='language', value='English')
|
||||
self.team_management_page.value_for_dropdown_field(field_id='country', value='Pakistan')
|
||||
|
||||
def verify_all_fields_exist(self):
|
||||
"""
|
||||
Verify the fields for create/edit page.
|
||||
"""
|
||||
self.assertEqual(
|
||||
self.create_or_edit_team_page.message_for_field('name'),
|
||||
self.team_management_page.message_for_field('name'),
|
||||
'A name that identifies your team (maximum 255 characters).'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.create_or_edit_team_page.message_for_textarea_field('description'),
|
||||
self.team_management_page.message_for_textarea_field('description'),
|
||||
'A short description of the team to help other learners understand '
|
||||
'the goals or direction of the team (maximum 300 characters).'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.create_or_edit_team_page.message_for_field('country'),
|
||||
self.team_management_page.message_for_field('country'),
|
||||
'The country that team members primarily identify with.'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.create_or_edit_team_page.message_for_field('language'),
|
||||
self.team_management_page.message_for_field('language'),
|
||||
'The language that team members primarily use to communicate with each other.'
|
||||
)
|
||||
|
||||
@@ -932,7 +950,6 @@ class CreateTeamTest(TeamFormActions):
|
||||
super(CreateTeamTest, self).setUp()
|
||||
self.set_team_configuration({'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]})
|
||||
|
||||
self.create_or_edit_team_page = CreateOrEditTeamPage(self.browser, self.course_id, self.topic)
|
||||
self.browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, self.topic)
|
||||
self.browse_teams_page.visit()
|
||||
|
||||
@@ -960,14 +977,14 @@ class CreateTeamTest(TeamFormActions):
|
||||
Then I should see the error message and highlighted fields.
|
||||
"""
|
||||
self.verify_and_navigate_to_create_team_page()
|
||||
self.create_or_edit_team_page.submit_form()
|
||||
self.team_management_page.submit_form()
|
||||
|
||||
self.assertEqual(
|
||||
self.create_or_edit_team_page.validation_message_text,
|
||||
self.team_management_page.validation_message_text,
|
||||
'Check the highlighted fields below and try again.'
|
||||
)
|
||||
self.assertTrue(self.create_or_edit_team_page.error_for_field(field_id='name'))
|
||||
self.assertTrue(self.create_or_edit_team_page.error_for_field(field_id='description'))
|
||||
self.assertTrue(self.team_management_page.error_for_field(field_id='name'))
|
||||
self.assertTrue(self.team_management_page.error_for_field(field_id='description'))
|
||||
|
||||
def test_user_can_see_error_message_for_incorrect_data(self):
|
||||
"""
|
||||
@@ -982,7 +999,7 @@ class CreateTeamTest(TeamFormActions):
|
||||
self.verify_and_navigate_to_create_team_page()
|
||||
|
||||
# Fill the name field with >255 characters to see validation message.
|
||||
self.create_or_edit_team_page.value_for_text_field(
|
||||
self.team_management_page.value_for_text_field(
|
||||
field_id='name',
|
||||
value='EdX is a massive open online course (MOOC) provider and online learning platform. '
|
||||
'It hosts online university-level courses in a wide range of disciplines to a worldwide '
|
||||
@@ -994,13 +1011,13 @@ class CreateTeamTest(TeamFormActions):
|
||||
'edX has more than 4 million users taking more than 500 courses online.',
|
||||
press_enter=False
|
||||
)
|
||||
self.create_or_edit_team_page.submit_form()
|
||||
self.team_management_page.submit_form()
|
||||
|
||||
self.assertEqual(
|
||||
self.create_or_edit_team_page.validation_message_text,
|
||||
self.team_management_page.validation_message_text,
|
||||
'Check the highlighted fields below and try again.'
|
||||
)
|
||||
self.assertTrue(self.create_or_edit_team_page.error_for_field(field_id='name'))
|
||||
self.assertTrue(self.team_management_page.error_for_field(field_id='name'))
|
||||
|
||||
def test_user_can_create_new_team_successfully(self):
|
||||
"""
|
||||
@@ -1040,7 +1057,7 @@ class CreateTeamTest(TeamFormActions):
|
||||
}
|
||||
]
|
||||
with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events):
|
||||
self.create_or_edit_team_page.submit_form()
|
||||
self.team_management_page.submit_form()
|
||||
|
||||
# Verify that the page is shown for the new team
|
||||
team_page = TeamPage(self.browser, self.course_id)
|
||||
@@ -1072,7 +1089,7 @@ class CreateTeamTest(TeamFormActions):
|
||||
self.assertTrue(self.browse_teams_page.get_pagination_header_text().startswith('Showing 0 out of 0 total'))
|
||||
|
||||
self.verify_and_navigate_to_create_team_page()
|
||||
self.create_or_edit_team_page.cancel_team()
|
||||
self.team_management_page.cancel_team()
|
||||
|
||||
self.assertTrue(self.browse_teams_page.is_browser_on_page())
|
||||
self.assertTrue(self.browse_teams_page.get_pagination_header_text().startswith('Showing 0 out of 0 total'))
|
||||
@@ -1101,6 +1118,131 @@ class CreateTeamTest(TeamFormActions):
|
||||
self.verify_and_navigate_to_create_team_page()
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class DeleteTeamTest(TeamFormActions):
|
||||
"""
|
||||
Tests for deleting teams.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(DeleteTeamTest, self).setUp()
|
||||
|
||||
self.set_team_configuration(
|
||||
{'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]},
|
||||
global_staff=True
|
||||
)
|
||||
|
||||
self.team = self.create_teams(self.topic, num_teams=1)[0]
|
||||
self.team_page = TeamPage(self.browser, self.course_id, team=self.team)
|
||||
|
||||
#need to have a membership to confirm it gets deleted as well
|
||||
self.create_membership(self.user_info['username'], self.team['id'])
|
||||
|
||||
self.team_page.visit()
|
||||
|
||||
def test_cancel_delete(self):
|
||||
"""
|
||||
Scenario: The user should be able to cancel the Delete Team dialog
|
||||
Given I am staff user for a course with a team
|
||||
When I visit the Team profile page
|
||||
Then I should see the Edit Team button
|
||||
And When I click edit team button
|
||||
Then I should see the Delete Team button
|
||||
When I click the delete team button
|
||||
And I cancel the prompt
|
||||
And I refresh the page
|
||||
Then I should still see the team
|
||||
"""
|
||||
self.delete_team(cancel=True)
|
||||
self.assertTrue(self.team_management_page.is_browser_on_page())
|
||||
self.browser.refresh()
|
||||
self.team_management_page.wait_for_page()
|
||||
self.assertEqual(
|
||||
' '.join(('All Topics', self.topic['name'], self.team['name'])),
|
||||
self.team_management_page.header_page_breadcrumbs
|
||||
)
|
||||
|
||||
@ddt.data('Moderator', 'Community TA', 'Administrator', None)
|
||||
def test_delete_team(self, role):
|
||||
"""
|
||||
Scenario: The user should be able to see and navigate to the delete team page.
|
||||
Given I am staff user for a course with a team
|
||||
When I visit the Team profile page
|
||||
Then I should see the Edit Team button
|
||||
And When I click edit team button
|
||||
Then I should see the Delete Team button
|
||||
When I click the delete team button
|
||||
And I confirm the prompt
|
||||
Then I should see the browse teams page
|
||||
And the team should not be present
|
||||
"""
|
||||
# If role is None, remain logged in as global staff
|
||||
if role is not None:
|
||||
AutoAuthPage(
|
||||
self.browser,
|
||||
course_id=self.course_id,
|
||||
staff=False,
|
||||
roles=role
|
||||
).visit()
|
||||
self.team_page.visit()
|
||||
self.delete_team(require_notification=False)
|
||||
browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, self.topic)
|
||||
self.assertTrue(browse_teams_page.is_browser_on_page())
|
||||
self.assertNotIn(self.team['name'], browse_teams_page.team_names)
|
||||
|
||||
def delete_team(self, **kwargs):
|
||||
"""
|
||||
Delete a team. Passes `kwargs` to `confirm_prompt`.
|
||||
Expects edx.team.deleted event to be emitted, with correct course_id.
|
||||
Also expects edx.team.learner_removed event to be emitted for the
|
||||
membership that is removed as a part of the delete operation.
|
||||
"""
|
||||
|
||||
self.team_page.click_edit_team_button()
|
||||
self.team_management_page.wait_for_page()
|
||||
self.team_management_page.delete_team_button.click()
|
||||
|
||||
if 'cancel' in kwargs and kwargs['cancel'] is True:
|
||||
confirm_prompt(self.team_management_page, **kwargs)
|
||||
else:
|
||||
expected_events = [
|
||||
{
|
||||
'event_type': 'edx.team.deleted',
|
||||
'event': {
|
||||
'course_id': self.course_id,
|
||||
'team_id': self.team['id']
|
||||
}
|
||||
},
|
||||
{
|
||||
'event_type': 'edx.team.learner_removed',
|
||||
'event': {
|
||||
'course_id': self.course_id,
|
||||
'team_id': self.team['id'],
|
||||
'remove_method': 'team_deleted',
|
||||
'user_id': self.user_info['user_id']
|
||||
}
|
||||
}
|
||||
]
|
||||
with self.assert_events_match_during(
|
||||
event_filter=self.only_team_events, expected_events=expected_events
|
||||
):
|
||||
confirm_prompt(self.team_management_page, **kwargs)
|
||||
|
||||
def test_delete_team_updates_topics(self):
|
||||
"""
|
||||
Scenario: Deleting a team should update the team count on the topics page
|
||||
Given I am staff user for a course with a team
|
||||
And I delete a team
|
||||
When I navigate to the browse topics page
|
||||
Then the team count for the deletd team's topic should be updated
|
||||
"""
|
||||
self.delete_team(require_notification=False)
|
||||
BrowseTeamsPage(self.browser, self.course_id, self.topic).click_all_topics()
|
||||
topics_page = BrowseTopicsPage(self.browser, self.course_id)
|
||||
self.assertTrue(topics_page.is_browser_on_page())
|
||||
self.teams_page.verify_topic_team_count(0)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class EditTeamTest(TeamFormActions):
|
||||
"""
|
||||
@@ -1114,7 +1256,6 @@ class EditTeamTest(TeamFormActions):
|
||||
{'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]},
|
||||
global_staff=True
|
||||
)
|
||||
self.create_or_edit_team_page = CreateOrEditTeamPage(self.browser, self.course_id, self.topic)
|
||||
|
||||
self.team = self.create_teams(self.topic, num_teams=1)[0]
|
||||
self.team_page = TeamPage(self.browser, self.course_id, team=self.team)
|
||||
@@ -1204,7 +1345,7 @@ class EditTeamTest(TeamFormActions):
|
||||
},
|
||||
]
|
||||
with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events):
|
||||
self.create_or_edit_team_page.submit_form()
|
||||
self.team_management_page.submit_form()
|
||||
|
||||
self.team_page.wait_for_page()
|
||||
|
||||
@@ -1237,7 +1378,7 @@ class EditTeamTest(TeamFormActions):
|
||||
self.verify_and_navigate_to_edit_team_page()
|
||||
|
||||
self.fill_create_or_edit_form()
|
||||
self.create_or_edit_team_page.cancel_team()
|
||||
self.team_management_page.cancel_team()
|
||||
|
||||
self.team_page.wait_for_page()
|
||||
|
||||
@@ -1289,7 +1430,7 @@ class EditTeamTest(TeamFormActions):
|
||||
self.verify_and_navigate_to_edit_team_page()
|
||||
|
||||
self.fill_create_or_edit_form()
|
||||
self.create_or_edit_team_page.submit_form()
|
||||
self.team_management_page.submit_form()
|
||||
|
||||
self.team_page.wait_for_page()
|
||||
|
||||
@@ -1319,6 +1460,108 @@ class EditTeamTest(TeamFormActions):
|
||||
self.verify_and_navigate_to_edit_team_page()
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class EditMembershipTest(TeamFormActions):
|
||||
"""
|
||||
Tests for administrating from the team membership page
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(EditMembershipTest, self).setUp()
|
||||
|
||||
self.set_team_configuration(
|
||||
{'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]},
|
||||
global_staff=True
|
||||
)
|
||||
self.team_management_page = TeamManagementPage(self.browser, self.course_id, self.topic)
|
||||
self.team = self.create_teams(self.topic, num_teams=1)[0]
|
||||
|
||||
#make sure a user exists on this team so we can edit the membership
|
||||
self.create_membership(self.user_info['username'], self.team['id'])
|
||||
|
||||
self.edit_membership_page = EditMembershipPage(self.browser, self.course_id, self.team)
|
||||
self.team_page = TeamPage(self.browser, self.course_id, team=self.team)
|
||||
|
||||
def edit_membership_helper(self, role, cancel=False):
|
||||
"""
|
||||
Helper for common functionality in edit membership tests.
|
||||
Checks for all relevant assertions about membership being removed,
|
||||
including verify edx.team.learner_removed events are emitted.
|
||||
"""
|
||||
if role is not None:
|
||||
AutoAuthPage(
|
||||
self.browser,
|
||||
course_id=self.course_id,
|
||||
staff=False,
|
||||
roles=role
|
||||
).visit()
|
||||
|
||||
self.team_page.visit()
|
||||
self.team_page.click_edit_team_button()
|
||||
self.team_management_page.wait_for_page()
|
||||
|
||||
self.assertTrue(
|
||||
self.team_management_page.membership_button_present
|
||||
)
|
||||
|
||||
self.team_management_page.click_membership_button()
|
||||
self.edit_membership_page.wait_for_page()
|
||||
self.edit_membership_page.click_first_remove()
|
||||
if cancel:
|
||||
self.edit_membership_page.cancel_delete_membership_dialog()
|
||||
self.assertEqual(self.edit_membership_page.team_members, 1)
|
||||
else:
|
||||
expected_events = [
|
||||
{
|
||||
'event_type': 'edx.team.learner_removed',
|
||||
'event': {
|
||||
'course_id': self.course_id,
|
||||
'team_id': self.team['id'],
|
||||
'remove_method': 'removed_by_admin',
|
||||
'user_id': self.user_info['user_id']
|
||||
}
|
||||
}
|
||||
]
|
||||
with self.assert_events_match_during(
|
||||
event_filter=self.only_team_events, expected_events=expected_events
|
||||
):
|
||||
self.edit_membership_page.confirm_delete_membership_dialog()
|
||||
self.assertEqual(self.edit_membership_page.team_members, 0)
|
||||
self.assertTrue(self.edit_membership_page.is_browser_on_page)
|
||||
|
||||
@ddt.data('Moderator', 'Community TA', 'Administrator', None)
|
||||
def test_remove_membership(self, role):
|
||||
"""
|
||||
Scenario: The user should be able to remove a membership
|
||||
Given I am staff user for a course with a team
|
||||
When I visit the Team profile page
|
||||
Then I should see the Edit Team button
|
||||
And When I click edit team button
|
||||
Then I should see the Edit Membership button
|
||||
And When I click the edit membership button
|
||||
Then I should see the edit membership page
|
||||
And When I click the remove button and confirm the dialog
|
||||
Then my membership should be removed, and I should remain on the page
|
||||
"""
|
||||
self.edit_membership_helper(role, cancel=False)
|
||||
|
||||
@ddt.data('Moderator', 'Community TA', 'Administrator', None)
|
||||
def test_cancel_remove_membership(self, role):
|
||||
"""
|
||||
Scenario: The user should be able to remove a membership
|
||||
Given I am staff user for a course with a team
|
||||
When I visit the Team profile page
|
||||
Then I should see the Edit Team button
|
||||
And When I click edit team button
|
||||
Then I should see the Edit Membership button
|
||||
And When I click the edit membership button
|
||||
Then I should see the edit membership page
|
||||
And When I click the remove button and cancel the dialog
|
||||
Then my membership should not be removed, and I should remain on the page
|
||||
"""
|
||||
self.edit_membership_helper(role, cancel=True)
|
||||
|
||||
|
||||
@attr('shard_5')
|
||||
@ddt.ddt
|
||||
class TeamPageTest(TeamsTabBase):
|
||||
|
||||
Reference in New Issue
Block a user