e-nms_qa_automation/components/base_component.py

391 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""Базовый модуль для работы с компонентами страницы.
Содержит базовый класс для взаимодействия с элементами страницы через Playwright.
"""
from playwright.sync_api import Page, Locator, expect
from tools.logger import get_logger
logger = get_logger("BASE_COMPONENT")
logger.setLevel("INFO")
class BaseComponent:
"""Базовый компонент для работы с элементами страницы.
Предоставляет общие методы для взаимодействия с элементами:
- получение локаторов
- проверка видимости элементов
- работа с прокруткой
"""
def __init__(self, page: Page):
"""Инициализация базового компонента.
Args:
page: экземпляр страницы Playwright.
"""
self.page = page
# Действия:
def get_input_fields_locators(self, container_locator: Locator) -> dict:
"""Находит пары "метка-поле ввода" в контейнере с layout структурой.
Метод ищет элементы в структуре div.layout > div.flex, где:
- Первый div.flex содержит метку (текст в input элементе)
- Второй div.flex содержит соответствующее поле ввода
Поддерживает различные структуры:
- xs4 (метка) -> xs8 (поле ввода)
- xs4 (метка) -> xs1 (поле ввода)
- Любые другие парные flex контейнеры
Args:
container_locator: Контейнер, в котором искать поля ввода.
Returns:
Словарь, где ключ - текст метки, значение - Locator контейнера с полем ввода.
"""
fields_locators = {}
layouts = container_locator.locator("div.layout > div.flex").locator("..")
for i in range(layouts.count()):
layout = layouts.nth(i)
flex_containers = layout.locator("div.flex")
# Обрабатываем пары контейнеров
for j in range(0, flex_containers.count() - 1, 2):
label_container = flex_containers.nth(j)
input_container = flex_containers.nth(j + 1)
# Извлекаем текст метки
inputs = label_container.locator("input")
if inputs.count() > 0:
label_text = inputs.first.input_value().strip()
if label_text:
# Проверяем поле ввода
has_input = input_container.locator(
"input, textarea, select"
).count() > 0
not_found = fields_locators.get(label_text) is None
if has_input and not_found:
fields_locators[label_text] = input_container
return fields_locators
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_timeout(self, timeout: int) -> None:
"""
Ожидает указанное количество миллисекунд.
Args:
timeout: Время ожидания в миллисекундах
"""
self.page.wait_for_timeout(timeout)
# Закомментированный код сохранен без изменений
# 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}"
)