From 7b9f1f3fd9d9caa775f968649d28b71cdacd4429 Mon Sep 17 00:00:00 2001 From: Radislav Date: Sat, 22 Nov 2025 11:23:06 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B2=20main:=20-=20selection=5Fbar=5Fcomponent.py=20?= =?UTF-8?q?-=20sidebar=5Ffilter=5Fcomponent.py=20-=20selection=5Fbar=5Floc?= =?UTF-8?q?ators.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components_derived/selection_bar_component.py | 176 +++++++++++++++--- locators/selection_bar_locators.py | 14 ++ 2 files changed, 162 insertions(+), 28 deletions(-) diff --git a/components_derived/selection_bar_component.py b/components_derived/selection_bar_component.py index 1f1f515..52837a9 100644 --- a/components_derived/selection_bar_component.py +++ b/components_derived/selection_bar_component.py @@ -6,6 +6,7 @@ from playwright.sync_api import Page, Locator, expect from tools.logger import get_logger from locators.selection_bar_locators import SelectionBarLocators +from locators.combobox_locators import ComboboxLocators from components.dropdown_list_component import DropdownList from components.base_component import BaseComponent @@ -18,78 +19,197 @@ class SelectionBarComponent(BaseComponent): Предоставляет методы для взаимодействия с элементами компонента панели выбора значения. """ - def __init__(self, page: Page, locator: str | Locator): + def __init__(self, page: Page, locator_or_text: str | Locator) -> None: """Инициализирует компонент панели выбора значения. Args: page: Экземпляр страницы Playwright. - locator: Локатор панели выбора значения (строка или объект Locator) + locator_or_text: Локатор панели выбора значения (строка или объект Locator) + или текст для поиска """ - super().__init__(page) - # Локатор панели параметра фильтрации - self.selection_bar_locator = self.get_locator(locator) + # Определяем локатор в зависимости от типа параметра + if isinstance(locator_or_text, Locator): + # Если передан готовый Locator + self.selection_bar_locator = locator_or_text + elif locator_or_text.startswith(('//', '.', '#', 'xpath=', 'css=')): + # Если передан строковый локатор + self.selection_bar_locator = self.get_locator(locator_or_text) + else: + # Если передан текст - ищем по тексту label + xpath = SelectionBarLocators.COMBOBOX_BY_LABEL_XPATH.format(locator_or_text) + self.selection_bar_locator = self.page.locator(xpath) # При нажатии на панель появляется выпадающий список с параметрами фильтрации для выбора self.selected_values_list = DropdownList(self.page) # Действия: def clear_selections(self) -> None: - """ Удаление ранее выбранных значений """ - + """Удаление ранее выбранных значений""" selected_values = self.get_selected_values() if len(selected_values) > 0: - clear_button_locator = self.selection_bar_locator.\ - locator(SelectionBarLocators.CLEAR_SELECTION_BUTTON) + clear_button_locator = self.selection_bar_locator.locator( + SelectionBarLocators.CLEAR_SELECTION_BUTTON + ) clear_button_locator.click() def get_available_options(self) -> list[str]: - """Возвращает список всех доступных опций из выпадающего списка """ + """Возвращает список всех доступных опций из выпадающего списка. - logger.info("Получение списка доступных опций из выпадающего списка...") + Returns: + list[str]: Список доступных опций + """ + logger.info("Getting available options from dropdown list...") # Открываем выпадающий список self.open_values_list() # Ждем появления списка - self.page.wait_for_timeout(1000) + self.wait_for_timeout(1000) # Получаем все элементы списка - options = self.selected_values_list.get_item_names(SelectionBarLocators.LIST_ITEMS) + options = self.selected_values_list.get_item_names( + SelectionBarLocators.LIST_ITEMS + ) # Закрываем список (кликаем вне его) self.page.mouse.click(10, 10) - self.page.wait_for_timeout(500) + self.wait_for_timeout(500) - logger.info(f"Найдено доступных опций: {len(options)} - {options}") + logger.info(f"Found available options: {len(options)} - {options}") return options def get_selection_bar_title(self) -> str: - """ Возвращает название панели выбора значения """ - - title_locator = self.selection_bar_locator.locator("//label") + """Возвращает название панели выбора значения""" + title_locator = self.selection_bar_locator.locator(SelectionBarLocators.TITLE_LOCATOR) return title_locator.text_content() def get_selected_values(self) -> list[str]: - """ Возвращает список выбранных значений """ - - selected_values_locator = self.selection_bar_locator.\ - locator(SelectionBarLocators.PARAMETERS_SELECTED) - + """Возвращает список выбранных значений""" + selected_values_locator = self.selection_bar_locator.locator( + SelectionBarLocators.PARAMETERS_SELECTED + ) selected_values = selected_values_locator.all_inner_texts() return selected_values[0].splitlines() - def open_values_list(self) -> None: - """ Открытие выпадающего списка путем нажатия на панель выбора значения """ + def clear_combobox_field(self, field_name: str, field_locator: str) -> None: + """Очищает значение в combobox поле с помощью кнопки закрытия (крестика). + Args: + field_name: Название поля для очистки + field_locator: Локатор поля combobox + """ + logger.info(f"Clearing combobox field '{field_name}' using close button...") + + # Находим поле по локатору + field_container = self.page.locator(field_locator).first + + # Проверяем что поле видимо + if not field_container.is_visible(): + logger.info(f"Field '{field_name}' is not visible, skipping clearing") + return + + # Прокручиваем до поля + field_container.scroll_into_view_if_needed() + self.wait_for_timeout(500) + + # Ищем кнопку закрытия (крестик) внутри контейнера поля + close_button = field_container.locator( + ComboboxLocators.COMBOBOX_CLOSE_BUTTON + ) + + # Проверяем наличие и видимость кнопки закрытия + if close_button.count() > 0 and close_button.is_visible(): + # Если кнопка закрытия видима - кликаем на нее + close_button.click() + self.wait_for_timeout(500) + logger.info(f"Combobox field '{field_name}' cleared using close button") + else: + # Если кнопки закрытия нет, просто логируем этот факт + msg = f"Close button not found for field '{field_name}', clearing not performed" + logger.info(msg) + + def open_values_list(self) -> None: + """Открытие выпадающего списка путем нажатия на панель выбора значения""" expect(self.selection_bar_locator).to_be_visible() - self.selection_bar_locator.click() + + # Проверяем, не открыт ли уже список + parent_class = self.selection_bar_locator.get_attribute("class") + if parent_class and SelectionBarLocators.MENU_ACTIVE_CLASS in parent_class: + logger.info("Values list is already open") + return + + # Используем force click для обхода перекрывающих элементов + logger.info("Using force click to open the list") + self.selection_bar_locator.click(force=True) + + # Ждем появления выпадающего списка + self.wait_for_timeout(1500) def select_value(self, name: str) -> None: - """ Выбор значения из списка """ - + """Выбор значения из списка""" self.selected_values_list.check_item_with_text(name) self.selected_values_list.click_item_with_text(name) + def wait_for_timeout(self, timeout: int) -> None: + """Ожидает указанное количество миллисекунд. + + Args: + timeout: Время ожидания в миллисекундах + """ + self.page.wait_for_timeout(timeout) + # Проверки: + + def check_field_highlighted_error(self, field_name: str, field_locator: str) -> None: + """Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). + + Args: + field_name: Название поля для проверки + field_locator: Локатор поля для проверки + """ + logger.info(f"Checking field '{field_name}' for error highlighting...") + + field_element = self.page.locator(field_locator).first + + # Проверяем что поле видимо + self.check_visibility(field_element, f"Field '{field_name}' not found") + + # Ищем родительский контейнер + parent_container = field_element.locator(SelectionBarLocators.INPUT_PARENT_CONTAINER).first + + # Проверка классов ошибки с использованием локатора из SelectionBarLocators + if parent_container.count() > 0: + has_error = parent_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS).count() > 0 + + if not has_error: + raise AssertionError(f"Field '{field_name}' is not highlighted with error color") + + logger.info(f"Field '{field_name}' is correctly highlighted with error color") + + def check_field_not_highlighted_error(self, field_name: str, field_locator: str) -> None: + """Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна). + + Args: + field_name: Название поля для проверки + field_locator: Локатор поля для проверки + """ + logger.info(f"Checking field '{field_name}' for absence of error highlighting...") + + field_element = self.page.locator(field_locator).first + + # Проверяем что поле видимо + self.check_visibility(field_element, f"Field '{field_name}' not found") + + # Ищем родительский контейнер + parent_container = field_element.locator(SelectionBarLocators.INPUT_PARENT_CONTAINER).first + + # Проверяем отсутствие классов ошибки с использованием локатора из SelectionBarLocators + if parent_container.count() > 0: + has_error = parent_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS).count() > 0 + + if has_error: + raise AssertionError(f"Field '{field_name}' is highlighted with error") + + logger.info(f"Field '{field_name}' correctly has no error highlighting") diff --git a/locators/selection_bar_locators.py b/locators/selection_bar_locators.py index d49d1c0..4f610c2 100644 --- a/locators/selection_bar_locators.py +++ b/locators/selection_bar_locators.py @@ -11,6 +11,7 @@ class SelectionBarLocators: - Кнопок открытия и очистки - Выбранных значений - Элементов выпадающего списка + - Combobox полей """ OPEN_PARAMETERS_LIST_BUTTON = "div.v-input__icon--append" @@ -20,3 +21,16 @@ class SelectionBarLocators: # Локаторы для элементов выпадающего списка LISTBOX = "//div[@role='listbox']" LIST_ITEMS = "//div[@role='listbox']//div[@role='listitem']" + + # Локатор для родительского контейнера поля ввода + INPUT_PARENT_CONTAINER = "xpath=./ancestor::div[contains(@class, 'v-input')]" + + # CSS селекторы для ошибок валидации + ERROR_CSS_SELECTORS = ".error--text, .v-input--error" + + # Локаторы для заголовков и поиска по тексту + TITLE_LOCATOR = "//label" + COMBOBOX_BY_LABEL_XPATH = "//div[@role='combobox' and .//label[text()='{}']]" + + # Класс для проверки активности меню + MENU_ACTIVE_CLASS = "v-select--is-menu-active"