""" Acceptance tests for the teams feature. """ import json import random import time from uuid import uuid4 import ddt from dateutil.parser import parse from nose.plugins.attrib import attr from selenium.common.exceptions import TimeoutException from common.test.acceptance.fixtures import LMS_BASE_URL from common.test.acceptance.fixtures.course import CourseFixture from common.test.acceptance.fixtures.discussion import ForumsConfigMixin, MultipleThreadFixture, Thread from common.test.acceptance.pages.common.auto_auth import AutoAuthPage from common.test.acceptance.pages.common.utils import confirm_prompt from common.test.acceptance.pages.lms.course_info import CourseInfoPage from common.test.acceptance.pages.lms.learner_profile import LearnerProfilePage from common.test.acceptance.pages.lms.tab_nav import TabNavPage from common.test.acceptance.pages.lms.teams import ( BrowseTeamsPage, BrowseTopicsPage, EditMembershipPage, MyTeamsPage, TeamManagementPage, TeamPage, TeamsPage ) from common.test.acceptance.tests.helpers import EventsTestMixin, UniqueCourseTest, get_modal_alert TOPICS_PER_PAGE = 12 class TeamsTabBase(EventsTestMixin, ForumsConfigMixin, UniqueCourseTest): """Base class for Teams Tab tests""" def setUp(self): super(TeamsTabBase, self).setUp() self.tab_nav = TabNavPage(self.browser) self.course_info_page = CourseInfoPage(self.browser, self.course_id) self.teams_page = TeamsPage(self.browser, self.course_id) # TODO: Refactor so resetting events database is not necessary self.reset_event_tracking() self.enable_forums() def create_topics(self, num_topics): """Create `num_topics` test topics.""" return [{u"description": i, u"name": i, u"id": i} for i in map(str, xrange(num_topics))] def create_teams(self, topic, num_teams, time_between_creation=0): """Create `num_teams` teams belonging to `topic`.""" teams = [] for i in xrange(num_teams): team = { 'course_id': self.course_id, 'topic_id': topic['id'], 'name': 'Team {}'.format(i), 'description': 'Description {}'.format(i), 'language': 'aa', 'country': 'AF' } teams.append(self.post_team_data(team)) # Sadly, this sleep is necessary in order to ensure that # sorting by last_activity_at works correctly when running # in Jenkins. # THIS IS AN ANTI-PATTERN - DO NOT COPY. time.sleep(time_between_creation) return teams def post_team_data(self, team_data): """Given a JSON representation of a team, post it to the server.""" response = self.course_fixture.session.post( LMS_BASE_URL + '/api/team/v0/teams/', data=json.dumps(team_data), headers=self.course_fixture.headers ) self.assertEqual(response.status_code, 200) return json.loads(response.text) def create_memberships(self, num_memberships, team_id): """Create `num_memberships` users and assign them to `team_id`. The last user created becomes the current user.""" memberships = [] for __ in xrange(num_memberships): user_info = AutoAuthPage(self.browser, course_id=self.course_id).visit().user_info memberships.append(user_info) self.create_membership(user_info['username'], team_id) #pylint: disable=attribute-defined-outside-init self.user_info = memberships[-1] return memberships def create_membership(self, username, team_id): """Assign `username` to `team_id`.""" response = self.course_fixture.session.post( LMS_BASE_URL + '/api/team/v0/team_membership/', data=json.dumps({'username': username, 'team_id': team_id}), headers=self.course_fixture.headers ) return json.loads(response.text) def set_team_configuration(self, configuration, enroll_in_course=True, global_staff=False): """ Sets team configuration on the course and calls auto-auth on the user. """ #pylint: disable=attribute-defined-outside-init self.course_fixture = CourseFixture(**self.course_info) if configuration: self.course_fixture.add_advanced_settings( {u"teams_configuration": {u"value": configuration}} ) self.course_fixture.install() enroll_course_id = self.course_id if enroll_in_course else None #pylint: disable=attribute-defined-outside-init self.user_info = AutoAuthPage(self.browser, course_id=enroll_course_id, staff=global_staff).visit().user_info self.course_info_page.visit() def verify_teams_present(self, present): """ Verifies whether or not the teams tab is present. If it should be present, also checks the text on the page (to ensure view is working). """ if present: self.assertIn("Teams", self.tab_nav.tab_names) self.teams_page.visit() self.assertEqual(self.teams_page.active_tab(), 'browse') else: self.assertNotIn("Teams", self.tab_nav.tab_names) def verify_teams(self, page, expected_teams): """Verify that the list of team cards on the current page match the expected teams in order.""" def assert_team_equal(expected_team, team_card_name, team_card_description): """ Helper to assert that a single team card has the expected name and description. """ self.assertEqual(expected_team['name'], team_card_name) self.assertEqual(expected_team['description'], team_card_description) team_card_names = page.team_names team_card_descriptions = page.team_descriptions map(assert_team_equal, expected_teams, team_card_names, team_card_descriptions) def verify_my_team_count(self, expected_number_of_teams): """ Verify the number of teams shown on "My Team". """ # We are doing these operations on this top-level page object to avoid reloading the page. self.teams_page.verify_my_team_count(expected_number_of_teams) def only_team_events(self, event): """Filter out all non-team events.""" return event['event_type'].startswith('edx.team.') @ddt.ddt @attr(shard=5) class TeamsTabTest(TeamsTabBase): """ Tests verifying when the Teams tab is present. """ def test_teams_not_enabled(self): """ Scenario: teams tab should not be present if no team configuration is set Given I am enrolled in a course without team configuration When I view the course info page Then I should not see the Teams tab """ self.set_team_configuration(None) self.verify_teams_present(False) def test_teams_not_enabled_no_topics(self): """ Scenario: teams tab should not be present if team configuration does not specify topics Given I am enrolled in a course with no topics in the team configuration When I view the course info page Then I should not see the Teams tab """ self.set_team_configuration({u"max_team_size": 10, u"topics": []}) self.verify_teams_present(False) def test_teams_not_enabled_not_enrolled(self): """ Scenario: teams tab should not be present if student is not enrolled in the course Given there is a course with team configuration and topics And I am not enrolled in that course, and am not global staff When I view the course info page Then I should not see the Teams tab """ self.set_team_configuration( {u"max_team_size": 10, u"topics": self.create_topics(1)}, enroll_in_course=False ) self.verify_teams_present(False) def test_teams_enabled(self): """ Scenario: teams tab should be present if user is enrolled in the course and it has team configuration Given I am enrolled in a course with team configuration and topics When I view the course info page Then I should see the Teams tab And the correct content should be on the page """ self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(1)}) self.verify_teams_present(True) def test_teams_enabled_global_staff(self): """ Scenario: teams tab should be present if user is not enrolled in the course, but is global staff Given there is a course with team configuration And I am not enrolled in that course, but am global staff When I view the course info page Then I should see the Teams tab And the correct content should be on the page """ self.set_team_configuration( {u"max_team_size": 10, u"topics": self.create_topics(1)}, enroll_in_course=False, global_staff=True ) self.verify_teams_present(True) @ddt.data( 'topics/{topic_id}', 'topics/{topic_id}/search', 'teams/{topic_id}/{team_id}/edit-team', 'teams/{topic_id}/{team_id}' ) def test_unauthorized_error_message(self, route): """Ensure that an error message is shown to the user if they attempt to take an action which makes an AJAX request while not signed in. """ topics = self.create_topics(1) topic = topics[0] 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') url = self.browser.current_url.split('#')[0] self.browser.get( '{url}#{route}'.format( url=url, route=route.format( topic_id=topic['id'], team_id=team['id'] ) ) ) self.teams_page.wait_for_ajax() self.assertEqual( self.teams_page.warning_message, u"Your request could not be completed. Reload the page and try again." ) @ddt.data( ('browse', '.topics-list'), # TODO: find a reliable way to match the "My Teams" tab # ('my-teams', 'div.teams-list'), ('teams/{topic_id}/{team_id}', 'div.discussion-module'), ('topics/{topic_id}/create-team', 'div.create-team-instructions'), ('topics/{topic_id}', '.teams-list'), ('not-a-real-route', 'div.warning') ) @ddt.unpack def test_url_routing(self, route, selector): """Ensure that navigating to a URL route correctly updates the page content. """ topics = self.create_topics(1) topic = topics[0] self.set_team_configuration({ u'max_team_size': 10, u'topics': topics }) team = self.create_teams(topic, 1)[0] self.teams_page.visit() # Get the base URL (the URL without any trailing fragment) url = self.browser.current_url fragment_index = url.find('#') if fragment_index >= 0: url = url[0:fragment_index] self.browser.get( '{url}#{route}'.format( url=url, route=route.format( topic_id=topic['id'], team_id=team['id'] )) ) self.teams_page.wait_for_page() self.teams_page.wait_for_ajax() self.assertTrue(self.teams_page.q(css=selector).present) self.assertTrue(self.teams_page.q(css=selector).visible) @attr(shard=5) class MyTeamsTest(TeamsTabBase): """ Tests for the "My Teams" tab of the Teams page. """ def setUp(self): super(MyTeamsTest, self).setUp() self.topic = {u"name": u"Example Topic", u"id": "example_topic", u"description": "Description"} self.set_team_configuration({'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]}) self.my_teams_page = MyTeamsPage(self.browser, self.course_id) self.page_viewed_event = { 'event_type': 'edx.team.page_viewed', 'event': { 'page_name': 'my-teams', 'topic_id': None, 'team_id': None } } def test_not_member_of_any_teams(self): """ Scenario: Visiting the My Teams page when user is not a member of any team should not display any teams. Given I am enrolled in a course with a team configuration and a topic but am not a member of a team When I visit the My Teams page And I should see no teams And I should see a message that I belong to no teams. """ with self.assert_events_match_during(self.only_team_events, expected_events=[self.page_viewed_event]): self.my_teams_page.visit() self.assertEqual(len(self.my_teams_page.team_cards), 0, msg='Expected to see no team cards') self.assertEqual( self.my_teams_page.q(css='.page-content-main').text, [u'You are not currently a member of any team.'] ) def test_member_of_a_team(self): """ Scenario: Visiting the My Teams page when user is a member of a team should display the teams. Given I am enrolled in a course with a team configuration and a topic and am a member of a team When I visit the My Teams page Then I should see a pagination header showing the number of teams And I should see all the expected team cards And I should not see a pagination footer """ teams = self.create_teams(self.topic, 1) self.create_membership(self.user_info['username'], teams[0]['id']) with self.assert_events_match_during(self.only_team_events, expected_events=[self.page_viewed_event]): self.my_teams_page.visit() self.verify_teams(self.my_teams_page, teams) def test_multiple_team_members(self): """ Scenario: Visiting the My Teams page when user is a member of a team should display the teams. Given I am a member of a team with multiple members When I visit the My Teams page Then I should see the correct number of team members on my membership """ teams = self.create_teams(self.topic, 1) self.create_memberships(4, teams[0]['id']) self.my_teams_page.visit() self.assertEqual(self.my_teams_page.team_memberships[0], '4 / 10 Members') @attr(shard=5) @ddt.ddt class BrowseTopicsTest(TeamsTabBase): """ Tests for the Browse tab of the Teams page. """ def setUp(self): super(BrowseTopicsTest, self).setUp() self.topics_page = BrowseTopicsPage(self.browser, self.course_id) @ddt.data(('name', False), ('team_count', True)) @ddt.unpack def test_sort_topics(self, sort_order, reverse): """ Scenario: the user should be able to sort the list of topics by name or team count Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse topics Then I should see a list of topics for the course When I choose a sort order Then I should see the paginated list of topics in that order """ topics = self.create_topics(TOPICS_PER_PAGE + 1) self.set_team_configuration({u"max_team_size": 100, u"topics": topics}) for i, topic in enumerate(random.sample(topics, len(topics))): self.create_teams(topic, i) topic['team_count'] = i self.topics_page.visit() self.topics_page.sort_topics_by(sort_order) topic_names = self.topics_page.topic_names self.assertEqual(len(topic_names), TOPICS_PER_PAGE) self.assertEqual( topic_names, [t['name'] for t in sorted(topics, key=lambda t: t[sort_order], reverse=reverse)][:TOPICS_PER_PAGE] ) def test_sort_topics_update(self): """ Scenario: the list of topics should remain sorted after updates Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse topics and choose a sort order Then I should see the paginated list of topics in that order When I create a team in one of those topics And I return to the topics list Then I should see the topics in the correct sorted order """ topics = self.create_topics(3) self.set_team_configuration({u"max_team_size": 100, u"topics": topics}) self.topics_page.visit() self.topics_page.sort_topics_by('team_count') topic_name = self.topics_page.topic_names[-1] topic = [t for t in topics if t['name'] == topic_name][0] self.topics_page.browse_teams_for_topic(topic_name) browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, topic) browse_teams_page.wait_for_page() browse_teams_page.click_create_team_link() create_team_page = TeamManagementPage(self.browser, self.course_id, topic) create_team_page.create_team() team_page = TeamPage(self.browser, self.course_id) team_page.wait_for_page() team_page.click_all_topics() self.topics_page.wait_for_page() self.topics_page.wait_for_ajax() self.assertEqual(topic_name, self.topics_page.topic_names[0]) def test_list_topics(self): """ Scenario: a list of topics should be visible in the "Browse" tab Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse topics Then I should see a list of topics for the course """ self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(2)}) self.topics_page.visit() self.assertEqual(len(self.topics_page.topic_cards), 2) self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 1-2 out of 2 total')) self.assertFalse(self.topics_page.pagination_controls_visible()) self.assertFalse(self.topics_page.is_previous_page_button_enabled()) self.assertFalse(self.topics_page.is_next_page_button_enabled()) def test_topic_pagination(self): """ Scenario: a list of topics should be visible in the "Browse" tab, paginated 12 per page Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse topics Then I should see only the first 12 topics """ self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(20)}) self.topics_page.visit() self.assertEqual(len(self.topics_page.topic_cards), TOPICS_PER_PAGE) self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 1-12 out of 20 total')) self.assertTrue(self.topics_page.pagination_controls_visible()) self.assertFalse(self.topics_page.is_previous_page_button_enabled()) self.assertTrue(self.topics_page.is_next_page_button_enabled()) def test_go_to_numbered_page(self): """ Scenario: topics should be able to be navigated by page number Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse topics And I enter a valid page number in the page number input Then I should see that page of topics """ self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(25)}) self.topics_page.visit() self.topics_page.go_to_page(3) self.assertEqual(len(self.topics_page.topic_cards), 1) self.assertTrue(self.topics_page.is_previous_page_button_enabled()) self.assertFalse(self.topics_page.is_next_page_button_enabled()) def test_go_to_invalid_page(self): """ Scenario: browsing topics should not respond to invalid page numbers Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse topics And I enter an invalid page number in the page number input Then I should stay on the current page """ self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(13)}) self.topics_page.visit() self.topics_page.go_to_page(3) self.assertEqual(self.topics_page.get_current_page_number(), 1) def test_page_navigation_buttons(self): """ Scenario: browsing topics should not respond to invalid page numbers Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse topics When I press the next page button Then I should move to the next page When I press the previous page button Then I should move to the previous page """ self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(13)}) self.topics_page.visit() self.topics_page.press_next_page_button() self.assertEqual(len(self.topics_page.topic_cards), 1) self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 13-13 out of 13 total')) self.topics_page.press_previous_page_button() self.assertEqual(len(self.topics_page.topic_cards), TOPICS_PER_PAGE) self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 1-12 out of 13 total')) def test_topic_pagination_one_page(self): """ Scenario: Browsing topics when there are fewer topics than the page size i.e. 12 all topics should show on one page Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse topics And I should see corrected number of topic cards And I should see the correct page header And I should not see a pagination footer """ self.set_team_configuration({u"max_team_size": 10, u"topics": self.create_topics(10)}) self.topics_page.visit() self.assertEqual(len(self.topics_page.topic_cards), 10) self.assertTrue(self.topics_page.get_pagination_header_text().startswith('Showing 1-10 out of 10 total')) self.assertFalse(self.topics_page.pagination_controls_visible()) def test_topic_description_truncation(self): """ Scenario: excessively long topic descriptions should be truncated so as to fit within a topic card. Given I am enrolled in a course with a team configuration and a topic with a long description When I visit the Teams page And I browse topics Then I should see a truncated topic description """ initial_description = "A" + " really" * 50 + " long description" self.set_team_configuration( {u"max_team_size": 1, u"topics": [{"name": "", "id": "", "description": initial_description}]} ) self.topics_page.visit() truncated_description = self.topics_page.topic_descriptions[0] self.assertLess(len(truncated_description), len(initial_description)) self.assertTrue(truncated_description.endswith('...')) self.assertIn(truncated_description.split('...')[0], initial_description) def test_go_to_teams_list(self): """ Scenario: Clicking on a Topic Card should take you to the teams list for that Topic. Given I am enrolled in a course with a team configuration and a topic When I visit the Teams page And I browse topics And I click on the arrow link to view teams for the first topic Then I should be on the browse teams page """ topic = {u"name": u"Example Topic", u"id": u"example_topic", u"description": "Description"} self.set_team_configuration( {u"max_team_size": 1, u"topics": [topic]} ) self.topics_page.visit() self.topics_page.browse_teams_for_topic('Example Topic') browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, topic) browse_teams_page.wait_for_page() self.assertEqual(browse_teams_page.header_name, 'Example Topic') self.assertEqual(browse_teams_page.header_description, 'Description') def test_page_viewed_event(self): """ Scenario: Visiting the browse topics page should fire a page viewed event. Given I am enrolled in a course with a team configuration and a topic When I visit the browse topics page Then my browser should post a page viewed event """ topic = {u"name": u"Example Topic", u"id": u"example_topic", u"description": "Description"} self.set_team_configuration( {u"max_team_size": 1, u"topics": [topic]} ) events = [{ 'event_type': 'edx.team.page_viewed', 'event': { 'page_name': 'browse', 'topic_id': None, 'team_id': None } }] with self.assert_events_match_during(self.only_team_events, expected_events=events): self.topics_page.visit() @attr(shard=5) @ddt.ddt class BrowseTeamsWithinTopicTest(TeamsTabBase): """ Tests for browsing Teams within a Topic on the Teams page. """ TEAMS_PAGE_SIZE = 10 def setUp(self): super(BrowseTeamsWithinTopicTest, self).setUp() self.topic = {u"name": u"Example Topic", u"id": "example_topic", u"description": "Description"} self.max_team_size = 10 self.set_team_configuration({ 'course_id': self.course_id, 'max_team_size': self.max_team_size, 'topics': [self.topic] }) self.browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, self.topic) self.topics_page = BrowseTopicsPage(self.browser, self.course_id) def teams_with_default_sort_order(self, teams): """Return a list of teams sorted according to the default ordering (last_activity_at, with a secondary sort by open slots). """ return sorted( sorted(teams, key=lambda t: len(t['membership']), reverse=True), key=lambda t: parse(t['last_activity_at']).replace(microsecond=0), reverse=True ) def verify_page_header(self): """Verify that the page header correctly reflects the current topic's name and description.""" self.assertEqual(self.browse_teams_page.header_name, self.topic['name']) self.assertEqual(self.browse_teams_page.header_description, self.topic['description']) def verify_search_header(self, search_results_page, search_query): """Verify that the page header correctly reflects the current topic's name and description.""" self.assertEqual(search_results_page.header_name, 'Team Search') self.assertEqual( search_results_page.header_description, 'Showing results for "{search_query}"'.format(search_query=search_query) ) def verify_on_page(self, teams_page, page_num, total_teams, pagination_header_text, footer_visible): """ Verify that we are on the correct team list page. Arguments: teams_page (BaseTeamsPage): The teams page object that should be the current page. page_num (int): The one-indexed page number that we expect to be on total_teams (list): An unsorted list of all the teams for the current topic pagination_header_text (str): Text we expect to see in the pagination header. footer_visible (bool): Whether we expect to see the pagination footer controls. """ sorted_teams = self.teams_with_default_sort_order(total_teams) self.assertTrue(teams_page.get_pagination_header_text().startswith(pagination_header_text)) self.verify_teams( teams_page, sorted_teams[(page_num - 1) * self.TEAMS_PAGE_SIZE:page_num * self.TEAMS_PAGE_SIZE] ) self.assertEqual( teams_page.pagination_controls_visible(), footer_visible, msg='Expected paging footer to be ' + 'visible' if footer_visible else 'invisible' ) @ddt.data( ('open_slots', 'last_activity_at', True), ('last_activity_at', 'open_slots', True) ) @ddt.unpack def test_sort_teams(self, sort_order, secondary_sort_order, reverse): """ Scenario: the user should be able to sort the list of teams by open slots or last activity Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse teams within a topic Then I should see a list of teams for that topic When I choose a sort order Then I should see the paginated list of teams in that order """ teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 1) for i, team in enumerate(random.sample(teams, len(teams))): for _ in range(i): user_info = AutoAuthPage(self.browser, course_id=self.course_id).visit().user_info self.create_membership(user_info['username'], team['id']) team['open_slots'] = self.max_team_size - i # Re-authenticate as staff after creating users AutoAuthPage( self.browser, course_id=self.course_id, staff=True ).visit() self.browse_teams_page.visit() self.browse_teams_page.sort_teams_by(sort_order) team_names = self.browse_teams_page.team_names self.assertEqual(len(team_names), self.TEAMS_PAGE_SIZE) sorted_teams = [ team['name'] for team in sorted( sorted(teams, key=lambda t: t[secondary_sort_order], reverse=reverse), key=lambda t: t[sort_order], reverse=reverse ) ][:self.TEAMS_PAGE_SIZE] self.assertEqual(team_names, sorted_teams) def test_default_sort_order(self): """ Scenario: the list of teams should be sorted by last activity by default Given I am enrolled in a course with team configuration and topics When I visit the Teams page And I browse teams within a topic Then I should see a list of teams for that topic, sorted by last activity """ self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 1) self.browse_teams_page.visit() self.assertEqual(self.browse_teams_page.sort_order, 'last activity') def test_no_teams(self): """ Scenario: Visiting a topic with no teams should not display any teams. Given I am enrolled in a course with a team configuration and a topic When I visit the Teams page for that topic Then I should see the correct page header And I should see a pagination header showing no teams And I should see no teams And I should see a button to add a team And I should not see a pagination footer """ self.browse_teams_page.visit() self.verify_page_header() self.assertTrue(self.browse_teams_page.get_pagination_header_text().startswith('Showing 0 out of 0 total')) self.assertEqual(len(self.browse_teams_page.team_cards), 0, msg='Expected to see no team cards') self.assertFalse( self.browse_teams_page.pagination_controls_visible(), msg='Expected paging footer to be invisible' ) def test_teams_one_page(self): """ Scenario: Visiting a topic with fewer teams than the page size should all those teams on one page. Given I am enrolled in a course with a team configuration and a topic When I visit the Teams page for that topic Then I should see the correct page header And I should see a pagination header showing the number of teams And I should see all the expected team cards And I should see a button to add a team And I should not see a pagination footer """ teams = self.teams_with_default_sort_order( self.create_teams(self.topic, self.TEAMS_PAGE_SIZE, time_between_creation=1) ) self.browse_teams_page.visit() self.verify_page_header() self.assertTrue(self.browse_teams_page.get_pagination_header_text().startswith('Showing 1-10 out of 10 total')) self.verify_teams(self.browse_teams_page, teams) self.assertFalse( self.browse_teams_page.pagination_controls_visible(), msg='Expected paging footer to be invisible' ) def test_teams_navigation_buttons(self): """ Scenario: The user should be able to page through a topic's team list using navigation buttons when it is longer than the page size. Given I am enrolled in a course with a team configuration and a topic When I visit the Teams page for that topic Then I should see the correct page header And I should see that I am on the first page of results When I click on the next page button Then I should see that I am on the second page of results And when I click on the previous page button Then I should see that I am on the first page of results """ teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 1, time_between_creation=1) self.browse_teams_page.visit() self.verify_page_header() self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 11 total', True) self.browse_teams_page.press_next_page_button() self.verify_on_page(self.browse_teams_page, 2, teams, 'Showing 11-11 out of 11 total', True) self.browse_teams_page.press_previous_page_button() self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 11 total', True) def test_teams_page_input(self): """ Scenario: The user should be able to page through a topic's team list using the page input when it is longer than the page size. Given I am enrolled in a course with a team configuration and a topic When I visit the Teams page for that topic Then I should see the correct page header And I should see that I am on the first page of results When I input the second page Then I should see that I am on the second page of results When I input the first page Then I should see that I am on the first page of results """ teams = self.create_teams(self.topic, self.TEAMS_PAGE_SIZE + 10, time_between_creation=1) self.browse_teams_page.visit() self.verify_page_header() self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 20 total', True) self.browse_teams_page.go_to_page(2) self.verify_on_page(self.browse_teams_page, 2, teams, 'Showing 11-20 out of 20 total', True) self.browse_teams_page.go_to_page(1) self.verify_on_page(self.browse_teams_page, 1, teams, 'Showing 1-10 out of 20 total', True) def test_browse_team_topics(self): """ Scenario: User should be able to navigate to "browse all teams" and "search team description" links. Given I am enrolled in a course with teams enabled When I visit the Teams page for a topic Then I should see the correct page header And I should see the link to "browse teams in other topics" When I should navigate to that link Then I should see the topic browse page """ self.browse_teams_page.visit() self.verify_page_header() self.browse_teams_page.click_browse_all_teams_link() self.topics_page.wait_for_page() def test_search(self): """ Scenario: User should be able to search for a team Given I am enrolled in a course with teams enabled When I visit the Teams page for that topic And I search for 'banana' Then I should see the search result page And the search header should be shown And 0 results should be shown And my browser should fire a page viewed event for the search page And a searched event should have been fired """ # Note: all searches will return 0 results with the mock search server # used by Bok Choy. search_text = 'banana' self.create_teams(self.topic, 5) self.browse_teams_page.visit() events = [{ 'event_type': 'edx.team.page_viewed', 'event': { 'page_name': 'search-teams', 'topic_id': self.topic['id'], 'team_id': None } }, { 'event_type': 'edx.team.searched', 'event': { 'search_text': search_text, 'topic_id': self.topic['id'], 'number_of_results': 0 } }] with self.assert_events_match_during(self.only_team_events, expected_events=events, in_order=False): search_results_page = self.browse_teams_page.search(search_text) self.verify_search_header(search_results_page, search_text) self.assertTrue(search_results_page.get_pagination_header_text().startswith('Showing 0 out of 0 total')) def test_page_viewed_event(self): """ Scenario: Visiting the browse page should fire a page viewed event. Given I am enrolled in a course with a team configuration and a topic When I visit the Teams page Then my browser should post a page viewed event for the teams page """ self.create_teams(self.topic, 5) events = [{ 'event_type': 'edx.team.page_viewed', 'event': { 'page_name': 'single-topic', 'topic_id': self.topic['id'], 'team_id': None } }] with self.assert_events_match_during(self.only_team_events, expected_events=events): self.browse_teams_page.visit() def test_team_name_xss(self): """ Scenario: Team names should be HTML-escaped on the teams page Given I am enrolled in a course with teams enabled When I visit the Teams page for a topic, with a team name containing JS code Then I should not see any alerts """ self.post_team_data({ 'course_id': self.course_id, 'topic_id': self.topic['id'], 'name': '<script>alert("XSS")</script>', 'description': 'Description', 'language': 'aa', 'country': 'AF' }) with self.assertRaises(TimeoutException): self.browser.get(self.browse_teams_page.url) alert = get_modal_alert(self.browser) alert.accept() @attr(shard=5) class TeamFormActions(TeamsTabBase): """ 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.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.""" self.browse_teams_page.click_create_team_link() self.verify_page_header( title='Create a New Team', description='Create a new team if you can\'t find an existing team to join, ' 'or if you would like to learn with friends you know.', breadcrumbs='All Topics {topic_name}'.format(topic_name=self.topic['name']) ) def verify_and_navigate_to_edit_team_page(self): """Navigates to the edit team page and verifies.""" # pylint: disable=no-member self.assertEqual(self.team_page.team_name, self.team['name']) self.assertTrue(self.team_page.edit_team_button_present) self.team_page.click_edit_team_button() self.team_management_page.wait_for_page() # Edit page header. self.verify_page_header( title='Edit Team', description='If you make significant changes, make sure you notify ' 'members of the team before making these changes.', breadcrumbs='All Topics {topic_name} {team_name}'.format( topic_name=self.topic['name'], team_name=self.team['name'] ) ) def verify_team_info(self, name, description, location, language): """Verify the team information on team page.""" # pylint: disable=no-member self.assertEqual(self.team_page.team_name, name) self.assertEqual(self.team_page.team_description, description) self.assertEqual(self.team_page.team_location, location) self.assertEqual(self.team_page.team_language, language) def fill_create_or_edit_form(self): """Fill the create/edit team form fields with appropriate values.""" self.team_management_page.value_for_text_field( field_id='name', value=self.TEAMS_NAME, press_enter=False ) self.team_management_page.set_value_for_textarea_field( field_id='description', value=self.TEAM_DESCRIPTION ) 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.team_management_page.message_for_field('name'), 'A name that identifies your team (maximum 255 characters).' ) self.assertEqual( 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.team_management_page.message_for_field('country'), 'The country that team members primarily identify with.' ) self.assertEqual( self.team_management_page.message_for_field('language'), 'The language that team members primarily use to communicate with each other.' ) @ddt.ddt class CreateTeamTest(TeamFormActions): """ Tests for creating a new Team within a Topic on the Teams page. """ def setUp(self): super(CreateTeamTest, self).setUp() self.set_team_configuration({'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]}) self.browse_teams_page = BrowseTeamsPage(self.browser, self.course_id, self.topic) self.browse_teams_page.visit() def test_user_can_see_create_team_page(self): """ Scenario: The user should be able to see the create team page via teams list page. Given I am enrolled in a course with a team configuration and a topic When I visit the Teams page for that topic Then I should see the Create Team page link on bottom And When I click create team link Then I should see the create team page. And I should see the create team header And I should also see the help messages for fields. """ self.verify_and_navigate_to_create_team_page() self.verify_all_fields_exist() def test_user_can_see_error_message_for_missing_data(self): """ Scenario: The user should be able to see error message in case of missing required field. Given I am enrolled in a course with a team configuration and a topic When I visit the Create Team page for that topic Then I should see the Create Team header and form And When I click create team button without filling required fields Then I should see the error message and highlighted fields. """ self.verify_and_navigate_to_create_team_page() # `submit_form` clicks on a button, but that button doesn't always # have the click event handler registered on it in time. That's why # this test is flaky. Unfortunately, I don't know of a straightforward # way to write something that waits for that event handler to be bound # to the button element. So I used time.sleep as well, even though # the bok choy docs explicitly ask us not to: # http://bok-choy.readthedocs.io/en/latest/guidelines.html # Sorry! For the story to address this anti-pattern, see TNL-5820 time.sleep(0.5) self.team_management_page.submit_form() self.team_management_page.wait_for( lambda: self.team_management_page.validation_message_text, "Validation message text never loaded." ) self.assertEqual( self.team_management_page.validation_message_text, 'Check the highlighted fields below and try again.' ) 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): """ Scenario: The user should be able to see error message in case of increasing length for required fields. Given I am enrolled in a course with a team configuration and a topic When I visit the Create Team page for that topic Then I should see the Create Team header and form When I add text > than 255 characters for name field And I click Create button Then I should see the error message for exceeding length. """ self.verify_and_navigate_to_create_team_page() # Fill the name field with >255 characters to see validation message. 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 ' 'audience, some at no charge. It also conducts research into learning based on how ' 'people use its platform. EdX was created for students and institutions that seek to' 'transform themselves through cutting-edge technologies, innovative pedagogy, and ' 'rigorous courses. More than 70 schools, nonprofits, corporations, and international' 'organizations offer or plan to offer courses on the edX website. As of 22 October 2014,' 'edX has more than 4 million users taking more than 500 courses online.', press_enter=False ) self.team_management_page.submit_form() self.assertEqual( self.team_management_page.validation_message_text, 'Check the highlighted fields below and try again.' ) self.assertTrue(self.team_management_page.error_for_field(field_id='name')) def test_user_can_create_new_team_successfully(self): """ Scenario: The user should be able to create new team. Given I am enrolled in a course with a team configuration and a topic When I visit the Create Team page for that topic Then I should see the Create Team header and form When I fill all the fields present with appropriate data And I click Create button Then I expect analytics events to be emitted And I should see the page for my team And I should see the message that says "You are member of this team" And the new team should be added to the list of teams within the topic And the number of teams should be updated on the topic card And if I switch to "My Team", the newly created team is displayed """ AutoAuthPage(self.browser, course_id=self.course_id).visit() self.browse_teams_page.visit() self.verify_and_navigate_to_create_team_page() self.fill_create_or_edit_form() expected_events = [ { 'event_type': 'edx.team.created' }, { 'event_type': 'edx.team.learner_added', 'event': { 'add_method': 'added_on_create', } } ] with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events): self.team_management_page.submit_form() # Verify that the page is shown for the new team team_page = TeamPage(self.browser, self.course_id) team_page.wait_for_page() self.assertEqual(team_page.team_name, self.TEAMS_NAME) self.assertEqual(team_page.team_description, self.TEAM_DESCRIPTION) self.assertEqual(team_page.team_user_membership_text, 'You are a member of this team.') # Verify the new team was added to the topic list self.teams_page.click_specific_topic("Example Topic") self.teams_page.verify_topic_team_count(1) self.teams_page.click_all_topics() self.teams_page.verify_team_count_in_first_topic(1) # Verify that if one switches to "My Team" without reloading the page, the newly created team is shown. self.verify_my_team_count(1) def test_user_can_cancel_the_team_creation(self): """ Scenario: The user should be able to cancel the creation of new team. Given I am enrolled in a course with a team configuration and a topic When I visit the Create Team page for that topic Then I should see the Create Team header and form When I click Cancel button Then I should see teams list page without any new team. And if I switch to "My Team", it shows no teams """ 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() # We add a sleep here to allow time for the click event handler to bind # to the cancel button. Using time.sleep in bok-choy tests is, # generally, an anti-pattern. So don't copy this :). # For the story to address this anti-pattern, see TNL-5820 time.sleep(0.5) self.team_management_page.cancel_team() self.browse_teams_page.wait_for_page() self.assertTrue(self.browse_teams_page.get_pagination_header_text().startswith('Showing 0 out of 0 total')) self.teams_page.click_all_topics() self.teams_page.verify_team_count_in_first_topic(0) self.verify_my_team_count(0) def test_page_viewed_event(self): """ Scenario: Visiting the create team page should fire a page viewed event. Given I am enrolled in a course with a team configuration and a topic When I visit the create team page Then my browser should post a page viewed event """ events = [{ 'event_type': 'edx.team.page_viewed', 'event': { 'page_name': 'new-team', 'topic_id': self.topic['id'], 'team_id': None } }] with self.assert_events_match_during(self.only_team_events, expected_events=events): 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.team_management_page.wait_for_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) browse_teams_page.wait_for_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': { 'team_id': self.team['id'] } }, { 'event_type': 'edx.team.learner_removed', 'event': { '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) topics_page.wait_for_page() self.teams_page.verify_topic_team_count(0) @ddt.ddt class EditTeamTest(TeamFormActions): """ Tests for editing the team. """ def setUp(self): super(EditTeamTest, 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) self.team_page.visit() def test_staff_can_navigate_to_edit_team_page(self): """ Scenario: The user should be able to see and navigate to the edit 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 edit team page And I should see the edit team header And I should also see the help messages for fields """ self.verify_and_navigate_to_edit_team_page() self.verify_all_fields_exist() def test_staff_can_edit_team_successfully(self): """ Scenario: The staff should be able to edit team successfully. 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 team page And an analytics event should be fired When I edit all the fields with appropriate data And I click Update button Then I should see the page for my team with updated data """ self.verify_team_info( name=self.team['name'], description=self.team['description'], location='Afghanistan', language='Afar' ) self.verify_and_navigate_to_edit_team_page() self.fill_create_or_edit_form() expected_events = [ { 'event_type': 'edx.team.changed', 'event': { 'team_id': self.team['id'], 'field': 'country', 'old': 'AF', 'new': 'PK', 'truncated': [], } }, { 'event_type': 'edx.team.changed', 'event': { 'team_id': self.team['id'], 'field': 'name', 'old': self.team['name'], 'new': self.TEAMS_NAME, 'truncated': [], } }, { 'event_type': 'edx.team.changed', 'event': { 'team_id': self.team['id'], 'field': 'language', 'old': 'aa', 'new': 'en', 'truncated': [], } }, { 'event_type': 'edx.team.changed', 'event': { 'team_id': self.team['id'], 'field': 'description', 'old': self.team['description'], 'new': self.TEAM_DESCRIPTION, 'truncated': [], } }, ] with self.assert_events_match_during( event_filter=self.only_team_events, expected_events=expected_events, ): self.team_management_page.submit_form() self.team_page.wait_for_page() self.verify_team_info( name=self.TEAMS_NAME, description=self.TEAM_DESCRIPTION, location='Pakistan', language='English' ) def test_staff_can_cancel_the_team_edit(self): """ Scenario: The user should be able to cancel the editing of team. 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 team page Then I should see the Edit Team header When I click Cancel button Then I should see team page page without changes. """ self.verify_team_info( name=self.team['name'], description=self.team['description'], location='Afghanistan', language='Afar' ) self.verify_and_navigate_to_edit_team_page() self.fill_create_or_edit_form() self.team_management_page.cancel_team() self.team_page.wait_for_page() self.verify_team_info( name=self.team['name'], description=self.team['description'], location='Afghanistan', language='Afar' ) def test_student_cannot_see_edit_button(self): """ Scenario: The student should not see the edit team button. Given I am student for a course with a team When I visit the Team profile page Then I should not see the Edit Team button """ AutoAuthPage(self.browser, course_id=self.course_id).visit() self.team_page.visit() self.assertFalse(self.team_page.edit_team_button_present) @ddt.data('Moderator', 'Community TA', 'Administrator') def test_discussion_privileged_user_can_edit_team(self, role): """ Scenario: The user with specified role should see the edit team button. Given I am user with privileged role for a course with a team When I visit the Team profile page Then I should see the Edit Team button """ kwargs = { 'course_id': self.course_id, 'staff': False } if role is not None: kwargs['roles'] = role AutoAuthPage(self.browser, **kwargs).visit() self.team_page.visit() self.teams_page.wait_for_page() self.assertTrue(self.team_page.edit_team_button_present) self.verify_team_info( name=self.team['name'], description=self.team['description'], location='Afghanistan', language='Afar' ) self.verify_and_navigate_to_edit_team_page() self.fill_create_or_edit_form() self.team_management_page.submit_form() self.team_page.wait_for_page() self.verify_team_info( name=self.TEAMS_NAME, description=self.TEAM_DESCRIPTION, location='Pakistan', language='English' ) def test_page_viewed_event(self): """ Scenario: Visiting the edit team page should fire a page viewed event. Given I am enrolled in a course with a team configuration and a topic When I visit the edit team page Then my browser should post a page viewed event """ events = [{ 'event_type': 'edx.team.page_viewed', 'event': { 'page_name': 'edit-team', 'topic_id': self.topic['id'], 'team_id': self.team['id'] } }] with self.assert_events_match_during(self.only_team_events, expected_events=events): 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': { '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.edit_membership_page.wait_for_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): """Tests for viewing a specific team""" SEND_INVITE_TEXT = 'Send this link to friends so that they can join too.' def setUp(self): super(TeamPageTest, self).setUp() self.topic = {u"name": u"Example Topic", u"id": "example_topic", u"description": "Description"} def _set_team_configuration_and_membership( self, max_team_size=10, membership_team_index=0, visit_team_index=0, create_membership=True, another_user=False): """ Set team configuration. Arguments: max_team_size (int): number of users a team can have membership_team_index (int): index of team user will join visit_team_index (int): index of team user will visit create_membership (bool): whether to create membership or not another_user (bool): another user to visit a team """ #pylint: disable=attribute-defined-outside-init self.set_team_configuration( {'course_id': self.course_id, 'max_team_size': max_team_size, 'topics': [self.topic]} ) self.teams = self.create_teams(self.topic, 2) if create_membership: self.create_membership(self.user_info['username'], self.teams[membership_team_index]['id']) if another_user: AutoAuthPage(self.browser, course_id=self.course_id).visit() self.team_page = TeamPage(self.browser, self.course_id, self.teams[visit_team_index]) def setup_thread(self): """ Create and return a thread for this test's discussion topic. """ thread = Thread( id="test_thread_{}".format(uuid4().hex), commentable_id=self.teams[0]['discussion_topic_id'], body="Dummy text body.", context="standalone", ) thread_fixture = MultipleThreadFixture([thread]) thread_fixture.push() return thread def setup_discussion_user(self, role=None, staff=False): """Set this test's user to have the given role in its discussions. Role is one of 'Community TA', 'Moderator', 'Administrator', or 'Student'. """ kwargs = { 'course_id': self.course_id, 'staff': staff } if role is not None: kwargs['roles'] = role #pylint: disable=attribute-defined-outside-init self.user_info = AutoAuthPage(self.browser, **kwargs).visit().user_info def verify_teams_discussion_permissions(self, should_have_permission): """Verify that the teams discussion component is in the correct state for the test user. If `should_have_permission` is True, assert that the user can see controls for posting replies, voting, editing, and deleting. Otherwise, assert that those controls are hidden. """ thread = self.setup_thread() self.team_page.visit() self.assertEqual(self.team_page.discussion_id, self.teams[0]['discussion_topic_id']) discussion_page = self.team_page.discussion_page discussion_page.wait_for_page() self.assertTrue(discussion_page.is_discussion_expanded()) self.assertEqual(discussion_page.get_num_displayed_threads(), 1) discussion_page.show_thread(thread['id']) thread_page = discussion_page.thread_page assertion = self.assertTrue if should_have_permission else self.assertFalse assertion(thread_page.q(css='.post-header-actions').present) assertion(thread_page.q(css='.add-response').present) def test_discussion_on_my_team_page(self): """ Scenario: Team Page renders a discussion for a team to which I belong. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic of which I am a member When the team has a discussion with a thread And I visit the Team page for that team Then I should see a discussion with the correct discussion_id And I should see the existing thread And I should see controls to change the state of the discussion """ self._set_team_configuration_and_membership() self.verify_teams_discussion_permissions(True) @ddt.data(True, False) def test_discussion_on_other_team_page(self, is_staff): """ Scenario: Team Page renders a team discussion for a team to which I do not belong. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic of which I am not a member When the team has a discussion with a thread And I visit the Team page for that team Then I should see a discussion with the correct discussion_id And I should see the team's thread And I should not see controls to change the state of the discussion """ self._set_team_configuration_and_membership(create_membership=False) self.setup_discussion_user(staff=is_staff) self.verify_teams_discussion_permissions(False) @ddt.data('Moderator', 'Community TA', 'Administrator') def test_discussion_privileged(self, role): self._set_team_configuration_and_membership(create_membership=False) self.setup_discussion_user(role=role) self.verify_teams_discussion_permissions(True) def assert_team_details(self, num_members, is_member=True, max_size=10): """ Verifies that user can see all the information, present on detail page according to their membership status. Arguments: num_members (int): number of users in a team is_member (bool) default True: True if request user is member else False max_size (int): number of users a team can have """ self.assertEqual( self.team_page.team_capacity_text, self.team_page.format_capacity_text(num_members, max_size) ) self.assertEqual(self.team_page.team_location, 'Afghanistan') self.assertEqual(self.team_page.team_language, 'Afar') self.assertEqual(self.team_page.team_members, num_members) if num_members > 0: self.assertTrue(self.team_page.team_members_present) else: self.assertFalse(self.team_page.team_members_present) if is_member: self.assertEqual(self.team_page.team_user_membership_text, 'You are a member of this team.') self.assertTrue(self.team_page.team_leave_link_present) self.assertTrue(self.team_page.new_post_button_present) else: self.assertEqual(self.team_page.team_user_membership_text, '') self.assertFalse(self.team_page.team_leave_link_present) self.assertFalse(self.team_page.new_post_button_present) def test_team_member_can_see_full_team_details(self): """ Scenario: Team member can see full info for team. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic of which I am a member When I visit the Team page for that team Then I should see the full team detail And I should see the team members And I should see my team membership text And I should see the language & country And I should see the Leave Team and Invite Team """ self._set_team_configuration_and_membership() self.team_page.visit() self.assert_team_details( num_members=1, ) def test_other_users_can_see_limited_team_details(self): """ Scenario: Users who are not member of this team can only see limited info for this team. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic of which I am not a member When I visit the Team page for that team Then I should not see full team detail And I should see the team members And I should not see my team membership text And I should not see the Leave Team and Invite Team links """ self._set_team_configuration_and_membership(create_membership=False) self.team_page.visit() self.assert_team_details(is_member=False, num_members=0) def test_user_can_navigate_to_members_profile_page(self): """ Scenario: User can navigate to profile page via team member profile image. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic of which I am a member When I visit the Team page for that team Then I should see profile images for the team members When I click on the first profile image Then I should be taken to the user's profile page And I should see the username on profile page """ self._set_team_configuration_and_membership() self.team_page.visit() learner_name = self.team_page.first_member_username self.team_page.click_first_profile_image() learner_profile_page = LearnerProfilePage(self.browser, learner_name) learner_profile_page.wait_for_page() learner_profile_page.wait_for_field('username') self.assertTrue(learner_profile_page.field_is_visible('username')) def test_join_team(self): """ Scenario: User can join a Team if not a member already.. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic And I visit the Team page for that team Then I should see Join Team button And I should not see New Post button When I click on Join Team button Then there should be no Join Team button and no message And an analytics event should be emitted And I should see the updated information under Team Details And I should see New Post button And if I switch to "My Team", the team I have joined is displayed """ self._set_team_configuration_and_membership(create_membership=False) teams_page = BrowseTeamsPage(self.browser, self.course_id, self.topic) teams_page.visit() teams_page.view_first_team() self.assertTrue(self.team_page.join_team_button_present) expected_events = [ { 'event_type': 'edx.team.learner_added', 'event': { 'add_method': 'joined_from_team_view' } } ] with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events): self.team_page.click_join_team_button() self.assertFalse(self.team_page.join_team_button_present) self.assertFalse(self.team_page.join_team_message_present) self.assert_team_details(num_members=1, is_member=True) # Verify that if one switches to "My Team" without reloading the page, the newly joined team is shown. self.teams_page.click_all_topics() self.verify_my_team_count(1) def test_already_member_message(self): """ Scenario: User should see `You are already in a team` if user is a member of other team. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic And I am already a member of a team And I visit a team other than mine Then I should see `You are already in a team` message """ self._set_team_configuration_and_membership(membership_team_index=0, visit_team_index=1) self.team_page.visit() self.assertEqual(self.team_page.join_team_message, 'You already belong to another team.') self.assert_team_details(num_members=0, is_member=False) def test_team_full_message(self): """ Scenario: User should see `Team is full` message when team is full. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic And team has no space left And I am not a member of any team And I visit the team Then I should see `Team is full` message """ self._set_team_configuration_and_membership( create_membership=True, max_team_size=1, membership_team_index=0, visit_team_index=0, another_user=True ) self.team_page.visit() self.assertEqual(self.team_page.join_team_message, 'This team is full.') self.assert_team_details(num_members=1, is_member=False, max_size=1) def test_leave_team(self): """ Scenario: User can leave a team. Given I am enrolled in a course with a team configuration, a topic, and a team belonging to that topic And I am a member of team And I visit the team And I should not see Join Team button And I should see New Post button Then I should see Leave Team link When I click on Leave Team link Then user should be removed from team And an analytics event should be emitted And I should see Join Team button And I should not see New Post button And if I switch to "My Team", the team I have left is not displayed """ self._set_team_configuration_and_membership() self.team_page.visit() self.assertFalse(self.team_page.join_team_button_present) self.assert_team_details(num_members=1) expected_events = [ { 'event_type': 'edx.team.learner_removed', 'event': { 'remove_method': 'self_removal' } } ] with self.assert_events_match_during(event_filter=self.only_team_events, expected_events=expected_events): # I think we're seeing the same problem that we're seeing in # CreateTeamTest.test_user_can_see_error_message_for_missing_data. # We click on the "leave team" link after it's loaded, but before # its JavaScript event handler is added. Adding this sleep gives # enough time for that event handler to bind to the link. Sorry! # For the story to address this anti-pattern, see TNL-5820 time.sleep(0.5) self.team_page.click_leave_team_link() self.assert_team_details(num_members=0, is_member=False) self.assertTrue(self.team_page.join_team_button_present) # Verify that if one switches to "My Team" without reloading the page, the old team no longer shows. self.teams_page.click_all_topics() self.verify_my_team_count(0) def test_page_viewed_event(self): """ Scenario: Visiting the team profile page should fire a page viewed event. Given I am enrolled in a course with a team configuration and a topic When I visit the team profile page Then my browser should post a page viewed event """ self._set_team_configuration_and_membership() events = [{ 'event_type': 'edx.team.page_viewed', 'event': { 'page_name': 'single-team', 'topic_id': self.topic['id'], 'team_id': self.teams[0]['id'] } }] with self.assert_events_match_during(self.only_team_events, expected_events=events): self.team_page.visit()