check_prev_month_button_visibility() (метод DatePickerComponent)

check_next_month_button_visibility() (метод DatePickerComponent)

EventsContainerComponent (класс в components.events_container_component)

click_event_table_header_arrow() (метод EventsContainerComponent)

get_toolbar_export_to_csv_button() (метод EventsContainerComponent)

get_toolbar_export_to_pdf_button() (метод EventsContainerComponent)

check_events_table_column_descending_order() (метод EventsContainerComponent)

check_events_table_row_highlighting() (метод EventsContainerComponent)

check_events_table_first_row_visibility() (метод EventsContainerComponent)

check_events_table_last_row_visibility() (метод EventsContainerComponent)

check_events_table_verticall_scrolling() (метод EventsContainerComponent)

should_be_base_toolbar_buttons() (метод EventsContainerComponent)

JsonContainerComponent (класс в components.json_container_component)

ModalWindowComponent (класс в components.modal_window_component)

check_window_horizontal_scrolling() (метод ModalWindowComponent)

SettingsFormComponent (класс в components.settings_form_component)

check_toolbar_presence_by_locator_and_title() (метод ToolbarComponent)

SystemLogEventsContainer (класс в components_derived.container_system_log_events)

AddADUserModalWindow (класс в components_derived.modal_add_AD_user)

uncheck_active_directory_checkbox() (метод AddADUserModalWindow)

uncheck_push_notification_checkbox() (метод AddADUserModalWindow)

AddLocalUserModalWindow (класс в components_derived.modal_add_local_user)

check_active_directory_checkbox() (метод AddLocalUserModalWindow)

uncheck_active_directory_checkbox() (метод AddLocalUserModalWindow)

check_push_notification_checkbox() (метод AddLocalUserModalWindow)

uncheck_push_notification_checkbox() (метод AddLocalUserModalWindow)

close_window_by_toolbar_button() (метод AddLocalUserModalWindow)

ChangePasswordModalWindow (класс в components_derived.modal_change_password)

click_old_password_hidden_icon() (метод ChangePasswordModalWindow)

click_new_password_hidden_icon() (метод ChangePasswordModalWindow)

click_confirm_password_hidden_icon() (метод ChangePasswordModalWindow)

EditUserModalWindow (класс в components_derived.modal_edit_user)

uncheck_push_notification_checkbox() (метод EditUserModalWindow)

ViewTemplateModalWindow (класс в components_derived.modal_view_template)

close_window_by_toolbar_button() (метод ViewTemplateModalWindow)

ViewZTPTemplateModalWindow (класс в components_derived.modal_view_ztp_template)

close_window_by_toolbar_button() (метод ViewZTPTemplateModalWindow)

compare_modal_with_api_data() (метод ViewZTPTemplateModalWindow)

SelectionBarComponent (класс в components_derived.selection_bar_component)

check_field_not_highlighted_error() (метод SelectionBarComponent)

SidebarFilterComponent (класс в components_derived.sidebar_filter_component)

check_delete_session_confirm_window() (метод CurrentSessionsTab)

check_sessions_table_verticall_scrolling() (метод CurrentSessionsTab)

check_sessions_table_first_row_visibility() (метод CurrentSessionsTab)

check_sessions_table_last_row_visibility() (метод CurrentSessionsTab)

check_sessions_table_row_highlighting() (метод CurrentSessionsTab)

should_be_delete_button_on_sessions_table_row() (метод CurrentSessionsTab)

check_services_table_verticall_scrolling() (метод ServiceStatusTab)

check_services_table_first_row_visibility() (метод ServiceStatusTab)

check_services_table_last_row_visibility() (метод ServiceStatusTab)

check_services_table_row_highlighting() (метод ServiceStatusTab)

check_templates_table_first_row_visibility() (метод TemplatesTab)

check_templates_table_last_row_visibility() (метод TemplatesTab)

check_templates_table_vertical_scrolling() (метод ZTPTemplatesTab)

check_templates_table_first_row_visibility() (метод ZTPTemplatesTab)

check_templates_table_last_row_visibility() (метод ZTPTemplatesTab)

check_templates_table_row_highlighting() (метод ZTPTemplatesTab)

"""Модуль для работы с компонентом alert-окна в Playwright.

Содержит класс AlertComponent для взаимодействия с различными типами
alert-окон (error, success, info, warning) и проверки их состояния.
"""

from playwright.sync_api import Page, expect
from tools.logger import get_logger
from locators.alert_locators import AlertLocators
from elements.text_element import Text
from components.base_component import BaseComponent

logger = get_logger("ALERT")

class AlertComponent(BaseComponent):
    """Компонент для работы с alert-окнами Playwright.

    Поддерживает типы: error, success, info, warning.
    Позволяет проверять наличие, отсутствие и текст сообщений.
    """

    def __init__(self, page: Page) -> None:
        """Инициализирует компонент alert-окна.

        Args:
            page: Экземпляр страницы Playwright.
        """

        super().__init__(page)

        self.text = Text(page, AlertLocators.ALERT_MESSAGE, "Alert message")

    # Действия:
    def close_alert_by_text(self, text: str) -> None:
        """Закрывает alert-окно с заданным текстом с помощью кнопки закрытия.

        Args:
            text: Текст alert-окна, которое нужно закрыть.

        Raises:
            AssertionError: Если не удалось найти или закрыть alert-окно.
        """
        # Находим alert с нужным текстом
        alert_locator = self.page.get_by_role(
            AlertLocators.ALERT_ROLE
        ).filter(has_text=text)

        # Проверяем, что alert видим
        expect(alert_locator).to_be_visible()

        # Находим кнопку закрытия внутри alert
        close_button = alert_locator.locator(AlertLocators.ALERT_DISMISS_BUTTON)

        # Проверяем, что кнопка закрытия доступна и кликаем
        expect(close_button).to_be_visible()
        expect(close_button).to_be_enabled()

        # Кликаем по кнопке закрытия
        close_button.click()

        # Проверяем, что alert исчез после закрытия
        expect(alert_locator).to_be_hidden()

        logger.info(f"Alert with text '{text}' closed successfully")

    def get_alert_type(self) -> str:
        """Возвращает тип alert-окна.

        Returns:
            str: Тип alert-окна.

        Raises:
            ValueError: Если получен неподдерживаемый тип alert-окна.
        """

        class_attr = self.page.get_by_role(AlertLocators.ALERT_ROLE).locator(
            '>div'
        ).get_attribute('class')

        alert_type = None
        if 'v-alert' in class_attr:
            alert_type = class_attr.replace("v-alert ", "")

        alert_types = ["error", "success", "info", "warning"]
        if alert_type not in alert_types:
            raise ValueError("Unsupported type of alert window")

        return alert_type

    def get_text(self) -> str:
        """Возвращает текст сообщения из alert-окна.

        Returns:
            str: Текст сообщения.
        """

        return self.text.get_text(0)

    # Проверки:
    def check_alert_absence(self, text: str, timeout: int = 30000) -> None:
        """Проверяет отсутствие alert-окна с заданным текстом.

        Args:
            text: Текст для проверки.
            timeout: Время ожидания исчезновения (мс).

        Raises:
            AssertionError: Если окно не исчезает в течение заданного времени.
        """

        seconds = int(timeout/1000)
        msg = f"Alert window should disappear after {seconds} seconds"

        if text == "":
            expect(self.page.get_by_role(
                AlertLocators.ALERT_ROLE
            )).to_be_hidden(timeout=timeout), msg
            logger.info(f"Alert window successfully disappeared")
        else:
            ).filter(has_text=text)).to_be_hidden(timeout=timeout), msg
            logger.info(f"Alert window with text '{text}' successfully disappeared")

    def check_alert_presence(self, text: str) -> None:
        """Проверяет наличие alert-окна с заданным текстом.

        Args:
            text: Текст для проверки. Если пустая строка - проверяет только
                  наличие окна.

        Raises:
            AssertionError: Если alert-окно не найдено.
        """

        msg = "Alert window is missing"
            expect(self.page.get_by_role(AlertLocators.ALERT_ROLE)).to_be_visible(), msg
            logger.info(f"Alert window successfully displayed")
        else:
            ).filter(has_text=text)).to_be_visible(), msg
            logger.info(f"Alert window with text '{text}' successfully displayed")

    def check_text(self, alert_text: str) -> None:
        """Проверяет точное соответствие текста в alert-окне.

        Args:
            alert_text: Ожидаемый текст сообщения.

        Raises:
            AssertionError: Если текст не соответствует ожидаемому.
        """

        self.text.check_have_text(alert_text,
                                  "Unexpected message in alert window")

"""Базовый модуль для работы с компонентами страницы.

Содержит базовый класс для взаимодействия с элементами страницы через Playwright.
"""

from playwright.sync_api import Page, Locator, expect

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:

        Raises:
            AssertionError: если элемент не виден на странице.
        """

        expect(loc).to_be_visible(visible=True, timeout=12000), msg

    def is_scrollable_vertically(self, locator: str | Locator) -> bool:
        """Проверка возможности вертикальной прокрутки элемента.

        Args:
            locator: локатор элемента.

        Returns:
            bool: True если элемент можно прокрутить вертикально.
        """

        return loc.evaluate("el => el.scrollHeight > el.clientHeight")

    def is_scrollable_horizontally(self, locator: str | Locator) -> bool:
        """Проверка возможности горизонтальной прокрутки элемента.

        Args:

        Returns:
            bool: True если элемент можно прокрутить горизонтально.
        """

        return loc.evaluate("el => el.scrollWidth > el.clientWidth")

    # Методы прокрутки:
    def scroll_up(self, locator: str | Locator, timeout: int = 10000) -> None:
        """Прокрутка элемента до самого верха.

        Args:
            timeout: максимальное время ожидания в мс.

        Raises:
            AssertionError: если прокрутка не выполнена до верха.
        """

        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:

        Raises:
            AssertionError: если прокрутка не выполнена до конца.
        """

        attempt = 0

            # Получаем текущую позицию прокрутки и размеры
            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)")

            # Ждем загрузки контента

            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:
                # Прокрутки не произошло или контент не изменился
            else:

        # Если вышли из цикла - не смогли прокрутить до конца
        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

            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:

        Raises:
            AssertionError: если прокрутка не выполнена до левого края.
        """

        attempt = 0

            previous_position = loc.evaluate("el => el.scrollLeft")

            # Прокручиваем до левого края
            loc.evaluate("el => el.scrollTo(0, el.scrollTop)")

            current_position = loc.evaluate("el => el.scrollLeft")

            # Проверяем, достигли ли мы левого края
                return  # Успешно прокрутили до левого края

            else:

        # Если вышли из цикла - не смогли прокрутить до левого края
        final_position = loc.evaluate("el => el.scrollLeft")
        scroll_width = loc.evaluate("el => el.scrollWidth")
        client_width = loc.evaluate("el => el.clientWidth")

            f"Не удалось прокрутить до левого края. "
            f"Ширина контента: {scroll_width}, "
            f"Ширина видимой области: {client_width}"
        )

    def scroll_right(self, locator: str | Locator, timeout: int = 10000) -> None:
        """Прокрутка элемента до самого правого края.

        Args:

        Raises:
            AssertionError: если прокрутка не выполнена до правого края.
        """

        attempt = 0

            # Прокручиваем до правого края
            loc.evaluate("el => el.scrollTo(el.scrollWidth, el.scrollTop)")

            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:
            else:

        # Если вышли из цикла - не смогли прокрутить до правого края
        max_scroll_x = scroll_width - client_width

            f"Не удалось прокрутить до правого края. "
            f"Текущая позиция: {final_position}, ожидалось: {max_scroll_x}, "
        )

"""Модуль компонента карточки пользователя.

Содержит класс для работы с карточкой пользователя через Playwright.
"""

from playwright.sync_api import Page
from elements.button_element import Button

logger = get_logger("USER_CARD")

class CardComponent(BaseComponent):
    """Компонент карточки пользователя.

    Предоставляет методы для взаимодействия с элементами карточки.
    """

        """Инициализирует компонент карточки пользователя.

        Args:
        """

        self.logout_button = Button(
            page,
            page.get_by_role("button", name="Выйти"),
            "logout button"
        )

    def click_logout_button(self):
        """Нажимает кнопку выхода из системы.

        Выполняет клик по кнопке 'Выйти' в карточке пользователя.
        """

        self.logout_button.click()

    # (Методы проверок могут быть добавлены здесь в будущем)

"""Модуль компонента окна подтверждения действий.

Содержит класс ConfirmComponent для взаимодействия с окном подтверждения,
включая кнопки подтверждения, отмены и закрытия, а также проверки текста.
"""

from locators.confirm_locators import ConfirmLocators

logger = get_logger("CONFIRM_WINDOW")

class ConfirmComponent(BaseComponent):
    """Компонент окна подтверждения действий."""

    def __init__(self, page: Page, cancel_button_text: str, allow_button_text: str):
        """Инициализация компонента.

        Args:
            cancel_button_text: Текст кнопки отмены.
            allow_button_text: Текст кнопки подтверждения.
        """

        self.title = Text(page, ConfirmLocators.TITLE, "confirm title")
        self.text = Text(page, ConfirmLocators.TEXT, "confirm text")

        self.close_button = Button(page, ConfirmLocators.BUTTON_CLOSE, "confirm close button")
        self.cancel_button = Button(
            page,
            page.get_by_role("button", name=cancel_button_text).first,
            "confirm cancel button"
        )
        self.allow_button = Button(
            page,
            page.get_by_role("button", name=allow_button_text).first,
            "confirm allow button"
        )

    def click_allow_button(self) -> None:
        """Нажимает кнопку подтверждения действия."""

        self.allow_button.click()

    def click_cancel_button(self) -> None:
        """Нажимает кнопку отмены действия."""

        self.cancel_button.click()

    def click_close_button(self) -> None:
        """Нажимает кнопку закрытия окна подтверждения."""

        self.close_button.click()

    def scroll_window_left(self) -> None:
        """Прокручивает содержимое окна влево."""

        self.scroll_left(ConfirmLocators.CONFIRM)

    def scroll_window_right(self) -> None:
        """Прокручивает содержимое окна вправо."""

        self.scroll_right(ConfirmLocators.CONFIRM)

    def check_title(self, title: str, msg: str) -> None:
        """Проверяет текст заголовка окна подтверждения.

        Args:
            title: Ожидаемый текст заголовка.
            msg: Сообщение при ошибке.
        """

        self.title.check_have_text(title, msg)

    def check_text(self, text: str, msg: str) -> None:
        """Проверяет текст сообщения в окне подтверждения.

        Args:
            text: Ожидаемый текст сообщения.
        """

        self.text.check_have_text(text, msg)

    def check_window_horizontal_scrolling(self) -> bool:
        """Проверяет возможность горизонтальной прокрутки окна."""

        return self.is_scrollable_horizontally(ConfirmLocators.CONFIRM)

    def should_be_cancel_button(self) -> None:
        """Проверяет наличие и видимость кнопки Отмены."""
        self.cancel_button.check_visibility("Cancel button is missing")

    def should_be_allow_button(self) -> None:
        """Проверяет наличие и видимость кнопки Подтверждения."""
        self.allow_button.check_visibility("Allow button is missing")

    def check_cancel_button_text(self, expected_text: str) -> None:
        """Проверяет текст кнопки Отмены."""
        self.cancel_button.check_have_text(expected_text, "Cancel button text doesn't match expected")

    def check_allow_button_text(self, expected_text: str) -> None:
        """Проверяет текст кнопки Подтверждения."""
        self.allow_button.check_have_text(expected_text, "Allow button text doesn't match expected")

"""Модуль компонента средства выбора даты.

Содержит класс для работы с средством выбора даты через Playwright.
"""

import datetime
from locators.date_picker_locators import DatePickerLocators

logger = get_logger("DATE_PICKER")

class DatePickerComponent(BaseComponent):
    """Компонент средства выбора даты.

    Предоставляет методы для взаимодействия с элементами средства выбора даты.
    """

        """Инициализирует компонент средства выбора даты.

        Args:
        """

        self.date_picker_year_button = Button(page,
                                     DatePickerLocators.DATE_PICKER_TITLE_BUTTON_YEAR,
                                     "date_picker_year_button")

        loc = page.locator(DatePickerLocators.DATE_PICKER_HEADER).\
              get_by_role("button").locator("i.mdi-chevron-left")
        self.prev_month_button = Button(page, loc, "prev_month_button")

              get_by_role("button").locator("i.mdi-chevron-right")
        self.next_month_button = Button(page, loc, "next_month_button")

        self.current_month_year = Text(page,
                                     DatePickerLocators.DATE_PICKER_HEADER_VALUE,
                                     "current_month_year")

    def click_prev_month_button(self) -> None:
        """ Нажатие на кнопку перехода к предыдущему месяцу (стрелочка влево)."""

        self.prev_month_button.click()

    def click_next_month_button(self) -> None:
        """ Нажатие на кнопку перехода к следующему месяцу (стрелочка вправо)."""

        self.next_month_button.click()

    def click_year_button(self) -> None:
        """ Нажатие на кнопку текущего значения года в заголовке средства выбора даты."""

        self.date_picker_year_button.click()

    def get_year(self) -> str:
        """ Получение текущего значения года из заголовка средства выбора даты."""

        return self.date_picker_year_button.get_text(0).strip()

    def get_month_year(self) -> str:
        """ Получение текущего значения месяца и года."""

        return self.current_month_year.get_text(0).strip()

    def get_day(self) -> str:
        """ Получение текущего номера дня."""

        days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS)
        day_button_locator = days_table_locator.locator("//td/button[contains(@class, 'accent--text')]")

        return day_button_locator.text_content().strip()

    def select_day(self, day: str) -> None:
        """ Выбор дня месяца по его номеру."""

        visible = False

        days_table_locator.wait_for(timeout=300)
        day_button_locator = days_table_locator.locator("//td").get_by_role("button", name=day)
        visible = day_button_locator.is_visible()
        if visible:
            day_button_locator.click()
        else:
            assert False, f"Day number {day} is missing on date picker days table"

    def select_year_and_month(self, year: str, month: str) -> None:
        """ Выбор года и месяца по заданным значениям. Значения должны быть в формате гггг и мм"""

        try:
            month_index = int(month)
        except ValueError:
            assert False, f"Incorrect month value {month} for selection"

        assert month_index >= 1, f"Incorrect month value {month} for selection"
        assert month_index <= 12, f"Incorrect month value {month} for selection"

        current_year = int(self.get_year())
        if int(year) > current_year + 100 or int(year) < current_year - 100:
            assert False, f"Incorrect year value {year} for selection"

        self.click_year_button()

        years_list = self.page.locator(DatePickerLocators.DATE_PICKER_LIST_YEARS)
        years_list.wait_for(timeout=300)
        year_locator = f"{DatePickerLocators.DATE_PICKER_LIST_YEARS} li:has-text(\"{year}\")"
        self.page.locator(year_locator).click()

        months_table = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_MONTHS)
        months_table.wait_for(timeout=300)
        month_button = months_table.locator("//td").get_by_role("button").nth(month_index-1)
        month_button.click()

    def check_prev_month_button_visibility(self) -> None:
        """ Проверка видимости кнопки перехода к предыдущему месяцу (стрелочка влево)."""

        self.prev_month_button.check_visibility("Prev Month Button is missing")

    def check_next_month_button_visibility(self) -> None:
        """ Проверка видимости кнопки перехода к следующему месяцу (стрелочка вправо)."""

        self.next_month_button.check_visibility("Next Month Button is missing")

    def check_year_button_visibility(self) -> None:
        """ Проверка видимости кнопки текущего значения года в заголовке средства выбора даты."""

        self.date_picker_year_button.check_visibility("Current Year Button is missing")

    def check_content(self) -> None:
        """Проверка состава компонент средства выбора даты."""

        month_dict = {"1":"январь",
                      "2":"февраль",
                      "3":"март",
                      "4":"апрель",
                      "5":"май",
                      "6":"июнь",
                      "7":"июль",
                      "8":"август",
                      "9":"сентябрь",
                      "10":"октябрь",
                      "11":"ноябрь",
                      "12":"декабрь"}
        days_per_month = {"1":31,
                          "2":28,
                          "3":31,
                          "4":30,
                          "5":31,
                          "6":30,
                          "7":31,
                          "8":31,
                          "9":30,
                          "10":31,
                          "11":30,
                          "12":31}
        expected_weekdays = ["В", "П", "В", "С", "Ч", "П", "С"]

        expected_date = datetime.datetime.now()

        # Проверка наличия кнопки текущего значения года в заголовке средства выбора даты
        self.check_year_button_visibility()

        actual_year = self.get_year()
        assert actual_year == str(expected_date.year), \
            f"Expected year {expected_date.year} is not equal actual year {actual_year} on date picker title"

        # Проверка наличия кнопок перехода к предыдущему/следующему месяцу
        self.check_prev_month_button_visibility()
        self.check_next_month_button_visibility()

        # Проверка наличия и правильности текущей даты
        self.current_month_year.check_visibility("Month and Year values is missing on date picker body")

        y = expected_date.year
        m = str(expected_date.strftime("%m"))
        expected_month_year = f"{month_dict[m]} {y} г."
        actual_month_year = self.get_month_year()
        assert actual_month_year == expected_month_year, \
            f"Expected value {expected_month_year} is not equal actual value {actual_month_year} on date picker body"

        expected_day = str(expected_date.strftime("%d"))
        actual_day = self.get_day()
        assert actual_day == expected_day, \
            f"Expected day {expected_day} is not equal actual day {actual_day} on date picker body"

        # Проверка таблицы дней
        headers = days_table_locator.locator("th").all()
        actual_weekdays = []
        for header in headers:
            actual_weekdays.append(header.text_content())

        assert actual_weekdays == expected_weekdays, \
            f"Expected weekdays list {expected_weekdays} is not equal actual weekdays list {actual_weekdays} on date picker body"

        rows = days_table_locator.locator("//tbody/tr")
        days_counter = 0
        for i in range(rows.count()):
            row = rows.nth(i)
            cells = row.locator("td")
            for j in range(cells.count()):
                cell_text = cells.nth(j).inner_text()
                if cell_text != "":
                    days_counter += 1

        expected_day_counter = days_per_month[m]
        if m == "2" and self._is_leap_year(expected_date.year):
            expected_day_counter = 29

        assert days_counter == expected_day_counter, f"Incorrect amount of days {days_counter} on date picker body"

    def _is_leap_year(self, year: str) -> bool:
        """Проверка является ли год високосным."""

        if int(year) % 400 == 0:
            return True
        if int(year) % 100 == 0:
            return False
        if int(year) % 4 == 0:
        return False

"""Модуль dropdown_list_component содержит класс для работы с выпадающими списками.

