Skip to content

Commit

Permalink
Merge pull request #1290 from arenadata/develop
Browse files Browse the repository at this point in the history
Release 2021.11.22
  • Loading branch information
acmnu authored Nov 22, 2021
2 parents 596063a + 1265732 commit 178c9bf
Show file tree
Hide file tree
Showing 12 changed files with 2,776 additions and 53 deletions.
34 changes: 32 additions & 2 deletions tests/ui_tests/app/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@
"""Page objects for ConfigurationPage"""

import json
from typing import Union

import allure
from adcm_client.objects import Service
from adcm_client.objects import (
Service,
Component,
Cluster,
Host,
Provider,
)
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.remote.webelement import WebElement
Expand Down Expand Up @@ -55,16 +62,38 @@ def assert_field_is_editable(self, field_element, editable=True) -> None:
self.is_element_editable(field_element) == editable
), f"Field {field_element} should {'be editable' if editable else 'not be editable'}"

@staticmethod
def get_config_field_value(item: Union[Cluster, Service, Component, Host, Provider], field: str):
"""Gets field value by api"""
current_config = item.config()
try:
if 'group' in current_config:
if field in current_config['group']:
return current_config['group'][field]
return current_config['group']['structure_property'][field]

if field in current_config:
return current_config[field]
return current_config['structure_property'][field]
except KeyError as error:
raise AssertionError(f"No parameter {field} found by api") from error

@allure.step('Assert field: {field} to have value: {expected_value}')
def assert_field_content_equal(self, field_type, field, expected_value):
def assert_field_content_equal(
self, item: Union[Cluster, Service, Component, Host, Provider], field_type, field, expected_value
):
"""Assert field value based on field type and name"""

current_value = self.get_field_value_by_type(field, field_type)
current_api_value = self.get_config_field_value(item, field.text.split(":")[0])
if field_type in ('password', 'secrettext'):
# In case of password we have no raw password in API after writing.
if expected_value is not None and expected_value != "":
assert current_value is not None, "Password field expected to be filled"
assert current_api_value is not None, "Password field expected to be filled"
else:
assert current_value is None or current_value == "", "Password have to be empty"
assert current_api_value is None or current_api_value == "", "Password have to be empty"
else:
if field_type == 'file':
expected_value = 'test'
Expand All @@ -74,6 +103,7 @@ def assert_field_content_equal(self, field_type, field, expected_value):
assert set(map_config.values()) == set(map_config.values())
else:
assert current_value == expected_value, f"Field value with type {field_type} doesn't equal expected"
assert current_api_value == expected_value, f"Field value with type {field_type} doesn't equal expected"

@allure.step('Assert frontend errors presence and text')
def assert_alerts_presented(self, field_type):
Expand Down
2 changes: 2 additions & 0 deletions tests/ui_tests/app/locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from tests.ui_tests.app.helpers import bys


# pylint: disable=too-few-public-methods


Expand Down Expand Up @@ -86,6 +87,7 @@ class Common:

# Common elements
all_childs = bys.by_css("*")
common_popup = bys.by_css("simple-snack-bar")


class Cluster:
Expand Down
38 changes: 29 additions & 9 deletions tests/ui_tests/app/page/common/base_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ def close_info_popup(self):
self.find_and_click(CommonPopupLocators.hide_btn)
self.wait_element_hide(CommonPopupLocators.block)

def is_popup_presented_on_page(self) -> bool:
"""Check if popup is presented on page"""
return self.is_element_displayed(CommonPopupLocators.block, timeout=5)

def get_info_popup_text(self):
"""Get text from info popup"""
self.wait_element_visible(CommonPopupLocators.block)
Expand Down Expand Up @@ -153,10 +157,13 @@ def find_children(self, element: WebElement, child: Locator, timeout: int = None

loc_timeout = timeout or self.default_loc_timeout
with allure.step(f'Find element "{child.name}" on page'):
return WDW(element, loc_timeout).until(
EC.presence_of_all_elements_located([child.by, child.value]),
message=f"Can't find {child.name} on page " f"{self.driver.current_url} for {loc_timeout} seconds",
)
try:
return WDW(element, loc_timeout).until(
EC.presence_of_all_elements_located([child.by, child.value]),
message=f"Can't find {child.name} on page " f"{self.driver.current_url} for {loc_timeout} seconds",
)
except TimeoutException:
return []

def find_elements(self, locator: Locator, timeout: int = None) -> [WebElement]:
"""Find elements on current page."""
Expand All @@ -179,15 +186,17 @@ def _find_element():
)

element_name = element.name if isinstance(element, Locator) else element.text
return self._is_displayed(element_name, _find_element)
with allure.step(f'Check if {element_name} is displayed'):
return self._is_displayed(_find_element)

def is_child_displayed(self, parent: WebElement, child: Locator, timeout: Optional[int] = None) -> bool:
"""Checks if child element is displayed"""

def _find_child():
return self.find_child(parent, child, timeout=timeout or self.default_loc_timeout)

