diff --git a/components/navbar_component.py b/components/navbar_component.py index 4a8b318..d39c2f8 100644 --- a/components/navbar_component.py +++ b/components/navbar_component.py @@ -1,5 +1,6 @@ """Модуль компонента панели навигации. Содержит класс для работы с элементами навигации.""" +import re from playwright.sync_api import Page, Locator from tools.logger import get_logger from locators.navigation_panel_locators import NavigationPanelLocators @@ -51,6 +52,9 @@ class NavigationPanelComponent(BaseComponent): node_content = node.locator('div.v-treeview-node__content') if node_content.count() > 0: node_text = node_content.first.inner_text().strip() + node_texts = node_text.splitlines() + if len(node_texts) > 1: + node_text = node_texts[1] if item_name == node_text: node_attr = node.get_attribute('class') if "v-treeview-node--leaf" not in node_attr: diff --git a/pages/location_page.py b/pages/location_page.py index 066a3cc..de5ca4f 100644 --- a/pages/location_page.py +++ b/pages/location_page.py @@ -2,21 +2,20 @@ from playwright.sync_api import Page from components.toolbar_component import ToolbarComponent -from components_derived.frames.create_child_element_frame import ( - CreateChildElementFrame -) +from components_derived.frames.create_child_element_frame import CreateChildElementFrame from pages.base_page import BasePage -# =============== Локаторы ================================================ -PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i" -CREATE_BUTTON_ANCESTOR_DIV3 = "xpath=/ancestor::div[3]//button" -# ========================================================================= - - class LocationPage(BasePage): """Класс для работы со страницей локации.""" + # Константы локаторов + TOOLBAR_BUTTONS_LOCATOR = "//div[contains(@class, 'layout class--')]//span[@class='v-tooltip v-tooltip--bottom']//button" + + # Индексы кнопок + CREATE_BUTTON_INDEX = 0 # Первая кнопка + EDIT_BUTTON_INDEX = 1 # Вторая кнопка + def __init__(self, page: Page) -> None: """ Инициализирует страницу локации. @@ -29,49 +28,59 @@ class LocationPage(BasePage): # Инициализация тулбара self.toolbar = ToolbarComponent(page, "") - panel_header_locator = self.page.locator(PANEL_HEADER) + # Кнопка "Создать" - первая кнопка + create_button_locator = self.page.locator( + self.TOOLBAR_BUTTONS_LOCATOR + ).nth(self.CREATE_BUTTON_INDEX) - # Кнопка "Создать" - первая кнопка в тулбаре - create_button_locator = panel_header_locator.locator( - CREATE_BUTTON_ANCESTOR_DIV3 - ).nth(0) + # Кнопка "Изменить" - вторая кнопка + edit_button_locator = self.page.locator( + self.TOOLBAR_BUTTONS_LOCATOR + ).nth(self.EDIT_BUTTON_INDEX) - # Инициализация кнопки + # Инициализация кнопок self.toolbar.add_tooltip_button(create_button_locator, "create") + self.toolbar.add_tooltip_button(edit_button_locator, "edit") - # Инициализация фреймов (ленивая загрузка) self._create_child_frame = None - def click_create_button(self) -> CreateChildElementFrame: + def click_create_button(self) -> None: """ - Кликает на кнопку 'Создать' и возвращает фрейм создания. + Кликает на кнопку 'Создать'. Returns: - CreateChildElementFrame: Фрейм создания дочернего элемента + None """ - # Используем метод тулбара для клика self.toolbar.click_button("create") - self.wait_for_timeout(3000) - # Создаем и возвращаем фрейм - self._create_child_frame = CreateChildElementFrame(self.page) - return self._create_child_frame - - def is_create_button_visible(self) -> bool: + def click_edit_button(self) -> None: """ - Проверяет видимость кнопки 'Создать'. + Кликает на кнопку 'Изменить'. Returns: - bool: True если кнопка видима + None """ - button = self.toolbar.get_button_by_name("create") + self.toolbar.click_button("edit") + self.wait_for_timeout(2000) - if button is None: - return False + def should_be_toolbar_buttons(self) -> None: + """ + Проверяет наличие и функциональность кнопок тулбара. - return button.is_present(timeout=5000) and button.locator.is_visible() + Raises: + AssertionError: Если кнопки недоступны или подсказки неверны. + """ + # Проверяем кнопку "Создать" + self.toolbar.check_button_visibility("create") + self.toolbar.check_button_tooltip("create", "Создать") + + # Проверяем кнопку "Изменить" + self.toolbar.check_button_visibility("edit") + self.toolbar.check_button_tooltip("edit", "Изменить") + + self.wait_for_timeout(2000) def wait_for_timeout(self, timeout: int) -> None: """ @@ -80,4 +89,4 @@ class LocationPage(BasePage): Args: timeout: Время ожидания в миллисекундах """ - self.page.wait_for_timeout(timeout) \ No newline at end of file + self.page.wait_for_timeout(timeout) diff --git a/tests/e2e/create_elements/test_create_rack_element.py b/tests/e2e/create_elements/test_create_rack_element.py index 8449bd7..01965e3 100644 --- a/tests/e2e/create_elements/test_create_rack_element.py +++ b/tests/e2e/create_elements/test_create_rack_element.py @@ -50,15 +50,18 @@ class TestCreateRackElement: # Создаем экземпляр страницы локации self.location_page = LocationPage(browser) - #@pytest.mark.develop + @pytest.mark.develop def test_create_rack_content(self, browser: Page) -> None: """Тест создания дочернего элемента типа 'Стойка'.""" # Проверяем что кнопка "Создать" доступна - assert self.location_page.is_create_button_visible(), "Create button is not visible on the page" + self.location_page.should_be_toolbar_buttons() # Нажимаем кнопку "Создать" на тулбаре - create_child_frame = self.location_page.click_create_button() + self.location_page.click_create_button() + + # Создаем фрейм создания дочернего элемента + create_child_frame = CreateChildElementFrame(browser) # Нажимаем на плашку "Класс объекта учета" create_child_frame.open_object_class_combobox() @@ -82,7 +85,10 @@ class TestCreateRackElement: """Тест создания дочернего элемента типа 'Стойка'.""" # Нажимаем кнопку "Создать" на тулбаре - create_child_frame = self.location_page.click_create_button() + self.location_page.click_create_button() + + # Создаем фрейм создания дочернего элемента + create_child_frame = CreateChildElementFrame(browser) # Нажимаем на плашку "Класс объекта учета" create_child_frame.open_object_class_combobox() @@ -131,7 +137,7 @@ class TestCreateRackElement: # Проверяем, существует ли уже стойка с таким именем if not self._check_rack_exists(browser, rack_name): logger.info(f"Rack with name '{rack_name}' not found. Creating first rack.") - create_child_frame = self._create_rack(browser, rack_name) + self._create_rack(browser, rack_name) logger.info(f"First rack with name '{rack_name}' created successfully") else: logger.info(f"Rack with name '{rack_name}' already exists, proceeding to create second one") @@ -144,7 +150,10 @@ class TestCreateRackElement: self.main_page.wait_for_timeout(2000) # Нажимаем кнопку "Создать" на тулбаре - create_child_frame = self.location_page.click_create_button() + self.location_page.click_create_button() + + # Создаем фрейм создания дочернего элемента + create_child_frame = CreateChildElementFrame(browser) # Нажимаем на плашку "Класс объекта учета" create_child_frame.open_object_class_combobox() @@ -179,23 +188,76 @@ class TestCreateRackElement: logger.info("System prevented creating rack with duplicate name") + def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data): + """Выполняет один тест валидации обязательных полей. + + Args: + create_child_frame: Фрейм создания дочернего элемента + rack_maker: Объект для работы со стойкой + test_data: Словарь с данными теста + """ + # Распаковываем данные теста + name_value = test_data["name"] + height_value = test_data["height"] + depth_value = test_data["depth"] + expected_alert_height = test_data["expected_alert_height"] + expected_alert_depth = test_data["expected_alert_depth"] + + # Очистить поля + create_child_frame.clear_combobox_field("Глубина (мм)") + create_child_frame.clear_combobox_field("Высота в юнитах") + + # Заполняем данные + rack_maker.fill_rack_data( + name=name_value, + height=height_value, + depth=depth_value + ) + + # Нажимаем кнопку создания + create_child_frame.click_add_button() + create_child_frame.wait_for_timeout(3000) + + # Проверяем подсветку полей + if height_value: + create_child_frame.check_field_not_highlighted_error("Высота в юнитах") + else: + create_child_frame.check_field_highlighted_error("Высота в юнитах") + + if depth_value: + create_child_frame.check_field_not_highlighted_error("Глубина (мм)") + else: + create_child_frame.check_field_highlighted_error("Глубина (мм)") + + # Обрабатываем alert-окна + if not height_value: + create_child_frame.alert.check_alert_presence(expected_alert_height) + create_child_frame.alert.close_alert_by_text(expected_alert_height) + + if not depth_value: + create_child_frame.alert.check_alert_presence(expected_alert_depth) + create_child_frame.alert.close_alert_by_text(expected_alert_depth) + + # Проверяем, что остались на той же странице + create_child_frame.check_toolbar_title('Создать дочерний элемент в') + def test_required_fields_validation(self, browser: Page) -> None: """ Тест проверки обязательных полей при создании стойки. Проверяет, что система корректно валидирует обязательные поля: - - Поле 'Имя' должно быть заполнено - Поле 'Высота в юнитах' должно быть заполнено - Поле 'Глубина (мм)' должно быть заполнено """ - # Текст сообщения alert-окна - expected_alert_text_name = "поле Имя должно быть заполнено" expected_alert_text_height = "поле Высота в юнитах должно быть заполнено" expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено" # Нажимаем кнопку "Создать" на тулбаре - create_child_frame = self.location_page.click_create_button() + self.location_page.click_create_button() + + # Создаем фрейм создания дочернего элемента + create_child_frame = CreateChildElementFrame(browser) # Нажимаем на плашку "Класс объекта учета" create_child_frame.open_object_class_combobox() @@ -209,136 +271,57 @@ class TestCreateRackElement: # Проверяем наличие полей стойки rack_maker.check_rack_fields_presence() - # 1. Тест: Попытка создания стойки поля - default - logger.info("Test 1: Creating rack with default field values") + # Тестовые данные + test_cases = [ + { + "name": "Test 1: Creating rack with default field values", + "data": { + "name": "", + "height": "", + "depth": "", + "expected_alert_height": expected_alert_text_height, + "expected_alert_depth": expected_alert_text_depth + } + }, + { + "name": "Test 2: Required fields are not filled", + "data": { + "name": "", + "height": "", + "depth": "", + "expected_alert_height": expected_alert_text_height, + "expected_alert_depth": expected_alert_text_depth + } + }, + { + "name": "Test 3: Only 'Height in units' field is filled", + "data": { + "name": "", + "height": "42", + "depth": "", + "expected_alert_height": expected_alert_text_height, + "expected_alert_depth": expected_alert_text_depth + } + }, + { + "name": "Test 4: Only 'Depth (mm)' field is filled", + "data": { + "name": "", + "height": "", + "depth": "1000", + "expected_alert_height": expected_alert_text_height, + "expected_alert_depth": expected_alert_text_depth + } + } + ] - # Нажимаем кнопку создания без заполнения данных - create_child_frame.click_add_button() - create_child_frame.wait_for_timeout(2000) - - # Проверяем подсветку обязательных полей цветом ошибки - create_child_frame.check_field_highlighted_error("Высота в юнитах") - create_child_frame.check_field_highlighted_error("Глубина (мм)") - - logger.info("Validation for 'Name' field temporarily disabled - waiting for developer fix") - - # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах - create_child_frame.alert.check_alert_presence(expected_alert_text_height) - # Закрываем alert-окно Высота в юнитах - create_child_frame.alert.close_alert_by_text(expected_alert_text_height) - - # Проверяем наличие alert-окна с сообщением о заполнении Глубины (мм) - create_child_frame.alert.check_alert_presence(expected_alert_text_depth) - # Закрываем alert-окно Глубины (мм) - create_child_frame.alert.close_alert_by_text(expected_alert_text_depth) - - # Проверяем, что остались на той же странице - create_child_frame.check_toolbar_title('Создать дочерний элемент в') - logger.info("System prevented creating rack without height and depth") - create_child_frame.wait_for_timeout(2000) - - # 2. Тест: Обязательные поля не заполнены - logger.info("Test 2: Required fields are not filled") - - rack_maker.fill_rack_data( - name="", - height="", - depth="" - ) - - # Нажимаем кнопку создания без заполнения данных - create_child_frame.click_add_button() - create_child_frame.wait_for_timeout(2000) - - # Проверяем подсветку всех обязательных полей цветом ошибки - create_child_frame.check_field_highlighted_error("Высота в юнитах") - create_child_frame.check_field_highlighted_error("Глубина (мм)") - - logger.info("Validation for 'Name' field temporarily disabled - waiting for developer fix") - - # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах - create_child_frame.alert.check_alert_presence(expected_alert_text_height) - # Закрываем alert-окно Высота в юнитах - create_child_frame.alert.close_alert_by_text(expected_alert_text_height) - - # Проверяем наличие alert-окна с сообщением о заполнении Глубины (мм) - create_child_frame.alert.check_alert_presence(expected_alert_text_depth) - # Закрываем alert-окно Глубины (мм) - create_child_frame.alert.close_alert_by_text(expected_alert_text_depth) - - # Проверяем, что остались на той же странице - create_child_frame.check_toolbar_title('Создать дочерний элемент в') - logger.info("System prevented creating rack without name, height and depth") - create_child_frame.wait_for_timeout(2000) - - # 3. Тест: Заполняем только поле 'Высота в юнитах' - logger.info("Test 3: Only 'Height in units' field is filled") - - # Очистить поля - create_child_frame.clear_combobox_field("Глубина (мм)") - create_child_frame.clear_combobox_field("Высота в юнитах") - - # Очистить поля через заполнение пустыми значениями - rack_maker.fill_rack_data( - name="", - height="42", - depth="" - ) - - # Нажимаем кнопку создания без заполнения данных - create_child_frame.click_add_button() - create_child_frame.wait_for_timeout(2000) - - # Проверяем подсветку полей 'Имя' и 'Глубина (мм)' цветом ошибки - create_child_frame.check_field_not_highlighted_error("Высота в юнитах") - create_child_frame.check_field_highlighted_error("Глубина (мм)") - - # Проверяем, что НЕТ alert-окна для поля 'Высота в юнитах' - create_child_frame.alert.check_alert_absence(expected_alert_text_height, 1000) - - # Проверяем наличие alert-окна с сообщением о заполнении Глубины (мм) - create_child_frame.alert.check_alert_presence(expected_alert_text_depth) - # Закрываем alert-окно Глубины (мм) - create_child_frame.alert.close_alert_by_text(expected_alert_text_depth) - - # Проверяем, что остались на той же странице - create_child_frame.check_toolbar_title('Создать дочерний элемент в') - logger.info("System prevented creating rack without name and depth") - create_child_frame.wait_for_timeout(2000) - - # 4. Тест: Заполняем только поле 'Глубина (мм)' - logger.info("Test 4: Only 'Depth (mm)' field is filled") - - create_child_frame.clear_combobox_field("Глубина (мм)") - create_child_frame.clear_combobox_field("Высота в юнитах") - - rack_maker.fill_rack_data( - name="", - height="", - depth="1000" - ) - - create_child_frame.wait_for_timeout(5000) - - # Нажимаем кнопку создания - create_child_frame.click_add_button() - create_child_frame.wait_for_timeout(3000) - - # Проверяем подсветку полей 'Имя' и 'Высота в юнитах' цветом ошибки - create_child_frame.check_field_highlighted_error("Высота в юнитах") - create_child_frame.check_field_not_highlighted_error("Глубина (мм)") - - logger.info("Validation for 'Depth' field alert absence temporarily disabled") - - # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах - create_child_frame.alert.check_alert_presence(expected_alert_text_height) - # Закрываем alert-окно Высота в юнитах - create_child_frame.alert.close_alert_by_text(expected_alert_text_height) - - # Проверяем, что остались на той же странице - create_child_frame.check_toolbar_title('Создать дочерний элемент в') - logger.info("System prevented creating rack without name and height") - create_child_frame.wait_for_timeout(2000) + # Выполняем тестовые случаи + for test_case in test_cases: + logger.info(test_case["name"]) + self._perform_required_fields_test( + create_child_frame, rack_maker, test_case["data"] + ) + logger.info("System prevented creating rack with invalid required fields") # 5. Тест: Заполняем все обязательные поля logger.info("Test 5: All required fields are filled") @@ -364,7 +347,6 @@ class TestCreateRackElement: create_child_frame.wait_for_timeout(3000) # Проверяем, что НЕТ alert-окон для всех обязательных полей - create_child_frame.alert.check_alert_absence(expected_alert_text_name, 1000) create_child_frame.alert.check_alert_absence(expected_alert_text_height, 1000) create_child_frame.alert.check_alert_absence(expected_alert_text_depth, 1000) logger.info("No alert windows for required fields appeared - all fields filled correctly") @@ -373,7 +355,7 @@ class TestCreateRackElement: try: create_child_frame.check_toolbar_title('Создать дочерний элемент в') logger.warning("Rack creation may not have completed successfully") - except Exception as e: + except AssertionError: logger.info("Creation page closed - rack successfully created") logger.info("Required fields validation test completed successfully") @@ -397,11 +379,11 @@ class TestCreateRackElement: if element.is_visible(): logger.info(f"Rack with name '{rack_name}' found") return True - else: - logger.info(f"Rack with name '{rack_name}' not found") - return False - def _create_rack(self, browser: Page, rack_name: str) -> CreateChildElementFrame: + logger.info(f"Rack with name '{rack_name}' not found") + return False + + def _create_rack(self, browser: Page, rack_name: str) -> None: """Создает стойку.""" logger.info(f"Creating rack with name '{rack_name}'") @@ -410,7 +392,10 @@ class TestCreateRackElement: self.main_page.wait_for_timeout(2000) # Нажимаем кнопку "Создать" на тулбаре - create_child_frame = self.location_page.click_create_button() + self.location_page.click_create_button() + + # Создаем фрейм создания дочернего элемента + create_child_frame = CreateChildElementFrame(browser) # Нажимаем на плашку "Класс объекта учета" create_child_frame.open_object_class_combobox() @@ -431,5 +416,3 @@ class TestCreateRackElement: # Нажимаем кнопку создания create_child_frame.click_add_button() create_child_frame.wait_for_timeout(2000) - - return create_child_frame diff --git a/tests/e2e/test_expand_navigation_panel.py b/tests/e2e/test_expand_navigation_panel.py index 0689d55..633b6f7 100644 --- a/tests/e2e/test_expand_navigation_panel.py +++ b/tests/e2e/test_expand_navigation_panel.py @@ -100,7 +100,7 @@ class TestNavigationPanel: mp.wait_for_timeout(5000) - mp.click_subpanel_item("Физические устройства с опросом") + mp.click_subpanel_item("test-zone") mp.wait_for_timeout(3000) # Переходим Здание ЦОД 4 diff --git a/tools/__pycache__/__init__.cpython-313.pyc b/tools/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 71c14bd..0000000 Binary files a/tools/__pycache__/__init__.cpython-313.pyc and /dev/null differ diff --git a/tools/__pycache__/fix_python_project.cpython-313.pyc b/tools/__pycache__/fix_python_project.cpython-313.pyc deleted file mode 100644 index 425e28d..0000000 Binary files a/tools/__pycache__/fix_python_project.cpython-313.pyc and /dev/null differ diff --git a/tools/__pycache__/logger.cpython-313.pyc b/tools/__pycache__/logger.cpython-313.pyc deleted file mode 100644 index f0f4f81..0000000 Binary files a/tools/__pycache__/logger.cpython-313.pyc and /dev/null differ