Класс DropdownList наследует базовый функционал BaseComponent и добавляет
методы для взаимодействия с выпадающими списками на странице.
"""
import re
from playwright.sync_api import Page, Locator

logger = get_logger("DROPDOWN_LIST")

class DropdownList(BaseComponent):
    """Класс для работы с выпадающими списками.

    Наследует функциональность BaseElement и добавляет специфичные
    методы для выбора и проверки элементов списка.
    """

        """Инициализирует компонент выпадающего списка.

        Args:
           page: Экземпляр страницы Playwright.
        """

    def click_item_with_text(self, text: str) -> None:
        """Выбирает элемент списка по указанному тексту.

        Args:
            text (str): Текст элемента для выбора.
        """

        element = self.page.get_by_role("listitem").filter(has_text=text)
        if element.count() > 1:
            rtext = f"^{text}$"
            element = self.page.get_by_role("listitem").filter(
                has_text=re.compile(rtext)
            )
        element.click()

    def get_combobox_options(self, combobox_locator: str | Locator,
                            listbox_locator: str | Locator,
                            icon_locator: str | Locator = None) -> list[str]:
        """
        Получает список доступных опций из combobox.

        Args:
            combobox_locator: Локатор combobox
            listbox_locator: Локатор выпадающего списка
            icon_locator: Локатор иконки для клика (опционально)

        Returns:
            list[str]: Список доступных опций
        """
        logger.info("Getting combobox options list...")

        # Открываем combobox (если еще не открыт)
        self.open_combobox(combobox_locator, listbox_locator, icon_locator)

        options_list = self.get_item_names(listbox_locator)

        # Закрываем combobox (кликаем вне его)
        self.page.mouse.click(10, 10)
        self.page.wait_for_timeout(500)

        logger.info(f"Found options: {len(options_list)} - {options_list}")
        return options_list

    def get_item_names(self, locator: str | Locator) -> list[str]:
        """Возвращает тексты всех элементов по указанному локатору.

        Args:
            locator: Локатор элементов или строка с CSS/XPath.

        Returns:
            Список текстов элементов.
        """

        texts = loc.all_inner_texts()
        return texts[0].splitlines()

    def get_selected_combobox_value(self, combobox_locator: str | Locator,
                                   value_locator: str | Locator = None) -> str:
        """
        Получает выбранное значение из combobox.

        Args:
            value_locator: Локатор элемента с выбранным значением (опционально)

        Returns:
            str: Выбранное значение или пустая строка если ничего не выбрано
        """
        combobox = self.get_locator(combobox_locator)

        selected_value = ""

        if value_locator:
            # Используем переданный локатор для значения
            value_element = combobox.locator(value_locator)
            if value_element.count() > 0:
                selected_value = value_element.first.text_content().strip()
        else:
            # Ищем в span элементах по умолчанию
            span_locator = combobox.locator("span")
            if span_locator.count() > 0:
                for i in range(span_locator.count()):
                    span_text = span_locator.nth(i).text_content().strip()
                    if span_text and span_text not in ["Класс объекта учета"]:
                        selected_value = span_text
                        break

        logger.info(f"Selected combobox value: '{selected_value}'")
        return selected_value

    def open_combobox(self, combobox_locator: str | Locator,
                     listbox_locator: str | Locator,
                     icon_locator: str | Locator = None) -> None:
        """
        Открывает выпадающий список combobox.

        Args:
        """
        logger.info("Opening combobox...")

        listbox = self.get_locator(listbox_locator)

        # Прокручиваем до combobox
        combobox.scroll_into_view_if_needed()
        self.page.wait_for_timeout(1000)

        # Проверяем, не открыт ли уже список
        listbox_already_open = False
        listbox_count = listbox.count()

        if listbox_count > 0:
            listbox_already_open = listbox.first.is_visible()

        if not listbox_already_open:
            # Если указан локатор иконки, кликаем на него, иначе на сам combobox
            if icon_locator:
                icon = combobox.locator(icon_locator)
                icon.scroll_into_view_if_needed()
                icon.click(timeout=10000)
            else:
                combobox.click(timeout=10000)
            logger.info("Combobox click completed")
            self.page.wait_for_timeout(1000)

        # Проверяем что список открылся
        listbox_count_after = listbox.count()
        listbox_visible = False

        if listbox_count_after > 0:
            listbox_visible = listbox.first.is_visible()

        if listbox_visible:
            logger.info("Dropdown list found and opened")
        else:
            logger.warning("Failed to open dropdown list")

    def scroll_until_end(self, locator: str | Locator) -> None:
        """
        Скроллит список до тех пор, пока не перестанут подгружаться новые элементы.

        Args:
        """

        items_count = 0
        attempts = 0
        last_item_name = ""

        while attempts < max_attempts:
            self.page.wait_for_timeout(300)

            item_names = self.get_item_names(loc)
            current_count = len(item_names)

            if current_count == items_count:
                attempts += 1
            else:
                items_count = current_count
                attempts = 0

            last_item_name = item_names[current_count-1]
                has_text=last_item_name
            )
            element.scroll_into_view_if_needed()

        self.page.wait_for_timeout(300)
        self.check_item_with_text(last_item_name)

    def check_item_with_text(self, text: str) -> None:
        """Проверяет наличие и доступность элемента списка.

        Args:
            text (str): Текст элемента для проверки.

        Raises:
            AssertionError: Если элемент отсутствует или недоступен.
        """

            )
        enabled = element.is_enabled()
        if not enabled:
            assert False, f"Dropdown list item '{text}' is missing or disabled"

    def check_vertical_scrolling(self, locator: str | Locator) -> bool:
        """
        Проверяет функцию вертикального скроллинга списка.

        Args:

        Returns:
            True или False значение в зависимости от скроллируемый список или нет.
        """

        is_scrollable_vertically = self.is_scrollable_vertically(loc)
        if  is_scrollable_vertically:
            self.scroll_until_end(loc)

            first_item_name = item_names[0]

            self.scroll_up(loc)
            self.check_item_with_text(first_item_name)

        return is_scrollable_vertically

"""Модуль компонента панели событий. Содержит класс для работы с элементами панели."""

from locators.event_panel_locators import EventPanelLocators
from elements.tooltip_button_element import TooltipButton
from elements.tab_button_element import TabButton
from components_derived.container_system_log_events import SystemLogEventsContainer
from components_derived.user_card import UserCard

logger = get_logger("EVENT_PANEL")

class EventPanelComponent(BaseComponent):
    """Компонент панели событий. Предоставляет методы для взаимодействия с ней."""

        """Инициализирует компонент панели событий.

        Args:
        """

        self.states_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_STATES), "states_tab")
        self.actions_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_ACTIONS), "actions_tab")
        self.events_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_EVENTS), "events_tab")
        self.maintenance_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_MAINTENANCE), "maintenance_tab")
        self.system_log_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_SYSTEM_LOG), "system_log_tab")

        self.unknown_reason_button = TooltipButton(page, self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(0), "unknown_reason_button")
        self.warning_button = TooltipButton(page, self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(1), "warning_button")
        self.damage_button = TooltipButton(page, self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(2), "damage_button")
        self.failure_button = TooltipButton(page, self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(3), "failure_button")

        buttons_service_locators = self.page.locator(EventPanelLocators.BUTTONS_SERVICE).get_by_role("button").all()
        self.search_button = Button(page, buttons_service_locators[0], "search_button")
        self.user_button = Button(page, buttons_service_locators[1], "user_button")

    def click_expand_less_button(self) -> None:
        """Выполняет нажатие кнопки галочка вверх."""

        button_locator = self.page.locator(EventPanelLocators.TAB_EXPAND_BUTTONS).\
                         get_by_role("button").filter(has_text='expand_less')
        button_locator.click()

    def click_expand_more_button(self) -> None:
        """Выполняет нажатие кнопки галочка вниз."""

                         get_by_role("button").filter(has_text='expand_more')

    def click_system_log_tab(self) -> SystemLogEventsContainer:
        """Выполняет нажатие tab-кнопки Системный журнал."""

        self.system_log_tab.check_visibility("System log tab button is missing on event panel")
        self.system_log_tab.click()

        system_log_events = SystemLogEventsContainer(self.page, EventPanelLocators.CONTAINER_SYSTEM_LOG_EVENTS)
        return system_log_events

    def click_user_button(self) -> UserCard:
        """Выполняет нажатие кнопки пользователя."""

        self.should_be_user_button()
        self.user_button.click()

        user_card = UserCard(self.page)
        return user_card

    def do_logout(self) -> None:
        """Выполняет выход из системы."""

        user_card = self.click_user_button()
        user_card.click_logout_button()

    def get_event_tooltip_texts(self) -> []:
        """Возвращает список текстов всплывающих подсказок кнопок счетчиков событий."""

        tooltip_texts = []

        tooltip_texts.append(self.unknown_reason_button.get_tooltip_text())
        tooltip_texts.append(self.warning_button.get_tooltip_text())
        tooltip_texts.append(self.damage_button.get_tooltip_text())
        tooltip_texts.append(self.failure_button.get_tooltip_text())

        return tooltip_texts

    def get_event_button_values(self) -> {}:
        """Возвращает набор значений кнопок счетчиков событий."""

        event_buttons_texts = {}

        event_buttons_texts['Неизвестно'] = self.unknown_reason_button.get_text(0)
        event_buttons_texts['Предупреждение'] = self.warning_button.get_text(0)
        event_buttons_texts['Повреждение'] = self.damage_button.get_text(0)
        event_buttons_texts['Авария'] = self.failure_button.get_text(0)

        return event_buttons_texts

    def get_panel_position(self) -> str:
        """Возвращает текущее положение панели событий относительно страницы: "top", "center","bottom"."""

        style_attr = self.page.locator(EventPanelLocators.AREA_EVENTS).get_attribute("style")
        position = "bottom"

        if style_attr.find("display: none;") == -1:
            height = style_attr.replace("height: ","").replace(";", "")
            if height == "100%":
                position = "top"
            else:
                position = "center"

        return position

    def check_expand_less_button(self) -> bool:
        """Проверяет наличие кнопки галочка вверх."""

        try:
            _ = self.page.locator(EventPanelLocators.TAB_EXPAND_BUTTONS).\
                          get_by_role("button").filter(has_text='expand_less')
        except TimeoutError:
        return True

    def check_expand_more_button(self) -> bool:
        """Проверяет наличие кнопки галочка вниз"""

        try:
                          get_by_role("button").filter(has_text='expand_more')

    def should_be_user_button(self) -> None:
        """Проверяет наличие кнопки пользователя."""

        self.user_button.check_visibility("User button is missing on event panel")

    def should_be_search_button(self) -> None:
        """Проверяет наличие кнопки поиска."""

        self.search_button.check_visibility("Search button is missing on event panel")

    def should_be_tab_buttons(self) -> None:
        """Проверяет наличие блока tab-кнопок Состояния, Действия, События, Обслуживание, Системный журнал."""

        self.states_tab.check_have_text('Состояния', "Tab button with text Состояния is missing on event panel")
        self.actions_tab.check_have_text('Действия',"Tab button with text Действия is missing on event panel")
        self.events_tab.check_have_text('События', "Tab button with text События is missing on event panel")
        self.maintenance_tab.check_have_text('Обслуживание', "Tab button with text Обслуживание is missing on event panel")
        self.system_log_tab.check_have_text('Системный журнал', "Tab button with text Системный журнал is missing on event panel")

    def should_be_event_buttons(self) -> None:
        """Проверяет наличие блока кнопок-счетчиков событий."""

        self.unknown_reason_button.check_visibility("Unknown reason event button is missing on event panel")
        self.warning_button.check_visibility("Warning event button is missing on event panel")
        self.damage_button.check_visibility("Damage event button is missing on event panel")
        self.failure_button.check_visibility("Failure event button is missing on event panel")

"""Модуль компонента контейнера с перечнем событий. Содержит класс для работы с контейнерами,
их элементами и проверками."""

from locators.toolbar_locators import ToolbarLocators
from components_derived.sidebar_filter_component import SidebarFilterComponent
from components.toolbar_component import ToolbarComponent
from components.table_component import TableComponent

logger = get_logger("EVENTS_CONTANER")

class EventsContainerComponent(BaseComponent):
    """Компонент контейнера с перечнем событий. Предоставляет методы для взаимодействия с контейнером,
    его содержимым и проверок."""

    def __init__(self, page: Page, locator: str | Locator):
        """Инициализирует базовый контейнер.

        Args:
            page: Экземпляр страницы Playwright
            locator: Локатор контейнера (строка или объект Locator)
        """

        self.container_locator = self.get_locator(locator)

        # тулбар
        self.toolbar = ToolbarComponent(page, "")
        filter_button_locator = self.container_locator.locator(ToolbarLocators.TITLE).\
                                get_by_role("button")
        self.toolbar.add_button(filter_button_locator, "filter_button")

        export_buttons = self.container_locator.locator(ToolbarLocators.ITEMS).\
                                get_by_role("button").all()
        self.toolbar.add_tooltip_button(export_buttons[1], "export_to_csv_button")
        self.toolbar.add_tooltip_button(export_buttons[0], "export_to_pdf_button")

        # Таблица событий
        self.events_table = TableComponent(page)
        self.table_locator = "//div[@class='scrolltable']/div/table"

        # Кнопки пагинации в нижней части контейнера
        self.chevron_left = Button(page,
                                   self.container_locator.get_by_role("button").filter(has_text='chevron_left'),
                                   "chevron_left")
        self.chevron_right = Button(page,
                                   self.container_locator.get_by_role("button").filter(has_text='chevron_right'),
                                   "chevron_right")

        self.first_page = Button(page,
                                   self.container_locator.get_by_role("button").filter(has_text='first_page'),
                                   "first_page")
        self.last_page = Button(page,
                                   self.container_locator.get_by_role("button").filter(has_text='last_page'),
                                   "last_page")

        loc = self.container_locator.get_by_role("button").filter(has_text='chevron_left').\
              locator("xpath=..").get_by_role("button").nth(2)
        self.data_set_number = Button(page, loc, "data_set_number")

        self.sidebar_filter_locator = self.container_locator.locator("//aside//div[@class='scrollarea__container']")
        self.sidebar_filter = SidebarFilterComponent(self.page,
                                                     self.sidebar_filter_locator)

    def add_tab_to_toolbar(self, locator: str | Locator, name: str) -> None:
        """Добавление кнопки типа v-tabs к тулбару"""

        tabs_locator = self.get_locator(locator)
        self.toolbar.add_tab_button(self.container_locator.locator(tabs_locator), name)

    def click_chevron_left(self) -> None:
        """Нажатие кнопки получения предыдущего набора данных"""

        self.chevron_left.click()

    def click_chevron_right(self) -> None:
        """Нажатие кнопки получения следующего набора данных"""

        self.chevron_right.click()

    def click_first_page(self) -> None:
        """Нажатие кнопки перехода на первую сраницу"""

        self.first_page.click()

    def click_last_page(self) -> None:
        """Нажатие кнопки перехода на последнюю страницу"""

        self.last_page.click()

    def click_filter_button(self) -> SidebarFilterComponent:

        self.toolbar.click_button("filter_button")
        self.sidebar_filter.check_visibility(self.sidebar_filter_locator,
                                             "Filter sidebar is missing")
        return self.sidebar_filter

    def click_event_table_header_arrow(self, index: int) -> None:
        """ Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

        Args:
            index: Индекс ячейки в заголовке.
        """
        loc = self.container_locator.locator(self.table_locator)
        self.events_table.click_arrow_button(loc, index)

    def get_current_data_set_number(self) -> int:
        """Получение номера текущего набора данных"""

        try:
            data_set_number = int(self.data_set_number.get_text(0))
        except ValueError as e:
            assert False, f"Value Error: {e}"

        return data_set_number

    def get_arrow_button_state(self, index: int) -> str:
        """ Получение состояния кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

        Args:

        Returns:
            up, если это стрелочка вверх. down, если это стрелочка вниз.
        """
        return self.events_table.get_arrow_button_state(loc, index)

    def get_events_table_content(self) -> list[list[str]]:
        """Возвращает содержимое таблицы, включая заголовки.

        Returns:
            Двумерный список с содержимым таблицы.
        """

        return self.events_table.read(loc)

    def get_events_table_rows_count(self) -> int:
        """Возвращает количество строк в таблице (без заголовка).

        Returns:
            int: Количество строк с данными.

        Raises:
            AssertionError: Если таблица пуста.
        """

        return self.events_table.get_rows_count(loc)

    def get_sidebar_filter(self) -> SidebarFilterComponent:
        """Возвращает боковую панель фильтрации."""

    def get_toolbar_filter_button(self) -> Button:
        """Возвращает кнопку фильтрации."""

        return self.toolbar.get_button_by_name("filter_button")

    def get_toolbar_export_to_csv_button(self) -> TooltipButton:
        """Возвращает кнопку экспорта в csv."""

        return self.toolbar.get_button_by_name("export_to_csv_button")

    def get_toolbar_export_to_pdf_button(self) -> TooltipButton:
        """Возвращает кнопку экспорта в pdf."""

        return self.toolbar.get_button_by_name("export_to_pdf_button")

    def get_toolbar_tab_button(self, name: str) -> TabButton:
        """Возвращает кнопку типа v-tabs по имени."""

        return self.toolbar.get_button_by_name(name)

    def scroll_events_table_up(self) -> None:
        """Прокручивает таблицу событий вверх."""

        loc = self.container_locator.locator("//div[@class='scrolltable']//table/tbody")
        self.events_table.scroll_up(loc)

    def scroll_events_table_down(self) -> None:
        """Прокручивает таблицу событий вниз."""

        self.events_table.scroll_down(loc)

    def check_events_table_headers(self, actual_headers, expected_headers) -> None:
        """ Проверка соответствия заголовка таблицы ожидаемому"""

        self.events_table.check_table_headers(actual_headers, expected_headers)

    def check_events_table_column_descending_order(self,
                                                       index: int,
                                                       convert2timestamp=False) -> bool:
        """Проверка, что заданный столбец таблицы упорядочен по убыванию.

        Args:
            index: Индекс столбца.
            convert2timestamp: Конвертировать строковое представление даты и времени в Unix timestamp.

        Returns:
            True, если столбец таблицы упорядочен по убыванию. Иначе: False
        """

        return self.events_table.check_column_descending_order(loc, index, convert2timestamp)

    def check_events_table_row_highlighting(self, row_index: int) -> None:
        """Проверяет выделение указанной строки таблицы.

        Args:
            row_index: Индекс проверяемой строки.

        Raises:
            AssertionError: Если строка не выделена.
        """

        self.events_table.check_row_highlighting(loc, row_index)

    def check_events_table_first_row_visibility(self) -> None:
        """Проверяет видимость первой строки таблицы.

        Raises:
            AssertionError: Если строка не видна.
        """

        self.events_table.check_first_row_visibility(loc)

    def check_events_table_last_row_visibility(self) -> None:
        """Проверяет видимость последней строки таблицы.

        Raises:
        """

        self.events_table.check_last_row_visibility(loc)

    def check_events_table_verticall_scrolling(self) -> bool:
        """Проверяет возможность вертикальной прокрутки таблицы.

        Returns:
            bool: True если прокрутка возможна, иначе False.
        """

        return self.events_table.is_scrollable_vertically(loc)

    def is_chevron_left_disabled(self) -> bool:
        """Проверка видимости кнопки получения предыдущего набора данных"""

        return self.chevron_left.is_disabled()

    def is_chevron_right_disabled(self) -> bool:
        """Проверка видимости кнопки получения следующего набора данных"""

        return self.chevron_right.is_disabled()

    def is_first_page_disabled(self) -> bool:
        """Проверка видимости кнопки получения первой страницы с набором данных"""

        return self.first_page.is_disabled()

    def is_last_page_disabled(self) -> bool:
        """Проверка видимости кнопки получения последней страницы с набором данных"""

        return self.last_page.is_disabled()

    def should_be_all_disabled(self) -> None:
        """Проверка видимости кнопок пагинации: все кнопки disabled"""

        is_first_page_disabled = self.is_first_page_disabled()
        assert is_first_page_disabled, "First Page button should be disabled"

        is_chevron_left_disabled = self.is_chevron_left_disabled()
        assert is_chevron_left_disabled, "Arrow Left button should be disabled"

        is_chevron_right_disabled = self.is_chevron_right_disabled()
        assert is_chevron_right_disabled, "Arrow Right button should be disabled"

        is_last_page_disabled = self.is_last_page_disabled()
        assert is_last_page_disabled, "Last Page button should be disabled"

    def should_be_all_enabled(self) -> None:
        """Проверка видимости кнопок пагинации: все кнопки enabled"""

        assert not is_first_page_disabled, "First Page button should be enabled"

        assert not is_chevron_left_disabled, "Arrow Left button should be enabled"

        assert not is_chevron_right_disabled, "Arrow Right button should be enabled"

        assert not is_last_page_disabled, "Last Page button should be enabled"

    def should_be_initial_state(self) -> None:
        """Проверка видимости кнопок для первой страницы"""

    def should_be_final_state(self) -> None:
        """Проверка видимости кнопок для последней страницы"""

    def should_be_events_table(self) -> None:
        """Проверяет наличие таблицы событий.

        Raises:
            AssertionError: Если таблица отсутствует.
        """

        self.events_table.check_visibility(loc,
            "Events table is missing"
        )

    def should_be_toolbar(self) -> None:
        """Проверка наличия тулбара"""

        loc = self.container_locator.locator("//nav[contains(@class, 'v-toolbar')]").nth(0)
        self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar is missing")

    def should_be_base_toolbar_buttons(self) -> None:
        """Проверяет наличие и видимость базовых кнопок тулбара."""

        self.toolbar.check_button_visibility("filter_button")
        self.toolbar.check_button_visibility("export_to_pdf_button")
        self.toolbar.check_button_tooltip("export_to_pdf_button", "Скачать в формате PDF")
        self.toolbar.check_button_visibility("export_to_csv_button")
        self.toolbar.check_button_tooltip("export_to_csv_button", "Скачать в формате CSV")

    def should_be_pagination_buttons(self) -> None:
        """Проверяет наличие и видимость кнопок пагинации."""

        self.first_page.check_visibility("Pagination button 'first page' is missing")
        self.chevron_left.check_visibility("Pagination button 'chevron left' is missing")
        self.data_set_number.check_visibility("Pagination button 'data set number' is missing")
        self.chevron_right.check_visibility("Pagination button 'chevron right' is missing")
        self.last_page.check_visibility("Pagination button 'last page' is missing")

"""Модуль для работы с JSON-контейнерами на веб-страницах.

Содержит компонент для чтения и проверки JSON-данных в контейнерах.
Использует playwright для взаимодействия с элементами страницы.
"""

import json
import re
from typing import Any, Dict
import jsondiff

logger = get_logger("JSON_CONTAINER")

class JsonContainerComponent(BaseComponent):
    """Компонент для работы с JSON-данными на странице.

    Предоставляет методы чтения и проверки JSON-данных в контейнерах.
    """

        """Инициализирует JSON-контейнер.

        Args:
        """

    def format_json_string(self, json_string: str) -> str:
        """Форматирует строку JSON для корректного парсинга.

        Args:
            json_string: Сырая строка с JSON-данными.

        Returns:
            str: Отформатированная строка JSON.
        """
        lines = json_string.splitlines()
        formatted_lines = []
        stack = []  # Стек для отслеживания вложенности
        current_indent = 0

        for line in lines:
            line = line.strip()
            if not line:
                continue

            # Определяем тип текущей строки
            if line in ['{', '[']:
                formatted_lines.append('  ' * current_indent + line)
                stack.append(line)
                current_indent += 1
            elif line in ['}', ']']:
                current_indent -= 1
                if stack and stack[-1] in ['{', '[']:
                    stack.pop()
            elif re.match(r'^\d+:\{', line):
                formatted_lines.append('  ' * current_indent + '{')
                stack.append('{')
            elif ':' in line:
                key, value = line.split(':', 1)
                key = key.strip()
                value = value.strip()

                if not (key.startswith('"') and key.endswith('"')):
                    key = f'"{key}"'

                if value in ['{', '[']:
                    formatted_line = f'{key}: {value}'
                    formatted_lines.append('  ' * current_indent + formatted_line)
                    stack.append(value)
                    current_indent += 1
                elif value in ['}', ']']:
                    current_indent -= 1
                    if stack:
                        stack.pop()
                else:
                    if (value and not value.isdigit() and
                        not value.replace('.', '', 1).isdigit() and
                        value not in ['true', 'false', 'null'] and
                        not value.startswith('"') and not value.endswith('"') and
                        not value.startswith('{') and not value.startswith('[')):
                        value = f'"{value}"'

            else:

        # Добавляем запятые где необходимо
        result = []
        total_lines = len(formatted_lines)

        for i, current_line in enumerate(formatted_lines):
            if i < total_lines - 1:
                next_line = formatted_lines[i + 1]
                in_array = any(bracket == '[' for bracket in stack)

                # Упрощенная проверка условий для запятой
                no_comma_condition1 = current_line.endswith(('{', '[', ','))
                no_comma_condition2 = next_line.strip().endswith(('}', ']'))
                no_comma_condition3 = next_line.strip().startswith(('}', ']'))
                no_comma_condition4 = in_array and next_line.strip() == ']'

                should_add_comma = not (no_comma_condition1 or no_comma_condition2 or
                                      no_comma_condition3 or no_comma_condition4)

                # Специальный случай для элементов массива
                if (in_array and current_line.strip() == '}' and
                    next_line.strip() != ']' and not next_line.strip().startswith('}')):
                    should_add_comma = True

                if should_add_comma:
                    current_line += ','

            result.append(current_line)

        return '\n'.join(result)

    def read_data(self, locator: Any) -> Dict:
        """Читает и форматирует JSON-данные из указанного локатора.

        Args:
            locator: Локатор элемента с JSON-данными.

        Returns:
            dict: Распарсенный JSON-объект.

        Raises:
            json.JSONDecodeError: Если данные не могут быть преобразованы в JSON.
        """
        json_string = loc.inner_text()
        formatted_json_string = self.format_json_string(json_string)

        try:
            data = json.loads(formatted_json_string)
        except json.JSONDecodeError as e:
            logger.error("JSON decode error: %s", e)
            logger.error("Formatted JSON: %s", formatted_json_string)
            assert False, f"Invalid json content. Error: {e}"

        return data

    def check_json_equals(self, actual: Any, expected: Any, msg: str) -> None:
        """Сравнивает JSON-объекты на идентичность.

        Args:
            actual: Фактический JSON-объект.
            expected: Ожидаемый JSON-объект.
            msg: Сообщение об ошибке.

        Raises:
            AssertionError: Если объекты не идентичны.
        """
        diff = jsondiff.diff(expected, actual, syntax='symmetric')
        assert len(diff) == 0, f"{msg}. DIFF is {diff}"

"""Модуль компонента модального окна. Содержит класс для работы с модальными окнами,

from locators.modal_window_locators import ModalWindowLocators

logger = get_logger("MODAL_WINDOW")

class ModalWindowComponent(BaseComponent):
    """Компонент модального окна. Предоставляет методы для взаимодействия с окном,

        self.content_items = {}
        self.buttons = []

    def add_content_item(self, name: str, item: object) -> None:
        """Добавляет элемент содержимого в окно по заданному имени."""

        self.content_items[name] = item

    def get_content_item(self, name: str) -> object | None:
        """Возвращает элемент содержимого по имени или None, если не найден."""

        return self.content_items.get(name)

    def add_toolbar_title(self, title: str) -> None:
        """Добавляет заголовок в панель инструментов модального окна."""

        self.toolbar.add_title(title)

    def add_toolbar_button(self, locator: str, name: str) -> None:
        """Добавляет кнопку в панель инструментов модального окна."""

        self.toolbar.add_tooltip_button(locator, name)

    def add_button(self, locator: str, name: str) -> None:
        """Добавляет кнопку в модальное окно."""

        self.buttons.append(Button(self.page, locator, name))

    def get_button_by_name(self, name: str) -> Button | None:
        """Ищет и возвращает кнопку по имени или None, если не найдена."""

        for button in self.buttons:
            if button.name == name:
                return button
        return None

    def click_button(self, name: str) -> None:
        """Кликает по кнопке с заданным именем. Вызывает ошибку, если не найдена."""

        button = self.get_button_by_name(name)
        if button is None:
            assert False, f"Button with name '{name}' not found"
        button.click()

    def click_toolbar_close_button(self) -> None:
        """Кликает по кнопке закрытия в панели инструментов."""

        self.toolbar.click_button("close")

    def scroll_window_down(self) -> None:
        """Прокручивает содержимое окна вниз."""

        self.scroll_down(ModalWindowLocators.MODAL_WINDOW)

    def scroll_window_up(self) -> None:
        """Прокручивает содержимое окна вверх."""

        self.scroll_up(ModalWindowLocators.MODAL_WINDOW)

        self.scroll_left(ModalWindowLocators.MODAL_WINDOW)

        self.scroll_right(ModalWindowLocators.MODAL_WINDOW)

    def check_window_vertical_scrolling(self) -> bool:
        """Проверяет возможность вертикальной прокрутки окна."""

        return self.is_scrollable_vertically(ModalWindowLocators.MODAL_WINDOW)

        return self.is_scrollable_horizontally(ModalWindowLocators.MODAL_WINDOW)

    def check_by_window_title(self) -> None:
        """Проверяет наличие окна по заголовку."""

        self.toolbar.check_toolbar_presence(f"Modal window with '{self.toolbar.title}' is missing")

    def check_button_visibility(self, name: str) -> None:
        """Проверяет наличие кнопки по имени. Вызывает ошибку, если не найдена."""

        button.check_visibility(f"Button with name '{name}' is missing")

    def check_toolbar_button_visibility(self, name: str) -> None:
        """Проверяет наличие кнопки в панели инструментов."""

        self.toolbar.check_button_visibility(name)

    def check_toolbar_button_tooltip(self, name: str, tooltip: str) -> None:
        """Проверяет подсказку у кнопки в панели инструментов."""

        self.toolbar.check_button_tooltip(name, tooltip)

"""Модуль компонента панели навигации. Содержит класс для работы с элементами навигации."""

from locators.navigation_panel_locators import NavigationPanelLocators

logger = get_logger("NAVIGATION_PANEL")

class NavigationPanelComponent(BaseComponent):
    """Компонент панели навигации. Предоставляет методы для взаимодействия с ней."""

        """Инициализирует компонент панели навигации.

        Args:
        """

    def click_item(self, locator: str | Locator, item_name: str) -> None:
        """Кликает по элементу с указанным текстом.

        Args:
            locator: Локатор элемента или строка с CSS/XPath.
            item_name: Текст элемента для клика.
        """

        loc.get_by_text(item_name).click()

    def click_sub_item(self, node_root_locator: str | Locator, item_name: str, parent: None|str) -> None:
        """Кликает по вложенному элементу с указанным текстом.

        Args:
            node_root_locator: Локатор для поиска корневых элементов дерева.
        """

        def find_and_click_item(page, root_locator, item_name: str, parent: None|str) -> Locator|None:
            # Находим все локаторы корневых узлов на текущем уровне
            nodes_count = root_locator.locator('>div.v-treeview-node').count()

            # Если искомый элемент находится на данном уровне, вычисляем локатор и делаем клик
            if parent is None:
                for index in range(nodes_count):
                    node = root_locator.locator(f">div:nth-child({index + 1})").first
                    node_content = node.locator('div.v-treeview-node__content')
                    if node_content.count() > 0:
                        node_text = node_content.first.inner_text().strip()
                        if item_name == node_text:
                            node_attr = node.get_attribute('class')
                            if "v-treeview-node--leaf" not in node_attr:
                                toggle_button = node.locator(
                                    NavigationPanelLocators.NODE_ROOT
                                ).locator(NavigationPanelLocators.TOGGLE_BUTTON).first
                                toogle_class_attr = toggle_button.get_attribute('class')
                                if "v-treeview-node__toggle--open" not in toogle_class_attr:
                                    toggle_button.click()
                            else:
                                node.locator(NavigationPanelLocators.NODE_ROOT).click()
                            page.wait_for_timeout(1000)
                            return node

            # Если элемента нет, рекурсивно ищем дальше
            for index in range(nodes_count):
                node = root_locator.locator(f">div:nth-child({index + 1})").first

                # Извлекаем аттрибуты из корневого узла
                node_class_attr = node.get_attribute('class')

                is_expanded = False
                has_children = False

                # Проверяем лист это или начало поддерева
                if "v-treeview-node--leaf" not in node_class_attr:
                    # Проверяем, является ли узел раскрытым
                    class_attr = node.locator(
                        NavigationPanelLocators.NODE_ROOT
                    ).locator(NavigationPanelLocators.TOGGLE_BUTTON).first.get_attribute('class')
                    if "v-treeview-node__toggle--open" in class_attr:
                        is_expanded = True

                    # Если узел закрыт можем его раскрыть
                    if is_expanded is False:
                        toggle_button = node.locator(
                            NavigationPanelLocators.NODE_ROOT
                        ).locator(NavigationPanelLocators.TOGGLE_BUTTON).first
                        toggle_button.click()
                        # Ждем, пока дочерние элементы прогрузятся/появятся
                        page.wait_for_timeout(1000)

                    # Проверяем, имеет ли узел  дочерние элементы
                    children_count = node.locator('>div.v-treeview-node__children').count()
                    content = node.locator('>div.v-treeview-node__children').inner_html()
                    if children_count > 0 and len(content) != 0:
                        has_children = True

                    # Рекурсивный вызов для дочерних элементов
                    # Ищем дочерние элементы *внутри* текущего узла
                    if has_children and is_expanded:
                        child_nodes_locator = root_locator.locator(
                            f">div:nth-child({index + 1})"
                        ).locator('>div.v-treeview-node__children')
                        found_loc = find_and_click_item(
                            page, child_nodes_locator, item_name, parent=None
                        )
                        if found_loc:
                            if parent is None:
                                return found_loc
                            else:
                                root_texts = root_locator.locator(
                                    f">div:nth-child({index + 1})"
                                ).inner_text().splitlines()
                                if parent in root_texts:
                                    return found_loc

                    # закрываем узел, если в нем ничего не нашли
                    if is_expanded:

            # элемент с заданным именем не найден
            return None

        root_locator = self.get_locator(node_root_locator)
        if parent:
            parent_loc = find_and_click_item(self.page, root_locator, parent, parent=None)
            found = find_and_click_item(
                self.page, parent_loc.locator('>div.v-treeview-node__children'),
                item_name, parent=None
            )
        else:
            found = find_and_click_item(self.page, root_locator, item_name, parent=None)
        assert found, f"Navigation panel item {item_name} is missing"

        Args:

        Returns:
        """

        return loc.all_inner_texts()

    def traverse_panel_tree(self, node_root_locator: str | Locator, level=0, debug=False):
        """
        Рекурсивно обходит дерево v-treeview и выводит информацию об элементах.

        Args:
        """
        def traverse_tree(page, root_locator, level=0, debug=False):

                # Извлекаем текст и аттрибуты из корневого узла
                node_text = node.inner_text()

                if "v-treeview-node--leaf" in node_class_attr:
                    if debug:
                        leaf_msg = f'[{level}][{index}] {node_text} (LEAF, Expanded: {is_expanded}'
                        print(f"{leaf_msg}, Has Children: {has_children})")
                        print("-----------------------------------------")
                else:
                    class_attr = node.locator(NavigationPanelLocators.TOGGLE_BUTTON).get_attribute('class')

                        toggle_button = node.locator(NavigationPanelLocators.TOGGLE_BUTTON)
                        page.wait_for_timeout(300)

                    edited_node_text = node_text.replace("expand_more\n", "")

                    if debug:
                        # Выводим информацию об узле
                        node_msg = f'[{level}][{index}] {edited_node_text} (NODE, Expanded: {is_expanded}'
                        print(f"{node_msg}, Has Children: {has_children})")

                        traverse_tree(page, child_nodes_locator, level+1, debug)

        traverse_tree(self.page, root_locator, level=level, debug=debug)

    def check_item_visibility(self, locator: str | Locator, item_name: str) -> None:
        """Проверяет видимость элемента с указанным текстом.

        Args:
            item_name: Текст элемента для проверки.

        Note:
            Временная обработка для элементов с текстом 'Шаблоны'.
        """

        msg = f"Navigation panel item '{item_name}' is not visible"

        ## временно: в навигационной панели есть две панели с именем Шаблоны
        ## для их различия добавлены индексы Шаблоны_1 для Настройки/Шаблоны
        ## Шаблоны_2 для Настройки/ZTP/Шаблоны
        if item_name == "Шаблоны_1":
            loc = loc.get_by_text("Шаблоны").first
        elif item_name == "Шаблоны_2":
            loc = loc.get_by_text("Шаблоны").nth(1)
        else:
            loc = loc.get_by_text(item_name)
        self.check_visibility(loc, msg)

    def is_item_visible(self, locator: str | Locator, item_name: str) -> bool:
        """
        Проверяет видимость элемента с указанным текстом без выбрасывания исключения.

        Args:

        Returns:
            bool: True если элемент видим, False если нет.
        """
        element_locator = self.page.locator(locator).filter(has_text=item_name)

        # Сначала проверяем что элемент вообще существует
        if element_locator.count() == 0:

        return element_locator.is_visible()