return self._is_displayed(child.name, _find_child)
with allure.step(f'Check if {child.name} is displayed'):
return self._is_displayed(_find_child)

def assert_displayed_elements(self, locators: List[Locator]) -> None:
"""Asserts that list of elements is displayed."""
Expand Down Expand Up @@ -353,11 +362,10 @@ def hover_element(self, element: Union[Locator, WebElement]):
hover.perform()

@staticmethod
def _is_displayed(element_name: str, find_element_func: Callable[[], WebElement]) -> bool:
def _is_displayed(find_element_func: Callable[[], WebElement]) -> bool:
"""Calls `is_displayed` method on element returned by passed function"""
try:
with allure.step(f'Check {element_name}'):
return find_element_func().is_displayed()
return find_element_func().is_displayed()
except (
TimeoutException,
NoSuchElementException,
Expand All @@ -371,6 +379,18 @@ def click_back_button_in_browser(self):
"""Click back button in browser"""
self.driver.back()

@allure.step('Scroll to element')
def scroll_to(self, locator: Optional[Locator] = None, element: Optional[WebElement] = None) -> WebElement:
"""Scroll to element"""
element = element or self.find_element(locator)
# Hack for firefox because of move_to_element does not scroll to the element
# https://github.com/mozilla/geckodriver/issues/776
if self.driver.capabilities['browserName'] == 'firefox':
self.driver.execute_script('arguments[0].scrollIntoView(true)', element)
action = ActionChains(self.driver)
action.move_to_element(element).perform()
return element


class PageHeader(BasePageObject):
"""Class for header manipulating."""
Expand Down
91 changes: 91 additions & 0 deletions tests/ui_tests/app/page/common/configuration/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 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.

"""
Manipulations with different type of configuration parameters
"""
from contextlib import contextmanager
from typing import Dict

from selenium.webdriver.remote.webdriver import WebElement
from adcm_pytest_plugin.utils import wait_until_step_succeeds

from tests.ui_tests.app.page.common.base_page import BasePageObject
from tests.ui_tests.app.page.common.configuration.locators import CommonConfigMenu


class ConfigFieldsManipulator(BasePageObject):
"""
Class for handling different types of inputs on configuration page.
"""

def fill_password(self, password: str, row: WebElement, *, confirmation: str = None):
"""
Fill password and confirm password field with same value if confirmation is not provided explicitly
"""
password_input = self.find_child(row, CommonConfigMenu.ConfigRow.password)
password_input.send_keys(password)
if confirmation is not None:
confirm_input = self.find_child(row, CommonConfigMenu.ConfigRow.confirm_password)
confirm_input.send_keys(password)

def add_list_values(self, values: list, row: WebElement):
"""Add list values to config parameter in row"""
add_button = self.find_child(row, CommonConfigMenu.ConfigRow.add_item_btn)
for value in values:
with self._with_items_added(row):
self.scroll_to(element=add_button)
add_button.click()
item_to_fill = self._get_first_empty_input(row)
item_to_fill.send_keys(value)

def add_map_values(self, values: Dict[str, str], row: WebElement):
"""Add map values to config parameter in row"""
add_button = self.find_child(row, CommonConfigMenu.ConfigRow.add_item_btn)
for key, value in values.items():
with self._with_items_added(row):
self.scroll_to(element=add_button)
add_button.click()
item_to_fill = self._get_first_empty_map_input(row)
key_input = self.find_child(item_to_fill, CommonConfigMenu.ConfigRow.map_input_key)
value_input = self.find_child(item_to_fill, CommonConfigMenu.ConfigRow.map_input_value)
key_input.send_keys(key)
value_input.send_keys(value)

def _get_first_empty_input(self, row: WebElement) -> WebElement:
"""Get first empty field (simple search for inputs/textareas in row)"""
for item in self.find_children(row, CommonConfigMenu.ConfigRow.value, timeout=2):
if not item.get_attribute('value'):
return item
raise ValueError('All items in row has "value" or no items are presented')

def _get_first_empty_map_input(self, row: WebElement) -> WebElement:
"""Search for first empty (by key) input element for map and return 'parent' map WebElement"""
for item in self.find_children(row, CommonConfigMenu.ConfigRow.map_item, timeout=2):
key_input = self.find_child(item, CommonConfigMenu.ConfigRow.map_input_key)
if not key_input.get_attribute('value'):
return item
raise ValueError('All items in map has "value" in key input field or no items are presented')

@contextmanager
def _with_items_added(self, row: WebElement):
"""Wait for new items to appear after 'add new' button is clicked"""
before = len(self.find_children(row, CommonConfigMenu.ConfigRow.value, timeout=2))

yield

def check_new_item_appeared():
assert (
len(self.find_children(row, CommonConfigMenu.ConfigRow.value, timeout=1)) > before
), f'New item should appear in {row}, but there are still {before} rows'

wait_until_step_succeeds(check_new_item_appeared, timeout=10, period=1)
8 changes: 8 additions & 0 deletions tests/ui_tests/app/page/common/configuration/locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ class ConfigRow:
history = Locator(By.CSS_SELECTOR, "mat-list-item span.accent", "Row history")
reset_btn = Locator(By.CSS_SELECTOR, "button[mattooltip='Reset to default']", "Reset button")

# complex parameters
add_item_btn = Locator(
By.XPATH, ".//button//mat-icon[text()='add_circle_outline']", "Add item to parameter button"
)
map_item = Locator(By.CSS_SELECTOR, "div.item", "Map parameter item")
map_input_key = Locator(By.XPATH, ".//input[@formcontrolname='key']", "Map input key input")
map_input_value = Locator(By.XPATH, ".//input[@formcontrolname='value']", "Map input value input")

class ConfigGroup:
"""Configuration menu configuration group locators"""

Expand Down
32 changes: 26 additions & 6 deletions tests/ui_tests/app/page/common/configuration/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from tests.ui_tests.app.page.common.base_page import BasePageObject
from tests.ui_tests.app.page.common.configuration.locators import CommonConfigMenu
from tests.ui_tests.app.page.common.configuration.fields import ConfigFieldsManipulator


@dataclass
Expand All @@ -40,27 +41,30 @@ class CommonConfigMenuObj(BasePageObject):
def __init__(self, driver, base_url, config_class_locators=CommonConfigMenu):
super().__init__(driver, base_url)
self.locators = config_class_locators
self.fields = ConfigFieldsManipulator(self.driver, self.base_url)

def get_all_config_rows(self) -> List[WebElement]:
def get_all_config_rows(self, *, displayed_only: bool = True) -> List[WebElement]:
"""Return all config field rows"""
try:
return [r for r in self.find_elements(CommonConfigMenu.config_row, timeout=5) if r.is_displayed()]
if displayed_only:
return [r for r in self.find_elements(CommonConfigMenu.config_row, timeout=5) if r.is_displayed()]
return self.find_elements(CommonConfigMenu.config_row, timeout=5)
except TimeoutException:
return []

def get_config_row(self, display_name: str) -> WebElement:
"""Return config field row with provided display name"""
row_name = f'{display_name}:'
row_name = f'{display_name}:' if not display_name.endswith(':') else display_name
for row in self.get_all_config_rows():
if self.find_child(row, CommonConfigMenu.ConfigRow.name).text == row_name:
return row
raise AssertionError(f'Configuration field with name {display_name} was not found')

@allure.step('Saving configuration')
def save_config(self):
def save_config(self, load_timeout: int = 2):
"""Save current configuration"""
self.find_and_click(self.locators.save_btn)
self.wait_element_hide(self.locators.loading_text, timeout=2)
self.wait_element_hide(self.locators.loading_text, timeout=load_timeout)

@allure.step('Setting configuration description to {description}')
def set_description(self, description: str) -> str:
Expand All @@ -87,6 +91,11 @@ def compare_versions(self, compare_with: str, base_compare_version: Optional[str
# to hide select panel so it won't block other actions
self.find_element(self.locators.compare_to_select).send_keys(Keys.ESCAPE)

def click_on_advanced(self):
"""Click on advanced button and wait rows changed"""
with self.wait_rows_change():
self.find_and_click(CommonConfigMenu.advanced_label)

def get_input_value(
self,
row: WebElement,
Expand Down Expand Up @@ -175,7 +184,7 @@ def _click_group():
group.click()
assert (
_is_group_expanded(self.find_element(self.locators.group_btn(title))) != is_expanded
), f"Group should be{'' if _is_group_expanded else ' not '}expanded"
), f"Group should be{'' if is_expanded else ' not '}expanded"

wait_until_step_succeeds(_click_group, period=1, timeout=10)

Expand Down Expand Up @@ -274,3 +283,14 @@ def _assert_value():
assert self.get_history_in_row(row)[0] == value, "History row should contain old value"

wait_until_step_succeeds(_assert_value, timeout=4, period=0.5)

@allure.step('Scroll to group "{display_name}"')
def scroll_to_group(self, display_name: str) -> WebElement:
"""Scroll to parameter group by display name"""
return self.scroll_to(CommonConfigMenu.group_btn(display_name))

@allure.step('Scroll to field "{display_name}"')
def scroll_to_field(self, display_name: str) -> WebElement:
"""Scroll to parameter field by display name"""
row = self.get_config_row(display_name)
return self.scroll_to(element=row)
11 changes: 11 additions & 0 deletions tests/ui_tests/app/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,17 @@ def _menu_click(self, locator: tuple):
raise InvalidElementStateException
return self._click_element(locator)

def is_popup_presented_on_page(self, timeout: int = 5):
"""Get popup displayed status"""
try:
return self._getelement(Common.common_popup, timer=timeout).is_displayed()
except TimeoutException:
return False

def assert_no_popups_displayed(self, timeout: int = 3):
"""Assert there is no popups displayed"""
assert not self.is_popup_presented_on_page(timeout=timeout), "There is a popup with error on the page"


class Ui(BasePage):
"""This class describes main menu and returns specified page in POM"""
Expand Down
Loading

0 comments on commit 178c9bf

Please sign in to comment.