"""Модуль компонента таблицы. Содержит класс для работы с табличными данными."""
from datetime import datetime
from playwright.sync_api import Page, expect, Locator
from tools.logger import get_logger
from components.base_component import BaseComponent
logger = get_logger("TABLE")
[документация]
class TableComponent(BaseComponent):
"""Компонент таблицы. Предоставляет методы для работы с табличными данными."""
[документация]
def __init__(self, page: Page):
"""Инициализирует компонент таблицы.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page)
# Действия:
[документация]
def datetime2timestamp(self, date_string: str) -> float|None:
""" Конвертация строкового представления даты и времени в Unix timestamp
Args:
date_string: Строка с датой и временем в формате d.m.Y H:M:S.
Returns:
float: Unix timestamp.
None: конвертация невозможна
"""
# Формат, соответствующий строке с датой и временем
format_string = "%d.%m.%Y %H:%M:%S"
try:
date_object = datetime.strptime(date_string, format_string)
return date_object.timestamp()
except ValueError:
return None
[документация]
def get_row_locator(self, table_locator: str | Locator, row_index: int) -> Locator | None:
"""Возвращает локатор строки по индексу.
Args:
table_locator: Локатор таблицы.
row_index: Индекс строки.
Returns:
Локатор строки или None, если индекс вне диапазона.
"""
table = self.get_locator(table_locator)
rows = table.locator("//tbody/tr")
if row_index in range(rows.count()):
return rows.nth(row_index)
else:
return None
[документация]
def get_rows_count(self, locator: str | Locator) -> int:
"""Возвращает количество строк в таблице (без заголовка).
Returns:
int: Количество строк с данными.
Raises:
AssertionError: Если таблица пуста.
"""
table_content = self.read(locator)
rows_count = len(table_content)
if rows_count == 0:
assert False, "The contents of the table are missing"
return rows_count - 1
[документация]
def read(self, locator: str | Locator) -> list[list[str]]:
"""Читает данные таблицы, включая заголовки.
Args:
locator: Локатор таблицы.
Returns:
Двумерный список с данными таблицы.
"""
table_data = []
table = self.get_locator(locator)
# Чтение заголовка таблицы
header_cells = table.locator("//thead/tr")
header_cell_text = header_cells.nth(0).inner_text()
header_data = header_cell_text.split('\n')
table_data.append(header_data)
# Чтение ячеек таблицы
rows = table.locator("//tbody/tr")
for i in range(rows.count()):
row = rows.nth(i)
cells = row.locator("td")
row_data = []
for j in range(cells.count()):
cell_text = cells.nth(j).inner_text()
row_data.append(cell_text)
table_data.append(row_data)
return table_data
# Проверки:
[документация]
def check_content(self,
locator: str | Locator,
expected_headers: list[str],
check_table_not_empty: bool = True) -> None:
"""Проверяет содержимое таблицы.
Проверяет заголовки и наличие данных в таблице.
Args:
locator: Локатор таблицы.
expected_headers: Список ожидаемых заголовков таблицы.
check_table_not_empty: Флаг проверки, что таблица не пустая.
По умолчанию True.
Raises:
AssertionError: Если таблица пуста (при check_table_not_empty=True)
или заголовки неверны.
"""
table_content = self.read(locator)
if len(table_content) == 0:
assert False, "The contents of the table are missing"
# Проверка заголовков таблицы
self.check_table_headers(table_content[0], expected_headers)
# Проверка наличия данных в таблице
if len(table_content) == 1:
if check_table_not_empty:
assert False, "Table body is missing"
else:
logger.info("Таблица пустая (не содержит строк с данными)")
[документация]
def check_column_descending_order(self,
locator: str | Locator,
index: int,
convert2timestamp=False) -> bool:
"""Проверка, что заданный столбец таблицы упорядочен по убыванию.
Args:
locator: Локатор таблицы.
index: Индекс столбца.
convert2timestamp: Конвертировать строковое представление даты и времени в Unix timestamp
Returns:
True, если столбец таблицы упорядочен по убыванию. Иначе: False
"""
table_content = self.read(locator)
if len(table_content) == 0:
assert False, "The contents of the table are missing"
del table_content[0]
assert index in range(len(table_content[0])), \
"Column index is out of range"
column = []
for i in range(len(table_content)):
if convert2timestamp:
timestamp = self.datetime2timestamp(table_content[i][index])
assert timestamp, f"Error conversation to timestamp for {table_content[i][index]}"
column.append(timestamp)
else:
column.append(table_content[i][index])
return all(column[i] >= column[i+1] for i in range(len(column) - 1))
[документация]
def check_first_row_visibility(self, locator: str | Locator) -> None:
"""Проверяет видимость первой строки таблицы.
Args:
locator: Локатор таблицы.
"""
table = self.get_locator(locator)
first_row = table.locator("//tbody/tr").first
expect(first_row).to_be_visible(), "The first table row is not visible"
[документация]
def check_last_row_visibility(self, locator: str | Locator) -> None:
"""Проверяет видимость последней строки таблицы.
Args:
locator: Локатор таблицы.
"""
table = self.get_locator(locator)
last_row = table.locator("//tbody/tr").last
expect(last_row).to_be_visible(), "The last table row is not visible"
[документация]
def check_row_highlighting(self, locator: str | Locator, row_index: int) -> None:
"""Проверяет изменение цвета строки при наведении.
Args:
locator: Локатор таблицы.
row_index: Индекс проверяемой строки.
"""
table = self.get_locator(locator)
row = table.locator("//tbody/tr").nth(row_index)
row.scroll_into_view_if_needed()
hover_element = row.locator(".body-row-hover")
initial_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
row.hover()
self.page.wait_for_timeout(300)
new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
assert initial_color != new_color, "Color of row did not change when hovering the cursor"