"""Модуль компонента формы ввода и отображения полей настроек. Содержит класс для работы с формами ввода
и отображения полей настроек,их элементами и проверками."""

from locators.settings_form_locators import SettingsFormLocators

class SettingsFormComponent(BaseComponent):
    """Компонент формы ввода и отображения полей настроек. Предоставляет методы для взаимодействия с формой,
    ее содержимым и проверок."""

        """Добавляет элемент содержимого в форму по заданному имени."""

        """Добавляет кнопку в форму."""

    def check_vertical_scrolling(self, locator: str| Locator) -> bool:
        """Проверяет возможность вертикальной прокрутки формы."""

        return self.is_scrollable_vertically(locator)

        """Проверяет наличие тулбара.

        Raises:
            AssertionError: Если тулбар или кнопка редактирования отсутствуют.
        """

        self.toolbar.check_toolbar_presence_by_locator_and_title(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER,
                                                                 "Session settings form toolbar is missing")

"""Модуль компонента таблицы. Содержит класс для работы с табличными данными."""

from datetime import datetime
from playwright.sync_api import Page, expect, Locator

logger = get_logger("TABLE")

class TableComponent(BaseComponent):
    """Компонент таблицы. Предоставляет методы для работы с табличными данными."""

        """Инициализирует компонент таблицы.

        Args:
        """

    def click_arrow_button(self, table_locator: str | Locator, index: int) -> None:

        Args:
            table_locator: Локатор таблицы.
        """

        arrow_button = self.get_header_cell_button(table_locator, index)
        assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"
        arrow_button.click()

    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()

    def get_arrow_button_state(self, table_locator: str | Locator, index: int) -> str:

        Args:

        Returns:
        """

        state = arrow_button.inner_text()
        if state == "keyboard_arrow_up":
            return "up"
        elif state == "keyboard_arrow_down":
            return "down"
        else:
            assert False, f"Got unsupported arrow state: {state}"

    def get_header_cell_button(self, table_locator: str | Locator, index: int) -> Locator:
        """ Поиск кнопки в ячейке заголовка таблицы

        Args:

        Returns:
            Локатор строки кнопки.

        Raises:
            AssertionError: Если индекс вне диапазона.
        """

        table = self.get_locator(table_locator)
        header_cells_count = table.locator("//thead/tr/th").count()
        assert index in range(header_cells_count), "Header cell index is out of range"
        return table.locator("//thead/tr/th").nth(index).get_by_role("button")

    def get_row_locator(self, table_locator: str | Locator, row_index: int) -> Locator | None:
        """Возвращает локатор строки по индексу.

        Args:
            row_index: Индекс строки.

        Returns:
            Локатор строки или None, если индекс вне диапазона.
        """

        rows = table.locator("//tbody/tr")

        if row_index in range(rows.count()):
            return rows.nth(row_index)
        else:

    def get_rows_count(self, locator: str | Locator) -> int:

        Returns:

        Raises:
        """

        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)

        # Чтение ячеек таблицы
            row_data = []
                row_data.append(cell_text)
            table_data.append(row_data)

        return table_data

    def check_table_headers(self, actual_headers, expected_headers) -> None:

        is_equals = True

        arrow_state = ["keyboard_arrow_down", "keyboard_arrow_up"]

        for item in actual_headers:
            item = item.strip()

            if item in arrow_state:
                continue

            if item not in expected_headers:
                is_equals = False

        assert is_equals, \
            f"Expected events table headers {expected_headers} are not equal {actual_headers}"

    def check_content(self,
                    locator: str | Locator,
                    expected_headers: list[str],
                    check_table_not_empty: bool = True) -> None:
        """Проверяет содержимое таблицы.

        Проверяет заголовки и наличие данных в таблице.

        Args:
            expected_headers: Список ожидаемых заголовков таблицы.
            check_table_not_empty: Флаг проверки, что таблица не пустая.
                                По умолчанию True.

        Raises:
            AssertionError: Если таблица пуста (при check_table_not_empty=True)
                        или заголовки неверны.
        """

        if len(table_content) == 0:

        # Проверка заголовков таблицы
        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:
            convert2timestamp: Конвертировать строковое представление даты и времени в Unix timestamp

        Returns:
        """

        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:
        """

        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:
        """

        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:
        """

        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()

        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"

"""Модуль компонента тулбара (панели инструментов).

Содержит класс ToolbarComponent для работы с элементами тулбара:
- Управление кнопками и их подсказками
- Проверка видимости элементов
- Взаимодействие с панелью инструментов
"""

logger = get_logger("TOOLBAR")

class ToolbarComponent(BaseComponent):
    """Компонент тулбара. Предоставляет методы для работы с панелью инструментов.

    Args:
        page (Page): Экземпляр страницы Playwright
        title (str): Заголовок тулбара
    """

    def __init__(self, page: Page, title: str) -> None:
        """Инициализирует компонент тулбара с указанным заголовком."""
        self.title = title

    def add_title(self, title: str) -> None:
        """Устанавливает новый заголовок тулбара.

        Args:
            title (str): Новый заголовок
        """

    def add_tooltip_button(self, locator: Locator, name: str) -> None:
        """Добавляет кнопку с подсказкой в тулбар.

        Args:
            locator (Locator): Локатор кнопки
            name (str): Уникальное имя кнопки
        """
        self.buttons.append(TooltipButton(self.page, locator, name))

    def add_tab_button(self, locator: Locator, name: str) -> None:
        """Добавляет кнопку типа v-tab в тулбар.

        Args:
        """
        self.buttons.append(TabButton(self.page, locator, name))

    def add_button(self, locator: Locator, name: str) -> None:
        """Добавляет кнопку в тулбар.

        Args:
        """

    def get_button_by_name(self, name: str
                          ) -> TooltipButton | TabButton | Button | None:
        """Возвращает кнопку по имени.

        Args:
            name (str): Имя кнопки

        Returns:
            TooltipButton | TabButton | Button | None: Найденная кнопка или None
        """

        """Кликает по кнопке тулбара.

        Args:

        Raises:
            AssertionError: Если кнопка не найдена
        """
            raise AssertionError(f"Unsupported button name {name}")

    def get_toolbar_title_text(self, locator: str = ToolbarLocators.TITLE,
                              filter_text: str | None = None,
                              timeout: int = 5000) -> str:
        """Получает заголовок тулбара окна.

        Args:
            locator: Локатор для заголовка тулбара
            filter_text: Текст для фильтрации заголовка (опционально)
            timeout: Таймаут ожидания в миллисекундах

        Returns:
            str: Текст заголовка тулбара

        Raises:
            Exception: Если не удалось получить заголовок
        """
        # Получаем локатор заголовка
        title_locator = self.get_locator(locator)

        # Фильтруем по тексту если указан
        if filter_text:
            title_locator = title_locator.filter(has_text=filter_text)

        # Ждем появления заголовка с помощью expect
        expect(title_locator).to_be_visible(timeout=timeout)

        # Получаем текст заголовка
        title_text = title_locator.text_content().strip()
        logger.info("Toolbar title: '%s'", title_text)

        return title_text

    def is_button_present(self, name: str) -> bool:
        """Проверяет наличие кнопки.

        Args:

        Returns:
            bool: True если кнопка присутствует

        Raises:
            AssertionError: Если имя кнопки не поддерживается
        """
        return button.is_present(timeout=1000)  # Ожидание 1 секунда

    def is_button_not_present(self, name: str) -> bool:
        """Проверяет отсутствие кнопки.

        Args:

        Returns:
            bool: True если кнопка отсутствует

        Raises:
        """
        return button.is_not_present(timeout=1000)  # Ожидание 1 секунда

    def check_toolbar_presence(self, message: str) -> None:
        """Проверяет видимость тулбара.

        Args:
            message (str): Сообщение об ошибке если тулбар не виден
        """
        locator = self.get_locator(ToolbarLocators.TITLE).filter(
            has_text=self.title
        )
        expect(locator).to_be_visible(), message

    def check_toolbar_presence_by_locator(self, locator: str | Locator,
                                         message: str) -> None:

        Args:
            locator: Локатор тулбара
        """
        locator = self.get_locator(locator)

    def check_toolbar_presence_by_locator_and_title(self, locator: str | Locator,
                                                   message: str) -> None:

        Args:
        """
        locator = self.get_locator(locator).filter(has_text=self.title)

        """Проверяет наличие и видимость кнопки с предварительной прокруткой.

        Args:

        Raises:
            AssertionError: Если кнопка не найдена или не видна
        """

        button.locator.scroll_into_view_if_needed()
        button.check_visibility(f"Button with name {name} is missing")

    def check_button_tooltip(self, name: str, tooltip: str) -> None:
        """Проверяет текст подсказки кнопки.

        Args:
            tooltip (str): Ожидаемый текст подсказки

        Raises:
            AssertionError: Если текст подсказки не совпадает
        """
        button.check_tooltip_with_text(tooltip)

"""Модуль контейнера для отображения событий системного журнала.

Содержит класс для работы с контейнером для отображения событий
системного журнала через Playwright.
"""

from components.events_container_component import EventsContainerComponent

logger = get_logger("SYSTEM_LOG_EVENTS_CONTAINER")

class SystemLogEventsContainer(EventsContainerComponent):
    """Компонент контейнера для отображения событий системного журнала.

    Предоставляет методы для взаимодействия с элементами
    контейнера для отображения событий системного журнала.
    """

        """Инициализирует компонент контейнера для отображения событий системного журнала.

        Args:
        """

        super().__init__(page, locator)

        self.add_tab_to_toolbar(ToolbarLocators.TABS, "events")

        sidebar_filter = self.get_sidebar_filter()
        sidebar_filter.add_filtering_parameter("filter_type", "Тип")
        sidebar_filter.add_filtering_parameter("filter_strictness", "Строгость")
        sidebar_filter.add_filtering_parameter("filter_host", "Хост")

        """Проверяет содержимое контейнера для отображения событий системного журнала."""

        expected_headers = ['ТИП', 'ВРЕМЯ', 'СТРОГОСТЬ', 'ХОСТ', 'ОПИСАНИЕ']

        self.should_be_toolbar()
        self.should_be_base_toolbar_buttons()

        events_tab = self.get_toolbar_tab_button("events")
        events_tab_text = events_tab.get_text(0)
        assert events_tab_text.find("chevron_right") != -1, "Should be icon 'chevron_right'"
        assert events_tab_text.find("События") != -1, "Tab button with text События is missing on toolbar"

        self.should_be_events_table()
        events_table = self.get_events_table_content()

        if len(events_table) == 0:
            assert False, "The contents of the events table are missing"

        self.check_events_table_headers(events_table[0], expected_headers)

        if len(events_table) == 1:
            assert False, "Table body is missing"

        self.should_be_pagination_buttons()

        sidebar_filter = self.click_filter_button()
        sidebar_filter.check_content()

        filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type")
        filter_type_title = filter_type_bar.get_selection_bar_title()
        assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing"

        filter_strictness_bar = sidebar_filter.get_filtering_parameter("filter_strictness")
        filter_strictness_title = filter_strictness_bar.get_selection_bar_title()
        assert filter_strictness_title == "Строгость", "Filtering parameter bar 'Строгость' is missing"

        filter_host_bar = sidebar_filter.get_filtering_parameter("filter_host")
        filter_host_title = filter_host_bar.get_selection_bar_title()
        assert filter_host_title == "Хост", "Filtering parameter bar 'Хост' is missing"

"""Модуль компонента задания даты и времени.

Содержит класс для работы с компонентом задания даты и времени через Playwright.
"""

# from elements.text_element import Text
from elements.text_input_element import TextInput
from components.date_picker_component import DatePickerComponent

logger = get_logger("DATE_INPUT")

class DateInput(BaseComponent):
    """Компонент задания даты и времени.

    Предоставляет методы для взаимодействия с элементами компонентом задания даты и времени.
    """

        """Инициализирует компонент задания даты и времени.

        Args:
            locator: Локатор формы ввода даты и времени (строка или объект Locator)
        """

        self.date_input_locator = self.get_locator(locator)

        self.switch_mode_button = Button(page,
                                   self.date_input_locator.get_by_role("button"),
                                   "switch_mode_button")

        loc = self.date_input_locator.get_by_placeholder("дд.мм.гггг")
        self.date_input_field = TextInput(page, loc, "date_input")

        loc = self.date_input_locator.get_by_placeholder("00:00")
        self.time_input_field = TextInput(page, loc, "time_input")

        self.date_picker = DatePickerComponent(self.page)

    def click_switch_mode_button(self) -> None:
        """ Нажатие на кнопку переключения режимов ввода (текстовый или календарь)."""

        self.switch_mode_button.click()

    def get_date_picker(self) -> DatePickerComponent:
        """ Возвращает экземпляр компонента средства выбора даты. """

        return self.date_picker

    def get_date_field_value(self) -> str:
        """ Возвращает текущее значение поля ввода даты. """

        return self.date_input_field.get_input_value()

    def get_time_field_value(self) -> str:
        """ Возвращает текущее значение поля ввода времени. """

        return self.time_input_field.get_input_value()

    def input_date(self, date: str) -> None:
        """ Ввод даты в формате дд.мм.гггг """

        day, month, year = date.split('.')
        assert len(day) == 2, "Incorrect day format: should be 'dd.mm.yyyy'"
        assert len(month) == 2, "Incorrect month format: should be 'dd.mm.yyyy'"
        assert len(year) == 4, "Incorrect year format: should be 'dd.mm.yyyy'"

        try:
            _ = int(day)
            assert False, f"Incorrect day value {day} for selection"

        try:
            _ = int(month)

        try:
            _ = int(year)

        if self.is_text_input_mode():
            self.date_input_field.check_editable_input("Text field for date input should be editable")
            self.date_input_field.clear()
            self.date_input_field.input_value(date)
        else:
            self.date_picker.select_year_and_month(year, month)
            self.date_picker.select_day(day)

    def input_time(self, time: str) -> None:
        """ Ввод даты в формате чч:мм """

        hours, minutes = time.split(':')
        assert len(hours) == 2, "Incorrect time format: should be 'hh:mm'"
        assert len(minutes) == 2, "Incorrect time format: should be 'hh:mm'"

        try:
            _ = int(hours)
            assert False, f"Incorrect hours value {hours} for selection"

        try:
            _ = int(minutes)
            assert False, f"Incorrect minutes value {minutes} for selection"

        self.time_input_field.check_editable_input("Text field for date input should be editable")
        self.time_input_field.clear()
        self.time_input_field.input_value(time)

    def check_content(self, label: str) -> None:
        """Проверка состава компонента ввода даты."""

        self.check_switch_mode_button_visibility()

        label_locator = self.date_input_locator.get_by_label(label)
        expect(label_locator).to_be_visible()

        self.date_input_field.check_visibility("Text field for date input is missing")
        self.date_input_field.check_empty_input("Text field for date input should be empty")

        self.click_switch_mode_button()

        self.date_picker.check_content()

        self.input_date("11.11.2011")

        label_locator = self.date_input_locator.get_by_label("Время")
        self.time_input_field.check_visibility("Text field for time input is missing")
        current_time_value = self.get_time_field_value()
        assert current_time_value == "00:00", \
            "Should be empty time input field"

    def check_switch_mode_button_visibility(self) -> None:
        """ Проверка видимости кнопки переключения режимов ввода."""

        self.switch_mode_button.check_visibility("Switch Mode Button is missing")

    def is_text_input_mode(self) -> bool:
        """ Проверка текстового режима ввода."""

        result = False

        inner_text = self.switch_mode_button.get_text(0).strip()
        if inner_text == "keyboard":
            result = True
        return result

"""Модуль modal_add_user содержит класс для работы с модальным окном добавления пользователя.

