Исходный код components.base_component
"""Базовый модуль для работы с компонентами страницы.
Содержит базовый класс для взаимодействия с элементами страницы через 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}"
)