"""Модуль dropdown_list_component содержит класс для работы с выпадающими списками. Класс DropdownList наследует базовый функционал BaseComponent и добавляет методы для взаимодействия с выпадающими списками на странице. """ import re from playwright.sync_api import Page, Locator from tools.logger import get_logger from components.base_component import BaseComponent logger = get_logger("DROPDOWN_LIST") class DropdownList(BaseComponent): """Класс для работы с выпадающими списками. Наследует функциональность BaseElement и добавляет специфичные методы для выбора и проверки элементов списка. """ def __init__(self, page: Page): """Инициализирует компонент выпадающего списка. Args: page: Экземпляр страницы Playwright. """ super().__init__(page) # Действия: def click_item_with_text(self, text: str) -> None: """Выбирает элемент списка по указанному тексту. Args: text (str): Текст элемента для выбора. """ element = self.page.get_by_role("listitem").filter(has_text=text) if element.count() > 1: rtext = f"^{text}$" element = self.page.get_by_role("listitem").filter(has_text=re.compile(rtext)) element.click() def get_item_names(self, locator: str | Locator) -> list[str]: """Возвращает тексты всех элементов по указанному локатору. Args: locator: Локатор элементов или строка с CSS/XPath. Returns: Список текстов элементов. """ loc = self.get_locator(locator) texts = loc.all_inner_texts() return texts[0].splitlines() def scroll_until_end(self, locator: str | Locator) -> None: """ Скроллит список до тех пор, пока не перестанут подгружаться новые элементы. Args: locator: Локатор элементов или строка с CSS/XPath. """ loc = self.get_locator(locator) items_count = 0 attempts = 0 max_attempts = 3 last_item_name = "" while attempts < max_attempts: self.page.wait_for_timeout(300) item_names = self.get_item_names(loc) current_count = len(item_names) if current_count == items_count: attempts += 1 else: items_count = current_count attempts = 0 last_item_name = item_names[current_count-1] element = self.page.get_by_role("listitem").filter(has_text=last_item_name) element.scroll_into_view_if_needed() self.page.wait_for_timeout(300) self.check_item_with_text(last_item_name) def open_combobox(self, combobox_locator: str | Locator, listbox_locator: str | Locator, icon_locator: str | Locator = None) -> None: """ Открывает выпадающий список combobox. Args: combobox_locator: Локатор combobox listbox_locator: Локатор выпадающего списка icon_locator: Локатор иконки для клика (опционально) """ logger.info("Открытие combobox...") combobox = self.get_locator(combobox_locator) listbox = self.get_locator(listbox_locator) # Прокручиваем до combobox combobox.scroll_into_view_if_needed() self.page.wait_for_timeout(1000) # Проверяем, не открыт ли уже список listbox_already_open = False listbox_count = listbox.count() if listbox_count > 0: listbox_already_open = listbox.first.is_visible() if not listbox_already_open: # Если указан локатор иконки, кликаем на него, иначе на сам combobox if icon_locator: icon = combobox.locator(icon_locator) icon.scroll_into_view_if_needed() icon.click(timeout=10000) else: combobox.click(timeout=10000) logger.info("Клик на combobox выполнен") self.page.wait_for_timeout(1000) # Проверяем что список открылся listbox_count_after = listbox.count() listbox_visible = False if listbox_count_after > 0: listbox_visible = listbox.first.is_visible() if listbox_visible: logger.info("Выпадающий список найден и открыт") else: logger.warning("Не удалось открыть выпадающий список") def get_combobox_options(self, combobox_locator: str | Locator, listbox_locator: str | Locator, icon_locator: str | Locator = None) -> list[str]: """ Получает список доступных опций из combobox. Args: combobox_locator: Локатор combobox listbox_locator: Локатор выпадающего списка icon_locator: Локатор иконки для клика (опционально) Returns: list[str]: Список доступных опций """ logger.info("Получение списка опций combobox...") # Открываем combobox (если еще не открыт) self.open_combobox(combobox_locator, listbox_locator, icon_locator) options_list = self.get_item_names(listbox_locator) # Закрываем combobox (кликаем вне его) self.page.mouse.click(10, 10) self.page.wait_for_timeout(500) logger.info(f"Найдено опций: {len(options_list)} - {options_list}") return options_list def get_selected_combobox_value(self, combobox_locator: str | Locator, value_locator: str | Locator = None) -> str: """ Получает выбранное значение из combobox. Args: combobox_locator: Локатор combobox value_locator: Локатор элемента с выбранным значением (опционально) Returns: str: Выбранное значение или пустая строка если ничего не выбрано """ combobox = self.get_locator(combobox_locator) selected_value = "" if value_locator: # Используем переданный локатор для значения value_element = combobox.locator(value_locator) if value_element.count() > 0: selected_value = value_element.first.text_content().strip() else: # Ищем в span элементах по умолчанию span_locator = combobox.locator("span") if span_locator.count() > 0: for i in range(span_locator.count()): span_text = span_locator.nth(i).text_content().strip() if span_text and span_text not in ["Класс объекта учета"]: # Можно сделать исключения параметром selected_value = span_text break logger.info(f"Выбранное значение combobox: '{selected_value}'") return selected_value # Проверки: def check_item_with_text(self, text: str) -> None: """Проверяет наличие и доступность элемента списка. Args: text (str): Текст элемента для проверки. Raises: AssertionError: Если элемент отсутствует или недоступен. """ element = self.page.get_by_role("listitem").filter(has_text=text) if element.count() > 1: rtext = f"^{text}$" element = self.page.get_by_role("listitem").filter(has_text=re.compile(rtext)) enabled = element.is_enabled() if not enabled: assert False, f"Dropdown list item '{text}' is missing or disabled" def check_vertical_scrolling(self, locator: str | Locator) -> bool: """ Проверяет функцию вертикального скроллинга списка. Args: locator: Локатор элементов или строка с CSS/XPath. Returns: True или False значение в зависимости от скроллируемый список или нет. """ loc = self.get_locator(locator) is_scrollable_vertically = self.is_scrollable_vertically(loc) if is_scrollable_vertically: self.scroll_until_end(loc) item_names = self.get_item_names(loc) first_item_name = item_names[0] self.scroll_up(loc) self.page.wait_for_timeout(300) self.check_item_with_text(first_item_name) return is_scrollable_vertically