Класс AddUserModalWindow наследует базовый функционал ModalWindowComponent
и реализует специфичные методы для работы с формами добавления пользователей.
"""

import re
from elements.checkbox_element import Checkbox
from data.roles_dict import roles_dict
from components.modal_window_component import ModalWindowComponent
from components.dropdown_list_component import DropdownList
from components.confirm_component import ConfirmComponent

logger = get_logger("ADD_USER_FROM_ACTIVE_DIRECTORY_MODAL_WINDOW")

class AddADUserModalWindow(ModalWindowComponent):
    """Модальное окно добавления нового пользователя.

    Наследует ModalWindowComponent и добавляет элементы формы:
    - Поля ввода (имя, пароль, email и др.)
    - Чекбоксы (Active Directory, Push-уведомления)
    - Выпадающие списки групп, пользователей AD, ролей
    - Кнопки действий
    """

        """Инициализирует элементы формы добавления пользователя."""

        # Локаторы элементов формы
        input_form_locator = page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
        text_field_locator = f"xpath={ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}"
        label_locator = ModalWindowLocators.LABEL_INPUT_FORM_USER_DATA

        # Настройка заголовка и кнопки закрытия тулбара
        self.window_title = "Добавить нового пользователя"
        locator_button_toolbar_close = self.page.get_by_role("navigation").filter(
            has_text=re.compile(self.window_title)
        ).get_by_role("button")

        self.add_toolbar_title(self.window_title)
        self.add_toolbar_button(locator_button_toolbar_close, "close")

        # Добавление элементов формы
        checkbox_1 = Checkbox(
            page,
            input_form_locator.get_by_role("checkbox").nth(0),
            "active_directory"
        )
        self.add_content_item("active_directory_checkbox", checkbox_1)

        label_1 = Text(
            page,
            self.page.locator(label_locator).nth(0),
            "active_directory_checkbox_label"
        )
        self.add_content_item("active_directory_checkbox_label", label_1)

        # Начальный набор полей формы
        # Поле Группа
        group_loc = input_form_locator.get_by_role("combobox").nth(0)
        group_input = TextInput(page, group_loc, "group_input")
        self.add_content_item("group_input", group_input)
        self.add_content_item(
            "group_list",
            DropdownList(page)
        )

        locator_button_search = self.page.get_by_role("button", name="Поиск")
        self.add_button(locator_button_search, "search")

        # Поле Имя
        # loc = input_form_locator.locator("xpath=div[2]").locator(text_field_locator)
        loc = input_form_locator.locator("xpath=div[3]").locator(text_field_locator)
        name_input = TextInput(page, loc, "name_input")
        self.add_content_item("name_input", name_input)

        # Чекбокс "Блокировка" - индекс 1
        checkbox_2 = Checkbox(
            page,
            input_form_locator.get_by_role("checkbox").nth(1),
            "blocking"
        )
        self.add_content_item("blocking_checkbox", checkbox_2)

        # Метка "Блокировка" - индекс 1
        label_2 = Text(
            page,
            self.page.locator(label_locator).nth(1),
            "blocking_checkbox_label"
        )
        self.add_content_item("blocking_checkbox_label", label_2)

        # Поле Роль
        role_loc = input_form_locator.get_by_role("combobox").nth(1)
        role_input = TextInput(page, role_loc, "role_input")
        self.add_content_item("role_input", role_input)
            "roles_list",
        )

        # Поле Комментарий
        loc = input_form_locator.locator("xpath=div[7]").locator(text_field_locator)
        commentary_input = TextInput(page, loc, "commentary_input")
        self.add_content_item("commentary_input", commentary_input)

        # Поле E-mail
        loc = input_form_locator.locator("xpath=div[8]").locator(text_field_locator)
        email_input = TextInput(page, loc, "email_input")
        self.add_content_item("email_input", email_input)

        # Поле Номер для СМС
        loc = input_form_locator.locator("xpath=div[9]").locator(text_field_locator)
        phone_input = TextInput(page, loc, "phone_input")
        self.add_content_item("phone_input", phone_input)

        # Чекбокс "Подписка на Push-уведомления" - индекс 2
        checkbox_3 = Checkbox(
            page,
            input_form_locator.get_by_role("checkbox").nth(2),
            "push_notification"
        )
        self.add_content_item("push_notification_checkbox", checkbox_3)

        # Метка "Подписка на Push-уведомления" - индекс 2
        label_3 = Text(
            page,
            self.page.locator(label_locator).nth(2),
            "push_notification_checkbox_label"
        )
        self.add_content_item("push_notification_checkbox_label", label_3)

        # Добавление кнопок действий
        locator_button_add = self.page.get_by_role("button", name="Добавить")
        self.add_button(locator_button_add, "add")

        locator_button_close = self.page.get_by_role("button", name="Закрыть")
        self.add_button(locator_button_close, "close")

        self.new_user_confirm = ConfirmComponent(page, " Отмена ", " Добавить ")

    def check_active_directory_checkbox(self):
        """Включает чек-бокс Active Directory. """

        self.get_content_item("active_directory_checkbox").check(force=True)

    def uncheck_active_directory_checkbox(self):
        """Выключает чек-бокс Active Directory. """

        self.get_content_item("active_directory_checkbox").uncheck(force=True)

    def check_blocking_checkbox(self):
        """Включает чек-бокс Блокировка."""

        self.get_content_item("blocking_checkbox").check(force=True)

    def uncheck_blocking_checkbox(self):
        """Выключает чек-бокс Блокировка."""

        self.get_content_item("blocking_checkbox").uncheck(force=True)

    def check_push_notification_checkbox(self):
        """Включает чек-бокс Push-уведомления."""

        self.get_content_item("push_notification_checkbox").check(force=True)

    def uncheck_push_notification_checkbox(self):
        """Выключает чек-бокс Push-уведомления."""

        self.get_content_item("push_notification_checkbox").uncheck(force=True)

    def update_input_form_fields(self, expand):
        """Персчитывает локаторы полей формы ввода при добавлении/удалении дополнительного поля. """

        input_form_locator = self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
        # text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA

        if expand:
            new_loc = input_form_locator.locator("xpath=div[4]").locator(text_field_locator)
            self.get_content_item("name_input").update_locator(new_loc)

            new_loc = input_form_locator.locator("xpath=div[8]").locator(text_field_locator)
            self.get_content_item("commentary_input").update_locator(new_loc)

            new_loc = input_form_locator.locator("xpath=div[9]").locator(text_field_locator)
            self.get_content_item("email_input").update_locator(new_loc)

            new_loc = input_form_locator.locator("xpath=div[10]").locator(text_field_locator)
            self.get_content_item("phone_input").update_locator(new_loc)

            role_loc = input_form_locator.get_by_role("combobox").nth(2)
            self.get_content_item("role_input").update_locator(role_loc)
        else:
            new_loc = input_form_locator.locator("xpath=div[3]").locator(text_field_locator)

            new_loc = input_form_locator.locator("xpath=div[7]").locator(text_field_locator)

            role_loc = input_form_locator.get_by_role("combobox").nth(1)

    def new_user(self, user_data):
        """Заполняет форму и добавляет нового пользователя.

        Args:
            user_data (dict): Данные пользователя (имя, роль, пароль и др.)
        """

        menu_locator = self.page.locator(ModalWindowLocators.MENU_INPUT_FORM_USER_DATA)

        # Поле "Группа" - выбор из списка
        group_name = user_data.get("group")
        if group_name is None:
            assert False, "Value of 'group' is missing"

        # Поле "Пользователи AD" - выбор из списка
        name_AD = user_data.get("name_AD")
        if name_AD is None:
            assert False, "Value of 'name_AD' is missing"

        # Поле "Имя" - если определено (не None) вводим вручную
        name = user_data.get("name")

        # Поле "Роль" - выбор из списка
        role = user_data.get("role")
        if role is None:
            assert False, "Value of 'role' is missing"

        # Поиск и выбор заданной группы из списка существующих
        group_field = self.get_content_item("group_input")
        group_field.click()

        group_list = self.get_content_item("group_list")
        group_list.scroll_until_end(menu_locator)
        group_names = group_list.get_item_names(menu_locator)
        if group_name not in group_names:
            assert False, f"Required group name {group_name} is missing"

        group_list.check_item_with_text(group_name)
        group_list.click_item_with_text(group_name)

        # Нажатие кнопки "Поиск"
        search_button = self.get_button_by_name("search")
        search_button.click()

        count = input_form_locator.get_by_role("combobox").count()
        if count == 2:
            assert False, f"Selected group {group_name} is empty. Use another group."

        # Если в группе есть пользователи, открывается новое поле, персчет локаторов
        self.update_input_form_fields(expand=True)

        # Поиск и выбор заданного пользователя AD из списка существующих
        user_AD_loc = input_form_locator.get_by_role("combobox").nth(1)
        user_AD_input = TextInput(self.page, user_AD_loc, "user_AD_input")
        self.add_content_item("user_AD_input", user_AD_input)
            "user_AD_list",
            DropdownList(self.page)
        )

        user_AD_input.click()
        user_AD_list = self.get_content_item("user_AD_list")
        user_AD_list.scroll_until_end(menu_locator)
        user_AD_names = group_list.get_item_names(menu_locator)
        if name_AD not in user_AD_names:
            assert False, f"Required user name {name_AD} is missing"

        user_AD_list.check_item_with_text(name_AD)
        user_AD_list.click_item_with_text(name_AD)

        # Заполнение поля "Имя" (ручной ввод) если задано
        if name:
            name_field = self.get_content_item("name_input")
            name_field.input_value(name)

        # Поиск и выбор заданной роли из списка существующих
        role_field = self.get_content_item("role_input")
        role_field.click()

        roles_list = self.get_content_item("roles_list")
        roles_list.check_item_with_text(user_data["role"])
        roles_list.click_item_with_text(user_data["role"])

        if user_data.get("commentary"):
            input_field = self.get_content_item("commentary_input")
            input_field.input_value(user_data["commentary"])

        if user_data.get("email"):
            input_field = self.get_content_item("email_input")
            input_field.input_value(user_data["email"])

        if user_data.get("phone_number"):
            input_field = self.get_content_item("phone_input")
            input_field.input_value(user_data["phone_number"])

        if user_data.get("blocking_checked"):
            checkbox = self.get_content_item("blocking_checkbox")
            if user_data["blocking_checked"]:
                checkbox.check()
            else:
                checkbox.uncheck()

        if user_data.get("push_notification_checked"):
            checkbox = self.get_content_item("push_notification_checkbox")
            if user_data["push_notification_checked"]:
            else:

        # Отправка формы
        add_button = self.get_button_by_name("add")
        add_button.click()

        # Подтверждение действия
        title = "Добавить нового пользователя"
        self.new_user_confirm.check_title(
            title,
            f"Confirmation dialog window with title '{title}' is missing"
        )
        self.new_user_confirm.click_allow_button()

    def close_window(self):
        """Закрывает модальное окно через кнопку 'Закрыть'."""

        close_button = self.get_button_by_name("close")

    def close_window_by_toolbar_button(self):
        """Закрывает модальное окно через кнопку в тулбаре."""

        self.click_toolbar_close_button()

    def check_content(self):
        """Проверяет наличие и корректность всех элементов формы."""

        self.check_by_window_title()

        is_checked = self.get_content_item("active_directory_checkbox").is_checked()
        if not is_checked:
            assert False, \
            "The checkbox 'Active Directory'should be checked for the add user from Active Directory window"

        self.check_toolbar_button_visibility("close")
        self.check_toolbar_button_tooltip("close", "Закрыть")

        no_op_names = ["roles_list", "group_list"]

        for name in self.content_items.keys():
            item = self.get_content_item(name)

            if name == "active_directory_checkbox_label":
                item.check_have_text(
                    "Active Directory",
                    "Label 'Active Directory' is missing"
                )
            elif name == "blocking_checkbox_label":
                    "Блокировка",
                    "Label 'Блокировка' is missing"
               )
            elif name == "push_notification_checkbox_label":
                    "Подписка на Push-уведомления",
                    "Label 'Подписка на Push-уведомления' is missing"
                )
            elif name == "group_input":
                item.click()
                group_list = self.get_content_item("group_list")
                group_list.check_visibility(menu_locator,
                                          "Groups list is missing")

                is_scrollable_vertically = group_list.check_vertical_scrolling(menu_locator)
                assert is_scrollable_vertically, "Groups list should be scrollable_vertically"
                self.page.keyboard.press("Escape")
            elif name == "role_input":
                roles_list = self.get_content_item("roles_list")
                roles_list.check_visibility(menu_locator,
                                          "Roles list is missing")

                is_scrollable_vertically = roles_list.check_vertical_scrolling(menu_locator)
                assert not is_scrollable_vertically, \
                            "Roles list should not be scrollable_vertically"

                for role in roles_dict.values():
                    # временно, пока есть несоответствие со списком ролей в вкладке Сессии
                    if role == "Пользователь":
                        continue
                    roles_list.check_item_with_text(role)
            elif name in no_op_names:
                continue
            else:
                print(f"check item: {name}")
                item.check_visibility(
                    f"Modal window content item with name '{name}' is missing"
                )

        # Дополнительная проверка состояния чекбоксов
        blocking_checkbox = self.get_content_item("blocking_checkbox")
        is_blocking_checked = blocking_checkbox.is_checked()
        assert not is_blocking_checked, (
            "Checkbox 'Блокировка' should not be checked by default"
        )

        push_checkbox = self.get_content_item("push_notification_checkbox")
        is_push_checked = push_checkbox.is_checked()
        assert not is_push_checked, (
            "Checkbox 'Подписка на Push-уведомления' should not be checked by default"
        )

        self.check_button_visibility("search")
        self.check_button_visibility("add")
        self.check_button_visibility("close")

        # Проверка что поле "Пользователи AD" появилось после поиска
        )

        user_AD_list.check_visibility(menu_locator,
                                  "Users AD list is missing")
        is_scrollable_vertically = user_AD_list.check_vertical_scrolling(menu_locator)
        assert is_scrollable_vertically, "Users AD list should be scrollable_vertically"
        self.page.keyboard.press("Escape")

        self.get_content_item("name_input").check_visibility(
                                           "Modal window content item with name 'name_input' is missing")
        self.get_content_item("role_input").check_visibility(
                                           "Modal window content item with name 'role_input' is missing")
        self.get_content_item("commentary_input").check_visibility(
                                           "Modal window content item with name 'commentary_input' is missing")
        self.get_content_item("email_input").check_visibility(
                                           "Modal window content item with name 'email_input' is missing")
        self.get_content_item("phone_input").check_visibility(
                                           "Modal window content item with name 'phone_input' is missing")

"""Модуль modal_add_local_user содержит класс для работы
с модальным окном добавления локального пользователя.

Класс AddLocalUserModalWindow наследует базовый функционал ModalWindowComponent
"""

import re

logger = get_logger("ADD_LOCAL_USER_MODAL_WINDOW")

class AddLocalUserModalWindow(ModalWindowComponent):

    - Чекбоксы (Active Directory, Блокировка, Push-уведомления)
    - Выпадающий список ролей
    """

        text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA
        input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA

        locator_button_toolbar_close = (
            self.page.get_by_role("navigation")
            .filter(has_text=re.compile(self.window_title))
            .get_by_role("button")
        )

        # Добавление элементов формы по порядку расположения в окне
            page,
            self.page.locator(input_form_locator).get_by_role("checkbox").nth(0),
        )

            page,
        )

        # Поле Имя
        loc = f"{input_form_locator}/div[2]/{text_field_locator}"
        name_input = TextInput(page, self.page.locator(loc), "name_input")

            page,
            self.page.locator(input_form_locator).get_by_role("checkbox").nth(1),
        )

            page,
        )

        # Поле Роль
        role_loc = self.page.locator(input_form_locator).get_by_role("combobox").nth(0)
        self.add_content_item("roles_list", DropdownList(page))

        # Поле Пароль
        loc = f"{input_form_locator}/div[5]/{text_field_locator}"
        password_input = TextInput(page, self.page.locator(loc), "password_input")
        self.add_content_item("password_input", password_input)

        loc = f"{input_form_locator}/div[6]/{text_field_locator}"
        commentary_input = TextInput(page, self.page.locator(loc), "commentary_input")

        loc = f"{input_form_locator}/div[7]/{text_field_locator}"
        email_input = TextInput(page, self.page.locator(loc), "email_input")

        loc = f"{input_form_locator}/div[8]/{text_field_locator}"
        phone_input = TextInput(page, self.page.locator(loc), "phone_input")

            page,
            self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
            .get_by_role("checkbox").nth(2),
        )

            page,
        )

        # Добавление компонента подтверждения/отмены заведения пользователя

     # Действия:
        """Включает чек-бокс Active Directory."""

        """Выключает чек-бокс Active Directory."""

        Args:
        """

        fields = user_data.keys()

        if "name" in fields:
            input_field = self.get_content_item("name_input")
            input_field.input_value(user_data["name"])

        if "role" in fields:
            role_field = self.get_content_item("role_input")
            role_field.click()

            roles_list = self.get_content_item("roles_list")
            roles_list.check_item_with_text(user_data["role"])
            roles_list.click_item_with_text(user_data["role"])

        if "password" in fields:
            input_field = self.get_content_item("password_input")
            input_field.input_value(user_data["password"])

        if "commentary" in fields:

        if "email" in fields:

        if "phone_number" in fields:

        if "blocking_checked" in fields:
            else:

        if "push_notification_checked" in fields:
            else:

            title,
        )

        if is_checked:
            assert False, (
                "The checkbox 'Active Directory' should not be checked for "
                "the add local user window"
            )

        input_fields = ["name_input", "password_input",
                        "commentary_input", "email_input", "phone_input"]

        for name in self.content_items:

                )
                )
                )
                roles_list.check_visibility(menu_locator, "Roles list is missing")

                assert not is_scrollable_vertically, (
                    "Roles list should not be scrollable_vertically"
                )

                        continue
            elif name in input_fields:
                item.check_editable_input(
                    f"Input field with name '{name}' should be editable"
                    )
            elif name == "roles_list":
                continue
            else:
                print(item)
                )

        )

        )

"""Модуль modal_change_password содержит класс для работы с окном изменения пароля текущего пользователя.

Класс ChangePasswordModalWindow наследует базовый функционал ModalWindowComponent
и реализует методы для изменения пароля пользователя.
"""

from locators.text_input_locators import TextInputLocators
from elements.icon_element import Icon
from data.environment import host
from components.alert_component import AlertComponent

logger = get_logger("CHANGE_PASSWORD_MODAL_WINDOW")

class ChangePasswordModalWindow(ModalWindowComponent):
    """Модальное окно изменения пароля текущего пользователя.

    Наследует ModalWindowComponent и добавляет:
    - Поля задания пароля
    - Кнопки действий (Сохранить, Отменить)
    """

        """Инициализирует элементы формы редактирования пользователя."""

        modal_window_locator = page.locator(ModalWindowLocators.MODAL_WINDOW)

        # Тулбар с заголовком
        user_name = host.get_current_user_name()
        self.add_toolbar_title(f"Изменить пароль для пользователя {user_name}?")

        # Поля ввода пароля
        loc = modal_window_locator.get_by_label("Введите текущий пароль *")
        old_password_input = TextInput(page, loc, "old_password_input")
        self.add_content_item("old_password_input", old_password_input)

        icon_locator = loc.locator("xpath=../..").locator(TextInputLocators.ICON_PASSWORD_HIDING)
        old_password_hidden_icon = Icon(page, icon_locator,
                                         "old password hidden icon")
        self.add_content_item("old_password_hidden_icon", old_password_hidden_icon)

        loc = modal_window_locator.get_by_label("Введите новый пароль *")
        new_password_input = TextInput(page, loc, "new_password_input")
        self.add_content_item("new_password_input", new_password_input)

        new_password_hidden_icon = Icon(page, icon_locator,
                                         "new password hidden icon")
        self.add_content_item("new_password_hidden_icon", new_password_hidden_icon)

        loc = modal_window_locator.get_by_label("Введите повторно новый пароль *")
        confirm_password_input = TextInput(page, loc, "confirm_password_input")
        self.add_content_item("confirm_password_input", confirm_password_input)

        confirm_password_hidden_icon = Icon(page, icon_locator,
                                           "confirm password hidden icon")
        self.add_content_item("confirm_password_hidden_icon", confirm_password_hidden_icon)

        input_form_error_message = Text(page,
                                       modal_window_locator.locator(TextInputLocators.INPUT_FORM_MESSAGE),
                                       "input form error message")
        self.add_content_item("input_form_error_message", input_form_error_message)

        locator_button_save = self.page.get_by_role("button", name="Сохранить")
        self.add_button(locator_button_save, "save")

        locator_button_cancel = self.page.get_by_role("button", name="Отменить")
        self.add_button(locator_button_cancel, "cancel")

        # Alert при успешном добавлении пользователя
        self.alert = AlertComponent(page)

        """Нажимает кнопку 'Отменить'"""

        self.get_button_by_name("cancel").click()

    def click_old_password_hidden_icon(self) -> None:
        """Нажатие на иконку скрытия старого пароля."""

        self.get_content_item("old_password_hidden_icon").click()

    def click_new_password_hidden_icon(self) -> None:
        """Нажатие на иконку скрытия нового пароля."""

        self.get_content_item("new_password_hidden_icon").click()

    def click_confirm_password_hidden_icon(self) -> None:
        """Нажатие на иконку скрытия пароля подтверждения."""

        self.get_content_item("confirm_password_hidden_icon").click()

    def change_password(self, old_password: str, new_password: str):
        """Заполняет элементы формы, нажимает кнопку 'Сохранить'"""

        error = ""

        self.get_content_item("old_password_input").input_value(old_password)
        self.get_content_item("new_password_input").input_value(new_password)
        self.get_content_item("confirm_password_input").input_value(new_password)

        button_save = self.get_button_by_name("save")

        if button_save.is_disabled():
            error_message = self.get_content_item("input_form_error_message")
            error = error_message.get_text(0)
            return False, error

        button_save.click()

        is_changed = False

        alert_type = self.alert.get_alert_type()
        if alert_type == "success":
            self.alert.check_alert_presence(' Пароль успешно изменён ')
            self.alert.check_alert_absence(' Пароль успешно изменён ')
            is_changed = True
        elif alert_type == "error":
            self.alert.check_alert_presence(' Неверно указан текущий пароль ')
            self.alert.check_alert_absence(' Неверно указан текущий пароль ')

            error = "Old password is not equal real password"
        else:
            error = f"Got unexpected alert type {alert_type}"

        return is_changed, error

    def get_password_inputs(self) -> []:
        """Возвращает список полей ввода пароля (для тестовых целей)."""

        text_inputs = []
        text_inputs.append(self.get_content_item("old_password_input"))
        text_inputs.append(self.get_content_item("new_password_input"))
        text_inputs.append(self.get_content_item("confirm_password_input"))
        return text_inputs

        self.get_content_item("old_password_input").check_visibility(
                                  "Old password input form is missing"
                             )
        old_password_hidden_icon = self.get_content_item("old_password_hidden_icon")
        old_password_hidden_icon.check_visibility(
                                  "Old password hidden icon is missing"
                             )
        is_hidden_state = old_password_hidden_icon.is_password_hidden()
        assert is_hidden_state, "Old password hidden icon should be in hidden state"

        self.get_content_item("new_password_input").check_visibility(
                                  "New password input form is missing"
                             )
        new_password_hidden_icon = self.get_content_item("new_password_hidden_icon")
        new_password_hidden_icon.check_visibility(
                                  "New password hidden icon is missing"
                             )
        is_hidden_state = new_password_hidden_icon.is_password_hidden()
        assert is_hidden_state, "New password hidden icon should be in hidden state"

        self.get_content_item("confirm_password_input").check_visibility(
                                  "Confirm password input form is missing"
                             )
        confirm_password_hidden_icon = self.get_content_item("confirm_password_hidden_icon")
        confirm_password_hidden_icon.check_visibility(
                                  "Confirm password hidden icon is missing"
                             )
        is_hidden_state = confirm_password_hidden_icon.is_password_hidden()
        assert is_hidden_state, "Confirm password hidden icon should be in hidden state"

        self.check_button_visibility("cancel")

        is_disabled = button_save.is_disabled()
        assert is_disabled, "Button 'Сохранить' should be hidden"

    def check_error_message(self, text: str) -> None:
        """Проверяет сообщение об ошибке, возникшее при заполнении полей формы."""

        error_message = self.get_content_item("input_form_error_message")
        error_message.check_have_text(text, "Unexpected error message")

"""Модуль modal_edit_user содержит класс для работы с окном редактирования пользователя.

Класс EditUserModalWindow наследует базовый функционал ModalWindowComponent
и реализует методы для редактирования данных пользователя.
"""

import re

logger = get_logger("EDIT_USER_MODAL_WINDOW")

class EditUserModalWindow(ModalWindowComponent):
    """Модальное окно редактирования пользователя.

    - Поля редактирования данных
    - Чекбоксы настроек
    - Кнопки действий (Сохранить, Удалить и др.)
    """

    def __init__(self, page: Page, user_name: str):

        # Настройка заголовка и кнопки закрытия
        self.window_title = user_name
        )

        # Добавление полей формы
        # Поле Имя
        loc = (
            self.page.locator(input_form_locator)
            .locator("xpath=div[1]")
            .locator(text_field_locator)
        )

        # Поле Роль

        loc = (
            .locator("xpath=div[4]")
        )

        loc = (
            .locator("xpath=div[5]")
        )

        loc = (
            .locator("xpath=div[6]")
        )

        # Добавление чекбоксов и их меток

        # Чекбокс "Блокировка" - теперь индекс 0 (т.к. нет Active Directory)
            page,
            .get_by_role("checkbox").nth(0),
        )
        self.add_content_item("blocking_checkbox", checkbox_1)

        # Метка "Блокировка" - индекс 0
            page,
        )
        self.add_content_item("blocking_checkbox_label", label_1)

        # Чекбокс "Подписка на Push-уведомления" - индекс 1
            page,
            .get_by_role("checkbox").nth(1),
        )
        self.add_content_item("push_notification_checkbox", checkbox_2)

        # Метка "Подписка на Push-уведомления" - индекс 1
            page,
        )
        self.add_content_item("push_notification_checkbox_label", label_2)

        locator_button_delete = self.page.get_by_role("button", name="Удалить")
        self.add_button(locator_button_delete, "delete")

        locator_button_reset = self.page.get_by_role("button", name="Сбросить пароль")
        self.add_button(locator_button_reset, "reset_password")

        # Инициализация компонентов подтверждения
        self.save_user_confirm = ConfirmComponent(page, " Отмена ", " Сохранить ")
        self.delete_user_confirm = ConfirmComponent(page, " Отмена ", " Удалить ")

        """Закрывает окно через кнопку 'Закрыть'."""

        """Закрывает окно через кнопку в тулбаре."""

    def delete_user(self):
        """Удаляет пользователя с подтверждением."""

        delete_button = self.get_button_by_name("delete")
        delete_button.click()

        title = "Удаление"
        self.delete_user_confirm.check_title(
            title,
        )
        self.delete_user_confirm.click_allow_button()

    def edit_user(self, user_data):
        """Редактирует данные пользователя.

        Args:
            user_data (dict): Данные для обновления (имя, роль и др.)
        """

            else:

            else:

        save_button = self.get_button_by_name("save")
        save_button.click()

        title = "Сохранение"
        self.save_user_confirm.check_title(
            title,
        )
        self.save_user_confirm.click_allow_button()

    def reset_password(self):
        """Инициирует сброс пароля пользователя."""

        reset_password_button = self.get_button_by_name("reset_password")
        reset_password_button.click()

    def check_content(self, user_name, role):
        """Проверяет наличие и корректность элементов окна.

        Args:
            user_name (str): Ожидаемое имя пользователя
            role (str): Ожидаемая роль пользователя
        """

            if name == "push_notification_checkbox_label":
                )
                )
            elif name == "name_input":
                name_field = self.get_content_item("name_input")
                text_value = name_field.get_input_value()
                assert text_value == user_name, (
                    f"Expected user name '{user_name}' is not equal "
                    f"real user name '{text_value}'"
                )
                roles_list.check_item_with_text(role)
                continue
            else:
                )

        self.check_button_visibility("save")
        self.check_button_visibility("delete")
        self.check_button_visibility("reset_password")

"""Модуль modal_view_template содержит класс для работы с модальным окном шаблона.

Класс ViewTemplateModalWindow наследует базовый функционал ModalWindowComponent
и реализует методы просмотра модального окна шаблона.
"""

import re
from components.json_container_component import JsonContainerComponent
from locators.json_container_locators import JsonContainerLocators

logger = get_logger("VIEW_TEMPLATE_MODAL_WINDOW")

class ViewTemplateModalWindow(ModalWindowComponent):
    """Модальное окно шаблона.

    Наследует ModalWindowComponent и добавляет функционал для:
    1. Инициализации модального окна с конкретным шаблоном
    2. Закрытия модального окна через тулбар
    3. Проверки содержимого модального окна
    4. Проверки содержимого JSON контейнера
    """

    def __init__(self, page: Page, title: str):
        """Инициализирует элементы формы модального окна шаблона."""

        self.window_title = title

        # Инициализация JSON контейнера
        self.json_container = JsonContainerComponent(page)

        Проверяет:
        1. Наличие заголовка окна с именем шаблона
        2. Видимость кнопки закрытия
        3. Подсказку кнопки закрытия
        """

    def verify_json_container_content(self, template_data: dict) -> None:
        """Проверяет соответствие данных контейнера данным из API.

        Args:
            template_data: Данные шаблона из API.
        """
        # Читаем данные из контейнера
        actual_data = self.json_container.read_data(JsonContainerLocators.CONTAINER)

        # Сравниваем actual_data с данными конкретного шаблона
        self.json_container.check_json_equals(
            actual_data,
            template_data,
            "Expected json content is not equal actual:"
        )

"""Модуль modal_view_ztp_template содержит класс для работы с модальным окном шаблона ZTP.

Класс ViewZTPTemplateModalWindow наследует базовый функционал ModalWindowComponent
и реализует методы просмотра модального окна шаблона Zero Touch Provisioning.
"""

import re

logger = get_logger("VIEW_ZTP_TEMPLATE_MODAL_WINDOW")

class ViewZTPTemplateModalWindow(ModalWindowComponent):
    """Модальное окно шаблона Zero Touch Provisioning.

    1. Инициализации модального окна с конкретным шаблоном ZTP
    2. Закрытия модального окна
    3. Получения конфигурационных данных шаблона ZTP
    4. Проверки содержимого модального окна
    5. Сравнения данных с API специфичными для ZTP
    """

        """Инициализирует элементы формы модального окна шаблона ZTP."""

        # Настройка заголовка и кнопок закрытия

    def close_window(self) -> None:

        """Проверяет наличие и корректность элементов окна ZTP шаблона.

        4. Наличие специфичных полей для ZTP
        """

    def get_modal_window_data(self) -> dict:
        """Извлекает данные из модального окна шаблона ZTP и структурирует по кодам и значениям.

        Returns:
            dict: Данные в формате {'код': 'значение'} как в API ZTP
        """
        modal_data = {}

        # Получаем все значения из input полей
        input_locator = self.get_locator(ModalWindowLocators.MODAL_WINDOW_TEXT_FIELD_INPUT)

        # Проверка наличия элементов
        input_count = input_locator.count()
        if input_count == 0:
            logger.warning("Поля ввода не найдены в модальном окне ZTP")
            return modal_data

        all_values = []

        # Обрабатываем каждое поле с обработкой возможных ошибок
        for i in range(input_count):
            input_field = input_locator.nth(i)

            # Проверяем, что элемент видим и доступен
            if not input_field.is_visible():
                logger.debug("Поле %s не видимо, пропускаем", i)
                continue

            # Получаем значение с обработкой возможных ошибок состояния элемента
            if input_field.is_visible():
                value = input_field.input_value().strip()
                if value:  # Игнорируем пустые значения
                    all_values.append(value)
            else:
                logger.debug("Поле %s стало невидимым после проверки, пропускаем", i)

        logger.info("Все значения из полей ZTP шаблона: %s", all_values)

        # Анализируем пары код-значение для ZTP формата
        i = 0
        while i < len(all_values) - 1:
            current_value = all_values[i]
            next_value = all_values[i + 1]

            # Для ZTP шаблонов могут быть как числовые коды, так и строковые идентификаторы
            if current_value.isdigit() or self._is_ztp_field_code(current_value):
                # Текущее значение - код, следующее - значение
                modal_data[current_value] = next_value
                i += 2  # Перескакиваем через пару
            else:
                # Если текущее значение не подходит как код, ищем следующую пару
                i += 1

        # Добавляем имя шаблона с ключом 'template'
        if all_values:
            modal_data['template'] = all_values[-1]

        logger.info("Структурированные данные из модального окна ZTP: %s", modal_data)
        return modal_data

    def _is_ztp_field_code(self, value: str) -> bool:
        """Проверяет, является ли значение кодом поля ZTP.

        Args:
            value: Проверяемое значение

        Returns:
            bool: True если значение похоже на код поля ZTP
        """
        ztp_field_patterns = [
            'vendorCode',
            'authentication',
            'deviceType',
            'authenticationOption',
            'manufacturer'
        ]

        return any(pattern.lower() in value.lower() for pattern in ztp_field_patterns)

    def compare_modal_with_api_data(self, modal_data: dict, api_data: dict,
                                   title: str) -> None:
        """Сравнивает данные из модального окна ZTP с данными из API.

        Args:
            modal_data: Данные из модального окна
            api_data: Данные из API ответа
            title: Имя шаблона для проверки
        """
        errors = []

        # Для ZTP API данные уже содержат нужные ключи
        api_data_adapted = api_data.copy()

        # Сравниваем все поля
        for code, expected_value in api_data_adapted.items():
            if code in modal_data:
                actual_value = modal_data[code]
                if str(actual_value) != str(expected_value):
                    error_msg = (
                        f"Расхождение для поля {code}: "
                        f"модальное окно='{actual_value}', API='{expected_value}'"
                    )
                    logger.error(error_msg)
                    errors.append(error_msg)
            else:
                error_msg = f"Поле {code} не найдено в модальном окне ZTP"
                logger.error(error_msg)
                errors.append(error_msg)

        # Дополнительная проверка имени шаблона
        modal_template = modal_data.get('template', '')
        if modal_template != title:
            error_msg = (
                f"Расхождение в имени шаблона ZTP: "
                f"модальное окно='{modal_template}', ожидается='{title}'"
            )
            logger.error(error_msg)
            errors.append(error_msg)

        # Если есть расхождения, выбрасываем ошибку
        if errors:
            error_details = "\n".join(errors)
                f"Обнаружены расхождения для ZTP шаблона '{title}':\n{error_details}"
            )

        logger.info("Данные модального окна ZTP соответствуют API для шаблона '%s'", title)

