253 lines
9.5 KiB
Python
253 lines
9.5 KiB
Python
"""Модуль 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) -> None:
|
||
"""Инициализирует компонент выпадающего списка.
|
||
|
||
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_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("Getting combobox options list...")
|
||
|
||
# Открываем 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"Found options: {len(options_list)} - {options_list}")
|
||
return options_list
|
||
|
||
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 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"Selected combobox value: '{selected_value}'")
|
||
return selected_value
|
||
|
||
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("Opening 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 click completed")
|
||
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("Dropdown list found and opened")
|
||
else:
|
||
logger.warning("Failed to open dropdown list")
|
||
|
||
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 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
|