From 47d5306c5d53cc0a688dfa3d9205f0d4c1646378 Mon Sep 17 00:00:00 2001 From: nsubbot Date: Wed, 10 Dec 2025 18:06:39 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D1=82=D1=8B=D1=85=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8?= =?UTF-8?q?=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/eventbar_component.py | 29 +- components/expand_button_component.py | 72 +++++ components/table_component.py | 83 +++++- .../container_system_log_events.py | 10 +- components_derived/modal_add_local_user.py | 106 +++---- elements/base_element.py | 7 +- pages/service_status_tab.py | 275 +++++++++++++++--- pages/users_tab.py | 11 +- tests/components/scrolling/run.bat | 2 +- .../scrolling/test_services_table.py | 6 +- tests/e2e/test_expand_navigation_panel.py | 30 +- tests/e2e/test_service_status_tab.py | 90 +++++- tests/e2e/test_templates_tab.py | 3 +- tests/e2e/test_ztp_config_tab.py | 2 + tests/e2e/test_ztp_templates_tab.py | 1 + tests/e2e/users/test_add_user.py | 31 +- tests/e2e/users/test_user_card.py | 1 + 17 files changed, 595 insertions(+), 164 deletions(-) create mode 100644 components/expand_button_component.py diff --git a/components/eventbar_component.py b/components/eventbar_component.py index d26a22f..266017f 100644 --- a/components/eventbar_component.py +++ b/components/eventbar_component.py @@ -28,13 +28,22 @@ class EventPanelComponent(BaseComponent): self.states_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_STATES), "states_tab") self.actions_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_ACTIONS), "actions_tab") self.events_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_EVENTS), "events_tab") - self.maintenance_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_MAINTENANCE), "maintenance_tab") + self.maintenance_tab = TabButton(page, + self.page.locator(EventPanelLocators.TAB_MAINTENANCE), "maintenance_tab") self.system_log_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_SYSTEM_LOG), "system_log_tab") - self.unknown_reason_button = TooltipButton(page, self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(0), "unknown_reason_button") - self.warning_button = TooltipButton(page, self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(1), "warning_button") - self.damage_button = TooltipButton(page, self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(2), "damage_button") - self.failure_button = TooltipButton(page, self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(3), "failure_button") + self.unknown_reason_button = TooltipButton(page, + self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(0), + "unknown_reason_button") + self.warning_button = TooltipButton(page, + self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(1), + "warning_button") + self.damage_button = TooltipButton(page, + self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(2), + "damage_button") + self.failure_button = TooltipButton(page, + self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(3), + "failure_button") buttons_service_locators = self.page.locator(EventPanelLocators.BUTTONS_SERVICE).get_by_role("button").all() self.search_button = Button(page, buttons_service_locators[0], "search_button") @@ -53,7 +62,7 @@ class EventPanelComponent(BaseComponent): button_locator = self.page.locator(EventPanelLocators.TAB_EXPAND_BUTTONS).\ get_by_role("button").filter(has_text='expand_more') - button_locator.click() + button_locator.click(force=True) def click_system_log_tab(self) -> SystemLogEventsContainer: """Выполняет нажатие tab-кнопки Системный журнал.""" @@ -68,7 +77,7 @@ class EventPanelComponent(BaseComponent): """Выполняет нажатие кнопки пользователя.""" self.should_be_user_button() - self.user_button.click() + self.user_button.click(force=True) user_card = UserCard(self.page) return user_card @@ -155,8 +164,10 @@ class EventPanelComponent(BaseComponent): self.states_tab.check_have_text('Состояния', "Tab button with text Состояния is missing on event panel") self.actions_tab.check_have_text('Действия',"Tab button with text Действия is missing on event panel") self.events_tab.check_have_text('События', "Tab button with text События is missing on event panel") - self.maintenance_tab.check_have_text('Обслуживание', "Tab button with text Обслуживание is missing on event panel") - self.system_log_tab.check_have_text('Системный журнал', "Tab button with text Системный журнал is missing on event panel") + self.maintenance_tab.check_have_text('Обслуживание', + "Tab button with text Обслуживание is missing on event panel") + self.system_log_tab.check_have_text('Системный журнал', + "Tab button with text Системный журнал is missing on event panel") def should_be_event_buttons(self) -> None: """Проверяет наличие блока кнопок-счетчиков событий.""" diff --git a/components/expand_button_component.py b/components/expand_button_component.py new file mode 100644 index 0000000..9772aba --- /dev/null +++ b/components/expand_button_component.py @@ -0,0 +1,72 @@ +"""Модуль expand_button_component содержит класс для работы с кнопкой расширения/уменьшения рабочей области вкладки +на странице.""" + +from playwright.sync_api import Page +from tools.logger import get_logger +from elements.button_element import Button +from components.base_component import BaseComponent + +logger = get_logger("EXPAND_BUTTON") + + +class ExpandButton(BaseComponent): + """Класс для работы с кнопкой расширения/сжатия рабочей области вкладки на странице. + + """ + def __init__(self, page: Page): + """Инициализирует компонент. + + Args: + page: Экземпляр страницы Playwright. + """ + + super().__init__(page) + + self.expand_work_area_button_locator = page.get_by_role("button").filter(has_text="navigate_") + self.expand_work_area_button = Button(page, + self.expand_work_area_button_locator, + "expand_work_area_button") + + # Действия: + def expand(self) -> None: + """Нажатие кнопки для расширения рабочей области вкладки""" + + can_do_expand = self.is_in_expand_state() + + if can_do_expand: + self.expand_work_area_button.click() + else: + assert False, "Work area already expanded" + + def reduce(self) -> None: + """Нажатие кнопки для сжатия рабочей области вкладки""" + + can_do_expand = self.is_in_expand_state() + + if can_do_expand: + assert False, "Work area already reduced" + else: + self.expand_work_area_button.click() + + # Проверки: + def is_in_expand_state(self) -> bool: + """Проверяет состояние кнопки, способность ее увеличить размер рабочей области вкладки""" + + button_state = self.expand_work_area_button_locator.text_content() + if button_state.find("before") != -1: + return True + elif button_state.find("next") != -1: + return False + else: + assert False, f"Got unexpected button state {button_state}" + + def should_be_button(self) -> None: + """Проверяет наличие кнопки расширения/сжатия рабочей области вкладки. + + Raises: + AssertionError: Если кнопка отсутствует. + """ + + self.expand_work_area_button.check_visibility( + "Expand work area button is missing on page" + ) diff --git a/components/table_component.py b/components/table_component.py index d099168..e749d9d 100644 --- a/components/table_component.py +++ b/components/table_component.py @@ -1,5 +1,6 @@ """Модуль компонента таблицы. Содержит класс для работы с табличными данными.""" +import math from datetime import datetime from playwright.sync_api import Page, expect, Locator from tools.logger import get_logger @@ -34,10 +35,10 @@ class TableComponent(BaseComponent): assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell" arrow_button.click() - def datetime2timestamp(self, date_string: str) -> float|None: + def datetime2timestamp(self, date_string: str, format_string = None) -> float|None: """ Конвертация строкового представления даты и времени в Unix timestamp Args: - date_string: Строка с датой и временем в формате d.m.Y H:M:S. + date_string: Строка с датой и временем в формате d.m.Y H:M:S (default value). Returns: float: Unix timestamp. @@ -45,7 +46,8 @@ class TableComponent(BaseComponent): """ # Формат, соответствующий строке с датой и временем - format_string = "%d.%m.%Y %H:%M:%S" + if format_string is None: + format_string = "%d.%m.%Y %H:%M:%S" try: date_object = datetime.strptime(date_string, format_string) @@ -75,6 +77,31 @@ class TableComponent(BaseComponent): else: assert False, f"Got unsupported arrow state: {state}" + def get_column(self, table_locator: str | Locator, index: int) -> list[str]: + """Возвращает столбец таблицы по индексу. + + Args: + table_locator: Локатор таблицы. + index: Индекс столбца. + + Returns: + Список значений требуемого столбца. + """ + + table_content = self.read(table_locator) + + if len(table_content) == 0: + assert False, "The contents of the table are missing" + + del table_content[0] + + assert index in range(len(table_content[0])), \ + "Column index is out of range" + column = [] + for i in range(len(table_content)): + column.append(table_content[i][index]) + return column + def get_header_cell_button(self, table_locator: str | Locator, index: int) -> Locator: """ Поиск кнопки в ячейке заголовка таблицы @@ -179,11 +206,14 @@ class TableComponent(BaseComponent): if item in arrow_state: continue + if item == '': + continue + if item not in expected_headers: is_equals = False assert is_equals, \ - f"Expected events table headers {expected_headers} are not equal {actual_headers}" + f"Expected table headers {expected_headers} are not equal {actual_headers}" def check_content(self, locator: str | Locator, @@ -296,3 +326,48 @@ class TableComponent(BaseComponent): new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor") assert initial_color != new_color, "Color of row did not change when hovering the cursor" + + def check_mui_table_row_highlighting(self, locator: str | Locator, + row_index: int, + offset_x: float, + offset_y: float, + scale_x: float, + scale_y: float) -> None: + """Проверяет изменение цвета строки при наведении. + + Args: + locator: Локатор таблицы. + row_index: Индекс проверяемой строки. + offset_x, offset_y: смещение координат таблицы относительно начала координат + scale_x, scale_y: коээфициенты масштабирования (причина: несовпадение масштабов контента страницы и фрейма) + """ + + table = self.get_locator(locator) + row = table.locator("tbody").locator(".MuiTableRow-root").nth(row_index) + + # Прокручиваем и ждем + row.scroll_into_view_if_needed() + self.page.wait_for_timeout(1000) + + # Получение "ограничительной рамки" строки + bounding_box = row.evaluate("el => el.getBoundingClientRect()") + assert bounding_box, "Requested row is not visible" + + # Получение текущего цвета фона + initial_color = row.evaluate("el => window.getComputedStyle(el).backgroundColor") + + # Вычисление координат целевой строки таблицы и перевод на нее курсора мыши + bounding_box = row.evaluate("el => el.getBoundingClientRect()") + + # center_x = (bounding_box["x"] + bounding_box["width"] / 2 + offset_x) * scale_x + # center_y = (bounding_box["y"] + bounding_box["height"] / 2 + offset_y) * scale_y + + center_x = (bounding_box["x"] + bounding_box["width"] / 2) * scale_x + offset_x + center_y = (bounding_box["y"] + bounding_box["height"] / 2) * scale_y + offset_y + + self.page.mouse.move(math.ceil(center_x), math.ceil(center_y), steps=5) + self.page.wait_for_timeout(1000) + + # Получение текущего цвета фона + new_color = row.evaluate("el => window.getComputedStyle(el).backgroundColor") + assert initial_color != new_color, "Color of row did not change when hovering the cursor" diff --git a/components_derived/container_system_log_events.py b/components_derived/container_system_log_events.py index bc99f6c..d4f6dac 100644 --- a/components_derived/container_system_log_events.py +++ b/components_derived/container_system_log_events.py @@ -32,15 +32,15 @@ class SystemLogEventsContainer(EventsContainerComponent): sidebar_filter = self.get_sidebar_filter() sidebar_filter.add_filtering_parameter("filter_type", "Тип") - sidebar_filter.add_filtering_parameter("filter_strictness", "Строгость") - sidebar_filter.add_filtering_parameter("filter_host", "Хост") + sidebar_filter.add_filtering_parameter("filter_strictness", "Критичность") + sidebar_filter.add_filtering_parameter("filter_host", "Объект") # Действия: # Проверки: def check_content(self) -> None: """Проверяет содержимое контейнера для отображения событий системного журнала.""" - expected_headers = ['ТИП', 'ВРЕМЯ', 'СТРОГОСТЬ', 'ХОСТ', 'ОПИСАНИЕ'] + expected_headers = ['ТИП', 'ВРЕМЯ', 'КРИТИЧНОСТЬ', 'ОБЪЕКТ', 'ОПИСАНИЕ'] self.should_be_toolbar() self.should_be_base_toolbar_buttons() @@ -71,8 +71,8 @@ class SystemLogEventsContainer(EventsContainerComponent): filter_strictness_bar = sidebar_filter.get_filtering_parameter("filter_strictness") filter_strictness_title = filter_strictness_bar.get_selection_bar_title() - assert filter_strictness_title == "Строгость", "Filtering parameter bar 'Строгость' is missing" + assert filter_strictness_title == "Критичность", "Filtering parameter bar 'Критичность' is missing" filter_host_bar = sidebar_filter.get_filtering_parameter("filter_host") filter_host_title = filter_host_bar.get_selection_bar_title() - assert filter_host_title == "Хост", "Filtering parameter bar 'Хост' is missing" + assert filter_host_title == "Объект", "Filtering parameter bar 'Объект' is missing" diff --git a/components_derived/modal_add_local_user.py b/components_derived/modal_add_local_user.py index 80c8178..fd04d28 100644 --- a/components_derived/modal_add_local_user.py +++ b/components_derived/modal_add_local_user.py @@ -39,7 +39,6 @@ class AddLocalUserModalWindow(ModalWindowComponent): # Локаторы элементов формы text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA - label_locator = ModalWindowLocators.LABEL_INPUT_FORM_USER_DATA # Настройка заголовка и кнопки закрытия тулбара self.window_title = "Добавить нового пользователя" @@ -52,42 +51,30 @@ class AddLocalUserModalWindow(ModalWindowComponent): self.add_toolbar_title(self.window_title) self.add_toolbar_button(locator_button_toolbar_close, "close") - # Добавление элементов формы по порядку расположения в окне - checkbox_1 = Checkbox( - page, - self.page.locator(input_form_locator).get_by_role("checkbox").nth(0), - "active_directory" - ) - - self.add_content_item("active_directory_checkbox", checkbox_1) - - label_1 = Text( - page, - self.page.locator(label_locator).nth(0), - "active_directory_checkbox_label" - ) - self.add_content_item("active_directory_checkbox_label", label_1) - # Поле Имя - loc = f"{input_form_locator}/div[2]/{text_field_locator}" + loc = f"{input_form_locator}/div[1]/{text_field_locator}" name_input = TextInput(page, self.page.locator(loc), "name_input") self.add_content_item("name_input", name_input) - # Чекбокс "Блокировка" - индекс 1 - checkbox_2 = Checkbox( - page, - self.page.locator(input_form_locator).get_by_role("checkbox").nth(1), - "blocking" - ) - self.add_content_item("blocking_checkbox", checkbox_2) - # Метка "Блокировка" - индекс 1 - label_2 = Text( + # Метка "Блокировка" + label_blocking_locator = self.page.locator(input_form_locator). \ + locator("//label").get_by_text("Блокировка") + label_blocking = Text( page, - self.page.locator(label_locator).nth(1), + label_blocking_locator, "blocking_checkbox_label" ) - self.add_content_item("blocking_checkbox_label", label_2) + + self.add_content_item("blocking_checkbox_label", label_blocking) + + # Чекбокс "Блокировка" + checkbox_blocking = Checkbox( + page, + label_blocking_locator.locator("../..").get_by_role("checkbox"), + "blocking" + ) + self.add_content_item("blocking_checkbox", checkbox_blocking) # Поле Роль role_loc = self.page.locator(input_form_locator).get_by_role("combobox").nth(0) @@ -96,41 +83,42 @@ class AddLocalUserModalWindow(ModalWindowComponent): self.add_content_item("roles_list", DropdownList(page)) # Поле Пароль - loc = f"{input_form_locator}/div[5]/{text_field_locator}" + loc = f"{input_form_locator}/div[4]/{text_field_locator}" password_input = TextInput(page, self.page.locator(loc), "password_input") self.add_content_item("password_input", password_input) # Поле Комментарий - loc = f"{input_form_locator}/div[6]/{text_field_locator}" + loc = f"{input_form_locator}/div[5]/{text_field_locator}" commentary_input = TextInput(page, self.page.locator(loc), "commentary_input") self.add_content_item("commentary_input", commentary_input) # Поле E-mail - loc = f"{input_form_locator}/div[7]/{text_field_locator}" + loc = f"{input_form_locator}/div[6]/{text_field_locator}" email_input = TextInput(page, self.page.locator(loc), "email_input") self.add_content_item("email_input", email_input) # Поле Номер для СМС - loc = f"{input_form_locator}/div[8]/{text_field_locator}" + loc = f"{input_form_locator}/div[7]/{text_field_locator}" phone_input = TextInput(page, self.page.locator(loc), "phone_input") self.add_content_item("phone_input", phone_input) - # Чекбокс "Подписка на Push-уведомления" - индекс 2 - checkbox_3 = Checkbox( + # Метка "Подписка на Push-уведомления" + label_push_locator = self.page.locator(input_form_locator). \ + locator("//label").get_by_text("Подписка на Push-уведомления") + label_push = Text( page, - self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA) - .get_by_role("checkbox").nth(2), - "push_notification" - ) - self.add_content_item("push_notification_checkbox", checkbox_3) - - # Метка "Подписка на Push-уведомления" - индекс 2 - label_3 = Text( - page, - self.page.locator(label_locator).nth(2), + label_push_locator, "push_notification_checkbox_label" ) - self.add_content_item("push_notification_checkbox_label", label_3) + self.add_content_item("push_notification_checkbox_label", label_push) + + # Чекбокс "Подписка на Push-уведомления" - индекс 2 + checkbox_push = Checkbox( + page, + label_push_locator.locator("../..").get_by_role("checkbox"), + "push_notification" + ) + self.add_content_item("push_notification_checkbox", checkbox_push) # Добавление кнопок действий locator_button_add = self.page.get_by_role("button", name="Добавить") @@ -143,16 +131,6 @@ class AddLocalUserModalWindow(ModalWindowComponent): self.new_user_confirm = ConfirmComponent(page, " Отмена ", " Добавить ") # Действия: - def check_active_directory_checkbox(self): - """Включает чек-бокс Active Directory.""" - - self.get_content_item("active_directory_checkbox").check(force=True) - - def uncheck_active_directory_checkbox(self): - """Выключает чек-бокс Active Directory.""" - - self.get_content_item("active_directory_checkbox").uncheck(force=True) - def check_blocking_checkbox(self): """Включает чек-бокс Блокировка.""" @@ -255,28 +233,16 @@ class AddLocalUserModalWindow(ModalWindowComponent): self.check_by_window_title() - is_checked = self.get_content_item("active_directory_checkbox").is_checked() - if is_checked: - assert False, ( - "The checkbox 'Active Directory' should not be checked for " - "the add local user window" - ) - self.check_toolbar_button_visibility("close") self.check_toolbar_button_tooltip("close", "Закрыть") - + input_fields = ["name_input", "password_input", "commentary_input", "email_input", "phone_input"] for name in self.content_items: item = self.get_content_item(name) - if name == "active_directory_checkbox_label": - item.check_have_text( - "Active Directory", - "Label 'Active Directory' is missing" - ) - elif name == "blocking_checkbox_label": + if name == "blocking_checkbox_label": item.check_have_text( "Блокировка", "Label 'Блокировка' is missing" diff --git a/elements/base_element.py b/elements/base_element.py index fd06eed..8ce5126 100644 --- a/elements/base_element.py +++ b/elements/base_element.py @@ -43,11 +43,14 @@ class BaseElement: return "base element" # Действия: - def click(self) -> None: + def click(self, force=False) -> None: """Выполняет клик по элементу.""" logger.info(f"Clicking {self.type_of} '{self.name}'") - self.locator.click() + if force: + self.locator.click(force=True) + else: + self.locator.click() def get_text(self, index: int) -> str: """Возвращает текст элемента по указанному индексу.""" diff --git a/pages/service_status_tab.py b/pages/service_status_tab.py index 9c3aee3..4a964ba 100644 --- a/pages/service_status_tab.py +++ b/pages/service_status_tab.py @@ -4,10 +4,12 @@ Позволяет проверять состояние и взаимодействовать с элементами вкладки. """ -from playwright.sync_api import Page -from locators.table_locators import TableLocators -from components.toolbar_component import ToolbarComponent +import math +from playwright.sync_api import Page, Locator, expect +from elements.text_element import Text +from elements.button_element import Button from components.table_component import TableComponent +from components.expand_button_component import ExpandButton from pages.base_page import BasePage @@ -26,9 +28,97 @@ class ServiceStatusTab(BasePage): super().__init__(page) - self.toolbar = ToolbarComponent(page, "Статус обслуживания") + self.iframe_container_locator = page.locator("div.container.iframe-wrapper") + self.iframe_locator = page.frame_locator("//iframe[@class='iframe-class']") + self.table_locator = self.iframe_locator.locator("div.MuiBox-root > div > table") + self.tab_title_locator = self.iframe_locator.locator("//h5[text()='Состояние контейнеров']") + self.update_button_locator = self.iframe_locator.get_by_role("button", name='Обновить') + self.analyzer_open_button_locator = self.iframe_locator.get_by_role("button", name='открыть анализатор') + + self.tab_title = Text(page, + self.tab_title_locator, + "tab_title") + self.update_button = Button(page, + self.update_button_locator, + "update_button") + self.analyzer_open_button = Button(page, + self.analyzer_open_button_locator, + "analyzer_open_button") + self.expand_work_area_button = ExpandButton(page) self.services_table = TableComponent(page) + # Действия: + def calculate_coordinates(self, locator: Locator) -> dict: + """Вычисление координат элемента для манипуляции с ним мышкой""" + + coordinates = {} + + offsets_scales = self.get_offsets_scales() + + bounding_box = locator.evaluate("el => el.getBoundingClientRect()") + assert bounding_box, "Required element is not visible" + + offset_x = offsets_scales["offset_x"] + offset_y = offsets_scales["offset_y"] + scale_x = offsets_scales["scale_x"] + scale_y = offsets_scales["scale_y"] + + coordinates["center_x"] = (bounding_box["x"] + bounding_box["width"] / 2) * scale_x + offset_x + coordinates["center_y"] = (bounding_box["y"] + bounding_box["height"] / 2) * scale_y + offset_y + return coordinates + + def get_offsets_scales(self) -> dict: + """Возвращает словарь, содержащий смещения контейнера фрейма и его коэффициенты""" + + offsets_scales = {} + + iframe_container_bounding_box = self.iframe_container_locator.\ + evaluate("el => el.getBoundingClientRect()") + assert iframe_container_bounding_box, "Iframe container is not visible" + + iframe_bounding_box = self.iframe_locator.locator("div#root > div"). \ + evaluate("el => el.getBoundingClientRect()") + assert iframe_bounding_box, "Iframe content is not visible" + + offsets_scales["offset_x"] = iframe_container_bounding_box["x"] + offsets_scales["offset_y"] = iframe_container_bounding_box["y"] + offsets_scales["scale_x"] = iframe_container_bounding_box["width"]/iframe_bounding_box["width"] + offsets_scales["scale_y"] = iframe_container_bounding_box["height"]/iframe_bounding_box["height"] + + return offsets_scales + + def datetime2timestamp(self, date_column: str) -> list[float]: + """ Конвертация строкового представления даты и времени в Unix timestamp + """ + + converted = [] + + for date in date_column: + timestamp = self.services_table.datetime2timestamp(date,format_string="%d-%m-%y %H:%M:%S") + assert timestamp, f"Error conversation to timestamp for {date}" + converted.append(timestamp) + + return converted + + def click_update_button(self) -> None: + """Нажатие кнопки 'Обновить'""" + + coordinates = self.calculate_coordinates(self.update_button_locator) + self.page.mouse.click(math.ceil(coordinates["center_x"]), + math.ceil(coordinates["center_y"]), delay=300) + + def get_column(self, index: int) -> list[str]: + """Возвращает столбец таблицы по индексу. + + Returns: + list[str]: Столбец таблицы по индексу. + + Raises: + AssertionError: Если таблица пуста или столбца с таким индексом в таблице нет. + """ + + return self.services_table.get_column(self.table_locator, index) + def get_rows_count(self) -> int: """Возвращает количество строк в таблице (без заголовка). @@ -39,18 +129,51 @@ class ServiceStatusTab(BasePage): AssertionError: Если таблица пуста. """ - return self.services_table.get_rows_count(TableLocators.TABLE_WORK_AREA) + return self.services_table.get_rows_count(self.table_locator) - def scroll_services_table_up(self) -> None: - """Прокручивает таблицу сервисов вверх.""" + def expand_tab(self) -> None: + """Расширяет рабочую область складки.""" - self.services_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER) + iframe_container_bounding_box = self.iframe_container_locator.\ + evaluate("el => el.getBoundingClientRect()") + widht_before = iframe_container_bounding_box["width"] - def scroll_services_table_down(self) -> None: - """Прокручивает таблицу сервисов вниз.""" + self.expand_work_area_button.expand() - self.services_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER) + iframe_container_bounding_box = self.iframe_container_locator.\ + evaluate("el => el.getBoundingClientRect()") + widht_after = iframe_container_bounding_box["width"] + assert widht_before < widht_after,"Services statuses tab should be expanded" + + def reduce_tab(self) -> None: + """Сжимает рабочую область складки.""" + + iframe_container_bounding_box = self.iframe_container_locator.\ + evaluate("el => el.getBoundingClientRect()") + widht_before = iframe_container_bounding_box["width"] + + self.expand_work_area_button.reduce() + + iframe_container_bounding_box = self.iframe_container_locator.\ + evaluate("el => el.getBoundingClientRect()") + widht_after = iframe_container_bounding_box["width"] + + assert widht_before > widht_after,"Services statuses tab should be reduced" + + def scroll_services_tab_up(self) -> None: + """Прокручивает содержимое вкладки вверх.""" + + self.tab_title_locator.scroll_into_view_if_needed() + + def scroll_services_tab_down(self) -> None: + """Прокручивает содержимое вкладки вниз.""" + + nrows = self.get_rows_count() + last_row = self.services_table.get_row_locator(self.table_locator, nrows-1) + last_row.scroll_into_view_if_needed() + + # Проверки: def check_services_table_content(self) -> None: """Проверяет содержимое таблицы сервисов. @@ -61,15 +184,54 @@ class ServiceStatusTab(BasePage): """ expected_headers = [ - 'Контейнер', - 'Время создания', - 'Статус', - 'Время работы', - 'Image ID', - 'Image ТЭГ' + 'СТЕК', + 'КОНТЕЙНЕР', + 'ТЭГ', + 'СТАТУС', + 'ВРЕМЯ СОЗДАНИЯ', + 'ЦЕЛОСТНОСТЬ', + 'ДЕЙСТВИЯ' ] - self.services_table.check_content(TableLocators.TABLE_WORK_AREA, expected_headers) + self.services_table.check_content(self.table_locator, expected_headers) + + # Проверяем наличие кнопок и тултипов в последней ячейке строки + rows_count = self.services_table.get_rows_count(self.table_locator) + for i in range(rows_count): + row_locator = self.services_table.get_row_locator(self.table_locator, i) + + layers_button = row_locator.get_by_role("button", name="Слои") + expect(layers_button).to_be_visible(), f"Layers button is missing in {i} row" + self.check_tooltip(layers_button, "Слои") + + lounch_button = row_locator.get_by_role("button", name="Запуск", exact=True) + expect(lounch_button).to_be_visible(), f"Lounch button is missing in {i} row" + self.check_tooltip(lounch_button, "Запуск") + + stop_button = row_locator.get_by_role("button", name="Остановка") + expect(stop_button).to_be_visible(), f"Stop button is missing in {i} row" + self.check_tooltip(stop_button, "Остановка") + + restart_button = row_locator.get_by_role("button", name="Перезапуск", exact=True) + expect(restart_button).to_be_visible(), f"Restart button is missing in {i} row" + self.check_tooltip(restart_button, "Перезапуск") + + + def check_tooltip(self, button_locator: Locator, expected_text: str) -> None: + """Проверка текста тултипа кнопки в последней ячейке строки""" + + # Наведение на элемент для отображения подсказки + coordinates = self.calculate_coordinates(button_locator) + + self.page.mouse.move(math.ceil(coordinates["center_x"]), + math.ceil(coordinates["center_y"]), steps=3) + + # Получение элемента подсказки и ее текста + tooltip = self.iframe_locator.get_by_role("tooltip").locator("div.MuiTooltip-tooltip").first + tooltip.wait_for(state="visible", timeout=5000) + + actual_text = tooltip.text_content().strip() + assert expected_text==actual_text, f"Should be tooltip with text: {expected_text}, got: {actual_text}" def check_services_table_verticall_scrolling(self) -> bool: """Проверяет возможность вертикальной прокрутки таблицы. @@ -78,18 +240,26 @@ class ServiceStatusTab(BasePage): bool: True если прокрутка возможна, иначе False. """ - return self.services_table.is_scrollable_vertically( - TableLocators.TABLE_SCROLL_CONTAINER - ) + loc = self.iframe_locator.locator("div.MuiBox-root > div > div").first + return loc.evaluate("el => el.scrollHeight > el.clientHeight") - def check_services_table_first_row_visibility(self) -> None: - """Проверяет видимость первой строки таблицы. + def check_services_tab_title_visibility(self) -> None: + """Проверяет видимость заголовка вкладки. Raises: AssertionError: Если строка не видна. """ - self.services_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA) + self.tab_title.check_visibility("The services tab title is not visible") + + # def check_services_table_first_row_visibility(self) -> None: + # """Проверяет видимость первой строки таблицы. + + # Raises: + # AssertionError: Если строка не видна. + # """ + + # self.services_table.check_first_row_visibility(self.table_locator) def check_services_table_last_row_visibility(self) -> None: """Проверяет видимость последней строки таблицы. @@ -98,7 +268,7 @@ class ServiceStatusTab(BasePage): AssertionError: Если строка не видна. """ - self.services_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA) + self.services_table.check_last_row_visibility(self.table_locator) def check_services_table_row_highlighting(self, row_index: int) -> None: """Проверяет выделение указанной строки таблицы. @@ -110,19 +280,27 @@ class ServiceStatusTab(BasePage): AssertionError: Если строка не выделена. """ - self.services_table.check_row_highlighting( - TableLocators.TABLE_WORK_AREA, - row_index + offsets_scales = self.get_offsets_scales() + + self.services_table.check_mui_table_row_highlighting( + self.table_locator, + row_index, + offsets_scales["offset_x"], + offsets_scales["offset_y"], + offsets_scales["scale_x"], + offsets_scales["scale_y"] ) - def should_be_toolbar(self) -> None: - """Проверяет наличие тулбара на вкладке. + def should_be_tab_title(self) -> None: + """Проверяет наличие заголовка вкладки. Raises: - AssertionError: Если тулбар отсутствует. + AssertionError: Если заголовок отсутствует. """ - self.toolbar.check_toolbar_presence("Toolbar is missing") + self.tab_title.check_visibility( + "Title of service statuses tab is missing" + ) def should_be_services_table(self) -> None: """Проверяет наличие таблицы сервисов. @@ -132,6 +310,37 @@ class ServiceStatusTab(BasePage): """ self.services_table.check_visibility( - TableLocators.TABLE_WORK_AREA, + self.table_locator, "Service statuses table is missing" ) + + def should_be_analyzer_open_button(self) -> None: + """Проверяет наличие кнопки 'navigate_before/navigate_next'. + + Raises: + AssertionError: Если кнопка отсутствует. + """ + + self.analyzer_open_button.check_visibility( + "Analyzer open button on bottom of service statuses tab is missing" + ) + + def should_be_expand_work_area_button(self) -> None: + """Проверяет наличие кнопки расширения/сжатия рабочей области вкладки. + + Raises: + AssertionError: Если кнопка отсутствует. + """ + + self.expand_work_area_button.should_be_button() + + def should_be_update_button(self) -> None: + """Проверяет наличие кнопки 'Обновить'. + + Raises: + AssertionError: Если кнопка отсутствует. + """ + + self.update_button.check_visibility( + "Update button on top of service statuses tab is missing" + ) diff --git a/pages/users_tab.py b/pages/users_tab.py index 76c2d6f..dd4ab56 100644 --- a/pages/users_tab.py +++ b/pages/users_tab.py @@ -87,11 +87,12 @@ class UsersTab(BasePage): add_user_window = self.get_modal_window("add_local_user") - auth_type = user_data.get("auth_type") - if auth_type == "active_directory": - add_user_window.check_active_directory_checkbox() - self.add_modal_window("add_AD_user", "") - add_user_window = self.get_modal_window("add_AD_user") + # skip as unsupported + # auth_type = user_data.get("auth_type") + # if auth_type == "active_directory": + # add_user_window.check_active_directory_checkbox() + # self.add_modal_window("add_AD_user", "") + # add_user_window = self.get_modal_window("add_AD_user") add_user_window.new_user(user_data) diff --git a/tests/components/scrolling/run.bat b/tests/components/scrolling/run.bat index 2e8cbd8..2d86da3 100644 --- a/tests/components/scrolling/run.bat +++ b/tests/components/scrolling/run.bat @@ -1,5 +1,5 @@ pytest -s -v --s="{'width': 300, 'height': 420}" test_navigation_panel.py -pytest -s -v --s="{'width': 300, 'height': 420}" test_services_table.py +pytest -s -v --s="{'width': 1500, 'height': 420}" test_services_table.py pytest -s -v --s="{'width': 300, 'height': 420}" test_json_container.py pytest -s -v --s="{'width': 300, 'height': 420}" test_user_modal_window.py pytest -s -v --s="{'width': 800, 'height': 200}" test_session_settings.py \ No newline at end of file diff --git a/tests/components/scrolling/test_services_table.py b/tests/components/scrolling/test_services_table.py index 9724220..97746e2 100644 --- a/tests/components/scrolling/test_services_table.py +++ b/tests/components/scrolling/test_services_table.py @@ -51,10 +51,10 @@ class TestServiceStatusTable: is_scrollable_vertically = sst.check_services_table_verticall_scrolling() assert is_scrollable_vertically, "Should be vertical scrolling" - sst.scroll_services_table_down() + sst.scroll_services_tab_down() sst.check_services_table_last_row_visibility() sst.wait_for_timeout(3000) - sst.scroll_services_table_up() - sst.check_services_table_first_row_visibility() + sst.scroll_services_tab_up() + sst.check_services_tab_title_visibility() sst.wait_for_timeout(2000) diff --git a/tests/e2e/test_expand_navigation_panel.py b/tests/e2e/test_expand_navigation_panel.py index 633b6f7..71d1b6d 100644 --- a/tests/e2e/test_expand_navigation_panel.py +++ b/tests/e2e/test_expand_navigation_panel.py @@ -88,11 +88,13 @@ class TestNavigationPanel: mp.wait_for_timeout(2000) # Открываем пункты панели с одинаковыми имнами, но разным расположением + mp.click_subpanel_item("Редактор") mp.click_subpanel_item("Шаблоны") mp.wait_for_timeout(2000) mp.click_subpanel_item("Zero Touch Provisioning") - mp.click_subpanel_item("Шаблоны", parent="Zero Touch Provisioning") + # unsupported + # mp.click_subpanel_item("Шаблоны", parent="Zero Touch Provisioning") mp.wait_for_timeout(2000) # Переходим к Объектам @@ -103,22 +105,26 @@ class TestNavigationPanel: mp.click_subpanel_item("test-zone") mp.wait_for_timeout(3000) - # Переходим Здание ЦОД 4 - mp.click_subpanel_item("Здание ЦОД 4") - mp.wait_for_timeout(3000) - - # Переходим к Стойка КСПД с указанием родителя - mp.click_subpanel_item("Стойка КСПД", parent="Здание ЦОД 4") + # Переходим к Стойке + mp.click_subpanel_item("Test-Rack-01") mp.wait_for_timeout(5000) + # Переходим Здание ЦОД 4 + # mp.click_subpanel_item("Здание ЦОД 4") + # mp.wait_for_timeout(3000) + + # # Переходим к Стойка КСПД с указанием родителя + # mp.click_subpanel_item("Стойка КСПД", parent="Здание ЦОД 4") + # mp.wait_for_timeout(5000) + # Переходим к Объектам mp.click_main_navigation_panel_item("Объекты") mp.click_main_navigation_panel_item("Объекты") # баг mp.wait_for_timeout(5000) - mp.click_subpanel_item("Виртуальные устройства") - mp.wait_for_timeout(3000) + # mp.click_subpanel_item("Виртуальные устройства") + # mp.wait_for_timeout(3000) - # Переходим к Стойка систем питания с указанием родителя - mp.click_subpanel_item("Стойка систем питания", parent="Виртуальные устройства") - mp.wait_for_timeout(5000) + # # Переходим к Стойка систем питания с указанием родителя + # mp.click_subpanel_item("Стойка систем питания", parent="Виртуальные устройства") + # mp.wait_for_timeout(5000) diff --git a/tests/e2e/test_service_status_tab.py b/tests/e2e/test_service_status_tab.py index 4be4768..58ee87f 100644 --- a/tests/e2e/test_service_status_tab.py +++ b/tests/e2e/test_service_status_tab.py @@ -11,7 +11,7 @@ from pages.service_status_tab import ServiceStatusTab from pages.main_page import MainPage from pages.login_page import LoginPage -pytest.skip("пропуск всех тестов в этом файле в связи с переходом на новый виджет", allow_module_level=True) +# pytest.skip("пропуск всех тестов в этом файле в связи с переходом на новый виджет", allow_module_level=True) # @pytest.mark.smoke class TestServiceStatusTab: @@ -20,6 +20,8 @@ class TestServiceStatusTab: Тесты покрывают следующие сценарии: 1. test_service_status_tab_content: Проверяет содержимое вкладки 'Статус обслуживания' 2. test_service_status_table_row_highlighting: Проверяет выделение строк в таблице сервисов + 3. test_service_status_tab_resize: Проверяет возможность расширения/сжатия рабочей области вкладки + 4. test_service_status_tab_reload: Проверяет возможность обновления контента после нажатия кнопки 'Обновить' """ @pytest.fixture(scope="function", autouse=True) @@ -58,8 +60,17 @@ class TestServiceStatusTab: sst = ServiceStatusTab(browser) - # Проверка тулбара вкладки - sst.should_be_toolbar() + # Проверка наличия заголовка вкладки + sst.should_be_tab_title() + + # Проверка наличия кнопки 'Обновить' + sst.should_be_update_button() + + # Проверка наличия кнопки 'Открыть анализатор' + sst.should_be_analyzer_open_button() + + # Проверка наличия кнопки расширения/сжатия рабочей области вкладки + sst.should_be_expand_work_area_button() # Проверка наличия таблицы статусов сервисов sst.should_be_services_table() @@ -70,6 +81,7 @@ class TestServiceStatusTab: # Проверка содержимого таблицы сервисов sst.check_services_table_content() + # @pytest.mark.develop def test_service_status_table_row_highlighting(self, browser: Page): """Проверяет выделение строк в таблице сервисов. @@ -79,9 +91,6 @@ class TestServiceStatusTab: sst = ServiceStatusTab(browser) - # Проверка тулбара вкладки - sst.should_be_toolbar() - # Проверка наличия таблицы статусов сервисов sst.should_be_services_table() @@ -92,3 +101,72 @@ class TestServiceStatusTab: sst.check_services_table_row_highlighting(0) sst.check_services_table_row_highlighting(rows_count - 1) sst.check_services_table_row_highlighting(int(rows_count / 2)) + + # @pytest.mark.develop + def test_service_status_tab_resize(self, browser: Page): + """Проверяет возможность расширения/сжатия рабочей области вкладки. + + Args: + browser: Экземпляр страницы Playwright. + """ + + sst = ServiceStatusTab(browser) + + # Проверка наличия таблицы статусов сервисов + sst.should_be_services_table() + + # Проверка наличия кнопки расширения/сжатия рабочей области вкладки + sst.should_be_expand_work_area_button() + + # Проверка возможности расширения рабочей области вкладки + sst.expand_tab() + + # Получение количества строк в таблице + rows_count = sst.get_rows_count() + + # Проверка выделения строк + sst.check_services_table_row_highlighting(rows_count - 1) + + # Проверка возможности сжатия рабочей области вкладки + sst.reduce_tab() + + # Проверка выделения строк + sst.check_services_table_row_highlighting(0) + + # @pytest.mark.develop + def test_service_status_tab_reload(self, browser: Page): + """Проверяет возможность обновления контента после нажатия кнопки 'Обновить'. + + Args: + browser: Экземпляр страницы Playwright. + """ + + sst = ServiceStatusTab(browser) + + # Проверка наличия кнопки 'Обновить' + sst.should_be_update_button() + + # Проверка наличия таблицы статусов сервисов + sst.should_be_services_table() + + # Сохраняем значения столбца 'Время создания' + creation_time_col_before = sst.get_column(4) + timestamps_before = sst.datetime2timestamp(creation_time_col_before) + + # Нажатие на кнопку 'Обновить' + sst.click_update_button() + browser.wait_for_load_state('domcontentloaded') + + # Проверка наличия таблицы статусов сервисов + sst.should_be_services_table() + + # Ожидание перед проверкой содержимого таблицы сервисов + browser.wait_for_timeout(1000) + + # Сохраняем значения столбца 'Время создания' + creation_time_col_after = sst.get_column(4) + timestamps_after = sst.datetime2timestamp(creation_time_col_after) + + assert len(timestamps_before) == len(timestamps_after), "Bad tab reload" + for i in range(len(timestamps_before)): + assert timestamps_before[i] < timestamps_after[i], "Date value after reload should be must be more recent" diff --git a/tests/e2e/test_templates_tab.py b/tests/e2e/test_templates_tab.py index a6090a3..7379824 100644 --- a/tests/e2e/test_templates_tab.py +++ b/tests/e2e/test_templates_tab.py @@ -31,7 +31,7 @@ class TestTemplatesTab: Выполняет: 1. Авторизацию в системе - 2. Переход на вкладку 'Шаблоны' через панель навигации + 2. Переход на вкладку 'Редактор/Шаблоны' через панель навигации """ # Авторизация в системе login_page = LoginPage(browser) @@ -43,6 +43,7 @@ class TestTemplatesTab: # Проверка и взаимодействие с элементами навигации main_page.should_be_navigation_panel() main_page.click_main_navigation_panel_item("Настройки") + main_page.click_subpanel_item("Редактор") main_page.click_subpanel_item("Шаблоны") # @pytest.mark.develop diff --git a/tests/e2e/test_ztp_config_tab.py b/tests/e2e/test_ztp_config_tab.py index 6d3fca9..3f5fdf1 100644 --- a/tests/e2e/test_ztp_config_tab.py +++ b/tests/e2e/test_ztp_config_tab.py @@ -10,6 +10,8 @@ from pages.login_page import LoginPage from pages.main_page import MainPage from pages.ztp_config_tab import ZTPConfigTab +pytest.skip("Пропуск всех тестов в этом файле в связи исключением данной функциональности", allow_module_level=True) + # @pytest.mark.smoke class TestZTPConfigTab: """Набор тестов для вкладки 'Конфигурация' в модуле Zero Touch Provisioning. diff --git a/tests/e2e/test_ztp_templates_tab.py b/tests/e2e/test_ztp_templates_tab.py index 0933772..2c5f5f7 100644 --- a/tests/e2e/test_ztp_templates_tab.py +++ b/tests/e2e/test_ztp_templates_tab.py @@ -10,6 +10,7 @@ from pages.login_page import LoginPage from pages.main_page import MainPage from pages.ztp_templates_tab import ZTPTemplatesTab +pytest.skip("Пропуск всех тестов в этом файле в связи исключением данной функциональности", allow_module_level=True) # @pytest.mark.smoke class TestZTPTemplatesTab: diff --git a/tests/e2e/users/test_add_user.py b/tests/e2e/users/test_add_user.py index ef495f4..c82a037 100644 --- a/tests/e2e/users/test_add_user.py +++ b/tests/e2e/users/test_add_user.py @@ -4,7 +4,6 @@ работы с пользователями системы. """ import pytest - from typing import Dict from playwright.sync_api import Page from pages.users_tab import UsersTab @@ -80,12 +79,16 @@ class TestUsersTabAddUser: """ ut = UsersTab(browser) - + ut.open_add_user_window() ut.check_add_user_window_content() - ut.transform_to_add_AD_user_window() - ut.check_add_AD_user_window_content() - ut.close_add_AD_user_window() + + # skip as unsupported + # ut.transform_to_add_AD_user_window() + # ut.check_add_AD_user_window_content() + # ut.close_add_AD_user_window() + + ut.close_add_user_window() # @pytest.mark.develop def test_add_user_window_close_buttons(self, browser: Page) -> None: @@ -97,20 +100,21 @@ class TestUsersTabAddUser: ut = UsersTab(browser) browser.wait_for_timeout(500) - + ut.open_add_user_window() ut.close_add_user_window_by_toolbar_button() ut.open_add_user_window() ut.close_add_user_window() - ut.open_add_user_window() - ut.transform_to_add_AD_user_window() - ut.close_add_AD_user_window_by_toolbar_button() + # skip as unsupported + # ut.open_add_user_window() + # ut.transform_to_add_AD_user_window() + # ut.close_add_AD_user_window_by_toolbar_button() - ut.open_add_user_window() - ut.transform_to_add_AD_user_window() - ut.close_add_AD_user_window() + # ut.open_add_user_window() + # ut.transform_to_add_AD_user_window() + # ut.close_add_AD_user_window() # @pytest.mark.develop def test_add_local_user(self, browser: Page, cleanup_users: None) -> None: @@ -125,6 +129,7 @@ class TestUsersTabAddUser: self._add_user(browser, user_data) # @pytest.mark.develop + @pytest.mark.skip(reason="This test is temporarily disabled as test unsupported feature") def test_add_AD_user(self, browser: Page, cleanup_users: None) -> None: """Проверяет добавление пользователя Active Directory. @@ -158,7 +163,7 @@ class TestUsersTabAddUser: if not user_exists: # Создаем пользователя впервые ut.open_add_user_window() - + if ut.add_new_user(user_data): # Ждем обновления таблицы с использованием ожиданий Playwright browser.wait_for_timeout(2000) diff --git a/tests/e2e/users/test_user_card.py b/tests/e2e/users/test_user_card.py index 304912f..c0c09ec 100644 --- a/tests/e2e/users/test_user_card.py +++ b/tests/e2e/users/test_user_card.py @@ -65,6 +65,7 @@ class TestUserCard: # Выход из системы текущего пользователя mp = MainPage(browser) + mp.wait_for_timeout(1000) mp.do_logout() # Авторизация администратором для очистки