"""Модуль компонента панели выбора значения.

Содержит класс для работы с компонентом панели выбора значения через Playwright.
"""

from locators.selection_bar_locators import SelectionBarLocators
from locators.combobox_locators import ComboboxLocators

logger = get_logger("SELECTION_BAR")

class SelectionBarComponent(BaseComponent):
    """Компонент панели выбора значения.

    Предоставляет методы для взаимодействия с элементами компонента панели выбора значения.
    """

    def __init__(self, page: Page, locator_or_text: str | Locator) -> None:
        """Инициализирует компонент панели выбора значения.

        Args:
            locator_or_text: Локатор панели выбора значения (строка или объект Locator)
                            или текст для поиска
        """

        # Определяем локатор в зависимости от типа параметра
        if isinstance(locator_or_text, Locator):
            # Если передан готовый Locator
            self.selection_bar_locator = locator_or_text
        elif locator_or_text.startswith(('//', '.', '#', 'xpath=', 'css=')):
            # Если передан строковый локатор
            self.selection_bar_locator = self.get_locator(locator_or_text)
        else:
            # Если передан текст - ищем по тексту label
            xpath = SelectionBarLocators.COMBOBOX_BY_LABEL_XPATH.format(locator_or_text)
            self.selection_bar_locator = self.page.locator(xpath)

        # При нажатии на панель появляется выпадающий список с параметрами фильтрации для выбора
        self.selected_values_list = DropdownList(self.page)

    def clear_selections(self) -> None:
        """Удаление ранее выбранных значений"""
        selected_values = self.get_selected_values()
        if len(selected_values) > 0:
            clear_button_locator = self.selection_bar_locator.locator(
                SelectionBarLocators.CLEAR_SELECTION_BUTTON
            )
            clear_button_locator.click()

    def get_available_options(self) -> list[str]:
        """Возвращает список всех доступных опций из выпадающего списка.

        Returns:
        """
        logger.info("Getting available options from dropdown list...")

        # Открываем выпадающий список
        self.open_values_list()

        # Ждем появления списка
        self.wait_for_timeout(1000)

        # Получаем все элементы списка
        options = self.selected_values_list.get_item_names(
            SelectionBarLocators.LIST_ITEMS
        )

        # Закрываем список (кликаем вне его)
        self.wait_for_timeout(500)

        logger.info(f"Found available options: {len(options)} - {options}")
        return options

    def get_selection_bar_title(self) -> str:
        """Возвращает название панели выбора значения"""
        title_locator = self.selection_bar_locator.locator(SelectionBarLocators.TITLE_LOCATOR)
        return title_locator.text_content()

    def get_selected_values(self) -> list[str]:
        """Возвращает список выбранных значений"""
        selected_values_locator = self.selection_bar_locator.locator(
            SelectionBarLocators.PARAMETERS_SELECTED
        )
        selected_values = selected_values_locator.all_inner_texts()
        return selected_values[0].splitlines()

    def clear_combobox_field(self, field_name: str, field_locator: str) -> None:
        """Очищает значение в combobox поле с помощью кнопки закрытия (крестика).

        Args:
            field_name: Название поля для очистки
            field_locator: Локатор поля combobox
        """
        logger.info(f"Clearing combobox field '{field_name}' using close button...")

        # Находим поле по локатору
        field_container = self.page.locator(field_locator).first

        # Проверяем что поле видимо
        if not field_container.is_visible():
            logger.info(f"Field '{field_name}' is not visible, skipping clearing")
            return

        # Прокручиваем до поля
        field_container.scroll_into_view_if_needed()

        # Ищем кнопку закрытия (крестик) внутри контейнера поля
        close_button = field_container.locator(
            ComboboxLocators.COMBOBOX_CLOSE_BUTTON
        )

        # Проверяем наличие и видимость кнопки закрытия
        if close_button.count() > 0 and close_button.is_visible():
            # Если кнопка закрытия видима - кликаем на нее
            close_button.click()
            self.wait_for_timeout(500)
            logger.info(f"Combobox field '{field_name}' cleared using close button")
        else:
            # Если кнопки закрытия нет, просто логируем этот факт
            msg = f"Close button not found for field '{field_name}', clearing not performed"
            logger.info(msg)

    def open_values_list(self) -> None:
        """Открытие выпадающего списка путем нажатия на панель выбора значения"""
        expect(self.selection_bar_locator).to_be_visible()

        parent_class = self.selection_bar_locator.get_attribute("class")
        if parent_class and SelectionBarLocators.MENU_ACTIVE_CLASS in parent_class:
            logger.info("Values list is already open")
            return

        # Используем force click для обхода перекрывающих элементов
        logger.info("Using force click to open the list")
        self.selection_bar_locator.click(force=True)

        # Ждем появления выпадающего списка
        self.wait_for_timeout(1500)

    def select_value(self, name: str) -> None:
        """Выбор значения из списка"""
        self.selected_values_list.check_item_with_text(name)
        self.selected_values_list.click_item_with_text(name)

    def wait_for_timeout(self, timeout: int) -> None:
        """Ожидает указанное количество миллисекунд.

        Args:
            timeout: Время ожидания в миллисекундах
        """
        self.page.wait_for_timeout(timeout)

    def check_field_highlighted_error(self, field_name: str, field_locator: str) -> None:
        """Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).

        Args:
            field_name: Название поля для проверки
            field_locator: Локатор поля для проверки
        """
        logger.info(f"Checking field '{field_name}' for error highlighting...")

        field_element = self.page.locator(field_locator).first

        self.check_visibility(field_element, f"Field '{field_name}' not found")

        # Ищем родительский контейнер
        parent_container = field_element.locator(SelectionBarLocators.INPUT_PARENT_CONTAINER).first

        # Проверка классов ошибки с использованием локатора из SelectionBarLocators
        if parent_container.count() > 0:
            has_error = parent_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS).count() > 0

            if not has_error:
                raise AssertionError(f"Field '{field_name}' is not highlighted with error color")

        logger.info(f"Field '{field_name}' is correctly highlighted with error color")

    def check_field_not_highlighted_error(self, field_name: str, field_locator: str) -> None:
        """Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).

        Args:
        """
        logger.info(f"Checking field '{field_name}' for absence of error highlighting...")

        # Проверяем отсутствие классов ошибки с использованием локатора из SelectionBarLocators

            if has_error:
                raise AssertionError(f"Field '{field_name}' is highlighted with error")

        logger.info(f"Field '{field_name}' correctly has no error highlighting")

"""Модуль компонента боковой панели формы ввода полей фильтрации отображения данных в панели событий. Содержит класс
для работы с формами ввода, их элементами и проверками."""

from components_derived.selection_bar_component import SelectionBarComponent
from components_derived.date_input_component import DateInput

logger = get_logger("SIDEBAR_FILTER")

class SidebarFilterComponent(BaseComponent):
    """Компонент боковой панели формы ввода полей фильтрации отображения данных в панели событий. Предоставляет методы
    для взаимодействия с формой, ее содержимым и проверок."""

        """Инициализирует компонент боковой панели формы ввода параметров фильтрации.

        Args:
            locator: Локатор контейнера (строка или объект Locator) для которого вызывается панель
        """

        self.sidebar_locator = self.get_locator(locator)

        self.toolbar = ToolbarComponent(page, "Настройки и параметры")

        # Поля ввода даты начала и даты окончания события
        self.start_time_filter = DateInput(page,
                                           self.sidebar_locator.locator(
                                           "//div[contains(@class, 'scrollarea__body')]/div").nth(0))
        self.finish_time_filter = DateInput(page,
                                           "//div[contains(@class, 'scrollarea__body')]/div").nth(1))

        # Поля задания параметров фильтрации (произвольное количество)
        self.filtering_parameters = {}

        # Кнопки задания/сброса параметров фильтрации
        self.apply_button = Button(page,
                                   self.sidebar_locator.get_by_role("button").filter(has_text='Применить Фильтры'),
                                   "apply_button")
        self.reset_button = Button(page,
                                   self.sidebar_locator.get_by_role("button").filter(has_text='Сбросить Фильтры'),
                                   "reset_button")

    def add_filtering_parameter(self, name: str, title: str) -> None:
        """Добавляет поле задания параметров фильтрации по заданному имени."""

        loc = self.sidebar_locator.locator("//div[contains(@class, 'scrollarea__body')]").\
                                   get_by_role("combobox").filter(has_text=title)
        self.filtering_parameters[name] = SelectionBarComponent(self.page, loc)

    def get_filtering_parameter(self, name: str) -> SelectionBarComponent | None:
        """Возвращает поле задания параметров фильтрации по заданному или None, если не найдено."""

        return self.filtering_parameters.get(name)

    def get_start_time_filter(self) -> DateInput:
        """Возвращает поле задания параметров фильтрации даты начала."""

        return self.start_time_filter

    def get_finish_time_filter(self) -> DateInput:
        """Возвращает поле задания параметров фильтрации даты окончания."""

        return self.finish_time_filter

    def click_apply_button(self) -> None:
        """Клик по кнопке применения фильтра."""

        self.apply_button.click()

    def click_reset_button(self) -> None:
        """Клик по кнопке сброса фильтра."""

        self.reset_button.click()

        """Проверяет наличие постоянных полей панели параметров фильтрации."""

        self.start_time_filter.check_content("Дата начала")
        self.finish_time_filter.check_content("Дата окончания")

        self.check_apply_button_visibility()
        self.check_reset_button_visibility()

    def check_apply_button_visibility(self) -> None:
        """Проверяет наличие кнопки применения фильтра."""

        self.apply_button.check_visibility("Apply Filter Button is missing")

    def check_reset_button_visibility(self) -> None:
        """Проверяет наличие кнопки сброса фильтра."""

        self.reset_button.check_visibility("Reset Filter Button is missing")

        Raises:
            AssertionError: Если тулбар отсутствует.
        """

        self.toolbar.check_toolbar_presence_by_locator_and_title(self.sidebar_locator,
                                                                 "Sidebar Filter form toolbar is missing")

"""

from locators.user_card_locators import UserCardLocators
from components_derived.modal_change_password import ChangePasswordModalWindow

class UserCard(BaseComponent):
    """Компонент карточка.

    """

        """Инициализирует компонент карточки.

        Args:
        """

        card_locator = page.locator(UserCardLocators.CARD_USER)

        # Обновленные локаторы согласно новой структуре карточки
        self.current_user_name = Text(
            page,
            card_locator.locator("xpath=/div/div[1]"),  # Изменено с div[2] на div[1]
            "current user name"
        )
        self.current_user_role = Text(
            page,
            card_locator.locator("xpath=/div/div[2]"),  # Изменено с div[3] на div[2]
            "current user role"
        )
        self.login_time = Text(
            page,
            card_locator.locator("xpath=/div/div[3]"),  # Изменено с div[4] на div[3]
            "login time"
        )
        self.session_time = Text(
            page,
            card_locator.locator("xpath=/div/div[4]"),  # Изменено с div[5] на div[4]
            "session time"  # Исправлено имя с "current user name" на "session time"
        )
            page,
        )
        self.change_password_button = Button(
            page,
            page.get_by_role("button", name="Изменить пароль"),
            "change password button"
        )
        self.close_button = Button(
            page,
            page.get_by_role("button", name="Закрыть"),
            "close button"
        )

        # окна, отрываемые после нажатия кнопок
        self.change_password_modal_window = ChangePasswordModalWindow(page)

    def click_change_password_button(self) -> ChangePasswordModalWindow:
        """Нажимает кнопку открытия окна изменения пароля.

        Выполняет клик по кнопке 'Изменить пароль' в карточке пользователя.
        """

        self.change_password_button.click()
        return self.change_password_modal_window

    def click_logout_button(self) -> None:

        """

        """Нажимает кнопку закрытия окна карточки пользователя.

        Выполняет клик по кнопке 'Закрыть' в карточке пользователя.
        """

        """Проверяет наличие и корректность элементов карточки пользователя
           в зависимости от его роли в системе."""

        current_user_credential = host.get_current_user_credential()

        name = current_user_credential["login"]
        text_to_check = f"Имя пользователя: {name}"
        self.current_user_name.check_have_text(
            text_to_check,
            f"Expected text {text_to_check} is missing in user card"
        )

        role = roles_dict.get(current_user_credential["role"])
            assert False, "Unknown user role in current user credential"
        text_to_check = f"Роль: {role}"
        self.current_user_role.check_have_text(
        )

        login_time_str = self.login_time.get_text(0)
        assert login_time_str.find("Время входа:") != -1, (
            "Expected text 'Время входа:' is missing in user card"
        )
        session_time_str = self.session_time.get_text(0)
        assert session_time_str.find("Время сессии:") != -1, (
            "Expected text 'Время сессии:' is missing in user card"
        )

        self.logout_button.check_visibility(
            "Logout button is missing on user card"
        )
        self.change_password_button.check_visibility(
            "Change password button is missing on user card"
        )

        self.close_button.check_visibility(
            "Close button is missing on user card"
        )

    def check_card_is_closed(self) -> None:
        """Проверяет, что карточка пользователя отсутствует.

        Raises:
            AssertionError: Если карточка пользователя все еще открыта.
        """
        card_locator = self.page.locator(UserCardLocators.CARD_USER).locator("xpath=../..")
        class_attr = card_locator.get_attribute('class')
        assert 'menuable__content__active' not in class_attr, "User card should be closed"

from pathlib import Path
import os
import inspect
from dotenv import load_dotenv

load_dotenv()

pytest_plugins = [
    'fixtures.pages'
]

def pytest_sessionfinish(session, exitstatus):
    """Генерация Markdown файлов с группировкой по классам"""
    if session.config.getoption("--generate-md"):
        tests_by_file = {}

        for item in session.items:
            if not (hasattr(item, 'function') and item.function.__doc__):
                continue

            file_path = str(item.fspath)
            file_name = os.path.splitext(os.path.basename(file_path))[0]

            if file_name not in tests_by_file:
                tests_by_file[file_name] = {}

            # Группируем тесты по классам
            class_name = item.cls.__name__ if hasattr(item, 'cls') else "Без класса"
            if class_name not in tests_by_file[file_name]:
                tests_by_file[file_name][class_name] = {
                    'doc': inspect.cleandoc(item.cls.__doc__) if hasattr(item, 'cls') and item.cls.__doc__ else "",
                    'tests': []
                }
            tests_by_file[file_name][class_name]['tests'].append(item)

        # Создаем директорию docs
        output_dir = Path("docs")
        output_dir.mkdir(exist_ok=True)

        # Генерируем файлы
        for file_name, classes in tests_by_file.items():
            md_content = f"# Документация тестов ({file_name}.py)\n\n"

            for class_name, class_data in classes.items():
                if class_name != "Без класса":
                    md_content += f"## Класс: {class_name}\n"
                    if class_data['doc']:
                        md_content += f"{class_data['doc']}\n\n"

                for item in class_data['tests']:
                    md_content += f"### {item.name}\n"
                    md_content += f"**Маркеры:** {', '.join(mark.name for mark in item.own_markers)}\n\n"
                    md_content += f"```python\n{inspect.cleandoc(item.function.__doc__)}\n```\n\n"

            output_file = output_dir / f"{file_name}.md"
            output_file.write_text(md_content, encoding='utf-8')

def pytest_addoption(parser):
    parser.addoption(
        "--generate-md",
        action="store_true",
        default=False,
        help="Генерировать Markdown документацию с группировкой по классам"
    )

"""Модуль constants содержит настройки и константы приложения.

Основной класс Constants предоставляет доступ к переменным окружения,
используемым для аутентификации и других настроек.
"""

import os

class Constants:
    """Хранит константы и переменные окружения.

    Получает значения из переменных окружения. Используется для аутентификации.

    Атрибуты:
        login (str): Логин для аутентификации.
        password (str): Пароль для аутентификации.
    """

    try:
        login = os.getenv('AUTH_LOGIN')
        password = os.getenv('AUTH_PASSWORD')
    except KeyError:
        print("LOGIN OR PASSWORD WASN'T FOUND")

"""Модуль environment содержит настройки окружения и URL-адресов.

Класс Environment предоставляет методы для работы с окружением,
токенами и URL-адресами API.
"""

import os
from typing import Dict

class Environment:
    """Управление окружением, URL-адресами и токенами.

    Атрибуты класса:
        TEST (str): Константа для тестового окружения.
        DEVELOP (str): Константа для dev-окружения.
        URLS (Dict[str, str]): Словарь URL для разных окружений.
    """

    TEST: str = 'test'
    DEVELOP: str = 'develop'

    URLS: Dict[str, str] = {
        TEST: 'http://192.168.2.76/',
        DEVELOP: 'http://192.168.2.69/'
    }

    def __init__(self) -> None:
        """Инициализирует объект окружения и токены."""

        try:
            self.env: str = os.getenv('ENV', self.TEST)
            self.current_user_credential = {}
        except KeyError:
            self.env: str = self.TEST

    def get_base_url(self) -> str:
        """Возвращает базовый URL для текущего окружения.

        Возвращает:
            str: Базовый URL с путем для теста или без него.
        """

        if self.env in self.URLS:
            # if self.env == self.TEST:
            #     return self.URLS[self.env] + "e-nms-ui/"
            return self.URLS[self.env]
        raise Exception(f"Unknown value of ENV variable {self.env}")

    def get_env_name(self) -> str:
        """Возвращает имя текущего окружения.

            str: имя текущего окружения.
        """
        return self.env

    def get_request_url(self) -> str:
        """Возвращает URL для API-запросов.

            str: URL для API-запросов.
        """

    def set_current_user_credential(self, credential: {}) -> None:
        """Сохраняет учетные данные текущего пользователя.

        Аргументы:
            credential ({}): Учетные данные текущего пользователя.
        """

        self.current_user_credential = credential

    def get_access_token(self) -> str:
        """Возвращает текущий токен доступа.

            str: Текущий токен доступа.
        """

        return self.current_user_credential.get("access_token")

    def get_current_user_name(self) -> str:
        """Возвращает имя текущего пользователя.

            str: Имя текущего пользователя.
        """

        return self.current_user_credential.get("login")

    def get_current_user_role(self) -> str:
        """Возвращает роль текущего пользователя.

            str: Роль текущего пользователя.
        """

        return self.current_user_credential.get("role")

    def get_current_user_id(self) -> str:
        """Возвращает id текущего пользователя.

            str: id текущего пользователя.
        """

        return self.current_user_credential.get("$id")

    def get_current_user_credential(self) -> {}:
        """Возвращает учетные данные текущего пользователя."""

        return self.current_user_credential

host: Environment = Environment()

"""Модуль base_element содержит базовый класс для работы с элементами страницы.

Класс BaseElement предоставляет основные методы взаимодействия с элементами
и их проверки через Playwright.
"""

from playwright.sync_api import Page, Locator, expect, TimeoutError

logger = get_logger("BASE_ELEMENT")

class BaseElement:
    """Базовый класс для работы с элементами страницы через Playwright.

    Предоставляет основные методы взаимодействия с элементами:
    клики, получение текста, ожидание и проверки состояния.
    """

    def __init__(self, page: Page, locator: str | Locator, name: str) -> None:
        """Инициализирует базовый элемент страницы.

        Args:
            locator: Локатор элемента (строка или объект Locator)
            name: Имя элемента для логирования
        """

        self.name = name
        self.locator: Locator

            self.locator = locator
            self.locator = self.page.locator(locator)
        else:

    @property
    def type_of(self) -> str:
        """Возвращает тип элемента (для логирования)."""

        return "base element"

    def click(self) -> None:
        """Выполняет клик по элементу."""

        logger.info(f"Clicking {self.type_of} '{self.name}'")
        self.locator.click()

    def get_text(self, index: int) -> str:
        """Возвращает текст элемента по указанному индексу."""

        logger.info(f"Get text for {self.type_of} '{self.name}'")
        return self.locator.nth(index).text_content()

    def update_locator(self, new_locator: Locator) -> None:
        """Меняет значение локатора для элемента"""

        logger.info(f"Update locator for {self.type_of} '{self.name}'")
        self.locator = new_locator

    def wait_for_element(self, timeout: int = 12000) -> None:
        """Ожидает появление элемента в течение заданного времени."""

        logger.info(f"Wait for {self.type_of} '{self.name}'")
        self.locator.wait_for(timeout=timeout)

    def check_have_text(self, text: str, msg: str) -> None:
        """Проверяет наличие указанного текста в элементе."""

        logger.info(f"Check that {self.type_of} '{self.name}' has text '{text}'")
        expect(self.locator).to_have_text(text), msg

    def check_visibility(self, msg: str) -> None:
        """Проверяет видимость элемента на странице."""

        logger.info(f"Check that {self.type_of} '{self.name}' is present")
        # print(f"Check that {self.type_of} '{self.name}' is present")
        # print(self.locator)
        expect(self.locator).to_be_visible(visible=True, timeout=12000), msg

    def is_present(self, timeout: int = 5000) -> bool:
        """Проверяет наличие элемента в течение заданного времени."""

        try:
            self.locator.wait_for(timeout=timeout)

    def is_not_present(self, timeout: int = 5000) -> bool:
        """Проверяет отсутствие элемента в течение заданного времени."""

        logger.info(f"Check that {self.type_of} '{self.name}' is missing")
        try:

"""Модуль button_element содержит класс для работы с кнопками на странице.

Класс Button наследует базовый функционал BaseElement и предоставляет
специфичные методы для работы с элементами типа 'кнопка'.
"""

from elements.base_element import BaseElement

logger = get_logger("BUTTON")

class Button(BaseElement):
    """Класс для работы с кнопками на странице.

    для кнопок методы и проверки.
    """

    @property
        """Возвращает тип элемента ('кнопка').

        Returns:
            str: Тип элемента - 'кнопка'.
        """

        return "button"

   # Действия:
    # (Методы действий будут добавлены по мере необходимости)

    def is_disabled(self) -> bool:
        """ Возвращает значение, отключена ли кнопка (является скрытой) """

        return self.locator.is_disabled()

"""Модуль checkbox_element содержит класс для работы с чекбоксами.

Класс Checkbox наследует базовый функционал BaseElement и добавляет
специфичные методы для работы с элементами типа 'чекбокс'.
"""

logger = get_logger("CHECKBOX")

class Checkbox(BaseElement):
    """Класс для работы с чекбоксами на странице.

    Наследует функциональность BaseElement и добавляет методы для
    взаимодействия с чекбоксами и проверки их состояния.
    """

    @property
        """Возвращает тип элемента ('чекбокс').

        Returns:
            str: Тип элемента - 'чекбокс'.
        """

        return "checkbox"

    def check(self, force=False) -> None:
        """Отмечает чекбокс (устанавливает галочку)."""

        logger.info(f"Checking checkbox '{self.name}'")
        if force:
            self.locator.check(force=True)
        else:
            self.locator.check()

    def uncheck(self, force=False) -> None:
        """Снимает отметку с чекбокса (убирает галочку)."""

        logger.info(f"Unchecking checkbox '{self.name}'")
        if force:
            self.locator.uncheck(force=True)
        else:
            self.locator.uncheck()

    def is_checked(self) -> bool:
        """Проверяет, отмечен ли чекбокс.

        Returns:
            bool: True если отмечен, False если нет.
        """

        logger.info(f"Checking if checkbox '{self.name}' is checked")
        return self.locator.is_checked()

"""Модуль icon_element содержит класс для работы с элементом типа иконка.

Класс Icon наследует базовый функционал BaseElement и предоставляет
методы для работы с текстовыми элементами на странице.
"""

logger = get_logger("ICON")

class Icon(BaseElement):
    """Класс для работы с элементом типа иконка.

    методы для взаимодействия с элементом типа иконка.
    """

    @property
        """Возвращает тип элемента ('иконка').

        Returns:
            str: Тип элемента - 'иконка'.
        """

        return "icon"

    def is_password_hidden(self) -> bool:
        """Проверяет состояние иконки скрытия пароля."""

        state_text = self.locator.text_content()
        state = True
        if state_text == "visibility":
            state = False
        return state

"""Модуль tab_button_element содержит класс для работы с кнопками типа v-tabs на странице.

Класс TabButton наследует базовый функционал BaseElement и предоставляет
специфичные методы для работы с элементами.
"""

logger = get_logger("BUTTON_TAB")

class TabButton(BaseElement):
    """Класс для работы с кнопками типа v-tabs на странице.

    """

    @property
        """Возвращает тип элемента ('кнопка-tab').

        Returns:
            str: Тип элемента - 'кнопка-tab'.
        """

        return "button_tab"

    # (Методы проверок будут добавлены по мере необходимости)

"""Модуль text_element содержит класс для работы с текстовыми элементами.

Класс Text наследует базовый функционал BaseElement и предоставляет
"""

logger = get_logger("TEXT")

class Text(BaseElement):
    """Класс для работы с текстовыми элементами страницы.

    методы для взаимодействия с текстовыми элементами.
    """

    @property
        """Возвращает тип элемента ('текст').

        Returns:
            str: Тип элемента - 'текст'.
        """

        return "text"

"""Модуль text_input_element содержит класс для работы с текстовыми полями ввода.

Класс TextInput наследует базовый функционал BaseElement и предоставляет
методы для взаимодействия с текстовыми полями ввода на странице.
"""

from playwright.sync_api import expect

logger = get_logger("TEXT_INPUT")

class TextInput(BaseElement):
    """Класс для работы с текстовыми полями ввода.

    Наследует функциональность BaseElement и добавляет методы
    для ввода, очистки и проверки текстовых полей.
    """

    @property
        """Возвращает тип элемента ('текстовое поле ввода').

        Returns:
            str: Тип элемента - 'текстовое поле ввода'.
        """

        return "text input"

    def get_input_value(self) -> str:
        """Возвращает текущее значение поля ввода.

        Returns:
            str: Текущее значение в поле.
        """

        logger.info(f'Getting value from text input "{self.name}"')
        return self.locator.input_value()

    def get_input_type(self) -> str:
        """Возвращает значение аттрибута type поля ввода.

        Returns:
            str: Значение аттрибута type.
        """

        return self.locator.get_attribute("type")

    def decrease_value(self) -> None:
        """Уменьшение на единицу значения поля настройки с помощью стрелочки вниз."""

        logger.info(f'Decrease input value for "{self.name}"')

        attr = self.locator.get_attribute("type")
        assert attr=="number", "Field type should be number to increase value"

        self.locator.hover()
        self.locator.wait_for(timeout=500)
        self.locator.press('ArrowDown')

    def increase_value(self) -> None:
        """Увеличение на единицу значения поля настройки с помощью стрелочки вверх."""

        logger.info(f'Increase input value for "{self.name}"')

        self.locator.press('ArrowUp')

    def input_value(self, value: str) -> None:
        """Вводит указанное значение в поле.

        Args:
            value (str): Значение для ввода.
        """

        logger.info(f'Inputting value "{value}" to text input "{self.name}"')
        self.locator.fill(value)

    def clear(self) -> None:
        """Очищает содержимое текстового поля."""

        logger.info(f'Clearing text input "{self.name}"')
        self.locator.press('Control+A')
        self.locator.press('Backspace')

    def check_empty_input(self, msg: str) -> None:
        """Проверяет, что поле ввода пустое.

        Args:
            msg (str): Сообщение об ошибке при неудачной проверке.

        Raises:
            AssertionError: Если поле не пустое.
        """

        logger.info(f'Checking that text input "{self.name}" is empty')
        expect(self.locator).to_be_empty(), msg

    def check_editable_input(self, msg: str) -> None:
        """Проверяет, что поле ввода редактируемое.

        Args:

        Raises:
        """

        logger.info(f'Checking that text input "{self.name}" is editable')
        expect(self.locator).to_be_editable(), msg

"""Модуль tooltip_button_element содержит класс для работы с кнопками с подсказками.

