"""Базовый модуль для работы с компонентами страницы. Содержит базовый класс для взаимодействия с элементами страницы через Playwright. """ from playwright.sync_api import Page, Locator, expect from tools.logger import get_logger logger = get_logger("BASE_COMPONENT") class BaseComponent: """Базовый компонент для работы с элементами страницы. Предоставляет общие методы для взаимодействия с элементами: - получение локаторов - проверка видимости элементов - работа с прокруткой """ def __init__(self, page: Page): """Инициализация базового компонента. Args: page: экземпляр страницы Playwright. """ self.page = page # Действия: def get_locator(self, locator: str | Locator) -> Locator: """Получение объекта Locator из строки или существующего Locator. Args: locator: строка с CSS/XPath селектором или объект Locator. Returns: Locator: объект для работы с элементом. Raises: TypeError: если передан некорректный тип локатора. """ if isinstance(locator, Locator): return locator elif isinstance(locator, str): return self.page.locator(locator) else: raise TypeError("locator value should be string type or Locator type") # Закомментированный код сохранен без изменений # def wait_for_all_elements(self, locator: Locator, timeout=5000): # loc = self.get_locator(locator) # elements = self.page.locator(loc).all() # # for element in elements: # self.page.locator(loc).wait_for(timeout=timeout) # # return elements # Проверки: def check_absence(self, locator: str | Locator, msg: str) -> None: """Проверка отсутствия элемента на странице. Args: locator: локатор элемента (строка или объект Locator). msg: сообщение об ошибке при неудачной проверке. Raises: AssertionError: если элемент виден на странице. """ loc = self.get_locator(locator) expect(loc).to_be_hidden(timeout=12000), msg def check_visibility(self, locator: str | Locator, msg: str) -> None: """Проверка видимости элемента на странице. Args: locator: локатор элемента (строка или объект Locator). msg: сообщение об ошибке при неудачной проверке. Raises: AssertionError: если элемент не виден на странице. """ loc = self.get_locator(locator) expect(loc).to_be_visible(visible=True, timeout=12000), msg def is_scrollable_vertically(self, locator: str | Locator) -> bool: """Проверка возможности вертикальной прокрутки элемента. Args: locator: локатор элемента. Returns: bool: True если элемент можно прокрутить вертикально. """ loc = self.get_locator(locator) return loc.evaluate("el => el.scrollHeight > el.clientHeight") def is_scrollable_horizontally(self, locator: str | Locator) -> bool: """Проверка возможности горизонтальной прокрутки элемента. Args: locator: локатор элемента. Returns: bool: True если элемент можно прокрутить горизонтально. """ loc = self.get_locator(locator) return loc.evaluate("el => el.scrollWidth > el.clientWidth") # Методы прокрутки: def scroll_up(self, locator: str | Locator, timeout: int = 10000) -> None: """Прокрутка элемента до самого верха. Args: locator: локатор элемента. timeout: максимальное время ожидания в мс. Raises: AssertionError: если прокрутка не выполнена до верха. """ loc = self.get_locator(locator) max_attempts = 3 attempt = 0 while attempt < max_attempts: # Получаем текущую позицию прокрутки previous_position = loc.evaluate("el => el.scrollTop") # Прокручиваем до верха loc.evaluate("el => el.scrollTo(0, 0)") # Ждем завершения прокрутки с использованием timeout loc.wait_for(timeout=min(2000, timeout)) # Получаем новую позицию прокрутки current_position = loc.evaluate("el => el.scrollTop") # Проверяем, достигли ли мы верха if current_position <= 1: # <= 1 для учета погрешности return # Успешно прокрутили до верха # Проверяем, была ли вообще прокрутка if current_position >= previous_position: attempt += 1 else: attempt = 0 # Сбрасываем счетчик, если была прокрутка # Если вышли из цикла - не смогли прокрутить до верха final_position = loc.evaluate("el => el.scrollTop") scroll_height = loc.evaluate("el => el.scrollHeight") client_height = loc.evaluate("el => el.clientHeight") raise AssertionError( f"Не удалось прокрутить до верха. " f"Текущая позиция: {final_position}, ожидалось: 0, " f"Высота контента: {scroll_height}, " f"Высота видимой области: {client_height}" ) def scroll_down(self, locator: str | Locator, timeout: int = 10000) -> None: """Прокрутка элемента до самого конца. Args: locator: локатор элемента. timeout: максимальное время ожидания в мс. Raises: AssertionError: если прокрутка не выполнена до конца. """ loc = self.get_locator(locator) max_attempts = 3 attempt = 0 while attempt < max_attempts: # Получаем текущую позицию прокрутки и размеры current_position = loc.evaluate("el => el.scrollTop") scroll_height = loc.evaluate("el => el.scrollHeight") client_height = loc.evaluate("el => el.clientHeight") # Проверяем, достигли ли мы конца ДО прокрутки if current_position + client_height >= scroll_height - 5: # -5 для учета погрешности return # Уже прокручено до конца # Прокручиваем до конца loc.evaluate("el => el.scrollTo(0, el.scrollHeight)") # Ждем загрузки контента loc.wait_for(timeout=min(2000, timeout)) # Получаем новую позицию прокрутки new_position = loc.evaluate("el => el.scrollTop") new_scroll_height = loc.evaluate("el => el.scrollHeight") new_client_height = loc.evaluate("el => el.clientHeight") # Проверяем, достигли ли мы конца if new_position + new_client_height >= new_scroll_height - 5: # -5 для учета погрешности return # Успешно прокрутили до конца # Проверяем, была ли прокрутка if new_position <= current_position and new_scroll_height <= scroll_height: # Прокрутки не произошло или контент не изменился attempt += 1 else: attempt = 0 # Сбрасываем счетчик, если была прокрутка # Если вышли из цикла - не смогли прокрутить до конца final_position = loc.evaluate("el => el.scrollTop") final_scroll_height = loc.evaluate("el => el.scrollHeight") final_client_height = loc.evaluate("el => el.clientHeight") max_scroll_y = final_scroll_height - final_client_height raise AssertionError( f"Не удалось прокрутить до конца. " f"Текущая позиция: {final_position}, ожидалось: {max_scroll_y}, " f"Высота контента: {final_scroll_height}, " f"Высота видимой области: {final_client_height}, " f"Сумма: {final_position + final_client_height}" ) def scroll_left(self, locator: str | Locator, timeout: int = 10000) -> None: """Прокрутка элемента до самого левого края. Args: locator: локатор элемента. timeout: максимальное время ожидания в мс. Raises: AssertionError: если прокрутка не выполнена до левого края. """ loc = self.get_locator(locator) max_attempts = 3 attempt = 0 while attempt < max_attempts: # Получаем текущую позицию прокрутки previous_position = loc.evaluate("el => el.scrollLeft") # Прокручиваем до левого края loc.evaluate("el => el.scrollTo(0, el.scrollTop)") # Ждем завершения прокрутки с использованием timeout loc.wait_for(timeout=min(2000, timeout)) # Получаем новую позицию прокрутки current_position = loc.evaluate("el => el.scrollLeft") # Проверяем, достигли ли мы левого края if current_position <= 1: # <= 1 для учета погрешности return # Успешно прокрутили до левого края # Проверяем, была ли вообще прокрутка if current_position >= previous_position: attempt += 1 else: attempt = 0 # Сбрасываем счетчик, если была прокрутка # Если вышли из цикла - не смогли прокрутить до левого края final_position = loc.evaluate("el => el.scrollLeft") scroll_width = loc.evaluate("el => el.scrollWidth") client_width = loc.evaluate("el => el.clientWidth") raise AssertionError( f"Не удалось прокрутить до левого края. " f"Текущая позиция: {final_position}, ожидалось: 0, " f"Ширина контента: {scroll_width}, " f"Ширина видимой области: {client_width}" ) def scroll_right(self, locator: str | Locator, timeout: int = 10000) -> None: """Прокрутка элемента до самого правого края. Args: locator: локатор элемента. timeout: максимальное время ожидания в мс. Raises: AssertionError: если прокрутка не выполнена до правого края. """ loc = self.get_locator(locator) max_attempts = 3 attempt = 0 while attempt < max_attempts: # Получаем текущую позицию прокрутки previous_position = loc.evaluate("el => el.scrollLeft") # Прокручиваем до правого края loc.evaluate("el => el.scrollTo(el.scrollWidth, el.scrollTop)") # Ждем завершения прокрутки с использованием timeout loc.wait_for(timeout=min(2000, timeout)) # Получаем новую позицию прокрутки current_position = loc.evaluate("el => el.scrollLeft") scroll_width = loc.evaluate("el => el.scrollWidth") client_width = loc.evaluate("el => el.clientWidth") # Проверяем, достигли ли мы правого края max_scroll_x = scroll_width - client_width if current_position >= max_scroll_x - 1: # -1 для учета погрешности return # Успешно прокрутили до правого края # Проверяем, была ли вообще прокрутка if current_position <= previous_position: attempt += 1 else: attempt = 0 # Сбрасываем счетчик, если была прокрутка # Если вышли из цикла - не смогли прокрутить до правого края final_position = loc.evaluate("el => el.scrollLeft") scroll_width = loc.evaluate("el => el.scrollWidth") client_width = loc.evaluate("el => el.clientWidth") max_scroll_x = scroll_width - client_width raise AssertionError( f"Не удалось прокрутить до правого края. " f"Текущая позиция: {final_position}, ожидалось: {max_scroll_x}, " f"Ширина контента: {scroll_width}, " f"Ширина видимой области: {client_width}" )