"""Модуль вкладки 'Статус обслуживания'. Содержит класс 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.tab_title = Text(page, self.tab_title_locator, "tab_title") self.update_button = Button(page, self.update_button_locator, "update_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) row_locator.scroll_into_view_if_needed() 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) 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_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" )