Класс TooltipButton наследует базовый функционал BaseElement и добавляет
методы для проверки всплывающих подсказок у кнопок.
"""

from locators.button_locators import ButtonLocators

logger = get_logger("TOOLTIP_BUTTON")

class TooltipButton(BaseElement):
    """Класс для работы с кнопками, имеющими всплывающие подсказки.

    для взаимодействия с подсказками кнопок.
    """

    @property
        """Возвращает тип элемента ('tooltip_button').

        Returns:
            str: Тип элемента - кнопка с подсказкой.
        """

        return "tooltip_button"

    def get_tooltip_text(self) -> str:
        """Возвращает текст всплывающей подсказки.

        Returns:
            str: Текст всплывающей подсказки.

        """

        # Наведение на элемент для отображения подсказки

        # Получение элемента подсказки
        tooltip = self.page.locator(ButtonLocators.TOOLTIP)

        tooltip.wait_for(state="visible", timeout=5000)

        return tooltip.text_content().strip()

    def wait_for_tooltip_to_disappear(self, timeout: int = 5000) -> None:
        """Ожидает исчезновения всех активных всплывающих подсказок."""

        tooltip_locator = self.page.locator(".v-tooltip__content.menuable__content__active")

        tooltip_locator.wait_for(state="hidden", timeout=timeout)

    def check_tooltip_with_text(self, expected_text: str) -> None:
        """Проверяет соответствие текста всплывающей подсказки.

        Args:
           expected_text (str): Ожидаемый текст подсказки.

        Raises:
            AssertionError: Если текст подсказки не соответствует ожидаемому.
        """

        # Проверка соответствия текста
        actual_text = tooltip.text_content().strip()
        assert actual_text == expected_text, (
            f"Текст подсказки не соответствует ожидаемому. "
            f"Ожидалось: '{expected_text}', получено: '{actual_text}'"
        )

"""Модуль pages содержит фикстуры и функции для работы с Playwright.

Предоставляет инструменты для управления браузером, контекстами и страницами
в тестах pytest, включая настройку параметров запуска.
"""

import ast
import pytest
from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright, Playwright
from _pytest.config.argparsing import Parser
from _pytest.fixtures import FixtureRequest

def pytest_addoption(parser: Parser):
    """Добавляет опции командной строки для настройки браузера.

    Args:
        parser: Парсер pytest для добавления опций.

    Опции:
        --bn: Браузер (chrome, remote_chrome, firefox)
        --h: Режим headless (True/False)
        --s: Размер окна {'width': int, 'height': int}
        --slow: Задержка между действиями (мс)
        --t: Таймаут по умолчанию (мс)
        --l: Локаль браузера
    """

    parser.addoption('--bn', action='store', default="chrome",
                   help="Choose browser: chrome, remote_chrome or firefox")
    parser.addoption('--h', action='store', default=False,
                   help='Choose headless: True or False')
    parser.addoption('--s', action='store', default="{'width': 1600, 'height': 900}",
                   help='Size window: width,height')
    # Закомментированные альтернативные размеры окон
    # parser.addoption('--s', action='store', default="{'width': 1920, 'height': 1080}",
    #                 help='Size window: width,height')
    # parser.addoption('--s', action='store', default="{'width': 1920, 'height': 300}",
    # parser.addoption('--s', action='store', default="{'width': 300, 'height': 420}",
    #               help='Size window: width,height')
    parser.addoption('--slow', action='store', default=200,
                   help='Choose slow_mo for robot action')
    parser.addoption('--t', action='store', default=60000,
                   help='Choose timeout')
    parser.addoption('--l', action='store', default='ru-RU',
                   help='Choose locale')
    # Закомментированная опция для Qase
    # parser.addini('qs_to_api_token', default=os.getenv("QASE_TOKEN"), help='Qase app token')

@pytest.fixture(scope='class')
def browser(request: FixtureRequest) -> Page:
    """Фикстура для управления экземпляром браузера.

    Args:
        request: Объект запроса pytest для доступа к конфигурации.

    Returns:
        Page: Экземпляр страницы браузера.

    Yields:
        Page: Страница для использования в тестах.

    Note:
        Автоматически закрывает браузер после тестов.
    """

    playwright = sync_playwright().start()

    # Выбор браузера на основе параметра командной строки
    if request.config.getoption("bn") == 'remote_chrome':
        browser = get_remote_chrome(playwright, request)
        context = get_context(browser, request, 'remote')
        page_data = context.new_page()
    elif request.config.getoption("bn") == 'firefox':
        browser = get_firefox_browser(playwright, request)
        context = get_context(browser, request, 'local')
    elif request.config.getoption("bn") == 'chrome':
        browser = get_chrome_browser(playwright, request)
    else:

    yield page_data

    # Очистка после завершения тестов
    for context in browser.contexts:
        context.close()
    browser.close()
    playwright.stop()

def get_firefox_browser(playwright: Playwright, request: FixtureRequest) -> Browser:
    """Создает и возвращает экземпляр Firefox браузера.

    Args:
        playwright: Экземпляр Playwright.
        request: Объект запроса pytest.

    Returns:
        Browser: Экземпляр Firefox.
    """

    return playwright.firefox.launch(
        headless=request.config.getoption("h"),
        slow_mo=request.config.getoption("slow"),
    )

def get_chrome_browser(playwright: Playwright, request: FixtureRequest) -> Browser:
    """Создает и возвращает экземпляр Chrome браузера.

    Args:

    Returns:
        Browser: Экземпляр Chrome.
    """

    return playwright.chromium.launch(
        args=['--s']
    )

def get_remote_chrome(playwright: Playwright, request: FixtureRequest) -> Browser:
    """Создает экземпляр Chrome для удаленного запуска.

    Args:

    Returns:
        Browser: Экземпляр Chrome в headless режиме.
    """

        headless=True,
        slow_mo=request.config.getoption("slow")
    )

def get_context(browser: Browser, request: FixtureRequest, start: str) -> BrowserContext:
    """Создает и настраивает контекст браузера.

    Args:
        browser: Экземпляр браузера.
        start: Тип запуска ('local' или 'remote').

    Returns:
        BrowserContext: Настроенный контекст.
    """

    if start == 'local':
       # current_viewport = json.loads(request.config.getoption('--s'))

        context = browser.new_context(
            # no_viewport=True,
            viewport= ast.literal_eval(request.config.getoption('--s')),
            locale=request.config.getoption('l')
        )
        context.set_default_timeout(
            timeout=request.config.getoption('t')
        )
        # Пример добавления кук (закомментировано)
        # context.add_cookies([{'url': 'https://example.ru', 'name': 'ab_test', 'value': 'd'}])
        return context

    elif start == 'remote':
            viewport=ast.literal_eval(request.config.getoption('--s')),
        )
        )

@pytest.fixture(scope="function")
def return_back(browser: Page):
    """Фикстура для возврата на предыдущую страницу.

    Args:
        browser: Экземпляр страницы браузера.
    """

    browser.go_back()

"""Базовый класс страницы для работы с Playwright.

Содержит общие методы для взаимодействия со страницей и API.
"""
import time
from typing import Dict, Any
from playwright.sync_api import Page, Response, APIRequestContext, expect

logger = get_logger("BASE_PAGE")

class BasePage:
    """Базовый класс для работы со страницами через Playwright.

    Атрибуты:
        page (Page): Экземпляр страницы Playwright.
    """

        """Инициализирует базовую страницу.

        Args:
            page (Page): Экземпляр страницы Playwright.
        """

    def current_url(self) -> str:
        """Возвращает текущий URL страницы.

        Returns:
            str: Текущий URL страницы.
        """
        return self.page.url

    def open(self, uri) -> Response | None:
        """Открывает указанный URI в браузере.

        Args:
            uri (str): URI для открытия (без базового URL).

        Returns:
            Response | None: Ответ сервера или None в случае ошибки.
        """
        return self.page.goto(f"{host.get_base_url()}{uri}", wait_until='domcontentloaded')

    def page_reload(self) -> None:
        """Перезагружает текущую страницу."""
        self.page.reload()

        Args:
            timeout (int): Время ожидания в миллисекундах.
        """

    def get_api_request_context(self) -> APIRequestContext:
        """Возвращает контекст API-запросов.

        Returns:
            APIRequestContext: Контекст для выполнения API-запросов.
        """
        return self.page.context.request

    def send_get_api_request(self, uri: str) -> Response:
        """Отправляет GET-запрос к API.

        Args:
            uri (str): URI API-эндпоинта (без базового URL).

        Returns:
            Response: Ответ сервера.
        """
        api_request_context = self.get_api_request_context()
        token = host.get_access_token()

        # Проверяем что токен получен
        if not token:
            logger.error("Failed to get access token: token is None or empty")
            # Возвращаем заглушечный response или бросаем исключение
            # В Playwright можно создать mock response если нужно

        headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"}
        full_url = f"{host.get_request_url()}{uri}"

        logger.debug("Sending GET request to: %s", full_url)
        response = api_request_context.get(full_url, headers=headers)

        # Логируем статус ответа
        logger.debug("GET response status: %s", response.status)

        return response

    def send_post_api_request(self, uri: str, payload: Dict) -> Response:
        """Отправляет POST-запрос к API."""

        headers = {
            "Accept": "application/json",
            "Content-Type": "application/json",
            "Authorization": f"Bearer {token}"
        }

        logger.debug("Sending POST request to: %s", full_url)

        # Сериализуем payload в JSON
        json_data = json.dumps(payload)

        # Проверяем что сериализация прошла успешно
        if json_data is None:
            logger.error("Failed to serialize payload to JSON: result is None")

        if not isinstance(json_data, str):
            logger.error("Failed to serialize payload to JSON: expected string got %s", type(json_data))

        response = api_request_context.post(
            full_url,
            headers=headers,
            data=json_data  # Передаем сериализованный JSON как data
        )

        logger.debug("POST response status: %s", response.status)

    def get_response_body(self, response: Response) -> dict | list | None:
        """Извлекает тело ответа в format JSON.

        Args:
            response (Response): Ответ сервера.

        Returns:
            dict | list | None: Распарсенное тело ответа или None в случае ошибки.
        """
        start_time = time.time()

        # Проверяем что response не None
        if response is None:
            logger.error("Response object is None")
            processing_time = time.time() - start_time
            logger.debug("Response processing time1: %.3f seconds", processing_time)

        # Проверяем статус ответа
        if response.status >= 400:
            logger.error("API request failed with status %s", response.status)
            logger.debug("Response processing time2: %.3f seconds", processing_time)

        # Пытаемся получить JSON
        json_result = response.json()

        # Проверяем что результат не None
        if json_result is None:
            logger.error("JSON parsing returned None")
            logger.debug("Response processing time3: %.3f seconds", processing_time)

        # Принимаем как словари, так и списки
        if not isinstance(json_result, (dict, list)):
            logger.error("Expected dict or list but got %s", type(json_result))
            logger.debug("Response processing time4: %.3f seconds", processing_time)

        processing_time = time.time() - start_time
        logger.debug("Response processing time5: %.3f seconds", processing_time)

        return json_result

        # Проверки:
    def check_URL(self, uri: str, msg: str) -> None:
        """Проверяет, что текущий URL соответствует ожидаемому.

        Args:
            uri (str): Ожидаемый URI (без базового URL).
            msg (str): Сообщение об ошибке при несоответствии.

        Raises:
            AssertionError: Если URL не соответствует ожидаемому.
        """
        expect(self.page).to_have_url(  # pylint: disable=expression-not-assigned
            f"{host.get_base_url()}{uri}",
            timeout=60000
        ), msg

    def check_equals(self, actual: Any, expected: Any, msg: str) -> None:
        """Проверяет равенство фактического и ожидаемого значений.

        Args:
            actual (Any): Фактическое значение.
            expected (Any): Ожидаемое значение.

        Raises:
            AssertionError: Если значения не равны.
        """
        assert actual == expected, msg

    def check_lists_equals(self, actual: list, expected: list, msg: str) -> None:
        """Рекурсивно проверяет равенство двух списков.

        Args:
            actual (list): Фактический список.
            expected (list): Ожидаемый список.

        Raises:
            AssertionError: Если списки не равны.
        """
        def compare_lists(list1: list, list2: list) -> bool:
            """Вспомогательная функция для рекурсивного сравнения списков.

            Args:
                list1 (list): Первый список для сравнения.
                list2 (list): Второй список для сравнения.

            Returns:
                bool: True если списки идентичны, иначе False.
            """
            if len(list1) != len(list2):
                return False
            for item1, item2 in zip(list1, list2):
                if isinstance(item1, list) and isinstance(item2, list):
                    if not compare_lists(item1, item2):
                        return False
                elif item1 != item2:
                    return False

        assert compare_lists(actual, expected), msg

"""Модуль вкладки 'Сеансы/Текущие'.

Содержит класс CurrentSessionsTab для работы с таблицей сеансов.
Позволяет проверять состояние и взаимодействовать с элементами вкладки.
"""

from locators.table_locators import TableLocators
from pages.base_page import BasePage

class CurrentSessionsTab(BasePage):
    """Класс для работы с вкладкой 'Сеансы/Текущие'.

    Предоставляет методы для взаимодействия с таблицей сеансов и проверки
    её состояния.

    Args:
        page: Экземпляр страницы Playwright.
    """

        """Инициализирует компоненты вкладки 'Сеансы/Текущие'."""

        self.toolbar = ToolbarComponent(page, "Сеансы")
        self.sessions_table = TableComponent(page)

        self.delete_session_confirm = ConfirmComponent(page, " Отмена ", " Удалить ")

    def get_rows_count(self) -> int:

        Returns:

        Raises:
        """

        return self.sessions_table.get_rows_count(TableLocators.TABLE_WORK_AREA)

    def get_delete_session_button_from_row(self, row_index: int) -> TooltipButton:
        """Возвращает кнопку удаления сеанса для указанной строки.

        Args:
            row_index: Индекс строки в таблице.

        Returns:
            TooltipButton: Кнопка с подсказкой.

        Raises:
            AssertionError: Если строка не найдена.
        """

        row_locator = self.sessions_table.get_row_locator(
            TableLocators.TABLE_WORK_AREA,
            row_index
        )
        assert isinstance(row_locator, Locator), f"Row with index {row_index} is missing"

        button_locator = row_locator.locator(ButtonLocators.BUTTON_DELETE_SESSION)
        return TooltipButton(self.page, button_locator, "delete_session_button")

    def get_session_token(self) -> str:
        """Возвращает токен текущего пользователя.

        Args:

        Returns:
            str: Токен текущего пользователя

        Raises:
        """
        return host.get_access_token()

    def find_session_in_table(self, token: str) -> int:
        """Ищет сеанс пользователя в таблице по выданному ему токену.

        Args:
            token (str): Токен пользователя

        Returns:
            int: Индекс строки или -1 если не найден

        Raises:
        """
        table_content = self.sessions_table.read(TableLocators.TABLE_WORK_AREA)

        del table_content[0]  # Удаляем заголовок

        for row_index, session_info in enumerate(table_content):
            if token in session_info:
                return row_index
        return -1

    def delete_session_by_token(self, token: str) -> None:
        """Удаляет сеанс по токену.

        Args:
            token (str): Токен сеанс для удаления

        Raises:
            AssertionError: Если сеанс не найден или удаление не удалось.
        """
        row_index = self.find_session_in_table(token)
        if row_index == -1:
            raise AssertionError(f"Session with token {token} not found")

        self.delete_session_by_index(row_index)

    def delete_session_by_index(self, row_index: int) -> None:
        """Удаляет сеанс по индексу строки.

        Args:
            row_index (int): Индекс строки в таблице

        Raises:
            AssertionError: Если удаление не удалось.
        """
        try:
            # Находим кнопку удаления сеанса и нажимаем на нее
            delete_session_button = self.get_delete_session_button_from_row(row_index)
            delete_session_button.click()

            # Подтверждаем удаление
            self.delete_session_confirm.click_allow_button()

            # Ждем обновления таблицы

        except Exception as e:
            raise AssertionError(f"Failed to delete session at index {row_index}: {e}")

    def scroll_sessions_table_up(self) -> None:
        """Прокручивает таблицу сеансов вверх."""

        self.sessions_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER)

    def scroll_sessions_table_down(self) -> None:
        """Прокручивает таблицу сеансов вниз."""

        self.sessions_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER)

    def check_delete_session_confirm_window(self):
        """ Проверяет контент и возможность горизонтального скроллинга окна подтверждения удаления сеанс. """

        # Поиск в таблице сеанс сроки для текущего пользователя
        session_token = self.get_session_token()
        row_index = self.find_session_in_table(session_token)
            assert False, "Session for this token has not been found"

        # Найти кнопку удаления сеанса и нажать на нее
        delete_session_button = self.get_delete_session_button_from_row(row_index)
        delete_session_button.click()

        # Проверка открытия окна подтверждения с заголовком "Удаление"
        self.delete_session_confirm.check_title(
            title,
            f"Confirmation dialog window text '{title}' is missing"
        )

        # Проверка текста в окне подтверждения
        confirm_message = f"Удалить сеанс {session_token}?"
        self.delete_session_confirm.check_text(
            confirm_message,
            "Confirmation dialog window text does not match what is expected"
        )

        # Проверка наличия и видимости кнопки "Отмена"
        self.delete_session_confirm.should_be_cancel_button()

        # Проверка наличия и видимости кнопки "Удалить"
        self.delete_session_confirm.should_be_allow_button()

        # Проверка текста на кнопках
        self.delete_session_confirm.check_cancel_button_text(" Отмена ")
        self.delete_session_confirm.check_allow_button_text(" Удалить ")

        # Проверка горизонтального скроллинга
        is_scrollable_horizontally = self.delete_session_confirm.check_window_horizontal_scrolling()
        assert is_scrollable_horizontally, "Should be horizontal scrolling"

        self.delete_session_confirm.scroll_window_right()
        self.page.wait_for_timeout(3000)
        self.delete_session_confirm.scroll_window_left()
        self.page.wait_for_timeout(2000)

        # Нажать кнопку "Отмена"
        self.delete_session_confirm.click_cancel_button()

    def check_sessions_table_content(self, verify: bool = False) -> None:
        """Проверяет содержимое таблицы сеансов.

        Args:
            verify: Проверять соответствие данных из БД. По умолчанию False.

        Raises:
            AssertionError: Если таблица пуста или заголовки неверны.
        """

        expected_headers = [
            'ID сеанса',
            'ID пользователя',
            'Время жизни',
            'Роль',
            'Адрес'
        ]

        len_table_content = len(table_content)

        if len_table_content == 0:

        actual_headers = table_content[0]

        self.check_equals(
            actual_headers,
            expected_headers,
            f"Expected table headers {expected_headers} are not equal {actual_headers}"
        )

        if len_table_content == 1:

        if verify:
            self.verify_sessions_table_content(table_content)

        for index in range(len_table_content - 1):
            self.should_be_delete_button_on_sessions_table_row(index, "Удалить")

    def check_sessions_table_verticall_scrolling(self) -> bool:

        Returns:
        """

        return self.sessions_table.is_scrollable_vertically(
            TableLocators.TABLE_SCROLL_CONTAINER
        )

    def check_sessions_table_first_row_visibility(self) -> None:

        Raises:
        """

        self.sessions_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA)

    def check_sessions_table_last_row_visibility(self) -> None:

        Raises:
        """

        self.sessions_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA)

    def check_sessions_table_row_highlighting(self, row_index: int) -> None:

        Args:

        Raises:
        """

        self.sessions_table.check_row_highlighting(
            row_index
        )

        """Проверяет наличие тулбара на вкладке.

        Raises:
        """

        self.toolbar.check_toolbar_presence("Toolbar is missing")

    def should_be_sessions_table(self) -> None:
        """Проверяет наличие таблицы сеансов.

        Raises:
        """

        self.sessions_table.check_visibility(
            "Sessions table is missing"
        )

    def should_be_delete_button_on_sessions_table_row(
        self,
        row_index: int,
        tooltip: str
    ) -> None:
        """Проверяет наличие кнопки удаления в строке таблицы.

        Args:
            tooltip: Ожидаемый текст подсказки.

        Raises:
            AssertionError: Если кнопка отсутствует или подсказка неверна.
        """
        delete_button = self.get_delete_session_button_from_row(row_index)

        # Ожидаем исчезновения предыдущих подсказок перед проверкой
        delete_button.wait_for_tooltip_to_disappear()

        delete_button.check_visibility(
            f"Delete session button is missing on {row_index} row"
        )
        delete_button.check_tooltip_with_text(tooltip)

        # Ожидаем исчезновения подсказки после проверки

    def should_be_session_in_table(self, token: str) -> None:
        """Проверяет наличие сеанса пользователя в таблице.

        Args:

        Raises:
            AssertionError: Если сеанс не найден.
        """
        found = self.find_session_in_table(token)
        if found == -1:

    def should_not_be_session_in_table(self, token: str) -> None:
        """Проверяет отсутствие сеанса пользователя в таблице.

        Args:

        Raises:
            AssertionError: Если сеанс найден.
        """
        if found != -1:
            assert False, "Session for this token has been found"

    def verify_sessions_table_content(self, sessions_table: list) -> None:
        """Сверяет данные таблицы с данными из БД.

        Args:
            sessions_table: Данные из таблицы на странице.

        Raises:
            AssertionError: Если данные не соответствуют.
        """

        expected_sessions_list = []

        # Отправка запроса к бэкенду для получения информации о сеансах
        response = self.send_get_api_request("e-nms/auth/sessions")
        response_body = self.get_response_body(response)

        for item in response_body:
            session_info = []
            session_info.append(item["id"])
            session_info.append(item["userId"])

            # Временно неподдерживаемое поле: время жизни сеанса
            session_info.append("")

            roles = []
            for role in item["roles"]:
                if role in roles_dict.keys():
                    roles.append(roles_dict[role])

            session_info.append(",".join(roles))
            session_info.append(item["ip"])

            expected_sessions_list.append(session_info)

        del sessions_table[0]  # Удаляем заголовок

        self.check_lists_equals(
            sessions_table,
            expected_sessions_list,
            "Actual sessions list is not equal expected users list on base db"
        )

"""Модуль license_tab содержит класс для работы с вкладкой 'Лицензии'.

Класс LicenseTab наследует BasePage и реализует методы для взаимодействия
с элементами вкладки лицензий и проверки их состояния.
"""

from locators.text_locators import TextLocators
from locators.input_locators import InputLocators

class LicenseTab(BasePage):
    """Класс для работы с вкладкой 'Лицензии'.

    Содержит методы для:
    - Взаимодействия с формой ввода лицензии
    - Проверки содержимого JSON-контейнера
    - Работы с элементами управления
    """

        """Инициализирует элементы вкладки 'Лицензии'.

        Args:
        """

        self.toolbar = ToolbarComponent(page, "Лицензии")

        self.input_form_title = Text(page, TextLocators.TITLE_LICENSE_INPUT_FORM, "input form title")
        self.license_id = Text(page, TextLocators.LICENSE_ID, "license id")
        self.license_id_input = TextInput(page, InputLocators.LICENSE_ID_UPDATE, "license id input")
        self.update_button = Button(page, ButtonLocators.BUTTON_LICENSE_UPDATE, "update license button")

    def fill_license_input_form(self, value: str) -> None:
        """Заполняет форму ввода лицензии указанным значением.

        Args:
            value: Значение для ввода
        """

        self.license_id_input.clear()
        self.license_id_input.input_value(value)
        self.update_button.click()

    def scroll_json_container_up(self) -> None:
        """Прокручивает JSON-контейнер вверх."""

        loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first
        self.json_container.scroll_up(loc)

    def scroll_json_container_down(self) -> None:
        """Прокручивает JSON-контейнер вниз."""

        self.json_container.scroll_down(loc)

    def check_json_container_verticall_scrolling(self) -> bool:
        """Проверяет возможность вертикальной прокрутки контейнера.

        Returns:
            bool: Доступность прокрутки
        """

        return self.json_container.is_scrollable_vertically(loc)

        """Проверяет наличие всех основных элементов вкладки."""

        self.should_be_json_container()
        self.should_be_input_form_title()
        self.should_be_empty_input_form()
        self.should_be_update_button()

        self.verify_json_container_content()

    def should_be_error_alert_window_with_text(self, text: str) -> None:
        """Проверяет наличие/отсутствие алерта с указанным текстом.

        Args:
            text: Текст для проверки
        """

        assert alert_type=="error", f"Expected error alert, but got {alert_type} alert"

        self.alert.check_alert_presence(text)
        self.alert.check_alert_absence(text)

        """Проверяет наличие панели инструментов."""

    def should_be_json_container(self) -> None:
        """Проверяет наличие JSON-контейнера."""

        self.json_container.check_visibility(
            JsonContainerLocators.CONTAINER,
            "Json container with license info is missing"
        )

    def should_be_input_form_title(self) -> None:
        """Проверяет заголовок формы и соответствие ID лицензии."""

        self.input_form_title.check_have_text(
            "Идентификатор:",
            "Input lisence id form title 'Идентификатор:' is missing"
        )

        actual_lisence_id = self.license_id.get_text(0).strip()

        # send request to backend to get license id
        response = self.send_get_api_request("e-cmdb/api/lic/deviceid")

            actual_lisence_id,
            response_body['deviceId'],
            f"Expected ID value {response_body['deviceId']} is not equal actual value {actual_lisence_id}"
        )

    def should_be_empty_input_form(self) -> None:
        """Проверяет пустоту формы ввода лицензии."""

        self.license_id_input.check_empty_input("Input lisence id form is missing or not empty")

    def should_be_update_button(self) -> None:
        """Проверяет наличие кнопки обновления лицензии."""

        button_text = "Обновить лицензию"
        self.update_button.check_have_text(
            button_text,
            f"Update button with text '{button_text}' is missing"
        )

    def verify_json_container_content(self) -> None:
        """Проверяет соответствие данных контейнера данным из API."""

        # send request to backend to get license info
        response = self.send_get_api_request("api/service-manager/license")

        if response.status == 200:
            response_body = self.get_response_body(response)
            expected_data = response_body["data"]["config"]["config"]["e-nms"]

            for key_1, item in actual_data.items():
                if not isinstance(item, dict):
                    assert actual_data[key_1] == expected_data[key_1],\
                        f"Expected json content is not equal actual: {actual_data[key_1]} {expected_data[key_1]}"
                    continue

                for key_2 in item:
                    assert actual_data[key_1][key_2] == expected_data[key_1][key_2],\
                        f"Expected json content is not equal actual: {actual_data[key_1][key_2]} {expected_data[key_1][key_2]}"
        else:
            assert False, f"No response from 'api/service-manager/license' request: {response.status_text}"

"""Модуль страницы локации."""

from components_derived.frames.create_child_element_frame import (
    CreateChildElementFrame
)

# =============== Локаторы ================================================
PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i"
CREATE_BUTTON_ANCESTOR_DIV3 = "xpath=/ancestor::div[3]//button"
# =========================================================================

class LocationPage(BasePage):
    """Класс для работы со страницей локации."""

        """
        Инициализирует страницу локации.

        Args:
        """

        # Инициализация тулбара

        panel_header_locator = self.page.locator(PANEL_HEADER)

        # Кнопка "Создать" - первая кнопка в тулбаре
        create_button_locator = panel_header_locator.locator(
            CREATE_BUTTON_ANCESTOR_DIV3
        ).nth(0)

        # Инициализация кнопки
        self.toolbar.add_tooltip_button(create_button_locator, "create")

        # Инициализация фреймов (ленивая загрузка)
        self._create_child_frame = None

    def click_create_button(self) -> CreateChildElementFrame:
        """
        Кликает на кнопку 'Создать' и возвращает фрейм создания.

        Returns:
            CreateChildElementFrame: Фрейм создания дочернего элемента
        """

        # Используем метод тулбара для клика
        self.toolbar.click_button("create")

        self.wait_for_timeout(3000)

        # Создаем и возвращаем фрейм
        self._create_child_frame = CreateChildElementFrame(self.page)
        return self._create_child_frame

    def is_create_button_visible(self) -> bool:
        """
        Проверяет видимость кнопки 'Создать'.

        Returns:
            bool: True если кнопка видима
        """
        button = self.toolbar.get_button_by_name("create")

        return button.is_present(timeout=5000) and button.locator.is_visible()

        """
        Ожидает указанное количество миллисекунд.

        Args:
        """

"""Модуль страницы авторизации.

