347 lines
14 KiB
Python
347 lines
14 KiB
Python
"""Модуль вкладки 'Статус обслуживания'.
|
||
|
||
Содержит класс ServiceStatusTab для работы с таблицей сервисов.
|
||
Позволяет проверять состояние и взаимодействовать с элементами вкладки.
|
||
"""
|
||
|
||
import math
|
||
from playwright.sync_api import Page, Locator, expect
|
||
from elements.text_element import Text
|
||
from elements.button_element import Button
|
||
from components.table_component import TableComponent
|
||
from components.expand_button_component import ExpandButton
|
||
from pages.base_page import BasePage
|
||
|
||
|
||
class ServiceStatusTab(BasePage):
|
||
"""Класс для работы с вкладкой 'Статус обслуживания'.
|
||
|
||
Предоставляет методы для взаимодействия с таблицей сервисов и проверки
|
||
её состояния.
|
||
|
||
Args:
|
||
page: Экземпляр страницы Playwright.
|
||
"""
|
||
|
||
def __init__(self, page: Page) -> None:
|
||
"""Инициализирует компоненты вкладки 'Статус обслуживания'."""
|
||
|
||
super().__init__(page)
|
||
|
||
self.iframe_container_locator = page.locator("div.container.iframe-wrapper")
|
||
self.iframe_locator = page.frame_locator("//iframe[@class='iframe-class']")
|
||
self.table_locator = self.iframe_locator.locator("div.MuiBox-root > div > table")
|
||
self.tab_title_locator = self.iframe_locator.locator("//h5[text()='Состояние контейнеров']")
|
||
self.update_button_locator = self.iframe_locator.get_by_role("button", name='Обновить')
|
||
self.analyzer_open_button_locator = self.iframe_locator.get_by_role("button", name='открыть анализатор')
|
||
|
||
self.tab_title = Text(page,
|
||
self.tab_title_locator,
|
||
"tab_title")
|
||
self.update_button = Button(page,
|
||
self.update_button_locator,
|
||
"update_button")
|
||
self.analyzer_open_button = Button(page,
|
||
self.analyzer_open_button_locator,
|
||
"analyzer_open_button")
|
||
self.expand_work_area_button = ExpandButton(page)
|
||
self.services_table = TableComponent(page)
|
||
|
||
# Действия:
|
||
def calculate_coordinates(self, locator: Locator) -> dict:
|
||
"""Вычисление координат элемента для манипуляции с ним мышкой"""
|
||
|
||
coordinates = {}
|
||
|
||
offsets_scales = self.get_offsets_scales()
|
||
|
||
bounding_box = locator.evaluate("el => el.getBoundingClientRect()")
|
||
assert bounding_box, "Required element is not visible"
|
||
|
||
offset_x = offsets_scales["offset_x"]
|
||
offset_y = offsets_scales["offset_y"]
|
||
scale_x = offsets_scales["scale_x"]
|
||
scale_y = offsets_scales["scale_y"]
|
||
|
||
coordinates["center_x"] = (bounding_box["x"] + bounding_box["width"] / 2) * scale_x + offset_x
|
||
coordinates["center_y"] = (bounding_box["y"] + bounding_box["height"] / 2) * scale_y + offset_y
|
||
return coordinates
|
||
|
||
def get_offsets_scales(self) -> dict:
|
||
"""Возвращает словарь, содержащий смещения контейнера фрейма и его коэффициенты"""
|
||
|
||
offsets_scales = {}
|
||
|
||
iframe_container_bounding_box = self.iframe_container_locator.\
|
||
evaluate("el => el.getBoundingClientRect()")
|
||
assert iframe_container_bounding_box, "Iframe container is not visible"
|
||
|
||
iframe_bounding_box = self.iframe_locator.locator("div#root > div"). \
|
||
evaluate("el => el.getBoundingClientRect()")
|
||
assert iframe_bounding_box, "Iframe content is not visible"
|
||
|
||
offsets_scales["offset_x"] = iframe_container_bounding_box["x"]
|
||
offsets_scales["offset_y"] = iframe_container_bounding_box["y"]
|
||
offsets_scales["scale_x"] = iframe_container_bounding_box["width"]/iframe_bounding_box["width"]
|
||
offsets_scales["scale_y"] = iframe_container_bounding_box["height"]/iframe_bounding_box["height"]
|
||
|
||
return offsets_scales
|
||
|
||
def datetime2timestamp(self, date_column: str) -> list[float]:
|
||
""" Конвертация строкового представления даты и времени в Unix timestamp
|
||
"""
|
||
|
||
converted = []
|
||
|
||
for date in date_column:
|
||
timestamp = self.services_table.datetime2timestamp(date,format_string="%d-%m-%y %H:%M:%S")
|
||
assert timestamp, f"Error conversation to timestamp for {date}"
|
||
converted.append(timestamp)
|
||
|
||
return converted
|
||
|
||
def click_update_button(self) -> None:
|
||
"""Нажатие кнопки 'Обновить'"""
|
||
|
||
coordinates = self.calculate_coordinates(self.update_button_locator)
|
||
self.page.mouse.click(math.ceil(coordinates["center_x"]),
|
||
math.ceil(coordinates["center_y"]), delay=300)
|
||
|
||
def get_column(self, index: int) -> list[str]:
|
||
"""Возвращает столбец таблицы по индексу.
|
||
|
||
Returns:
|
||
list[str]: Столбец таблицы по индексу.
|
||
|
||
Raises:
|
||
AssertionError: Если таблица пуста или столбца с таким индексом в таблице нет.
|
||
"""
|
||
|
||
return self.services_table.get_column(self.table_locator, index)
|
||
|
||
def get_rows_count(self) -> int:
|
||
"""Возвращает количество строк в таблице (без заголовка).
|
||
|
||
Returns:
|
||
int: Количество строк с данными.
|
||
|
||
Raises:
|
||
AssertionError: Если таблица пуста.
|
||
"""
|
||
|
||
return self.services_table.get_rows_count(self.table_locator)
|
||
|
||
def expand_tab(self) -> None:
|
||
"""Расширяет рабочую область складки."""
|
||
|
||
iframe_container_bounding_box = self.iframe_container_locator.\
|
||
evaluate("el => el.getBoundingClientRect()")
|
||
widht_before = iframe_container_bounding_box["width"]
|
||
|
||
self.expand_work_area_button.expand()
|
||
|
||
iframe_container_bounding_box = self.iframe_container_locator.\
|
||
evaluate("el => el.getBoundingClientRect()")
|
||
widht_after = iframe_container_bounding_box["width"]
|
||
|
||
assert widht_before < widht_after,"Services statuses tab should be expanded"
|
||
|
||
def reduce_tab(self) -> None:
|
||
"""Сжимает рабочую область складки."""
|
||
|
||
iframe_container_bounding_box = self.iframe_container_locator.\
|
||
evaluate("el => el.getBoundingClientRect()")
|
||
widht_before = iframe_container_bounding_box["width"]
|
||
|
||
self.expand_work_area_button.reduce()
|
||
|
||
iframe_container_bounding_box = self.iframe_container_locator.\
|
||
evaluate("el => el.getBoundingClientRect()")
|
||
widht_after = iframe_container_bounding_box["width"]
|
||
|
||
assert widht_before > widht_after,"Services statuses tab should be reduced"
|
||
|
||
def scroll_services_tab_up(self) -> None:
|
||
"""Прокручивает содержимое вкладки вверх."""
|
||
|
||
self.tab_title_locator.scroll_into_view_if_needed()
|
||
|
||
def scroll_services_tab_down(self) -> None:
|
||
"""Прокручивает содержимое вкладки вниз."""
|
||
|
||
nrows = self.get_rows_count()
|
||
last_row = self.services_table.get_row_locator(self.table_locator, nrows-1)
|
||
last_row.scroll_into_view_if_needed()
|
||
|
||
# Проверки:
|
||
def check_services_table_content(self) -> None:
|
||
"""Проверяет содержимое таблицы сервисов.
|
||
|
||
Проверяет заголовки и наличие данных в таблице.
|
||
|
||
Raises:
|
||
AssertionError: Если таблица пуста или заголовки неверны.
|
||
"""
|
||
|
||
expected_headers = [
|
||
'СТЕК',
|
||
'КОНТЕЙНЕР',
|
||
'ТЭГ',
|
||
'СТАТУС',
|
||
'ВРЕМЯ СОЗДАНИЯ',
|
||
'ЦЕЛОСТНОСТЬ',
|
||
'ДЕЙСТВИЯ'
|
||
]
|
||
|
||
self.services_table.check_content(self.table_locator, expected_headers)
|
||
|
||
# Проверяем наличие кнопок и тултипов в последней ячейке строки
|
||
rows_count = self.services_table.get_rows_count(self.table_locator)
|
||
for i in range(rows_count):
|
||
row_locator = self.services_table.get_row_locator(self.table_locator, i)
|
||
|
||
layers_button = row_locator.get_by_role("button", name="Слои")
|
||
expect(layers_button).to_be_visible(), f"Layers button is missing in {i} row"
|
||
self.check_tooltip(layers_button, "Слои")
|
||
|
||
lounch_button = row_locator.get_by_role("button", name="Запуск", exact=True)
|
||
expect(lounch_button).to_be_visible(), f"Lounch button is missing in {i} row"
|
||
self.check_tooltip(lounch_button, "Запуск")
|
||
|
||
stop_button = row_locator.get_by_role("button", name="Остановка")
|
||
expect(stop_button).to_be_visible(), f"Stop button is missing in {i} row"
|
||
self.check_tooltip(stop_button, "Остановка")
|
||
|
||
restart_button = row_locator.get_by_role("button", name="Перезапуск", exact=True)
|
||
expect(restart_button).to_be_visible(), f"Restart button is missing in {i} row"
|
||
self.check_tooltip(restart_button, "Перезапуск")
|
||
|
||
|
||
def check_tooltip(self, button_locator: Locator, expected_text: str) -> None:
|
||
"""Проверка текста тултипа кнопки в последней ячейке строки"""
|
||
|
||
# Наведение на элемент для отображения подсказки
|
||
coordinates = self.calculate_coordinates(button_locator)
|
||
|
||
self.page.mouse.move(math.ceil(coordinates["center_x"]),
|
||
math.ceil(coordinates["center_y"]), steps=3)
|
||
|
||
# Получение элемента подсказки и ее текста
|
||
tooltip = self.iframe_locator.get_by_role("tooltip").locator("div.MuiTooltip-tooltip").first
|
||
tooltip.wait_for(state="visible", timeout=5000)
|
||
|
||
actual_text = tooltip.text_content().strip()
|
||
assert expected_text==actual_text, f"Should be tooltip with text: {expected_text}, got: {actual_text}"
|
||
|
||
def check_services_table_verticall_scrolling(self) -> bool:
|
||
"""Проверяет возможность вертикальной прокрутки таблицы.
|
||
|
||
Returns:
|
||
bool: True если прокрутка возможна, иначе False.
|
||
"""
|
||
|
||
loc = self.iframe_locator.locator("div.MuiBox-root > div > div").first
|
||
return loc.evaluate("el => el.scrollHeight > el.clientHeight")
|
||
|
||
def check_services_tab_title_visibility(self) -> None:
|
||
"""Проверяет видимость заголовка вкладки.
|
||
|
||
Raises:
|
||
AssertionError: Если строка не видна.
|
||
"""
|
||
|
||
self.tab_title.check_visibility("The services tab title is not visible")
|
||
|
||
# def check_services_table_first_row_visibility(self) -> None:
|
||
# """Проверяет видимость первой строки таблицы.
|
||
|
||
# Raises:
|
||
# AssertionError: Если строка не видна.
|
||
# """
|
||
|
||
# self.services_table.check_first_row_visibility(self.table_locator)
|
||
|
||
def check_services_table_last_row_visibility(self) -> None:
|
||
"""Проверяет видимость последней строки таблицы.
|
||
|
||
Raises:
|
||
AssertionError: Если строка не видна.
|
||
"""
|
||
|
||
self.services_table.check_last_row_visibility(self.table_locator)
|
||
|
||
def check_services_table_row_highlighting(self, row_index: int) -> None:
|
||
"""Проверяет выделение указанной строки таблицы.
|
||
|
||
Args:
|
||
row_index: Индекс проверяемой строки.
|
||
|
||
Raises:
|
||
AssertionError: Если строка не выделена.
|
||
"""
|
||
|
||
offsets_scales = self.get_offsets_scales()
|
||
|
||
self.services_table.check_mui_table_row_highlighting(
|
||
self.table_locator,
|
||
row_index,
|
||
offsets_scales["offset_x"],
|
||
offsets_scales["offset_y"],
|
||
offsets_scales["scale_x"],
|
||
offsets_scales["scale_y"]
|
||
)
|
||
|
||
def should_be_tab_title(self) -> None:
|
||
"""Проверяет наличие заголовка вкладки.
|
||
|
||
Raises:
|
||
AssertionError: Если заголовок отсутствует.
|
||
"""
|
||
|
||
self.tab_title.check_visibility(
|
||
"Title of service statuses tab is missing"
|
||
)
|
||
|
||
def should_be_services_table(self) -> None:
|
||
"""Проверяет наличие таблицы сервисов.
|
||
|
||
Raises:
|
||
AssertionError: Если таблица отсутствует.
|
||
"""
|
||
|
||
self.services_table.check_visibility(
|
||
self.table_locator,
|
||
"Service statuses table is missing"
|
||
)
|
||
|
||
def should_be_analyzer_open_button(self) -> None:
|
||
"""Проверяет наличие кнопки 'navigate_before/navigate_next'.
|
||
|
||
Raises:
|
||
AssertionError: Если кнопка отсутствует.
|
||
"""
|
||
|
||
self.analyzer_open_button.check_visibility(
|
||
"Analyzer open button on bottom of service statuses tab is missing"
|
||
)
|
||
|
||
def should_be_expand_work_area_button(self) -> None:
|
||
"""Проверяет наличие кнопки расширения/сжатия рабочей области вкладки.
|
||
|
||
Raises:
|
||
AssertionError: Если кнопка отсутствует.
|
||
"""
|
||
|
||
self.expand_work_area_button.should_be_button()
|
||
|
||
def should_be_update_button(self) -> None:
|
||
"""Проверяет наличие кнопки 'Обновить'.
|
||
|
||
Raises:
|
||
AssertionError: Если кнопка отсутствует.
|
||
"""
|
||
|
||
self.update_button.check_visibility(
|
||
"Update button on top of service statuses tab is missing"
|
||
)
|