From f623281b2979294eec7ad010e61e2ad9de2ea598 Mon Sep 17 00:00:00 2001 From: Vlad Okhrimenko Date: Thu, 31 Jul 2014 14:40:32 +0300 Subject: [PATCH] Provide integration test for managing a project's members A new MembershipMenuRegion was implemented specifically for the task of dealing with Users being assigned to a Projects. It may be re-used in other tests as well, which deal with the same membership concept (e.g., Flavor Access control). The checker method .is_the_current_page() is changed to act either as a boolean method, or as an internal assert method. Co-Authored-By: Timur Sufiev Implements blueprint: horizon-integration-tests-coverage Change-Id: Ie661a1522951e1e86c461c6ec284fcf4a3e6d6fb --- .../test/integration_tests/config.py | 7 ++ .../test/integration_tests/horizon.conf | 6 + .../pages/identity/projectspage.py | 34 +++++- .../integration_tests/pages/pageobject.py | 15 ++- .../test/integration_tests/regions/menus.py | 113 ++++++++++++++++++ .../integration_tests/tests/test_projects.py | 35 ++++++ 6 files changed, 200 insertions(+), 10 deletions(-) diff --git a/openstack_dashboard/test/integration_tests/config.py b/openstack_dashboard/test/integration_tests/config.py index c4acee32bb..73d04ea083 100644 --- a/openstack_dashboard/test/integration_tests/config.py +++ b/openstack_dashboard/test/integration_tests/config.py @@ -47,6 +47,13 @@ IdentityGroup = [ cfg.StrOpt('admin_home_project', default='admin', help="Project to keep all objects belonging to an admin user."), + cfg.StrOpt('default_keystone_role', + default='Member', + help="Name of default role every user gets in his new project"), + cfg.StrOpt('default_keystone_admin_role', + default='admin', + help="Name of the role that grants admin rights to a user in " + "his project"), ] ImageGroup = [ diff --git a/openstack_dashboard/test/integration_tests/horizon.conf b/openstack_dashboard/test/integration_tests/horizon.conf index 27073816d8..808958a34f 100644 --- a/openstack_dashboard/test/integration_tests/horizon.conf +++ b/openstack_dashboard/test/integration_tests/horizon.conf @@ -60,6 +60,12 @@ admin_password=secretadmin # Project in which an admin user creates everything by default admin_home_project=admin +# Name of default role every user gets in his new project +default_keystone_role=Member + +# Name of the role that grants admin rights to a user in his project +default_keystone_admin_role=admin + [network] # The cidr block to allocate tenant ipv4 subnets from (string value) tenant_network_cidr=10.100.0.0/16 diff --git a/openstack_dashboard/test/integration_tests/pages/identity/projectspage.py b/openstack_dashboard/test/integration_tests/pages/identity/projectspage.py index b2821020f8..e377dc56cf 100644 --- a/openstack_dashboard/test/integration_tests/pages/identity/projectspage.py +++ b/openstack_dashboard/test/integration_tests/pages/identity/projectspage.py @@ -12,19 +12,31 @@ from openstack_dashboard.test.integration_tests.pages import basepage from openstack_dashboard.test.integration_tests.regions import forms +from openstack_dashboard.test.integration_tests.regions import menus from openstack_dashboard.test.integration_tests.regions import tables +class ProjectForm(forms.TabbedFormRegion): + FIELDS = (("name", "description", "enabled"), + {'members': menus.MembershipMenuRegion}) + + def __init__(self, driver, conf, tab=0): + super(ProjectForm, self).__init__( + driver, conf, field_mappings=self.FIELDS, default_tab=tab) + + class ProjectsTable(tables.TableRegion): name = 'tenants' - CREATE_PROJECT_FORM_FIELDS = (("name", "description", "enabled"),) @tables.bind_table_action('create') def create_project(self, create_button): create_button.click() - return forms.TabbedFormRegion( - self.driver, self.conf, - field_mappings=self.CREATE_PROJECT_FORM_FIELDS) + return ProjectForm(self.driver, self.conf) + + @tables.bind_row_action('update') + def update_members(self, members_button, row): + members_button.click() + return ProjectForm(self.driver, self.conf, tab=1) @tables.bind_table_action('delete') def delete_project(self, delete_button): @@ -72,3 +84,17 @@ class ProjectsPage(basepage.BaseNavigationPage): def get_project_id_from_row(self, name): row = self._get_row_with_project_name(name) return row.cells[self.PROJECT_ID_TABLE_NAME_COLUMN].text + + def allocate_user_to_project(self, user_name, roles, project_name): + row = self._get_row_with_project_name(project_name) + members_form = self.projects_table.update_members(row) + members_form.members.allocate_member(user_name) + members_form.members.allocate_member_roles(user_name, roles) + members_form.submit() + + def get_user_roles_at_project(self, user_name, project_name): + row = self._get_row_with_project_name(project_name) + members_form = self.projects_table.update_members(row) + roles = members_form.members.get_member_allocated_roles(user_name) + members_form.cancel() + return set(roles) diff --git a/openstack_dashboard/test/integration_tests/pages/pageobject.py b/openstack_dashboard/test/integration_tests/pages/pageobject.py index cf2e44d988..89c3b0f7d9 100644 --- a/openstack_dashboard/test/integration_tests/pages/pageobject.py +++ b/openstack_dashboard/test/integration_tests/pages/pageobject.py @@ -29,11 +29,14 @@ class PageObject(basewebobject.BaseWebObject): def page_title(self): return self.driver.title - def is_the_current_page(self): - self.assertIn(self._page_title, self.page_title, - "Expected to find %s in page title, instead found: %s" - % (self._page_title, self.page_title)) - return True + def is_the_current_page(self, do_assert=False): + found_expected_title = self.page_title.startswith(self._page_title) + if do_assert: + self.assertTrue( + found_expected_title, + "Expected to find %s in page title, instead found: %s" + % (self._page_title, self.page_title)) + return found_expected_title @property def login_url(self): @@ -87,4 +90,4 @@ class PageObject(basewebobject.BaseWebObject): def go_to_login_page(self): self.driver.get(self.login_url) - self.is_the_current_page() + self.is_the_current_page(do_assert=True) diff --git a/openstack_dashboard/test/integration_tests/regions/menus.py b/openstack_dashboard/test/integration_tests/regions/menus.py index a78085937a..2dd45738da 100644 --- a/openstack_dashboard/test/integration_tests/regions/menus.py +++ b/openstack_dashboard/test/integration_tests/regions/menus.py @@ -259,3 +259,116 @@ class ProjectDropDownRegion(DropDownMenuRegion): else: raise exceptions.NoSuchElementException( "Not found element with text: %s" % name) + + +class MembershipMenuRegion(baseregion.BaseRegion): + _available_members_locator = ( + by.By.CSS_SELECTOR, 'ul.available_members > ul.btn-group') + + _allocated_members_locator = ( + by.By.CSS_SELECTOR, 'ul.members > ul.btn-group') + + _add_remove_member_sublocator = ( + by.By.CSS_SELECTOR, 'li > a[href="#add_remove"]') + + _member_name_sublocator = ( + by.By.CSS_SELECTOR, 'li.member > span.display_name') + + _member_roles_widget_sublocator = (by.By.CSS_SELECTOR, 'li.role_options') + + _member_roles_widget_open_subsublocator = (by.By.CSS_SELECTOR, 'a.btn') + + _member_roles_widget_roles_subsublocator = ( + by.By.CSS_SELECTOR, 'ul.role_dropdown > li') + + def _get_member_name(self, element): + return element.find_element(*self._member_name_sublocator).text + + @property + def available_members(self): + return {self._get_member_name(el): el for el in + self._get_elements(*self._available_members_locator)} + + @property + def allocated_members(self): + return {self._get_member_name(el): el for el in + self._get_elements(*self._allocated_members_locator)} + + def allocate_member(self, name, available_members=None): + # NOTE(tsufiev): available_members here (and allocated_members below) + # are meant to be used for performance optimization to reduce the + # amount of calls to selenium by reusing still valid element reference + if available_members is None: + available_members = self.available_members + + available_members[name].find_element( + *self._add_remove_member_sublocator).click() + + def deallocate_member(self, name, allocated_members=None): + if allocated_members is None: + allocated_members = self.allocated_members + + allocated_members[name].find_element( + *self._add_remove_member_sublocator).click() + + def _get_member_roles_widget(self, name, allocated_members=None): + if allocated_members is None: + allocated_members = self.allocated_members + + return allocated_members[name].find_element( + *self._member_roles_widget_sublocator) + + def _get_member_all_roles(self, name, allocated_members=None): + roles_widget = self._get_member_roles_widget(name, allocated_members) + return roles_widget.find_elements( + *self._member_roles_widget_roles_subsublocator) + + @staticmethod + def _is_role_selected(role): + return 'selected' in role.get_attribute('class').split() + + @staticmethod + def _get_hidden_text(role): + return role.get_attribute('textContent') + + def get_member_available_roles(self, name, allocated_members=None, + strip=True): + roles = self._get_member_all_roles(name, allocated_members) + return [(self._get_hidden_text(role).strip() if strip else role) + for role in roles if not self._is_role_selected(role)] + + def get_member_allocated_roles(self, name, allocated_members=None, + strip=True): + roles = self._get_member_all_roles(name, allocated_members) + return [(self._get_hidden_text(role).strip() if strip else role) + for role in roles if self._is_role_selected(role)] + + def open_member_roles_dropdown(self, name, allocated_members=None): + widget = self._get_member_roles_widget(name, allocated_members) + button = widget.find_element( + *self._member_roles_widget_open_subsublocator) + button.click() + + def _switch_member_roles(self, name, roles2toggle, method, + allocated_members=None): + self.open_member_roles_dropdown(name, allocated_members) + roles = method(name, allocated_members, False) + roles2toggle = set(roles2toggle) + for role in roles: + role_name = role.text.strip() + if role_name in roles2toggle: + role.click() + roles2toggle.remove(role_name) + if not roles2toggle: + break + + def allocate_member_roles(self, name, roles2add, allocated_members=None): + self._switch_member_roles( + name, roles2add, self.get_member_available_roles, + allocated_members=allocated_members) + + def deallocate_member_roles(self, name, roles2remove, + allocated_members=None): + self._switch_member_roles( + name, roles2remove, self.get_member_allocated_roles, + allocated_members=allocated_members) diff --git a/openstack_dashboard/test/integration_tests/tests/test_projects.py b/openstack_dashboard/test/integration_tests/tests/test_projects.py index 5ccec67949..bb8f41f7dc 100644 --- a/openstack_dashboard/test/integration_tests/tests/test_projects.py +++ b/openstack_dashboard/test/integration_tests/tests/test_projects.py @@ -9,6 +9,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + from openstack_dashboard.test.integration_tests import helpers from openstack_dashboard.test.integration_tests.regions import messages @@ -36,3 +37,37 @@ class TestCreateDeleteProject(helpers.AdminTestCase): self.assertFalse( self.projects_page.find_message_and_dismiss(messages.ERROR)) self.assertFalse(self.projects_page.is_project_present(PROJECT_NAME)) + + +class TestModifyProject(helpers.AdminTestCase): + + def setUp(self): + super(TestModifyProject, self).setUp() + self.projects_page = self.home_pg.go_to_identity_projectspage() + self.projects_page.create_project(PROJECT_NAME) + self.assertTrue( + self.projects_page.find_message_and_dismiss(messages.SUCCESS)) + + def test_add_member(self): + admin_name = self.CONFIG.identity.admin_username + regular_role_name = self.CONFIG.identity.default_keystone_role + admin_role_name = self.CONFIG.identity.default_keystone_admin_role + roles2add = {regular_role_name, admin_role_name} + + self.projects_page.allocate_user_to_project( + admin_name, roles2add, PROJECT_NAME) + self.assertTrue( + self.projects_page.find_message_and_dismiss(messages.SUCCESS)) + self.assertFalse( + self.projects_page.find_message_and_dismiss(messages.ERROR)) + + user_roles = self.projects_page.get_user_roles_at_project( + admin_name, PROJECT_NAME) + self.assertEqual(roles2add, user_roles, + "The requested roles haven't been set for the user!") + + def tearDown(self): + if not self.projects_page.is_the_current_page(): + self.home_pg.go_to_identity_projectspage() + self.projects_page.delete_project(PROJECT_NAME) + super(TestModifyProject, self).tearDown()