# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time from selenium.common import exceptions from selenium.webdriver.common import by from openstack_dashboard.test.integration_tests.regions import baseregion class NavigationAccordionRegion(baseregion.BaseRegion): """Navigation menu located in the left.""" _project_security_groups_locator = ( by.By.CSS_SELECTOR, 'a[href*="/project/security_groups/"]') _project_key_pairs_locator = ( by.By.CSS_SELECTOR, 'a[href*="/project/key_pairs/"]') _settings_change_password_locator = ( by.By.CSS_SELECTOR, 'a[href*="/settings/password//"]') _project_bar_locator = (by.By.XPATH, ".//*[@id='main_content']//div[contains(text()," "'Project')]") @property def project_bar(self): return self._get_element(*self._project_bar_locator) _first_level_item_selected_locator = ( by.By.CSS_SELECTOR, '.panel.openstack-dashboard > a:not(.collapsed)') _second_level_item_selected_locator = ( by.By.CSS_SELECTOR, '.panel.openstack-panel-group > a:not(.collapsed)') _first_level_item_xpath_template = ( "//li[contains(concat('', @class, ''), 'panel openstack-dashboard') " "and contains(., '%s')]/a") _second_level_item_xpath_template = ( "//ul[contains(@class, 'in')]//li[contains(@class, " "'panel openstack-panel-group') and contains(., '%s')]/a") _third_level_item_xpath_template = ( "//ul[contains(@class, 'in')]//a[contains(concat('', @class, '')," "'list-group-item openstack-panel') and contains(., '%s')]") _parent_item_locator = (by.By.XPATH, '..') _menu_list_locator = (by.By.CSS_SELECTOR, 'a') _expanded_menu_class = "" _transitioning_menu_class = 'collapsing' def _get_first_level_item_locator(self, text): return (by.By.XPATH, self._first_level_item_xpath_template % text) def _get_second_level_item_locator(self, text): return (by.By.XPATH, self._second_level_item_xpath_template % text) def _get_third_level_item_locator(self, text): return (by.By.XPATH, self._third_level_item_xpath_template % text) def get_first_level_selected_item(self): if self._is_element_present(*self._first_level_item_selected_locator): return self._get_element(*self._first_level_item_selected_locator) else: return None def get_second_level_selected_item(self): if self._is_element_present(*self._second_level_item_selected_locator): return self._get_element(*self._second_level_item_selected_locator) else: return None @property def security_groups(self): return self._get_element(*self._project_security_groups_locator) @property def key_pairs(self): return self._get_element(*self._project_key_pairs_locator) @property def change_password(self): return self._get_element(*self._settings_change_password_locator) def _wait_until_transition_ends(self, item, to_be_expanded=False): def predicate(d): classes = item.get_attribute('class') if to_be_expanded: status = self._expanded_menu_class == classes else: status = self._expanded_menu_class is not classes return status and self._transitioning_menu_class not in classes self._wait_until(predicate) def _click_menu_item(self, text, loc_craft_func, get_selected_func=None, src_elem=None): """Click on menu item if not selected. Menu animation that visualize transition from one selection to another take some time - if clicked on item during this animation nothing happens, therefore it is necessary to wait for the transition to complete first. Third-level menus are handled differently from others. First, they do not need to collapse other 3rd-level menu item prior to clicking them (because 3rd-level menus are atomic). Second, clicking them doesn't initiate an animated transition, hence no need to wait until it finishes. Also an ambiguity is possible when matching third-level menu items, because different dashboards have panels with the same name (for example Volumes/Images/Instances both at Project and Admin dashboards). To avoid this ambiguity, an argument `src_elem` is used. Whenever dashboard or a panel group is clicked, its wrapper is returned to be used as `src_elem` in a subsequent call for clicking third-level item. This way the set of panel labels being matched is restricted to the descendants of that particular dashboard or panel group. """ is_already_within_required_item = False selected_item = None if get_selected_func is not None: selected_item = get_selected_func() if selected_item: if text != selected_item.text: # In case different item was chosen previously, collapse # it. Otherwise selenium will complain with # MoveTargetOutOfBoundsException selected_item.click() time.sleep(1) self._wait_until_transition_ends( self._get_menu_list_next_to_menu_title(selected_item)) else: is_already_within_required_item = True if not is_already_within_required_item: item = self._get_item(text, loc_craft_func, src_elem) item.click() if get_selected_func is not None: self._wait_until_transition_ends( self._get_menu_list_next_to_menu_title(item), to_be_expanded=True) return item return selected_item def _get_item(self, text, loc_craft_func, src_elem=None): item_locator = loc_craft_func(text) src_elem = src_elem or self.src_elem return src_elem.find_element(*item_locator) def _get_menu_list_next_to_menu_title(self, title_item): parent_item = title_item.find_element(*self._parent_item_locator) return parent_item.find_element(*self._menu_list_locator) def click_on_menu_items(self, first_level=None, second_level=None, third_level=None): src_elem = None if first_level: src_elem = self._click_menu_item( first_level, self._get_first_level_item_locator, self.get_first_level_selected_item) if second_level: src_elem = self._click_menu_item( second_level, self._get_second_level_item_locator, self.get_second_level_selected_item) if third_level: # NOTE(tsufiev): possible dashboard/panel group label passed as # `src_elem` is a sibling of