Содержит класс LoginPage для работы с элементами страницы входа в систему.
Использует Playwright для взаимодействия с UI и обработки ответов сервера.
"""

from data.constants import Constants

class LoginPage(BasePage):
    """Класс для работы со страницей авторизации.

    Атрибуты:
        login_input: Поле ввода логина.
        password_input: Поле ввода пароля.
        login_button: Кнопка входа.
        alert: Алерт окно с сообщением об ошибке.
    """

        """Инициализирует элементы страницы авторизации.

        Args:
        """

        self.login_input = TextInput(page, page.get_by_label("Имя пользователя"), "login input")
        self.password_input = TextInput(page, page.get_by_label("Пароль"), "password input")

        icon_locator = page.get_by_label("Пароль").locator("xpath=../..").locator(TextInputLocators.ICON_PASSWORD_HIDING)
        self.password_hidden_icon = Icon(page,
                                         icon_locator,
                                         "password hidden icon")
        self.login_button = Button(page, page.get_by_role("button"), "login button")

    def click_password_hidden_icon(self) -> None:
        """Нажатие на иконку скрытия пароля."""

        self.password_hidden_icon.click()

    def do_login(self, username: str = None, password: str = None) -> None:
        """Выполняет вход в систему.

        Использует переданные учетные данные или значения из Constants.
        Обрабатывает ответ сервера для получения токена доступа.

        Args:
            username: Логин пользователя. По умолчанию из Constants.
            password: Пароль пользователя. По умолчанию из Constants.

        Raises:
            AssertionError: Если после входа открылась неожиданная страница.
        """

        def handle_response(response):
            if "login" in response.url:
                response_body = self.get_response_body(response)
                if response_body:
                    host.set_current_user_credential(response_body)

        self.page.on("response", handle_response)

        self.open("")

        # Используем переданные значения или значения по умолчанию из Constants
        actual_username = username if username is not None else Constants.login
        actual_password = password if password is not None else Constants.password

        self.login_input.clear()
        self.login_input.input_value(actual_username)

        self.password_input.clear()
        self.password_input.input_value(actual_password)

        self.login_button.click()

        self.check_URL("dashboard", "An unexpected page has been opened")

    def do_unsuccessful_login(self, username: str = "someuser", password: str = "password") -> None:
        """Выполняет попытку входа с неверными учетными данными.

        Проверяет наличие сообщения об ошибке. Можно передать свои данные
        или использовать значения по умолчанию.

        Args:
            username: Неверный логин. По умолчанию "someuser".
            password: Неверный пароль. По умолчанию "password".
        """

        self.login_input.input_value(username)

        self.password_input.input_value(password)

        self.alert.check_alert_presence("Неверная пара логин/пароль")
        self.alert.check_alert_absence("Неверная пара логин/пароль")

    def is_password_icon_hidden(self) -> bool:
        """Проверяет состояние иконки скрытия символов пароля.
           Возвращает True если пароль будет скрыт, иначе False
        """

        return self.password_hidden_icon.is_password_hidden()

        """Проверяет видимость символов пароля."""

        input_type = self.password_input.get_input_type()
        if input_type == "password":
        elif input_type == "text":
        else:
            assert False, "Check password hidden error"

"""Модуль главной страницы приложения.

Содержит класс MainPage для работы с элементами главной страницы.
Включает взаимодействие с панелью навигации, кнопками и карточкой пользователя.
"""

from components.eventbar_component import EventPanelComponent
from components.navbar_component import NavigationPanelComponent

class MainPage(BasePage):
    """Класс для работы с главной страницей приложения.

    Атрибуты:
        navigation_panel: Компонент панели навигации.
        user_button: Кнопка пользователя.
        user_card: Карточка пользователя.
    """

        """Инициализирует элементы главной страницы.

        Args:
        """

        self.navigation_panel = NavigationPanelComponent(page)
        self.event_panel = EventPanelComponent(page)

    def click_events_panel_expand_less_button(self) -> None:

        self.event_panel.click_expand_less_button()

    def click_events_panel_expand_more_button(self) -> None:

        self.event_panel.click_expand_more_button()

    def click_events_panel_system_log_tab(self) -> SystemLogEventsContainer:

        return self.event_panel.click_system_log_tab()

    def click_main_navigation_panel_item(self, item_name: str) -> None:
        """Кликает по элементу основной панели навигации.

        Args:
            item_name: Название элемента для клика.
        """

        self.navigation_panel.click_item(
            NavigationPanelLocators.PANEL_MAIN,
            item_name
        )

    def click_subpanel_item(self, item_name: str, parent=None) -> None:
        """Выполняет рекурсивный поиск по панели навигации
        заданного элемента и делает клик по нему."""

        active_item_locator = self.page.locator(
            NavigationPanelLocators.PANEL_MAIN
        ).locator(NavigationPanelLocators.ACTIVE_CONTAINER)
        node_locator = active_item_locator.locator(
            NavigationPanelLocators.SUB_PANEL_MAIN
        ).locator(NavigationPanelLocators.TREEVIEW).first

        # Рекурсивный поиск в дереве v-treeview заданного элемента
        # и клик по нему
        self.navigation_panel.click_sub_item(
            node_locator, item_name, parent
        )

        return self.event_panel.click_user_button()

        self.event_panel.do_logout()

    def expand_navigation_subpanel(self) -> None:
        """Выполняет полное открытие активной главной
        навигационной подпанели."""

        # Рекурсивный обход дерева v-treeview выбранной подпанели
        # и вывод информации об элементах в режиме отладки (debug=True)
        self.navigation_panel.traverse_panel_tree(
            node_locator, debug=False
        )

    def get_event_counters_by_buttons(self) -> dict:

        return self.event_panel.get_event_button_values()

    def get_event_counters_by_tooltips(self) -> dict:
        """Возвращает набор текстов всплывающих подсказок
        кнопок счетчиков событий."""

        events = self.event_panel.get_event_tooltip_texts()

        event_counters = {}

        for event in events:
            if len(event) > 0:
                event_name, count = event.split(" ")
                event_counters[event_name] = count
        return event_counters

    def get_events_panel_position(self) -> str:
        """Возвращает текущее положение панели событий
        относительно страницы: "top", "center","bottom"."""

        return self.event_panel.get_panel_position()

    def scroll_navigation_panel_down(self) -> None:
        """Прокручивает панель навигации вниз."""

        self.navigation_panel.scroll_down(
            NavigationPanelLocators.PANEL_SCROLL_CONTAINER
        )

    def scroll_navigation_panel_up(self) -> None:
        """Прокручивает панель навигации вверх."""

        self.navigation_panel.scroll_up(
        )

        return self.event_panel.check_expand_less_button()

        """Проверяет наличие кнопки галочка вниз."""

        return self.event_panel.check_expand_more_button()

    def check_navigation_item_exists(self, item_name: str) -> bool:
        """Проверяет существование элемента в навигационной панели.

        Args:
            item_name: Название элемента для проверки

        Returns:
            bool: True если элемент существует, False если нет
        """
        return self.navigation_panel.is_item_visible(
            item_name
        )

    def check_navigation_panel_item_visibility(self, item_name: str) -> None:
        """Проверяет видимость элемента в панели навигации.

        Args:
            item_name: Название элемента для проверки.
        """

        self.navigation_panel.check_item_visibility(
            item_name
        )

    def check_navigation_panel_verticall_scrolling(self) -> bool:
        """Проверяет возможность вертикальной прокрутки панели.

        Returns:
        """

        return self.navigation_panel.is_scrollable_vertically(
        )

    def should_be_event_panel(self) -> None:
        """Проверяет наличие элементов панели событий."""

        ## to-do: кнопки галочки???
        self.event_panel.should_be_tab_buttons()
        self.event_panel.should_be_event_buttons()
        self.event_panel.should_be_search_button()
        self.event_panel.should_be_user_button()

    def should_be_navigation_panel(self) -> None:
        """Проверяет наличие панели навигации."""

        self.navigation_panel.check_visibility(
            "Navigation panel is missing"
        )

"""Модуль вкладки 'Статус обслуживания'.

Содержит класс ServiceStatusTab для работы с таблицей сервисов.
"""

class ServiceStatusTab(BasePage):
    """Класс для работы с вкладкой 'Статус обслуживания'.

    Предоставляет методы для взаимодействия с таблицей сервисов и проверки

    Args:
    """

        """Инициализирует компоненты вкладки 'Статус обслуживания'."""

        self.toolbar = ToolbarComponent(page, "Статус обслуживания")
        self.services_table = TableComponent(page)

        Returns:

        Raises:
        """

        return self.services_table.get_rows_count(TableLocators.TABLE_WORK_AREA)

    def scroll_services_table_up(self) -> None:
        """Прокручивает таблицу сервисов вверх."""

        self.services_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER)

    def scroll_services_table_down(self) -> None:
        """Прокручивает таблицу сервисов вниз."""

        self.services_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER)

    def check_services_table_content(self) -> None:
        """Проверяет содержимое таблицы сервисов.

        Raises:
        """

            'Контейнер',
            'Время создания',
            'Статус',
            'Время работы',
            'Image ID',
            'Image ТЭГ'
        ]

        self.services_table.check_content(TableLocators.TABLE_WORK_AREA, expected_headers)

    def check_services_table_verticall_scrolling(self) -> bool:

        Returns:
        """

        return self.services_table.is_scrollable_vertically(
        )

    def check_services_table_first_row_visibility(self) -> None:

        Raises:
        """

        self.services_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA)

    def check_services_table_last_row_visibility(self) -> None:

        Raises:
        """

        self.services_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA)

    def check_services_table_row_highlighting(self, row_index: int) -> None:

        Args:

        Raises:
        """

        self.services_table.check_row_highlighting(
            row_index
        )

        Raises:
        """

    def should_be_services_table(self) -> None:
        """Проверяет наличие таблицы сервисов.

        Raises:
        """

        self.services_table.check_visibility(
            "Service statuses table is missing"
        )

"""Модуль вкладки 'Сеансы/Настройки'.

Содержит класс SessionSettingsTab для работы с полями настройки сессий пользователей.
"""

import re
from components.settings_form_component import SettingsFormComponent

class SessionSettingsTab(BasePage):
    """Класс для работы с вкладкой 'Сеансы/Настройки'.

    Предоставляет методы для взаимодействия с полями настройки сессий пользователей.

    Args:
    """

        """Инициализирует компоненты вкладки 'Сеансы/Настройки'."""

        locator_button_1 = self.page.get_by_role("navigation").filter(
            has_text=re.compile("Настройки")
        ).get_by_role("button").nth(0)
        locator_button_2 = self.page.get_by_role("navigation").filter(
        ).get_by_role("button").nth(1)

        self.toolbar = ToolbarComponent(page, "Настройки")
        self.toolbar.add_tooltip_button(locator_button_1, "edit")
        self.toolbar.add_tooltip_button(locator_button_1, "save")
        self.toolbar.add_tooltip_button(locator_button_2, "cancel")

        # Форма для отображения/редактирования полей настроек сессии пользователя
        self.settings_form = SettingsFormComponent(page)

        self.settings_form.add_toolbar_title("Время жизни сеанса")

        admin_setting_label = Text(page,
                                   page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
                                   get_by_text('Администратор'),
                                   "admin_setting_label")
        self.settings_form.add_content_item("admin_setting_label", admin_setting_label)

        loc_admin = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
             get_by_label('Администратор')
        admin_setting = TextInput(page, loc_admin, "admin_setting")
        self.settings_form.add_content_item("admin_setting", admin_setting)

        operator_setting_label = Text(page,
                                   get_by_text('Оператор'),
                                   "operator_setting_label")
        self.settings_form.add_content_item("operator_setting_label", operator_setting_label)

        loc_oper = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
            get_by_label('Оператор')
        operator_setting = TextInput(page, loc_oper, "operator_setting")
        self.settings_form.add_content_item("operator_setting", operator_setting)

        manager_setting_label = Text(page,
                                   get_by_text('Контактное лицо'),
                                   "manager_setting_label")
        self.settings_form.add_content_item("manager_setting_label", manager_setting_label)

        loc_manager = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
            get_by_label('Контактное лицо')
        manager_setting = TextInput(page, loc_manager, "manager_setting")
        self.settings_form.add_content_item("manager_setting", manager_setting)

        inform_secur_user_setting_label = Text(page,
                                   get_by_text('Специалист информационной безопасности'),
                                   "inform_secur_user_setting_label")
        self.settings_form.add_content_item("inform_secur_user_setting_label", inform_secur_user_setting_label)

        loc_inform_secur_user = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
            get_by_label('Специалист информационной безопасности')
        inform_secur_user_setting = TextInput(page, loc_inform_secur_user, "inform_secur_user_setting")
        self.settings_form.add_content_item("inform_secur_user_setting", inform_secur_user_setting)

        collector_setting_label = Text(page,
                                   get_by_text('$collector'),
                                   "collector_setting_label")
        self.settings_form.add_content_item("collector_setting_label", collector_setting_label)

        loc_collector = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
            get_by_label('$collector')
        collector_setting = TextInput(page, loc_collector, "collector_setting")
        self.settings_form.add_content_item("collector_setting", collector_setting)

        # Окно сообщения об успешном/неуспешном сохранении настроек

        """Нажатие кнопки 'Отменить' на тулбаре."""

        self.toolbar.check_button_visibility("cancel")
        self.toolbar.get_button_by_name("cancel").click()

    def click_edit_button(self) -> None:
        """Нажатие кнопки 'Редактировать' на тулбаре."""

        self.toolbar.check_button_visibility("edit")
        self.toolbar.get_button_by_name("edit").click()

    def click_save_button(self) -> None:
        """Нажатие кнопки 'Сохранить' на тулбаре."""

        self.toolbar.check_button_visibility("save")
        self.toolbar.get_button_by_name("save").click()

    def decrease_setting_value(self, field_name: str) -> None:

        field = self.get_field_by_name(field_name)
        field.decrease_value()

    def increase_setting_value(self, field_name: str) -> None:

        field.increase_value()

    def get_field_by_name(self, field_name: str) -> TextInput:
        """Возвращает элемент поле ввода по его имени.

        Returns:
            TextInput: Элемент поле ввода.
        """

        if field_name == "administrator":
            field = self.settings_form.get_content_item("admin_setting")
        elif field_name == "operator":
            field = self.settings_form.get_content_item("operator_setting")
        elif field_name == "manager":
            field = self.settings_form.get_content_item("manager_setting")
        elif field_name == "inform_secur_user":
            field = self.settings_form.get_content_item("inform_secur_user_setting")
        elif field_name == '$collector':
            field = self.settings_form.get_content_item("collector_setting")
        else:
            assert False, f"Got unsupported field name {field_name}"
        return field

    def get_label_by_name(self, label_name: str) -> Text:
        """Возвращает элемент название поля ввода по его имени.

        Returns:
            Text: Элемент название поле ввода.
        """

        if label_name == "administrator":
            label = self.settings_form.get_content_item("admin_setting_label")
        elif label_name == "operator":
            label = self.settings_form.get_content_item("operator_setting_label")
        elif label_name == "manager":
            label = self.settings_form.get_content_item("manager_setting_label")
        elif label_name == "inform_secur_user":
            label = self.settings_form.get_content_item("inform_secur_user_setting_label")
        elif label_name == '$collector':
            label = self.settings_form.get_content_item("collector_setting_label")
        else:
            assert False, f"Got unsupported label name {label_name}"
        return label

    def get_setting_value(self, field_name: str) -> str:
        """Возвращает текущее значение требуемого поля настроек.

        Returns:
           str : Текущее значение требуемого поля настроек.
        """

        return self.get_field_by_name(field_name).get_input_value().strip()

    def get_settings_values(self) -> dict:
        """Возвращает словарь с текущими значениями настроек времени жизни сессии.

        Returns:
            dict: Cловарь с текущими значениями настроек времени жизни сессии.
        """

        current_values = {}

        current_values['administrator'] = self.settings_form.get_content_item("admin_setting").\
                                          get_input_value().strip()
        current_values['operator'] = self.settings_form.get_content_item("operator_setting").\
        current_values['manager'] = self.settings_form.get_content_item("manager_setting").\
        current_values['inform_secur_user'] = self.settings_form.get_content_item("inform_secur_user_setting").\
        current_values['$collector'] = self.settings_form.get_content_item("collector_setting").\
        return current_values

    def edit_settings(self, new_settings: dict) -> None:
        """Изменение текущих значений настроек времени жизни сессии.
        """

        self.click_edit_button()

        for key, value in new_settings.items():
            field = self.get_field_by_name(key)
            field.input_value(value)

        # temporararily
        self.click_cancel_button()

        # self.click_save_button()

        # alert_type = self.alert.get_alert_type()
        # if alert_type == "success":
        #     self.alert.check_alert_presence(' Параметры успешно\n обновлены ')
        #     self.alert.check_alert_absence(' Параметры успешно\n обновлены ')
        # else:
        #     assert False, f"Got unexpected alert type {alert_type}"

    def scroll_down(self) -> None:
        """Скроллинг вниз формы настроек времени жизни сессии.
        """

        locator = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).filter(
                  has_text="Время жизни сеанса")
        self.settings_form.scroll_down(locator)

    def scroll_up(self) -> None:
        """Скроллинг вверх формы настроек времени жизни сессии.
        """

        self.settings_form.scroll_up(locator)

        self.should_be_toolbar_buttons()

        self.should_be_form_toolbar()

        for name in self.settings_form.content_items.keys():
            item = self.settings_form.get_content_item(name)
            item.check_visibility(
                f"Session settings input form item with name '{name}' is missing"
            )

    def check_vertical_scrolling(self) -> bool:
        """Проверка возможности вертикального скроллинга формы настроек времени жизни сессии.
        """

        return self.settings_form.check_vertical_scrolling(locator)

        """Проверяет наличие тулбара вкладки.

        Raises:
        """
        loc = self.page.get_by_role("navigation").filter(
              has_text=re.compile("Настройки")).locator("div").nth(1)
        self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'Настройки' is missing")

    def should_be_toolbar_buttons(self) -> None:
        """Проверяет наличие и функциональность кнопок тулбара.

        Raises:
            AssertionError: Если кнопки недоступны или подсказки неверны.
        """

        self.toolbar.check_button_tooltip("edit", "Редактировать")

        self.toolbar.check_button_tooltip("save", "Сохранить")
        self.toolbar.check_button_tooltip("cancel", "Отменить")

    def should_be_form_toolbar(self) -> None:
        """Проверяет наличие тулбара формы редактирования настроек.

        Raises:
        """

        self.settings_form.should_be_toolbar()

    def verify_form_data(self, session_settings: dict) -> None:
        """Проверяет соответствие содержимого полей формы данным из БД.

        Raises:
            AssertionError: Если содержимое поля не соответствует данным из БД.
        """

        for key, value in session_settings.items():
            field_value = ""
            if key == "administrator":
                field_value = self.settings_form.get_content_item("admin_setting").get_input_value().strip()
            elif key == "operator":
                field_value = self.settings_form.get_content_item("operator_setting").get_input_value().strip()
            elif key == "manager":
                field_value = self.settings_form.get_content_item("manager_setting").get_input_value().strip()
            elif key == "inform_secur_user":
                field_value = self.settings_form.get_content_item("inform_secur_user_setting").get_input_value().strip()
            elif key == '$collector':
                field_value = self.settings_form.get_content_item("collector_setting").get_input_value().strip()
            else:
                assert False, f"Got unsupported field name {key}"
            assert field_value == str(value), f"{key} field value {field_value} is not equal value {value} from data base"

"""Модуль вкладки 'Шаблоны'.

Содержит класс TemplatesTab для работы с таблицей шаблонов.
"""

from components_derived.modal_view_template import ViewTemplateModalWindow

logger = get_logger("TEMPLATES_TAB")

class TemplatesTab(BasePage):
    """Класс для работы с вкладкой 'Шаблоны'.

    Предоставляет методы для взаимодействия с таблицей шаблонов и проверки

    Args:
    """

        """Инициализирует компоненты вкладки 'Шаблоны'."""

        self.toolbar = ToolbarComponent(page, "Шаблоны")

        self.templates_table = TableComponent(page)
        self.modal_windows = {}

    def add_modal_window(self, title: str) -> None:
        """Добавляет модальное окно в коллекцию.

        Args:
            title: Заголовок окна.
        """

        self.modal_windows[title] = ViewTemplateModalWindow(self.page, title)

    def get_modal_window(self, title: str) -> ViewTemplateModalWindow:
        """Возвращает модальное окно по заголовку.

        Args:

        Returns:
            ViewTemplateModalWindow: Экземпляр модального окна.

        Raises:
            AssertionError: Если окно не найдено.
        """

        modal_window = self.modal_windows.get(title)

        if modal_window is None:
            assert False, f"Modal window with title '{title}' not found"
        return modal_window

    def delete_modal_window(self, title: str) -> None:
        """Удаляет модальное окно из коллекции.

        Args:

        Raises:
        """

        if self.modal_windows.get(title) is None:
        self.modal_windows[title] = None

    def open_template_modal_by_index(self, row_index: int = 0) -> str:
        """Открывает модальное окно шаблона по клику на строку таблицы.

        Args:
            row_index: Индекс строки для клика (по умолчанию 0 - первая строка).

        Returns:
            str: Имя шаблона.
        """
        row_locator = self.templates_table.get_row_locator(
            row_index
        )
        row_locator.click()

        # Получаем имя шаблона из выбранной строки
        table_content = self.templates_table.read(TableLocators.TABLE_WORK_AREA)
        # +1 потому что первая строка - заголовки
        title = table_content[row_index + 1][0]

        # Добавляем модальное окно в коллекцию после открытия
        self.add_modal_window(title)

        return title

    def close_modal_window_by_toolbar_button(self, title: str) -> None:
        """Закрывает модальное окно через кнопку в тулбаре.

        Args:
        """

        modal_window = self.get_modal_window(title)
        modal_window.close_window_by_toolbar_button()
        self.delete_modal_window(title)

        Returns:

        Raises:
        """

        return self.templates_table.get_rows_count(TableLocators.TABLE_WORK_AREA)

    def scroll_templates_table_up(self) -> None:
        """Прокручивает таблицу шаблонов вверх."""

        self.templates_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER)

    def scroll_templates_table_down(self) -> None:
        """Прокручивает таблицу шаблонов вниз."""

        self.templates_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER)

    def scroll_modal_up(self) -> None:
        """Прокручивает содержимое модального окна вверх."""

        temp_modal = ModalWindowComponent(self.page)
        temp_modal.scroll_window_up()

    def scroll_modal_down(self) -> None:
        """Прокручивает содержимое модального окна вниз."""

        temp_modal.scroll_window_down()

    def extract_specific_template(self, title: str, response_data: dict) -> dict:
        """Извлекает структуру конкретного шаблона по title из данных API.

        Args:
            title: Имя шаблона для извлечения.
            response_data: Данные ответа от API.

        Returns:
            dict: Структура конкретного шаблона.

        Raises:
            AssertionError: Если шаблон с указанным именем не найден
                или структура ответа некорректна.
        """
        # Проверяем, что ответ является списком шаблонов
        assert isinstance(response_data, list), "API response is not a list of templates"

        # Ищем шаблон с указанным именем
        for template in response_data:
            if template.get('id') == title:
                logger.info("Found template: %s", title)
                return template

        # Если шаблон не найден
        available_templates = [t.get('id', 'Unknown') for t in response_data]

        # Генерируем понятное сообщение об ошибке
        error_msg = (
            f"Template '{title}' not found. "
            f"Available templates: {available_templates}"
        )
        logger.error(error_msg)
        assert False, error_msg

    def get_template_data_from_api(self, title: str) -> dict:
        """Получает данные шаблона из API.

        Args:
            title: Имя шаблона.

        Returns:
            dict: Данные шаблона из API.
        """
        # Отправляем запрос к backend для получения информации о шаблоне
        response = self.send_get_api_request("e-cmdb/api/device/template")

        # Извлекаем конкретный шаблон по имени из ответа API
        template_data = self.extract_specific_template(title, response_body)
        return template_data

    def verify_json_container_content(self, title: str) -> None:

        Args:
            title: Имя шаблона для проверки.
        """
        # Получаем данные шаблона из API
        template_data = self.get_template_data_from_api(title)

        # Получаем модальное окно и проверяем содержимое JSON контейнера
        modal_window.verify_json_container_content(template_data)

    def check_templates_modal_content(self, title: str) -> None:
        """Проверяет наличие и корректность элементов модального окна шаблона.

        Args:
            title: Имя шаблона для проверки заголовка окна.

        Raises:
            AssertionError: Если элементы окна некорректны.
        """
        modal_window.check_content()

    def check_templates_table_content(self) -> None:
        """Проверяет содержимое таблицы шаблонов.

        Raises:
        """

            'Имя',
            'Описание',
            'Тип устройства',
            'Производитель'
        ]

        self.templates_table.check_content(TableLocators.TABLE_WORK_AREA, expected_headers)

    def check_templates_table_vertical_scrolling(self) -> bool:

        Returns:
        """

        return self.templates_table.is_scrollable_vertically(
        )

    def check_templates_table_first_row_visibility(self) -> None:

        Raises:
        """

        self.templates_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA)

    def check_templates_table_last_row_visibility(self) -> None:

        Raises:
        """

        self.templates_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA)

    def check_templates_table_row_highlighting(self, row_index: int) -> None:

        Args:

        Raises:
        """

        self.templates_table.check_row_highlighting(
            row_index
        )

        Raises:
        """

    def should_be_templates_table(self) -> None:
        """Проверяет наличие таблицы шаблонов.

        Raises:
        """

        self.templates_table.check_visibility(
            "Templates table is missing"
        )

    def should_be_modal_window(self) -> None:
        """Проверяет наличие модального окна.

        Raises:
            AssertionError: Если модальное окно отсутствует.
        """
            ModalWindowLocators.MODAL_WINDOW,
            "Modal window is not visible"
        )

    def should_not_be_modal_window(self) -> None:
        """Проверяет, что модальное окно отсутствует.

        Raises:
            AssertionError: Если модальное окно все еще видно.
        """
        is_visible = self.page.locator(
            ModalWindowLocators.MODAL_WINDOW
        ).is_visible(timeout=1000)
        if is_visible:
            assert False, "Modal window should not be visible"

    def check_modal_vertical_scrolling(self) -> bool:
        """Проверяет возможность вертикального скроллинга в модальном окне.

        Returns:
            bool: True если скроллинг возможен, иначе False.
        """
        return temp_modal.check_window_vertical_scrolling()

"""Модуль вкладки 'Пользователи'.

Содержит класс UsersTab для работы с таблицей пользователей.
Позволяет управлять пользователями через модальные окна и проверять их состояние.
"""

import re
from components_derived.modal_edit_user import EditUserModalWindow
from components_derived.modal_add_local_user import AddLocalUserModalWindow
from components_derived.modal_add_AD_user import AddADUserModalWindow

class UsersTab(BasePage):
    """Класс для работы с вкладкой 'Пользователи'.

    Предоставляет методы для взаимодействия с таблицей пользователей,
    модальными окнами и проверки состояния элементов.

    Args:
    """

        """Инициализирует компоненты вкладки 'Пользователи'."""

            has_text=re.compile("Пользователи")

        self.toolbar = ToolbarComponent(page, "Пользователи")
        self.toolbar.add_tooltip_button(locator_button_1, "add_user")
        self.toolbar.add_tooltip_button(locator_button_2, "close")

        self.users_table = TableComponent(page)

    def add_modal_window(self, window_type: str, title: str) -> None:

        Args:
            window_type: Тип окна ('add_user' или 'edit_user').

        Raises:
            AssertionError: Если тип окна не поддерживается.
        """

        if window_type == "add_local_user":
            self.modal_windows["add_local_user"] = AddLocalUserModalWindow(self.page)
        elif window_type == "add_AD_user":
            self.modal_windows["add_AD_user"] = AddADUserModalWindow(self.page)
        elif window_type == "edit_user":
            self.modal_windows[title] = EditUserModalWindow(self.page, title)
        else:
            assert False, "Unsupported modal window type"

    def add_new_user(self, user_data: dict) -> bool:
        """Добавляет нового пользователя или обрабатывает ошибку при дубликате.

        Args:
            user_data: Данные пользователя (name, role, password).

        Returns:
            bool: True если пользователь успешно добавлен, False если пользователь уже существует.

        Raises:
            AssertionError: Если открылось alert окно отличное от success или error,
                        или если текст alert не соответствует ожидаемому.
        """

        add_user_window = self.get_modal_window("add_local_user")

        auth_type = user_data.get("auth_type")
        if auth_type == "active_directory":
            add_user_window.check_active_directory_checkbox()
            self.add_modal_window("add_AD_user", "")
            add_user_window = self.get_modal_window("add_AD_user")

        add_user_window.new_user(user_data)

        is_added = False
            self.alert.check_alert_presence(' Новый пользователь \n успешно добавлен! ')
            self.alert.check_alert_absence(' Новый пользователь \n успешно добавлен! ')
            is_added = True
            self.alert.check_alert_presence(f' Имя {user_data["name"]} уже \n используется ')
            self.alert.check_alert_absence(f' Имя {user_data["name"]} уже \n используется ')
        else:
            assert False, f"Got unexpected alert type {alert_type}"

        return is_added

    def close_add_AD_user_window(self) -> None:
        """Закрывает окно добавления пользователя."""

        self.close_modal_window("add_AD_user")

    def close_add_AD_user_window_by_toolbar_button(self) -> None:
        """Закрывает окно добавления пользователя через тулбар."""

        self.close_modal_window_by_toolbar_button("add_AD_user")

    def close_add_user_window(self) -> None:

        self.close_modal_window("add_local_user")

    def close_add_user_window_by_toolbar_button(self) -> None:

        self.close_modal_window_by_toolbar_button("add_local_user")

    def close_edit_user_window(self, title: str) -> None:
        """Закрывает окно редактирования пользователя.

        Args:
            title: Имя пользователя (заголовок окна).
        """

        self.close_modal_window(title)

    def close_edit_user_window_by_toolbar_button(self, title: str) -> None:
        """Закрывает окно редактирования через кнопку в тулбаре.

        Args:
        """

        self.close_modal_window_by_toolbar_button(title)

    def close_modal_window(self, title: str) -> None:
        """Закрывает модальное окно через кнопку закрытия.

        Args:
        """

        modal_window.close_window()

        Args:
        """

        Args:

        Raises:
        """

    def delete_user(self, user_name: str) -> None:
        """Удаляет пользователя.

        Args:
            user_name: Имя пользователя.

        Raises:
            AssertionError: Если нет сообщения об успешном удалении.
        """

        self.get_modal_window(user_name).delete_user()

        assert alert_type == "success", f"Expected success alert, but got {alert_type} alert"

        self.alert.check_alert_presence('\nПользователь удалён\n')
        self.alert.check_alert_absence('\nПользователь удалён\n')

    def edit_user(self, user_name: str, user_data: dict) -> None:

        Args:
            user_data: Новые данные пользователя.

        Raises:
            AssertionError: Если нет сообщения об успешном обновлении.
        """

        self.get_modal_window(user_name).edit_user(user_data)

        self.alert.check_alert_presence('\nОбновление успешно\n')
        self.alert.check_alert_absence('\nОбновление успешно\n')

    def find_user_in_table(self, name: str, role: str) -> int:
        """Ищет пользователя в таблице.

        Args:
            name: Имя пользователя.
            role: Роль пользователя.

        Returns:
            int: Индекс строки или -1 если не найден.

        Raises:
        """

        table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA)

        # Удаляем заголовок

        for row_index, user_info in enumerate(table_content):
            if name in user_info and role in user_info:
        return -1

    def get_modal_window(self, title: str) -> ModalWindowComponent:

        Args:

        Returns:
            ModalWindowComponent: Экземпляр модального окна.

        Raises:
        """

    def open_add_user_window(self) -> None:
        """Открывает окно добавления пользователя.

        Raises:
            AssertionError: Если кнопки недоступны или окно не открылось.
        """

        if self.toolbar.is_button_not_present("close"):
            self.toolbar.check_button_visibility("edit")
            self.toolbar.click_button("edit")

        self.toolbar.check_button_visibility("add_user")
        self.toolbar.click_button("add_user")
        self.page.wait_for_timeout(700)

        self.add_modal_window("add_local_user", "")
        self.get_modal_window("add_local_user").check_by_window_title()

    def open_edit_user_page_by_index(self, row_index: int) -> tuple:
        """Открывает окно редактирования по индексу строки.

        Args:

        Returns:
            tuple: (имя пользователя, роль).

        Raises:
            AssertionError: Если таблица пуста или индекс вне диапазона.
        """

        tmp_dict = {"admin": "Администратор", "manager": "Контактное лицо",
                   "operator": "Оператор"}

        if row_index >= len(table_content):
            assert False, "Row_index is out of range"

        user_name = table_content[row_index][0]
        for key, val in tmp_dict.items():
            if user_name == val:
                user_name = key

        role = table_content[row_index][2]

        self.page.locator(TableLocators.TABLE_WORK_AREA).locator(
            "//tbody/tr").nth(row_index).click()
        self.add_modal_window("edit_user", user_name)
        self.get_modal_window(user_name).check_by_window_title()

        return user_name, role

    def open_edit_user_page_by_user(self, user_name: str, role: str) -> None:
        """Открывает окно редактирования по имени и роли.

        Args:

        Raises:
            AssertionError: Если пользователь не найден.
        """

        row_index = self.find_user_in_table(user_name, role)
            assert False, f"User with name {user_name} and role {role} has not been found"

        self.page.locator(TableLocators.TABLE_WORK_AREA).locator("//tbody/tr").nth(row_index).click()

    def reset_password(self, user_name: str) -> str:
        """Сбрасывает пароль пользователя.

        Args:

        Returns:
            str: Новый пароль (если получен).
        """

        new_password = ""
        self.get_modal_window(user_name).reset_password()

        self.alert.check_alert_presence("")
        alert_message = self.alert.get_text()
        if len(alert_message) > 0:
            new_password = re.findall(r'[\d]+', alert_message)[0]

        return new_password

    def transform_to_add_AD_user_window(self):
        """Трансформирует модальное окно добавления локального пользователя
           в окно добавления пользователя Active Directory с помощью нажатия
           чек-бокса Active Directory.
        """

        self.get_modal_window("add_local_user").check_active_directory_checkbox()
        modal_window = self.modal_windows.get("add_AD_user")

    def transform_to_add_user_window(self):
        """Трансформирует модальное окно добавления пользователя Active Directory
           в окно добавления локального пользователя с помощью снятия отметки с
        """

        self.get_modal_window("add_AD_user").uncheck_active_directory_checkbox()
        modal_window = self.modal_windows.get("add_local_user")
            self.add_modal_window("add_local_user", "")

    def check_add_AD_user_window_content(self) -> None:
        """Проверяет содержимое окна добавления пользователя через Active Directory."""

        self.get_modal_window("add_AD_user").check_content()

    def check_add_user_window_content(self) -> None:
        """Проверяет содержимое окна добавления локального пользователя."""

        self.get_modal_window("add_local_user").check_content()

    def check_edit_user_window_content(self, user_name: str, role: str) -> None:
        """Проверяет содержимое окна редактирования.

        Args:
        """

        edit_user_window = self.get_modal_window(user_name)
        edit_user_window.check_content(user_name, role)

    def check_users_table_content(self, verify: bool = False) -> None:
        """Проверяет содержимое таблицы пользователей.

        Args:

        Raises:
        """

        expected_headers = ['Имя пользователя', 'Тип авторизации', 'Роль',
                           'E-mail', 'Номер для СМС']

        )

        if verify:
            self.verify_users_table_content(table_content)

        Raises:
        """

        Raises:
        """

        self.toolbar.check_button_visibility("close")
        self.toolbar.check_button_tooltip("add_user", "Добавить")
        self.toolbar.check_button_tooltip("close", "Закрыть")

        self.toolbar.get_button_by_name("close").click()

    def should_be_user_in_table(self, name: str, role: str) -> None:
        """Проверяет наличие пользователя в таблице.

        Args:

        Raises:
        """

        found = self.find_user_in_table(name, role)
            assert False, f"User with name {name} and role {role} has not been found"

    def should_be_users_table(self) -> None:
        """Проверяет наличие таблицы пользователей.

        Raises:
        """

        self.users_table.check_visibility(TableLocators.TABLE_WORK_AREA,"Users table is missing")

    def should_not_be_user_in_table(self, name: str, role: str) -> None:
        """Проверяет отсутствие пользователя в таблице.

        Args:

        Raises:
            AssertionError: Если пользователь найден.
        """

            assert False, f"User with name {name} and role {role} has been found"

    def verify_users_table_content(self, users_table: list) -> None:

        Args:
            users_table: Данные из таблицы на странице.

        Raises:
        """

        expected_users_list = []

        query = {
            "id": ["/catalogs/user"],
            "data": {
                "namePath": True,
                "children": {"flatten": True}
            }
        }

        response = self.send_post_api_request("e-cmdb/api/query", query)

        for item in response_body[0]["children"]:
            user_info = []
            user_name = item["name"]

            # НЕ преобразуем имя пользователя - оставляем как есть из БД
            user_info.append(user_name)

            if item["type_auth"] is not None:
                user_info.append(item["type_auth"])
            else:
                user_info.append("")

            if item["role"] is not None:
                role = item["role"]
                # Убрали вызов .keys()
                if role in roles_dict:
                    item["role"] = roles_dict[role]
                user_info.append(item["role"])
            else:

            if item["email"] is not None:
                user_info.append(item["email"])
            else:

            if item["sms_phone"] is not None:
                user_info.append(item["sms_phone"])
            else:

            expected_users_list.append(user_info)

        del users_table[0]

            users_table,
            expected_users_list,
            "Actual users list is not equal expected users list on base db"
        )

"""Модуль вкладки 'Конфигурация' в модуле Zero Touch Provisioning.

Содержит класс ZTPConfigTab для работы с таблицей конфигурации ZTP.
"""

logger = get_logger("ZTP_CONFIG_TAB")

class ZTPConfigTab(BasePage):
    """Класс для работы с вкладкой 'Конфигурация' в модуле Zero Touch Provisioning.

    Предоставляет методы для взаимодействия с таблицей конфигурации ZTP
    и проверки состояния элементов интерфейса. Включает функциональность для:
    - Проверки содержимого и структуры таблицы конфигурации
    - Прокрутки таблицы и проверки видимости строк
    - Выделения строк таблицы
    - Работы с кнопкой синхронизации (Reload) в заголовке таблицы
    - Проверки видимости и подсказок кнопок синхронизации

    Args:
    """

        """Инициализирует компоненты вкладки 'Конфигурация'.

        Args:
            page: Экземпляр страницы Playwright для взаимодействия с браузером.
        """

        # Компонент тулбара вкладки конфигурации
        self.toolbar = ToolbarComponent(page, "Конфигурация")

        # Компонент таблицы конфигурации ZTP
        self.ztp_config_table = TableComponent(page)

    def click_sync_button(self, index: int) -> None:
        """Нажимает кнопку 'Reload' в указанной ячейке заголовка таблицы.

        Перед кликом проверяет наличие кнопки.

        Args:
            index: Индекс ячейки в заголовке таблицы.

        Raises:
            AssertionError: Если индекс выходит за пределы или кнопка не найдена.
        """

        # Проверяем наличие кнопки перед кликом
        sync_button = self.ztp_config_table.get_header_cell_button(TableLocators.TABLE_WORK_AREA, index)
        assert sync_button.is_enabled(), f"Sync button is missing in {index} header cell"

        # Выполняем клик по кнопке
        sync_button.click()
        logger.info("Нажата кнопка синхронизации (Reload)")

        """Возвращает количество строк в таблице конфигурации (без заголовка).

        Returns:
            int: Количество строк с данными конфигурации ZTP.

        Raises:
            AssertionError: Если таблица пуста или не загружена.
        """
        return self.ztp_config_table.get_rows_count(TableLocators.TABLE_WORK_AREA)

    def scroll_config_table_up(self) -> None:
        """Прокручивает таблицу конфигурации вверх до начала."""
        self.ztp_config_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER)

    def scroll_config_table_down(self) -> None:
        """Прокручивает таблицу конфигурации вниз до конца."""
        self.ztp_config_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER)

    def check_config_table_content(self) -> None:
        """Проверяет содержимое таблицы конфигурации ZTP.

        Проверяет соответствие заголовков таблицы ожидаемым значениям
        и наличие данных в таблице.

        Raises:
            AssertionError: Если таблица пуста или заголовки не соответствуют ожидаемым.
        """
        # Ожидаемые заголовки столбцов таблицы конфигурации ZTP
            'sync',
            'IDENTIFIER',
            'MAC',
            'DHCPRELAY',
            'ШАБЛОН',
            'IPSETTODEVICE',
            'СТАТУС',
            'ДЕЙСТВИЯ'
        ]

        self.ztp_config_table.check_content(
            False
        )

    def check_sync_button_visibility(self, index: int) -> None:
        """Проверяет видимость кнопки синхронизации (Reload) в указанной ячейке заголовка.

        Args:

        Raises:
            AssertionError: Если кнопка не видна на странице.
        """

        # Прокручиваем к элементу для гарантии видимости
        sync_button.scroll_into_view_if_needed()

        # Проверяем видимость кнопки
        assert sync_button.is_visible(), f"Sync button in header cell {index} is not visible"

        logger.info("Кнопка синхронизации (Reload) видима")

    def check_sync_button_tooltip(self, index: int, expected_tooltip: str = "reload") -> None:
        """Проверяет текст подсказки кнопки синхронизации (Reload).

        Использует стандартный механизм проверки подсказок из ToolbarComponent.

        Args:
            expected_tooltip: Ожидаемый текст подсказки (по умолчанию 'reload').

        Raises:
            AssertionError: Если подсказка не соответствует ожидаемой или отсутствует.
        """
        # Убеждаемся, что кнопка существует

        sync_tooltip_button = TooltipButton(self.page, sync_button, "sync_button")

        # Проверка подсказки
        sync_tooltip_button.check_tooltip_with_text(expected_tooltip)

        logger.info(
            "Подсказка кнопки синхронизации (Reload) в ячейке %d: '%s'",
            index,
            expected_tooltip
        )

    def check_config_table_vertical_scrolling(self) -> bool:
        """Проверяет возможность вертикальной прокрутки таблицы конфигурации.

        Returns:
            bool: True если таблица поддерживает вертикальную прокрутку, иначе False.
        """
        return self.ztp_config_table.is_scrollable_vertically(
        )

    def check_config_table_first_row_visibility(self) -> None:
        """Проверяет видимость первой строки таблицы конфигурации.

        Raises:
            AssertionError: Если первая строка таблицы не видна.
        """
        self.ztp_config_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA)

    def check_config_table_last_row_visibility(self) -> None:
        """Проверяет видимость последней строки таблицы конфигурации.

        Raises:
            AssertionError: Если последняя строка таблицы не видна.
        """
        self.ztp_config_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA)

    def check_config_table_row_highlighting(self, row_index: int) -> None:
        """Проверяет выделение указанной строки таблицы конфигурации.

        Args:
            row_index: Индекс проверяемой строки (0-based, без учета заголовка).

        Raises:
            AssertionError: Если строка не выделена после взаимодействия.
        """
        self.ztp_config_table.check_row_highlighting(
            TableLocators.TABLE_WORK_AREA, row_index
        )

        """Проверяет наличие и видимость тулбара на вкладке конфигурации.

        Raises:
            AssertionError: Если тулбар отсутствует на странице.
        """

    def should_be_config_table(self) -> None:
        """Проверяет наличие и видимость таблицы конфигурации ZTP.

        Raises:
            AssertionError: Если таблица конфигурации отсутствует на странице.
        """
        self.ztp_config_table.check_visibility(
            TableLocators.TABLE_WORK_AREA, "Configuration table is missing"
        )

"""Модуль вкладки 'Шаблоны' в модуле Zero Touch Provisioning.

Содержит класс ZTPTemplatesTab для работы с таблицей шаблонов.
"""

from components_derived.modal_view_ztp_template import ViewZTPTemplateModalWindow

logger = get_logger("ZTP_TEMPLATES_TAB")

class ZTPTemplatesTab(BasePage):
    """Класс для работы с вкладкой 'Шаблоны' в модуле Zero Touch Provisioning.

    Предоставляет методы для взаимодействия с таблицей шаблонов, модальными окнами
    - Работы с таблицей шаблонов (прокрутка, проверка строк, выделение)
    - Управления модальными окнами просмотра шаблонов
    - Сравнения данных UI с API ответами

    Args:
    """

        self.ztp_templates_table = TableComponent(page)

        Args:
        """
        self.modal_windows[title] = ViewZTPTemplateModalWindow(self.page, title)

    def get_modal_window(self, title: str) -> ViewZTPTemplateModalWindow:

        Args:

        Returns:
            ViewZTPTemplateModalWindow: Экземпляр модального окна шаблона.

        Raises:
        """

        Args:

        Raises:
        """

        Args:

        Returns:
        """
        row_locator = self.ztp_templates_table.get_row_locator(
        )

        # Создаем временный экземпляр модального окна для получения заголовка
        temp_modal = ViewZTPTemplateModalWindow(self.page, "")
        title = temp_modal.toolbar.get_toolbar_title_text(
            ModalWindowLocators.MODAL_WINDOW_TITLE
        )

        Args:
        """

        """Закрывает модальное окно через кнопку 'Закрыть'.

        Args:
        """

        Returns:

        Raises:
        """
        return self.ztp_templates_table.get_rows_count(TableLocators.TABLE_WORK_AREA)

        self.ztp_templates_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER)

        self.ztp_templates_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER)

    def check_ztp_templates_modal_content(self, title: str) -> None:

        Args:

        Raises:
        """

    def check_ztp_templates_table_content(self) -> None:

        Raises:
        """
            'Производитель',
            'authenticationOption'
        ]

        self.ztp_templates_table.check_content(TableLocators.TABLE_WORK_AREA, expected_headers)

        Returns:
        """
        return self.ztp_templates_table.is_scrollable_vertically(
        )

        Raises:
        """
        self.ztp_templates_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA)

        Raises:
        """
        self.ztp_templates_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA)

        Args:

        Raises:
        """
        self.ztp_templates_table.check_row_highlighting(
        )

        Raises:
        """

        Raises:
        """
        self.ztp_templates_table.check_visibility(
            TableLocators.TABLE_WORK_AREA, "Templates table is missing"
        )

        Raises:
        """
            ModalWindowLocators.MODAL_WINDOW, "Modal window is not visible"
        )

        Raises:
        """

        Returns:
        """

    def verify_template_data_with_api(self, title: str) -> None:
        """Проверяет соответствие данных модального окна данным из API.

        Процесс проверки:
        1. Получает модальное окно по имени шаблона
        2. Извлекает данные из полей ввода модального окна
        3. Отправляет API запрос для получения эталонных данных шаблона
        4. Сравнивает данные из UI с данными из API
        5. Выбрасывает assertion при обнаружении расхождений

        Args:
            title: Имя шаблона для проверки (должно совпадать с id в API).

        Raises:
            AssertionError: Если:
                - Модальное окно не найдено
                - API запрос завершился с ошибкой (status != 200)
                - Обнаружены расхождения между данными UI и API
                - Имя шаблона в модальном окне не соответствует ожидаемому
        """
        # Получаем модальное окно

        # Читаем данные модального окна
        actual_data = modal_window.get_modal_window_data()

        # Читаем данные из API
        encoded_title = title.replace(" ", "%20")
        url = f"e-nms/DHCP/showOptPattern?template={encoded_title}"
        response = self.send_get_api_request(url)

        if response.status != 200:
            logger.error("API request failed with status: %s", response.status)
            assert False, f"API request failed with status: {response.status}"

        # Извлекаем данные из ответа
        response_data = response.json()
        template_data = response_data['data']

        modal_window.compare_modal_with_api_data(actual_data, template_data, title)

#!/usr/bin/env python3
"""Модуль исправления структуры Python-проекта.

Автоматически выполняет:
1. Удаление BOM-маркеров из файлов
2. Создание недостающих __init__.py
3. Логирование всех операций

Использование: python fix_python_project.py [путь_к_проекту]
"""

import os
import sys
from typing import List, Set

# Настройки обработки проекта
INIT_IGNORED_DIRS: Set[str] = {'tests', '.git', '__pycache__', 'venv', 'env', '.idea', '.vscode'}
TARGET_EXTENSIONS: tuple = ('.py', '.json', '.txt')
INIT_TEMPLATE: str = """# Auto-generated by fix_python_project.py
\"\"\"Package initialization.\"\"\"
"""

class ProjectFixer:
    """Исправляет структуру Python-проекта.

    Атрибуты:
        root_dir: Корневая директория проекта.
        log: Лог выполненных операций.
    """

    def __init__(self, root_dir: str = '.'):
        """Инициализирует экземпляр ProjectFixer.

        Args:
            root_dir: Корневая директория проекта. По умолчанию '.'.
        """

        self.root_dir = os.path.abspath(root_dir)
        self.log: List[str] = []

    def remove_bom(self, filepath: str) -> bool:
        """Удаляет BOM-маркер из файла.

        Args:
            filepath: Путь к файлу.

        Returns:
            bool: True если BOM был удалён.

        Raises:
            Exception: При ошибке чтения/записи.
        """

        try:
            with open(filepath, 'rb') as f:
                content = f.read()

            if content.startswith(b'\xEF\xBB\xBF'):
                with open(filepath, 'wb') as f:
                    f.write(content[3:])
                self.log.append(f"REMOVED BOM: {filepath}")
                return True
            self.log.append(f"ERROR processing {filepath}: {str(e)}")

    def should_skip_init(self, dir_path: str) -> bool:
        """Проверяет нужно ли пропустить директорию.

        Args:
            dir_path: Путь к директории.

        Returns:
            bool: True если директорию нужно пропустить.
        """

        dir_name = os.path.basename(dir_path)
        return (dir_name in INIT_IGNORED_DIRS or
                dir_name.startswith('.'))

    def needs_init_py(self, dir_path: str) -> bool:
        """Проверяет нужно ли создать __init__.py.

        Args:

        Returns:
            bool: True если __init__.py требуется.
        """

        if self.should_skip_init(dir_path):

        try:
            items = os.listdir(dir_path)
            has_py_files = any(f.endswith('.py') and f != '__init__.py' for f in items)
            has_init = '__init__.py' in items
            return has_py_files and not has_init
        except Exception:

    def create_init_py(self, dir_path: str) -> bool:
        """Создаёт файл __init__.py.

        Args:

        Returns:
            bool: True если файл создан.

        Raises:
            Exception: При ошибке создания.
        """

        init_path = os.path.join(dir_path, '__init__.py')
        try:
            with open(init_path, 'w', encoding='utf-8') as f:
                f.write(INIT_TEMPLATE)
            self.log.append(f"CREATED INIT: {init_path}")
            self.log.append(f"ERROR creating {init_path}: {str(e)}")

    def process_directory(self):
        """Рекурсивно обрабатывает проект."""

        for root, dirs, files in os.walk(self.root_dir):
            # Обработка файлов с целевыми расширениями
            for file in files:
                if file.endswith(TARGET_EXTENSIONS):
                    self.remove_bom(os.path.join(root, file))

            # Создание __init__.py где это необходимо
            if self.needs_init_py(root):
                self.create_init_py(root)

    def save_log(self, log_file: str = 'project_fix.log'):
        """Сохраняет лог операций в файл.

        Args:
            log_file: Имя файла лога. По умолчанию 'project_fix.log'.
        """

        with open(log_file, 'w', encoding='utf-8') as f:
            f.write("\n".join(self.log))

if __name__ == '__main__':
    # Обработка аргументов командной строки
    target_dir = sys.argv[1] if len(sys.argv) > 1 else '.'

    # Инициализация и запуск обработки
    fixer = ProjectFixer(target_dir)
    print(f"Исправление структуры проекта в: {fixer.root_dir}")

    fixer.process_directory()
    fixer.save_log()

    # Вывод результатов
    print(f"Готово! Внесено {len(fixer.log)} изменений.")
    print("Подробности сохранены в project_fix.log")

"""Модуль настройки логгера.

Содержит функцию для создания и настройки логгера
с выводом в стандартный поток.
"""

import logging

def get_logger(name: str) -> logging.Logger:
    """Создаёт и настраивает логгер.

    Args:
        name: Имя логгера (обычно __name__ модуля).

    Returns:
        logging.Logger: Настроенный экземпляр логгера.

    Пример:
        logger = get_logger(__name__)
        logger.debug("Тестовое сообщение")
    """

    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)

    # Настройка обработчика для вывода в консоль
    handler = logging.StreamHandler()
    handler.setLevel(logging.DEBUG)

    # Формат сообщений: время | имя логгера | уровень | сообщение
    formatter = logging.Formatter('%(asctime)s | %(name)s | %(levelname)s | %(message)s')
    handler.setFormatter(formatter)

    logger.addHandler(handler)

    return logger