diff --git a/changelog.txt b/changelog.txt index 47c7b2f..b7a0781 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,4 +7,9 @@ tooltiplocator - components\toolbar_component.py - добавлен tooltiplocator в сигнатуру функции check_button_tooltip, изменены функции add_button и get_button_by_name - pages\users_tab.py - переписана функция should_be_toolbar_buttons - pages\session_tab.py - вкладка "Сессии" -- tests\e2e\test_sessions_tab.py - тест вкладки "Сессии" \ No newline at end of file +- tests\e2e\test_sessions_tab.py - тест вкладки "Сессии" +===================23.07.2025========================== +- Все файлы прошли проверку pylint, внесены исправления для фикса замечаний линтера +- Возвращено заведение пользователя с введением пароля +- Актуализированы тесты под текущее состояние интерфейса пользователя версии 1.7 + \ No newline at end of file diff --git a/components/alert_component.py b/components/alert_component.py index dd0fcdc..eb6b332 100644 --- a/components/alert_component.py +++ b/components/alert_component.py @@ -1,18 +1,16 @@ from playwright.sync_api import Page, expect - -from components.base_component import BaseComponent -from elements.text_element import Text - from tools.logger import get_logger +from elements.text_element import Text +from components.base_component import BaseComponent logger = get_logger("ALERT") class AlertComponent(BaseComponent): """Компонент для работы с alert-окнами. - + Поддерживает различные типы alert-окон: error, success, info, warning. - + Атрибуты: page: экземпляр страницы Playwright alert_type: тип alert-окна (error/success/info/warning) @@ -21,69 +19,74 @@ class AlertComponent(BaseComponent): def __init__(self, page: Page, alert_type: str): """Инициализация компонента alert-окна. - + Args: page: экземпляр страницы Playwright alert_type: тип alert-окна (error/success/info/warning) - + Raises: ValueError: если передан неподдерживаемый тип alert-окна """ + super().__init__(page) - + alert_types = ["error", "success", "info", "warning"] if alert_type not in alert_types: raise ValueError("Unsupported type of alert window") - + self.alert_type = alert_type - self.text = Text(page, f"//div[@class='v-alert {self.alert_type}']/div", "Alert message") + self.text = Text(page, f"//div[@class='v-alert {self.alert_type}']/div", "Alert message") # Действия: def get_text(self) -> str: """Получение текста сообщения из alert-окна. - + Returns: str: текст сообщения alert-окна """ + return self.text.get_text(0) - + # Проверки: - def check_presence(self, text: str): + def check_alert_presence(self, text: str): """Проверка наличия alert-окна с заданным текстом. - + Args: text: текст для проверки (если пустая строка - проверяется только наличие окна) - + Raises: AssertionError: если alert-окно не найдено """ + msg = f"No {self.alert_type} alert window on page" if text == "": expect(self.page.get_by_role("alert")).to_be_visible(), msg else: expect(self.page.get_by_role("alert").filter(has_text=text)).to_be_visible(), msg - - def check_absence(self, text: str, timeout: int = 30000): + + def check_alert_absence(self, text: str, timeout: int = 30000): """Проверка отсутствия alert-окна с заданным текстом. - + Args: text: текст для проверки timeout: время ожидания исчезновения (в миллисекундах) - + Raises: AssertionError: если alert-окно не исчезает в течение заданного времени """ seconds = int(timeout/1000) - msg = f"Alert {self.alert_type} window should disappear after {seconds} seconds" + msg = f"Alert {self.alert_type} window should disappear after {seconds} seconds" expect(self.page.get_by_role("alert").filter(has_text=text)).to_be_hidden(timeout=timeout), msg def check_text(self, alert_text: str): """Проверка точного соответствия текста в alert-окне. - + Args: alert_text: ожидаемый текст сообщения - + Raises: AssertionError: если текст не соответствует ожидаемому """ - self.text.check_have_text(alert_text, f"Unexpected message in alert {self.alert_type} window") \ No newline at end of file + + self.text.check_have_text(alert_text, + f"Unexpected message in alert {self.alert_type} window") diff --git a/components/base_component.py b/components/base_component.py index 45e8543..bbe395e 100644 --- a/components/base_component.py +++ b/components/base_component.py @@ -1,48 +1,171 @@ from playwright.sync_api import Page, Locator, expect - from tools.logger import get_logger logger = get_logger("BASE_COMPONENT") class BaseComponent: - """Базовый компонент для работы с элементами страницы.""" - + """Базовый компонент для работы с элементами страницы. + + Предоставляет общие методы для взаимодействия с элементами: + - получение локаторов + - проверка видимости элементов + - работа с прокруткой + + Атрибуты: + page: экземпляр страницы Playwright + """ + 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 check_presence(self, locator: str | Locator, msg: str): + + # Закомментированный код сохранен без изменений + # 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_presence(self, locator, msg): + """Проверка видимости элемента на странице. + + Args: + locator: локатор элемента (строка или объект Locator) + msg: сообщение об ошибке при неудачной проверке + + Raises: + AssertionError: если элемент не виден на странице + """ loc = self.get_locator(locator) expect(loc).to_be_visible(visible=True, timeout=12000), msg - - def is_scrollable_vertically(self, locator: str | Locator) -> bool: + + def is_scrollable_vertically(self, locator) -> bool: + """Проверка возможности вертикальной прокрутки элемента. + + Args: + locator: локатор элемента + + Returns: + bool: True если элемент можно прокрутить вертикально + """ loc = self.get_locator(locator) return loc.evaluate("el => el.scrollHeight > el.clientHeight") - - def is_scrollable_horizontally(self, locator: str | Locator) -> bool: + + def is_scrollable_horizontally(self, locator) -> bool: + """Проверка возможности горизонтальной прокрутки элемента. + + Args: + locator: локатор элемента + + Returns: + bool: True если элемент можно прокрутить горизонтально + """ loc = self.get_locator(locator) return loc.evaluate("el => el.scrollWidth > el.clientWidth") - - def scroll_up(self, locator: str | Locator): + + # Методы прокрутки: + def scroll_up(self, locator): + """Прокрутка элемента вверх. + + Args: + locator: локатор элемента + + Raises: + AssertionError: если прокрутка не выполнена до конца + """ loc = self.get_locator(locator) loc.evaluate("el => el.scrollTo(0, 0)") - - def scroll_down(self, locator: str | Locator): + loc.wait_for(timeout=2000) + + # Проверка позиции прокрутки + scroll_position = loc.evaluate("el => el.scrollTop") + assert scroll_position == 0, "Invalid postion after scroll up" + + def scroll_down(self, locator): + """Прокрутка элемента вниз. + + Args: + locator: локатор элемента + + Raises: + AssertionError: если прокрутка не выполнена до конца + """ loc = self.get_locator(locator) loc.evaluate("el => el.scrollTo(0, el.scrollHeight)") - - def scroll_left(self, locator: str | Locator): + loc.wait_for(timeout=2000) + + # Проверка позиции прокрутки + scroll_position = loc.evaluate("el => el.scrollTop") + assert scroll_position > 0, "Invalid postion after scroll down" + + def scroll_left(self, locator): + """Прокрутка элемента влево. + + Args: + locator: локатор элемента + + Raises: + AssertionError: если прокрутка не выполнена до конца + """ loc = self.get_locator(locator) + width = loc.evaluate("el => el.scrollWidth") - - def scroll_right(self, locator: str | Locator): + loc.scroll_into_view_if_needed() + self.page.mouse.wheel(-width, 0) + + loc.wait_for(timeout=2000) + + # Проверка позиции прокрутки + scroll_position = loc.evaluate("el => el.scrollLeft") + assert scroll_position == 0, "Invalid postion after scroll left" + + def scroll_right(self, locator): + """Прокрутка элемента вправо. + + Args: + locator: локатор элемента + + Raises: + AssertionError: если прокрутка не выполнена до конца + """ loc = self.get_locator(locator) - width = loc.evaluate("el => el.scrollWidth") \ No newline at end of file + + width = loc.evaluate("el => el.scrollWidth") + loc.scroll_into_view_if_needed() + self.page.mouse.wheel(width, 0) + + loc.wait_for(timeout=2000) + + # Проверка позиции прокрутки + scroll_position = loc.evaluate("el => el.scrollLeft") + max_scroll_x = loc.evaluate("el => el.scrollWidth - el.clientWidth") + assert scroll_position >= max_scroll_x, "Invalid postion after scroll right" diff --git a/components/card_component.py b/components/card_component.py index c7f5e86..68721a2 100644 --- a/components/card_component.py +++ b/components/card_component.py @@ -1,18 +1,16 @@ from playwright.sync_api import Page - -from components.base_component import BaseComponent -from elements.button_element import Button - from tools.logger import get_logger +from elements.button_element import Button +from components.base_component import BaseComponent logger = get_logger("USER_CARD") class CardComponent(BaseComponent): """Компонент карточки пользователя. - + Предоставляет методы для взаимодействия с элементами карточки пользователя. - + Атрибуты: page: экземпляр страницы Playwright logout_button: кнопка выхода из системы @@ -20,25 +18,25 @@ class CardComponent(BaseComponent): def __init__(self, page: Page): """Инициализация компонента карточки пользователя. - + Args: page: экземпляр страницы Playwright """ super().__init__(page) - + self.logout_button = Button( - page, - page.get_by_role("button", name="Выйти"), + page, + page.get_by_role("button", name="Выйти"), "logout button" ) - + # Действия: def click_logout_button(self): """Нажатие кнопки выхода из системы. - + Выполняет клик по кнопке 'Выйти' в карточке пользователя. """ self.logout_button.click() - + # Проверки: - # (Методы проверок могут быть добавлены здесь в будущем) \ No newline at end of file + # (Методы проверок могут быть добавлены здесь в будущем) diff --git a/components/confirm_component.py b/components/confirm_component.py index f716527..c9e54d1 100644 --- a/components/confirm_component.py +++ b/components/confirm_component.py @@ -1,54 +1,52 @@ from playwright.sync_api import Page - -from components.base_component import BaseComponent -from elements.button_element import Button -from elements.text_element import Text -from locators.confirm_locators import ConfirmLocators - from tools.logger import get_logger +from locators.confirm_locators import ConfirmLocators +from elements.text_element import Text +from elements.button_element import Button +from components.base_component import BaseComponent logger = get_logger("CONFIRM_WINDOW") class ConfirmComponent(BaseComponent): """Компонент окна подтверждения действий.""" - + def __init__(self, page: Page, cancel_button_text: str, allow_button_text: str): super().__init__(page) - + 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, + 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, + 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 check_title(self, title: str, msg: str) -> None: """Проверка текста заголовка окна подтверждения.""" self.title.check_have_text(title, msg) - + def check_text(self, text: str, msg: str) -> None: """Проверка текста сообщения в окне подтверждения.""" - self.text.check_have_text(text, msg) \ No newline at end of file + self.text.check_have_text(text, msg) diff --git a/components/json_container_component.py b/components/json_container_component.py index 5034fb9..335c04b 100644 --- a/components/json_container_component.py +++ b/components/json_container_component.py @@ -1,58 +1,57 @@ -from playwright.sync_api import Page import json import jsondiff - -from components.base_component import BaseComponent +from playwright.sync_api import Page from tools.logger import get_logger +from components.base_component import BaseComponent logger = get_logger("JSON_CONTAINER") class JsonContainerComponent(BaseComponent): """Компонент для работы с JSON-данными на странице. - + Предоставляет методы для чтения и проверки JSON-данных, отображаемых в специальных контейнерах на странице. - + Атрибуты: page: экземпляр страницы Playwright """ def __init__(self, page: Page): """Инициализация JSON-контейнера. - + Args: page: экземпляр страницы Playwright """ super().__init__(page) - + # Действия: def read_data(self, locator): """Чтение и форматирование JSON-данных из указанного локатора. - + Args: locator: локатор элемента, содержащего JSON-данные - + Returns: dict: распарсенный JSON-объект - + Raises: json.JSONDecodeError: если данные не могут быть преобразованы в JSON """ def format_json_string(json_string): """Вспомогательная функция для форматирования строки JSON. - + Args: json_string: сырая строка с JSON-данными - + Returns: str: отформатированная строка JSON """ substrings = json_string.splitlines() formatted_string_list = [] last_substring = substrings.pop() - - for substring in substrings: + + for substring in substrings: if substring.find(':') == -1: if substring == '{': formatted_string_list.append(substring) @@ -63,38 +62,38 @@ class JsonContainerComponent(BaseComponent): else: formatted_string_list.append(substring + ',') continue - + key, value = substring.split(':') s = ':'.join(['"' + key + '" ', " " + value]) - + if value == '{': formatted_string_list.append(s) else: formatted_string_list.append(s + ',') - + s2 = formatted_string_list.pop() formatted_string_list.append(s2.rstrip(',')) formatted_string_list.append(last_substring) - + return " ".join(formatted_string_list) - + # Чтение JSON-содержимого из рабочей области loc = self.get_locator(locator) json_string = loc.inner_text() formatted_json_string = format_json_string(json_string) - return json.loads(formatted_json_string) - + return json.loads(formatted_json_string) + # Проверки: def check_json_equals(self, actual, expected, msg): """Сравнение 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}" \ No newline at end of file + assert len(diff) == 0, f"{msg}. DIFF is {diff}" diff --git a/components/modal_window_component.py b/components/modal_window_component.py index b71592d..c35c56b 100644 --- a/components/modal_window_component.py +++ b/components/modal_window_component.py @@ -1,11 +1,9 @@ from playwright.sync_api import Page - -from components.base_component import BaseComponent -from components.toolbar_component import ToolbarComponent -from elements.button_element import Button -from locators.modal_window_locators import ModalWindowLocators - from tools.logger import get_logger +from locators.modal_window_locators import ModalWindowLocators +from elements.button_element import Button +from components.toolbar_component import ToolbarComponent +from components.base_component import BaseComponent logger = get_logger("MODAL_WINDOW") @@ -23,81 +21,81 @@ class ModalWindowComponent(BaseComponent): def add_content_item(self, name: str, item: object) -> None: """Добавление элемента содержимого в окно.""" self.content_items[name] = item - + def get_content_item(self, name: str) -> object | 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_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: """Поиск кнопки по имени.""" 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) - + def scroll_window_left(self) -> None: """Прокрутка содержимого окна влево.""" self.scroll_left(ModalWindowLocators.MODAL_WINDOW) - + def scroll_window_right(self) -> None: """Прокрутка содержимого окна вправо.""" self.scroll_right(ModalWindowLocators.MODAL_WINDOW) - + # Проверки: def check_window_vertical_scrolling(self) -> bool: """Проверка возможности вертикальной прокрутки.""" return self.is_scrollable_vertically(ModalWindowLocators.MODAL_WINDOW) - + def check_window_horizontal_scrolling(self) -> bool: """Проверка возможности горизонтальной прокрутки.""" return self.is_scrollable_horizontally(ModalWindowLocators.MODAL_WINDOW) - + def check_by_window_title(self) -> None: """Проверка наличия окна по заголовку.""" - self.toolbar.check_presence(f"Modal window with '{self.toolbar.title}' is missing") - + self.toolbar.check_toolbar_presence(f"Modal window with '{self.toolbar.title}' is missing") + def check_button_presence(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.check_presence(f"Button with name '{name}' is missing") - + def check_toolbar_button_presence(self, name: str) -> None: """Проверка наличия кнопки в панели инструментов.""" self.toolbar.check_button_presence(name) - + def check_toolbar_button_tooltip(self, name: str, tooltip: str) -> None: """Проверка подсказки кнопки в панели инструментов.""" - self.toolbar.check_button_tooltip(name, tooltip) \ No newline at end of file + self.toolbar.check_button_tooltip(name, tooltip) diff --git a/components/navbar_component.py b/components/navbar_component.py index ce130b5..6d9721a 100644 --- a/components/navbar_component.py +++ b/components/navbar_component.py @@ -1,9 +1,7 @@ from playwright.sync_api import Page, Locator - -from components.base_component import BaseComponent -from locators.navigation_panel_locators import NavigationPanelLocators - from tools.logger import get_logger +from locators.navigation_panel_locators import NavigationPanelLocators +from components.base_component import BaseComponent logger = get_logger("NAVIGATION_PANEL") @@ -13,35 +11,46 @@ class NavigationPanelComponent(BaseComponent): def __init__(self, page: Page): super().__init__(page) - + # Действия: def get_item_names(self, locator: str | Locator) -> list[str]: """Получает тексты всех элементов по указанному локатору.""" loc = self.get_locator(locator) return loc.all_inner_texts() - + def click_item(self, locator: str | Locator, item_name: str) -> None: """Кликает по элементу с указанным текстом.""" loc = self.get_locator(locator) loc.get_by_text(item_name).click() - + def click_sub_item(self, locator: str | Locator, sublevel_number: int, item_name: str) -> None: """Кликает по вложенному элементу с указанным текстом.""" root_locator = self.get_locator(NavigationPanelLocators.NODE_ROOT) children_locator = self.get_locator(NavigationPanelLocators.NODE_CHILDREN) - + loc = self.get_locator(locator) - + if sublevel_number == 1: loc.locator(root_locator).get_by_text(item_name).click() elif sublevel_number == 2: loc.locator(children_locator).locator(root_locator).get_by_text(item_name).click() else: raise ValueError("the navigation panel has two levels of nesting only") - + # Проверки: def check_item_visibility(self, locator: str | Locator, item_name: str) -> None: """Проверяет видимость элемента с указанным текстом.""" - loc = self.get_locator(locator).get_by_text(item_name) + msg = f"Navigation panel item '{item_name}' is not visible" - self.check_presence(loc, msg) \ No newline at end of file + + ## временно: в навигационной панели есть две панели с именем Шаблоны + ## для их различия добавлены индексы Шаблоны_1 для Настройки/Шаблоны + ## Шаблоны_2 для Настройки/ZTP/Шаблоны + loc = self.get_locator(locator) + 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_presence(loc, msg) diff --git a/components/table_component.py b/components/table_component.py index 422b531..d6b8d83 100644 --- a/components/table_component.py +++ b/components/table_component.py @@ -1,8 +1,6 @@ from playwright.sync_api import Page, expect, Locator - -from components.base_component import BaseComponent - from tools.logger import get_logger +from components.base_component import BaseComponent logger = get_logger("TABLE") @@ -12,20 +10,31 @@ class TableComponent(BaseComponent): def __init__(self, page: Page): super().__init__(page) - + # Действия: + def get_row_locator(self, table_locator, row_index) -> Locator | None: + """Конструирует локатор для строки с заданным индексом.""" + table = self.get_locator(table_locator) + + rows = table.locator("//tbody/tr") + + if row_index in range(rows.count()): + return rows.nth(row_index) + else: + return None + def read(self, locator: str | Locator) -> list[list[str]]: """Читает данные из таблицы.""" table_data = [] - + table = self.get_locator(locator) - + # Чтение заголовка таблицы header_cells = table.locator("//thead/tr") header_cell_text = header_cells.nth(0).inner_text() header_data = header_cell_text.split('\n') table_data.append(header_data) - + # Чтение ячеек таблицы rows = table.locator("//tbody/tr") for i in range(rows.count()): @@ -36,33 +45,33 @@ class TableComponent(BaseComponent): cell_text = cells.nth(j).inner_text() row_data.append(cell_text) table_data.append(row_data) - + return table_data - + # Проверки: def check_first_row_visibility(self, locator: str | Locator) -> None: """Проверяет видимость первой строки таблицы.""" table = self.get_locator(locator) first_row = table.locator("//tbody/tr").first expect(first_row).to_be_visible(), "The first table row is not visible" - + def check_last_row_visibility(self, locator: str | Locator) -> None: """Проверяет видимость последней строки таблицы.""" table = self.get_locator(locator) last_row = table.locator("//tbody/tr").last expect(last_row).to_be_visible(), "The last table row is not visible" - + def check_row_highlighting(self, locator: str | Locator, row_index: int) -> None: """Проверяет подсветку строки при наведении.""" table = self.get_locator(locator) row = table.locator("//tbody/tr").nth(row_index) - + row.scroll_into_view_if_needed() hover_element = row.locator(".body-row-hover") initial_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor") - + row.hover() self.page.wait_for_timeout(300) - + new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor") - assert initial_color == new_color, "Color of row did not change when hovering the cursor" \ No newline at end of file + assert initial_color != new_color, "Color of row did not change when hovering the cursor" diff --git a/components/toolbar_component.py b/components/toolbar_component.py index 39c2890..a056aaa 100644 --- a/components/toolbar_component.py +++ b/components/toolbar_component.py @@ -1,8 +1,8 @@ from playwright.sync_api import Page, expect, Locator -from components.base_component import BaseComponent -from elements.tooltip_button_element import TooltipButton -from locators.toolbar_locators import ToolbarLocators from tools.logger import get_logger +from locators.toolbar_locators import ToolbarLocators +from elements.tooltip_button_element import TooltipButton +from components.base_component import BaseComponent logger = get_logger("TOOLBAR") @@ -105,7 +105,7 @@ class ToolbarComponent(BaseComponent): raise AssertionError(f"Unsupported button name {name}") return button.is_not_present(timeout=1000) # Ожидание 1 секунда - def check_presence(self, message: str) -> None: + def check_toolbar_presence(self, message: str) -> None: """Проверяет видимость тулбара. Args: @@ -141,4 +141,4 @@ class ToolbarComponent(BaseComponent): button = self.get_button_by_name(name) if button is None: raise AssertionError(f"Unsupported button name {name}") - button.check_tooltip_with_text(ToolbarLocators.TOOLTIP, tooltip) \ No newline at end of file + button.check_tooltip_with_text(ToolbarLocators.TOOLTIP, tooltip) diff --git a/conftest.py b/conftest.py index a6f9de4..7cbe222 100644 --- a/conftest.py +++ b/conftest.py @@ -1,8 +1,7 @@ -from dotenv import load_dotenv -import inspect from pathlib import Path -import pytest import os +import inspect +from dotenv import load_dotenv load_dotenv() @@ -14,17 +13,17 @@ 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]: @@ -33,26 +32,26 @@ def pytest_sessionfinish(session, exitstatus): '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') @@ -62,4 +61,4 @@ def pytest_addoption(parser): action="store_true", default=False, help="Генерировать Markdown документацию с группировкой по классам" - ) \ No newline at end of file + ) diff --git a/data/constants.py b/data/constants.py index 92f5ba2..bd81b39 100644 --- a/data/constants.py +++ b/data/constants.py @@ -1,6 +1,5 @@ import os - class Constants: """Класс для хранения констант и переменных окружения. @@ -16,4 +15,4 @@ class Constants: login = os.getenv('AUTH_LOGIN') password = os.getenv('AUTH_PASSWORD') except KeyError: - print("LOGIN OR PASSWORD WASN'T FOUND") \ No newline at end of file + print("LOGIN OR PASSWORD WASN'T FOUND") diff --git a/data/environment.py b/data/environment.py index 9abecef..7a735a7 100644 --- a/data/environment.py +++ b/data/environment.py @@ -1,6 +1,5 @@ import os -from typing import Dict, Optional - +from typing import Dict class Environment: """Класс для работы с окружением и URL-адресами.""" @@ -29,20 +28,20 @@ class Environment: 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_request_url(self) -> str: """Возвращает URL для API-запросов.""" if self.env in self.URLS: return self.URLS[self.env] raise Exception(f"Unknown value of ENV variable {self.env}") - + def set_access_token(self, token: str) -> None: """Устанавливает токен доступа.""" self.token = token - + def get_access_token(self) -> str: """Возвращает текущий токен доступа.""" return self.token -host: Environment = Environment() \ No newline at end of file +host: Environment = Environment() diff --git a/data/roles_dict.py b/data/roles_dict.py index 8fd8d4b..832bf3e 100644 --- a/data/roles_dict.py +++ b/data/roles_dict.py @@ -5,4 +5,4 @@ roles_dict = { "operator": "Оператор", "inform_secur_user": "Специалист информационной безопасности", "user": "Пользователь" -} \ No newline at end of file +} diff --git a/elements/base_element.py b/elements/base_element.py index 0bf25c1..8c5a907 100644 --- a/elements/base_element.py +++ b/elements/base_element.py @@ -1,11 +1,8 @@ from playwright.sync_api import Page, Locator, expect, TimeoutError -from typing import Optional - from tools.logger import get_logger logger = get_logger("BASE_ELEMENT") - class BaseElement: """Базовый класс для работы с элементами страницы.""" @@ -27,29 +24,29 @@ class BaseElement: # Действия: def click(self) -> None: - logger.info(f'Clicking {self.type_of} "{self.name}"') + 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}"') + logger.info(f"Get text for {self.type_of} '{self.name}'") return self.locator.nth(index).text_content() def wait_for_element(self, timeout: int = 12000) -> None: - logger.info(f'Wait for {self.type_of} "{self.name}"') + 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}"') + logger.info(f"Check that {self.type_of} '{self.name}' has text '{text}'") expect(self.locator).to_have_text(text), msg def check_presence(self, msg: str) -> None: - logger.info(f'Check that {self.type_of} "{self.name}" is present') + logger.info(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: - logger.info(f'Check that {self.type_of} "{self.name}" is present') + logger.info(f"Check that {self.type_of} '{self.name}' is present") try: self.locator.wait_for(timeout=timeout) except TimeoutError: @@ -57,9 +54,9 @@ class BaseElement: return True def is_not_present(self, timeout: int = 5000) -> bool: - logger.info(f'Check that {self.type_of} "{self.name}" is missing') + logger.info(f"Check that {self.type_of} '{self.name}' is missing") try: self.locator.wait_for(timeout=timeout) except TimeoutError: return True - return False \ No newline at end of file + return False diff --git a/elements/button_element.py b/elements/button_element.py index f5bad7c..39434a7 100644 --- a/elements/button_element.py +++ b/elements/button_element.py @@ -1,7 +1,5 @@ -from playwright.sync_api import expect - -from elements.base_element import BaseElement from tools.logger import get_logger +from elements.base_element import BaseElement logger = get_logger("BUTTON") @@ -25,4 +23,4 @@ class Button(BaseElement): # (Методы действий будут добавлены по мере необходимости) # Проверки: - # (Методы проверок будут добавлены по мере необходимости) \ No newline at end of file + # (Методы проверок будут добавлены по мере необходимости) diff --git a/elements/checkbox_element.py b/elements/checkbox_element.py index b11a7db..a1ad559 100644 --- a/elements/checkbox_element.py +++ b/elements/checkbox_element.py @@ -1,5 +1,5 @@ -from elements.base_element import BaseElement from tools.logger import get_logger +from elements.base_element import BaseElement logger = get_logger("CHECKBOX") @@ -22,12 +22,12 @@ class Checkbox(BaseElement): # Действия: def check(self) -> None: """Устанавливает чекбокс в отмеченное состояние.""" - logger.info(f'Checking checkbox "{self.name}"') + logger.info(f"Checking checkbox '{self.name}'") self.locator.check() def uncheck(self) -> None: """Снимает отметку с чекбокса.""" - logger.info(f'Unchecking checkbox "{self.name}"') + logger.info(f"Unchecking checkbox '{self.name}'") self.locator.uncheck() # Проверки: @@ -37,5 +37,5 @@ class Checkbox(BaseElement): Returns: True, если чекбокс отмечен, иначе False. """ - logger.info(f'Checking if checkbox "{self.name}" is checked') - return self.locator.is_checked() \ No newline at end of file + logger.info(f"Checking if checkbox '{self.name}' is checked") + return self.locator.is_checked() diff --git a/elements/dropdown_list_element.py b/elements/dropdown_list_element.py index 1680b58..4de02e3 100644 --- a/elements/dropdown_list_element.py +++ b/elements/dropdown_list_element.py @@ -1,10 +1,8 @@ -from playwright.sync_api import expect -from elements.base_element import BaseElement from tools.logger import get_logger +from elements.base_element import BaseElement logger = get_logger("DROPDOWN_LIST") - class DropdownList(BaseElement): """Класс для работы с выпадающими списками на странице. @@ -43,4 +41,4 @@ class DropdownList(BaseElement): logger.info(f'Checking item with text "{text}" in dropdown "{self.name}"') enabled = self.page.get_by_role("listitem").filter(has_text=text).is_enabled() if not enabled: - assert False, f"Dropdown list item '{text}' is missing or disabled" \ No newline at end of file + assert False, f"Dropdown list item '{text}' is missing or disabled" diff --git a/elements/text_element.py b/elements/text_element.py index 11b853b..99f0de9 100644 --- a/elements/text_element.py +++ b/elements/text_element.py @@ -1,9 +1,8 @@ -from elements.base_element import BaseElement from tools.logger import get_logger +from elements.base_element import BaseElement logger = get_logger("TEXT") - class Text(BaseElement): """Класс для работы с текстовыми элементами на странице. @@ -23,4 +22,4 @@ class Text(BaseElement): # (Методы действий будут добавлены по мере необходимости) # Проверки: - # (Методы проверок будут добавлены по мере необходимости) \ No newline at end of file + # (Методы проверок будут добавлены по мере необходимости) diff --git a/elements/text_input_element.py b/elements/text_input_element.py index 356c274..ea1ced7 100644 --- a/elements/text_input_element.py +++ b/elements/text_input_element.py @@ -1,6 +1,6 @@ from playwright.sync_api import expect -from elements.base_element import BaseElement from tools.logger import get_logger +from elements.base_element import BaseElement logger = get_logger("TEXT_INPUT") @@ -56,4 +56,4 @@ class TextInput(BaseElement): AssertionError: Если поле не пустое. """ logger.info(f'Checking that text input "{self.name}" is empty') - expect(self.locator).to_be_empty(), msg \ No newline at end of file + expect(self.locator).to_be_empty(), msg diff --git a/elements/tooltip_button_element.py b/elements/tooltip_button_element.py index d5eeb8a..45f148a 100644 --- a/elements/tooltip_button_element.py +++ b/elements/tooltip_button_element.py @@ -1,9 +1,8 @@ -from elements.base_element import BaseElement from tools.logger import get_logger +from elements.base_element import BaseElement logger = get_logger("TOOLTIP_BUTTON") - class TooltipButton(BaseElement): """Класс элемента кнопки с всплывающей подсказкой. @@ -31,13 +30,13 @@ class TooltipButton(BaseElement): """ # Наведение на элемент для отображения подсказки self.locator.hover() - + # Получение элемента подсказки tooltip = self.page.locator(tooltip_locator) - + # Проверка соответствия текста actual_text = tooltip.text_content().strip() assert actual_text == expected_text, ( f"Текст подсказки не соответствует ожидаемому. " f"Ожидалось: '{expected_text}', получено: '{actual_text}'" - ) \ No newline at end of file + ) diff --git a/fixtures/pages.py b/fixtures/pages.py index c51b5ba..af326ac 100644 --- a/fixtures/pages.py +++ b/fixtures/pages.py @@ -5,7 +5,6 @@ import pytest from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright, Playwright -import os from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureRequest @@ -24,20 +23,24 @@ def pytest_addoption(parser: Parser): --t: Таймаут по умолчанию (мс) --l: Локаль браузера """ - parser.addoption('--bn', action='store', default="chrome", + parser.addoption('--bn', action='store', default="chrome", help="Choose browser: chrome, remote_chrome or firefox") - parser.addoption('--h', action='store', default=False, + parser.addoption('--h', action='store', default=False, help='Choose headless: True or False') - parser.addoption('--s', action='store', default={'width': 1600, 'height': 900}, + 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}, help='Size window: width,height') - parser.addoption('--slow', action='store', default=200, + # 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}, + # help='Size window: width,height') + # 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, + parser.addoption('--t', action='store', default=60000, help='Choose timeout') - parser.addoption('--l', action='store', default='ru-RU', + 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') @@ -60,7 +63,7 @@ def browser(request: FixtureRequest) -> Page: Автоматически закрывает браузер и контексты после завершения тестов. """ playwright = sync_playwright().start() - + # Выбор браузера на основе параметра командной строки if request.config.getoption("bn") == 'remote_chrome': browser = get_remote_chrome(playwright, request) @@ -78,9 +81,9 @@ def browser(request: FixtureRequest) -> Page: browser = get_chrome_browser(playwright, request) context = get_context(browser, request, 'local') page_data = context.new_page() - + yield page_data - + # Очистка после завершения тестов for context in browser.contexts: context.close() @@ -177,8 +180,8 @@ def get_context(browser: Browser, request: FixtureRequest, start: str) -> Browse @pytest.fixture(scope="function") def return_back(browser: Page): """Фикстура для возврата на предыдущую страницу в браузере. - + Args: browser: Экземпляр страницы браузера. """ - browser.go_back() \ No newline at end of file + browser.go_back() diff --git a/locators/button_locators.py b/locators/button_locators.py index f11e243..f20d313 100644 --- a/locators/button_locators.py +++ b/locators/button_locators.py @@ -1,6 +1,4 @@ class ButtonLocators: BUTTON_LICENSE_UPDATE = "//div[@class='scrollarea__footer']//button" - - - - \ No newline at end of file + TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]" + BUTTON_DELETE_SESSION = "button.v-btn--icon svg[fill='#4caf50']" diff --git a/locators/confirm_locators.py b/locators/confirm_locators.py index 35db559..7f6783e 100644 --- a/locators/confirm_locators.py +++ b/locators/confirm_locators.py @@ -10,4 +10,4 @@ class ConfirmLocators: CONFIRM = "//div[contains(@class, 'v-dialog--active')]" TITLE = "//div[@class='v-card__title']/h3" BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']" - TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']" \ No newline at end of file + TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']" diff --git a/locators/event_panel_locators.py b/locators/event_panel_locators.py index bc583d8..4483d86 100644 --- a/locators/event_panel_locators.py +++ b/locators/event_panel_locators.py @@ -5,4 +5,4 @@ class EventPanelLocators: BUTTONS_BLOCK (str): XPath локатор блока кнопок в панели инструментов. Находится во втором блоке элементов toolbar'а внутри контентной области. """ - BUTTONS_BLOCK = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]" \ No newline at end of file + BUTTONS_BLOCK = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]" diff --git a/locators/input_locators.py b/locators/input_locators.py index e055929..658335c 100644 --- a/locators/input_locators.py +++ b/locators/input_locators.py @@ -9,4 +9,4 @@ class InputLocators: - Контейнер поля ввода (v-input__control) - Непосредственно текстовое поле (textarea) """ - LICENSE_ID_UPDATE = "//div[@class='scrollarea__footer']//div[@class='v-input__control']//textarea" \ No newline at end of file + LICENSE_ID_UPDATE = "//div[@class='scrollarea__footer']//div[@class='v-input__control']//textarea" diff --git a/locators/json_container_locators.py b/locators/json_container_locators.py index 07a3a70..e4630b8 100644 --- a/locators/json_container_locators.py +++ b/locators/json_container_locators.py @@ -8,4 +8,4 @@ class JsonContainerLocators: Ищет div с классом, содержащим 'scrollarea__body'. """ CONTAINER = "//div[contains(@class,'jv-container')]" - SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]" \ No newline at end of file + SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]" diff --git a/locators/modal_window_locators.py b/locators/modal_window_locators.py index 2321ce0..441da20 100644 --- a/locators/modal_window_locators.py +++ b/locators/modal_window_locators.py @@ -4,17 +4,17 @@ class ModalWindowLocators: Атрибуты: MODAL_WINDOW (str): XPath локатор активного модального окна. INPUT_FORM_USER_DATA (str): XPath локатор формы для ввода пользовательских данных. - TEXT_FIELD_INPUT_FORM_USER_DATA (str): Относительный XPath текстового поля ввода + TEXT_FIELD_INPUT_FORM_USER_DATA (str): Относительный XPath текстового поля ввода внутри формы пользовательских данных. - ROLES_FIELD_INPUT_FORM_USER_DATA (str): Относительный XPath поля выбора ролей + ROLES_FIELD_INPUT_FORM_USER_DATA (str): Относительный XPath поля выбора ролей внутри формы пользовательских данных. ROLES_MENU_INPUT_FORM_USER_DATA (str): XPath локатор активного меню выбора ролей. LABEL_INPUT_FORM_USER_DATA (str): XPath локатор метки поля ввода в форме. """ MODAL_WINDOW = "//div[contains(@class, 'v-dialog--active')]" - + INPUT_FORM_USER_DATA = "//form[@class='v-form']" TEXT_FIELD_INPUT_FORM_USER_DATA = "xpath=div[2]/div/div/div/div/input" ROLES_FIELD_INPUT_FORM_USER_DATA = "xpath=div[2]/div/div/div/div/div[1]" ROLES_MENU_INPUT_FORM_USER_DATA = "//div[contains(@class, 'menuable__content__active')]" - LABEL_INPUT_FORM_USER_DATA = "//label[contains(@class,'v-label')]/span" \ No newline at end of file + LABEL_INPUT_FORM_USER_DATA = "//label[contains(@class,'v-label')]/span" diff --git a/locators/navigation_panel_locators.py b/locators/navigation_panel_locators.py index f65f099..39956d2 100644 --- a/locators/navigation_panel_locators.py +++ b/locators/navigation_panel_locators.py @@ -14,6 +14,6 @@ class NavigationPanelLocators: """ PANEL_MAIN = "//ul[contains(@class, 'v-expansion-panel')]" PANEL_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body') and .//ul[contains(@class, 'v-expansion-panel')]]" - + NODE_ROOT = "//div[contains(@class,'v-treeview-node__root')]" - NODE_CHILDREN = "//div[contains(@class,'v-treeview-node__children')]" \ No newline at end of file + NODE_CHILDREN = "//div[contains(@class,'v-treeview-node__children')]" diff --git a/locators/table_locators.py b/locators/table_locators.py index c6dbeae..01c27c7 100644 --- a/locators/table_locators.py +++ b/locators/table_locators.py @@ -10,4 +10,4 @@ class TableLocators: содержащего таблицу с классом scrolltable__container. """ TABLE_WORK_AREA = "//div[@class='scrollarea__body']/div/div/div/table" - TABLE_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body') and .//table[@class='scrolltable__container']]//tbody" \ No newline at end of file + TABLE_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body') and .//table[@class='scrolltable__container']]//tbody" diff --git a/locators/text_locators.py b/locators/text_locators.py index 6818c0d..28a3bd9 100644 --- a/locators/text_locators.py +++ b/locators/text_locators.py @@ -8,4 +8,4 @@ class TextLocators: Ищет span с классами 'title' и 'text_select' (выделяемый текст). """ TITLE_LICENSE_INPUT_FORM = "//span[@class='title']" - LICENSE_ID = "//span[@class='title text_select']" \ No newline at end of file + LICENSE_ID = "//span[@class='title text_select']" diff --git a/locators/toolbar_locators.py b/locators/toolbar_locators.py index c4b915f..a16603d 100644 --- a/locators/toolbar_locators.py +++ b/locators/toolbar_locators.py @@ -12,4 +12,4 @@ class ToolbarLocators: - 'menuable__content__active' (показанное состояние) """ TITLE = "//nav//div[contains(@class, 'v-toolbar__title')]" - TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]" \ No newline at end of file + TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]" diff --git a/modal_windows/modal_add_user.py b/modal_windows/modal_add_user.py index a00a232..97c8b5d 100644 --- a/modal_windows/modal_add_user.py +++ b/modal_windows/modal_add_user.py @@ -1,14 +1,15 @@ -from playwright.sync_api import Page -from components.confirm_component import ConfirmComponent -from components.modal_window_component import ModalWindowComponent -from elements.checkbox_element import Checkbox -from elements.dropdown_list_element import DropdownList -from elements.text_element import Text -from elements.text_input_element import TextInput -from locators.modal_window_locators import ModalWindowLocators -from data.roles_dict import roles_dict import re +from playwright.sync_api import Page from tools.logger import get_logger +from locators.modal_window_locators import ModalWindowLocators +from elements.text_input_element import TextInput +from elements.text_element import Text +from elements.dropdown_list_element import DropdownList +from elements.checkbox_element import Checkbox +from data.roles_dict import roles_dict +from components.modal_window_component import ModalWindowComponent +from components.confirm_component import ConfirmComponent + logger = get_logger("ADD_USER_MODAL_WINDOW") @@ -29,87 +30,87 @@ class AddUserModalWindow(ModalWindowComponent): def __init__(self, page: Page): """Инициализация компонентов модального окна добавления пользователя.""" super().__init__(page) - + # Локаторы элементов формы text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA roles_field_locator = ModalWindowLocators.ROLES_FIELD_INPUT_FORM_USER_DATA input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA label_locator = ModalWindowLocators.LABEL_INPUT_FORM_USER_DATA roles_menu_locator = ModalWindowLocators.ROLES_MENU_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, - self.page.get_by_role("checkbox").nth(0), + page, + self.page.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), + page, + self.page.locator(label_locator).nth(0), "active_directory_checkbox_label" ) self.add_content_item("active_directory_checkbox_label", label_1) - - loc = self.page.locator(input_form_locator).locator("xpath=div[2]").locator(text_field_locator) - type_auth_input = TextInput(page, loc, "type_auth_input") - self.add_content_item("type_auth_input", type_auth_input) - - loc = self.page.locator(input_form_locator).locator("xpath=div[3]").locator(text_field_locator) + + loc = self.page.locator(input_form_locator).locator("xpath=div[2]").locator(text_field_locator) name_input = TextInput(page, loc, "name_input") self.add_content_item("name_input", name_input) - - role_loc = self.page.locator(input_form_locator).locator("xpath=div[4]").locator(roles_field_locator) + + role_loc = self.page.locator(input_form_locator).locator("xpath=div[3]").locator(roles_field_locator) role_input = TextInput(page, role_loc, "role_input") self.add_content_item("role_input", role_input) self.add_content_item( - "roles_list", + "roles_list", DropdownList(page, roles_menu_locator, "roles_list") ) + loc = self.page.locator(input_form_locator).locator("xpath=div[4]").locator(text_field_locator) + password_input = TextInput(page, loc, "password_input") + self.add_content_item("password_input", password_input) + loc = self.page.locator(input_form_locator).locator("xpath=div[5]").locator(text_field_locator) commentary_input = TextInput(page, loc, "commentary_input") self.add_content_item("commentary_input", commentary_input) - + loc = self.page.locator(input_form_locator).locator("xpath=div[6]").locator(text_field_locator) email_input = TextInput(page, loc, "email_input") self.add_content_item("email_input", email_input) - + loc = self.page.locator(input_form_locator).locator("xpath=div[7]").locator(text_field_locator) phone_input = TextInput(page, loc, "phone_input") self.add_content_item("phone_input", phone_input) - + checkbox_2 = Checkbox( - page, - page.get_by_role("checkbox").nth(1), + page, + page.get_by_role("checkbox").nth(1), "push_notification" ) self.add_content_item("push_notification_checkbox", checkbox_2) - + label_2 = Text( - page, - self.page.locator(label_locator).nth(1), + page, + self.page.locator(label_locator).nth(1), "push_notification_checkbox_label" ) self.add_content_item("push_notification_checkbox_label", label_2) - + # Добавление кнопок действий 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 new_user(self, user_data): @@ -118,9 +119,9 @@ class AddUserModalWindow(ModalWindowComponent): Args: user_data (dict): Словарь с данными пользователя. Может содержать ключи: - active_directory_checked (bool): Состояние чекбокса Active Directory - - type_auth (str): Тип авторизации - name (str): Имя пользователя - role (str): Роль пользователя + - password (str): Пароль пользователя - commentary (str): Комментарий - email (str): Email - phone_number (str): Номер телефона @@ -130,57 +131,57 @@ class AddUserModalWindow(ModalWindowComponent): AssertionError: Если подтверждающее окно не отображается """ fields = user_data.keys() - + if "active_directory_checked" in fields: checkbox = self.get_content_item("active_directory_checkbox") if user_data["active_directory_checked"]: checkbox.check() else: checkbox.uncheck() - - if "type_auth" in fields: - input_field = self.get_content_item("type_auth_input") - input_field.input_value(user_data["type_auth"]) - + 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 "commentary" in fields: + + if "password" in fields: + input_field = self.get_content_item("password_input") + input_field.input_value(user_data["password"]) + + if "commentary" in fields: input_field = self.get_content_item("commentary_input") - input_field.input_value(user_data["commentary"]) - - if "email" in fields: + input_field.input_value(user_data["commentary"]) + + if "email" in fields: input_field = self.get_content_item("email_input") - input_field.input_value(user_data["email"]) - - if "phone_number" in fields: + input_field.input_value(user_data["email"]) + + if "phone_number" in fields: input_field = self.get_content_item("phone_input") input_field.input_value(user_data["phone_number"]) - + if "push_notification_checked" in fields: checkbox = self.get_content_item("push_notification_checkbox") if user_data["push_notification_checked"]: checkbox.check() else: checkbox.uncheck() - + # Отправка формы add_button = self.get_button_by_name("add") add_button.click() - + # Подтверждение действия title = "Добавить нового пользователя" self.new_user_confirm.check_title( - title, + title, f"Confirmation dialog window with title '{title}' is missing" ) self.new_user_confirm.click_allow_button() @@ -189,41 +190,44 @@ class AddUserModalWindow(ModalWindowComponent): """Закрывает модальное окно с помощью кнопки 'Закрыть'.""" close_button = self.get_button_by_name("close") close_button.click() - + def close_window_by_toolbar_button(self): """Закрывает модальное окно с помощью кнопки закрытия в тулбаре.""" self.click_toolbar_close_button() - + def check_content(self): """Проверяет наличие и корректность всех элементов модального окна. - + Raises: AssertionError: Если какой-либо элемент отсутствует или содержит некорректные данные """ self.check_by_window_title() - + self.check_toolbar_button_presence("close") self.check_toolbar_button_tooltip("close", "Закрыть") - + 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", + "Active Directory", "Label 'Active Directory' is missing" ) elif name == "push_notification_checkbox_label": item.check_have_text( - "Подписка на Push-уведомления", + "Подписка на Push-уведомления", "Label 'Подписка на Push-уведомления' is missing" ) elif name == "role_input": item.click() roles_list = self.get_content_item("roles_list") roles_list.check_presence("Roles list is missing") - + for role in roles_dict.values(): + # временно, пока есть несоответствие со списком ролей в вкладке Сессии + if role == "Пользователь": + continue roles_list.check_item_with_text(role) elif name == "roles_list": continue @@ -231,6 +235,6 @@ class AddUserModalWindow(ModalWindowComponent): item.check_presence( f"Modal window content item with name '{name}' is missing" ) - + self.check_button_presence("add") - self.check_button_presence("close") \ No newline at end of file + self.check_button_presence("close") diff --git a/modal_windows/modal_edit_user.py b/modal_windows/modal_edit_user.py index 431275b..07ab645 100644 --- a/modal_windows/modal_edit_user.py +++ b/modal_windows/modal_edit_user.py @@ -1,13 +1,13 @@ -from playwright.sync_api import Page -from components.confirm_component import ConfirmComponent -from components.modal_window_component import ModalWindowComponent -from elements.checkbox_element import Checkbox -from elements.dropdown_list_element import DropdownList -from elements.text_element import Text -from elements.text_input_element import TextInput -from locators.modal_window_locators import ModalWindowLocators import re +from playwright.sync_api import Page from tools.logger import get_logger +from locators.modal_window_locators import ModalWindowLocators +from elements.text_input_element import TextInput +from elements.text_element import Text +from elements.dropdown_list_element import DropdownList +from elements.checkbox_element import Checkbox +from components.modal_window_component import ModalWindowComponent +from components.confirm_component import ConfirmComponent logger = get_logger("EDIT_USER_MODAL_WINDOW") @@ -29,80 +29,76 @@ class EditUserModalWindow(ModalWindowComponent): def __init__(self, page: Page, user_name: str): """Инициализация компонентов модального окна редактирования пользователя.""" super().__init__(page) - + # Локаторы элементов формы text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA roles_field_locator = ModalWindowLocators.ROLES_FIELD_INPUT_FORM_USER_DATA input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA label_locator = ModalWindowLocators.LABEL_INPUT_FORM_USER_DATA roles_menu_locator = ModalWindowLocators.ROLES_MENU_INPUT_FORM_USER_DATA - + # Настройка заголовка и кнопки закрытия self.window_title = user_name 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") - + # Добавление полей формы - loc = self.page.locator(input_form_locator).locator("xpath=div[1]").locator(text_field_locator) - type_auth_input = TextInput(page, loc, "type_auth_input") - self.add_content_item("type_auth_input", type_auth_input) - - loc = self.page.locator(input_form_locator).locator("xpath=div[2]").locator(text_field_locator) + loc = self.page.locator(input_form_locator).locator("xpath=div[1]").locator(text_field_locator) name_input = TextInput(page, loc, "name_input") self.add_content_item("name_input", name_input) - - role_loc = self.page.locator(input_form_locator).locator("xpath=div[3]").locator(roles_field_locator) + + role_loc = self.page.locator(input_form_locator).locator("xpath=div[2]").locator(roles_field_locator) role_input = TextInput(page, role_loc, "role_input") self.add_content_item("role_input", role_input) self.add_content_item( - "roles_list", + "roles_list", DropdownList(page, roles_menu_locator, "roles_list") ) - - loc = self.page.locator(input_form_locator).locator("xpath=div[4]").locator(text_field_locator) + + loc = self.page.locator(input_form_locator).locator("xpath=div[3]").locator(text_field_locator) commentary_input = TextInput(page, loc, "commentary_input") self.add_content_item("commentary_input", commentary_input) - - loc = self.page.locator(input_form_locator).locator("xpath=div[5]").locator(text_field_locator) + + loc = self.page.locator(input_form_locator).locator("xpath=div[4]").locator(text_field_locator) email_input = TextInput(page, loc, "email_input") self.add_content_item("email_input", email_input) - - loc = self.page.locator(input_form_locator).locator("xpath=div[6]").locator(text_field_locator) + + loc = self.page.locator(input_form_locator).locator("xpath=div[5]").locator(text_field_locator) phone_input = TextInput(page, loc, "phone_input") self.add_content_item("phone_input", phone_input) - + # Добавление чекбоксов и их меток checkbox_2 = Checkbox( - page, - page.get_by_role("checkbox").nth(0), + page, + page.get_by_role("checkbox").nth(0), "push_notification" ) self.add_content_item("push_notification_checkbox", checkbox_2) - + label_2 = Text( - page, - self.page.locator(label_locator).nth(0), + page, + self.page.locator(label_locator).nth(0), "push_notification_checkbox_label" ) self.add_content_item("push_notification_checkbox_label", label_2) - + # Добавление кнопок действий locator_button_save = self.page.get_by_role("button", name="Сохранить") self.add_button(locator_button_save, "save") - + 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") - + locator_button_close = self.page.get_by_role("button", name="Закрыть") self.add_button(locator_button_close, "close") - + # Инициализация компонентов подтверждения self.save_user_confirm = ConfirmComponent(page, " Отмена ", " Сохранить ") self.delete_user_confirm = ConfirmComponent(page, " Отмена ", " Удалить ") @@ -111,33 +107,32 @@ class EditUserModalWindow(ModalWindowComponent): """Закрывает модальное окно с помощью кнопки 'Закрыть'.""" close_button = self.get_button_by_name("close") close_button.click() - + def close_window_by_toolbar_button(self): """Закрывает модальное окно с помощью кнопки закрытия в тулбаре.""" self.click_toolbar_close_button() - + def delete_user(self): """Удаляет пользователя с подтверждением действия. - + Raises: AssertionError: Если окно подтверждения не отображается """ delete_button = self.get_button_by_name("delete") delete_button.click() - + title = "Удаление" self.delete_user_confirm.check_title( - title, + title, f"Confirmation dialog window with title '{title}' is missing" ) self.delete_user_confirm.click_allow_button() - + def edit_user(self, user_data): """Редактирует данные пользователя. - + Args: user_data (dict): Словарь с обновляемыми данными пользователя. Может содержать: - - type_auth (str): Тип авторизации - name (str): Имя пользователя - role (str): Роль пользователя - commentary (str): Комментарий @@ -146,48 +141,44 @@ class EditUserModalWindow(ModalWindowComponent): - push_notification_checked (bool): Состояние чекбокса уведомлений """ fields = user_data.keys() - - if "type_auth" in fields: - input_field = self.get_content_item("type_auth_input") - input_field.input_value(user_data["type_auth"]) - + 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 "commentary" in fields: + + if "commentary" in fields: input_field = self.get_content_item("commentary_input") - input_field.input_value(user_data["commentary"]) - - if "email" in fields: + input_field.input_value(user_data["commentary"]) + + if "email" in fields: input_field = self.get_content_item("email_input") - input_field.input_value(user_data["email"]) - - if "phone_number" in fields: + input_field.input_value(user_data["email"]) + + if "phone_number" in fields: input_field = self.get_content_item("phone_input") input_field.input_value(user_data["phone_number"]) - + if "push_notification_checked" in fields: checkbox = self.get_content_item("push_notification_checkbox") if user_data["push_notification_checked"]: checkbox.check() else: checkbox.uncheck() - + save_button = self.get_button_by_name("save") save_button.click() - + title = "Сохранение" self.save_user_confirm.check_title( - title, + title, f"Confirmation dialog window with title '{title}' is missing" ) self.save_user_confirm.click_allow_button() @@ -196,27 +187,27 @@ class EditUserModalWindow(ModalWindowComponent): """Инициирует сброс пароля пользователя.""" 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): Ожидаемая роль пользователя - + Raises: AssertionError: Если какой-либо элемент отсутствует или содержит некорректные данные """ self.check_by_window_title() self.check_toolbar_button_presence("close") self.check_toolbar_button_tooltip("close", "Закрыть") - + for name in self.content_items.keys(): item = self.get_content_item(name) - + if name == "push_notification_checkbox_label": item.check_have_text( - "Подписка на Push-уведомления", + "Подписка на Push-уведомления", "Label 'Подписка на Push-уведомления' is missing" ) elif name == "name_input": @@ -236,8 +227,8 @@ class EditUserModalWindow(ModalWindowComponent): item.check_presence( f"Modal window content item with name '{name}' is missing" ) - + self.check_button_presence("save") self.check_button_presence("delete") self.check_button_presence("reset_password") - self.check_button_presence("close") \ No newline at end of file + self.check_button_presence("close") diff --git a/pages/base_page.py b/pages/base_page.py index 6b8c701..f64b8c1 100644 --- a/pages/base_page.py +++ b/pages/base_page.py @@ -1,10 +1,10 @@ """Базовый класс страницы для работы с Playwright.""" -from playwright.sync_api import Page, Response, APIRequestContext, expect -from typing import Any, Dict, List, Optional -from data.environment import host -from tools.logger import get_logger import json +from typing import Any, Dict, List, Optional +from playwright.sync_api import Page, Response, APIRequestContext, expect +from tools.logger import get_logger +from data.environment import host logger = get_logger("BASE_PAGE") @@ -82,4 +82,4 @@ class BasePage: return False return True - assert compare_lists(actual, expected), msg \ No newline at end of file + assert compare_lists(actual, expected), msg diff --git a/pages/license_tab.py b/pages/license_tab.py index bf77836..0d21a06 100644 --- a/pages/license_tab.py +++ b/pages/license_tab.py @@ -1,20 +1,19 @@ -from pages.base_page import BasePage -from components.alert_component import AlertComponent -from elements.button_element import Button -from components.json_container_component import JsonContainerComponent -from elements.text_element import Text -from elements.text_input_element import TextInput -from components.toolbar_component import ToolbarComponent -from locators.button_locators import ButtonLocators -from locators.json_container_locators import JsonContainerLocators -from locators.input_locators import InputLocators -from locators.text_locators import TextLocators from playwright.sync_api import Page - +from locators.text_locators import TextLocators +from locators.input_locators import InputLocators +from locators.json_container_locators import JsonContainerLocators +from locators.button_locators import ButtonLocators +from elements.text_input_element import TextInput +from elements.text_element import Text +from elements.button_element import Button +from components.toolbar_component import ToolbarComponent +from components.json_container_component import JsonContainerComponent +from components.alert_component import AlertComponent +from pages.base_page import BasePage class LicenseTab(BasePage): """Класс для работы с вкладкой 'Лицензии'. - + Атрибуты: page (Page): Экземпляр страницы Playwright. toolbar (ToolbarComponent): Компонент панели инструментов. @@ -28,15 +27,15 @@ class LicenseTab(BasePage): def __init__(self, page: Page) -> None: """Инициализирует элементы вкладки 'Лицензии'. - + Args: page: Экземпляр страницы Playwright. """ super().__init__(page) - + self.toolbar = ToolbarComponent(page, "Лицензии") self.json_container = JsonContainerComponent(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") @@ -47,34 +46,34 @@ class LicenseTab(BasePage): # Действия: 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-контейнер вниз.""" loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first self.json_container.scroll_down(loc) - - # Проверки: + + # Проверки: def check_json_container_verticall_scrolling(self) -> bool: """Проверяет возможность вертикальной прокрутки JSON-контейнера. - + Returns: bool: True если контейнер можно прокручивать, иначе False. """ loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first - return self.json_container.is_scrollable_vertically(loc) - + return self.json_container.is_scrollable_vertically(loc) + def check_content(self) -> None: """Проверяет наличие всех основных элементов на вкладке.""" self.should_be_toolbar() @@ -82,50 +81,50 @@ class LicenseTab(BasePage): self.should_be_input_form_title() self.should_be_empty_input_form() self.should_be_update_button() - + def should_be_error_alert_window_with_text(self, text: str) -> None: """Проверяет наличие и отсутствие алерта с указанным текстом. - + Args: text: Текст для проверки в алерте. """ - self.error_alert.check_presence(text) - self.error_alert.check_absence(text) + self.error_alert.check_alert_presence(text) + self.error_alert.check_alert_absence(text) def should_be_toolbar(self) -> None: """Проверяет наличие панели инструментов.""" - self.toolbar.check_presence("Toolbar is missing") - + self.toolbar.check_toolbar_presence("Toolbar is missing") + def should_be_json_container(self) -> None: """Проверяет наличие JSON-контейнера с информацией о лицензии.""" self.json_container.check_presence( 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") response_body = self.get_response_body(response) - + self.check_equals( 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 = "Обновить лицензию" @@ -133,7 +132,7 @@ class LicenseTab(BasePage): button_text, f"Update button with text '{button_text}' is missing" ) - + def verify_json_container_content(self) -> None: """Проверяет соответствие содержимого JSON-контейнера данным из API.""" actual_data = self.json_container.read_data(JsonContainerLocators.CONTAINER) @@ -141,13 +140,13 @@ class LicenseTab(BasePage): # send request to backend to get license info response = self.send_get_api_request("e-cmdb/api/lic") response_body = self.get_response_body(response) - + ## temporarily del response_body["netManagment"] response_body["ui"].pop("lcc") - + self.json_container.check_json_equals( actual_data, response_body, "Expected json content is not equal actual:" - ) \ No newline at end of file + ) diff --git a/pages/login_page.py b/pages/login_page.py index 6b93b3d..536a770 100644 --- a/pages/login_page.py +++ b/pages/login_page.py @@ -1,17 +1,14 @@ from playwright.sync_api import Page - -from elements.button_element import Button from elements.text_input_element import TextInput +from elements.button_element import Button +from data.environment import host +from data.constants import Constants from components.alert_component import AlertComponent from pages.base_page import BasePage -from data.constants import Constants -from data.environment import host - - class LoginPage(BasePage): """Класс для работы со страницей авторизации. - + Атрибуты: page (Page): Экземпляр страницы Playwright. login_input (TextInput): Поле ввода логина. @@ -22,28 +19,28 @@ class LoginPage(BasePage): def __init__(self, page: Page) -> None: """Инициализирует элементы страницы авторизации. - + Args: page: Экземпляр страницы Playwright. """ super().__init__(page) - + self.login_input = TextInput(page, page.get_by_label("Имя пользователя"), "login input") self.password_input = TextInput(page, page.get_by_label("Пароль"), "password input") self.login_button = Button(page, page.get_by_role("button"), "login button") - + self.error_alert = AlertComponent(page, "error") - + def do_login(self, username: str = None, password: str = None) -> None: """Выполняет вход в систему. - + Если username/password не указаны, использует значения из Constants. Обрабатывает ответ сервера для получения токена доступа. - + Args: username: Логин пользователя. Если None, используется значение из Constants. password: Пароль пользователя. Если None, используется значение из Constants. - + Raises: AssertionError: Если после входа открылась неожиданная страница. """ @@ -53,44 +50,44 @@ class LoginPage(BasePage): if response_body: token = response_body.get("access_token") host.set_access_token(token) - + 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.open("") - + self.login_input.clear() self.login_input.input_value(username) - + self.password_input.clear() self.password_input.input_value(password) - + self.login_button.click() - - self.error_alert.check_presence("Неверная пара логин/пароль") - self.error_alert.check_absence("Неверная пара логин/пароль") \ No newline at end of file + + self.error_alert.check_alert_presence("Неверная пара логин/пароль") + self.error_alert.check_alert_absence("Неверная пара логин/пароль") diff --git a/pages/main_page.py b/pages/main_page.py index 47e312a..ec5ae82 100644 --- a/pages/main_page.py +++ b/pages/main_page.py @@ -1,15 +1,14 @@ -from pages.base_page import BasePage -from elements.button_element import Button -from components.card_component import CardComponent -from components.navbar_component import NavigationPanelComponent +from playwright.sync_api import Page from locators.navigation_panel_locators import NavigationPanelLocators from locators.event_panel_locators import EventPanelLocators -from playwright.sync_api import Page - +from elements.button_element import Button +from components.navbar_component import NavigationPanelComponent +from components.card_component import CardComponent +from pages.base_page import BasePage class MainPage(BasePage): """Класс для работы с главной страницей приложения. - + Атрибуты: page (Page): Экземпляр страницы Playwright. navigation_panel (NavigationPanelComponent): Компонент панели навигации. @@ -19,63 +18,63 @@ class MainPage(BasePage): def __init__(self, page: Page) -> None: """Инициализирует элементы главной страницы. - + Args: page: Экземпляр страницы Playwright. """ super().__init__(page) - + self.navigation_panel = NavigationPanelComponent(page) - + locators = self.page.locator(EventPanelLocators.BUTTONS_BLOCK).get_by_role("button").all() self.user_button = Button(page, locators[0], "search_button") self.user_button = Button(page, locators[1], "user_button") - + self.user_card = CardComponent(page) # Действия: 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_configuration_navigation_panel_item(self, item_name: str) -> None: """Кликает по элементу подраздела 'Конфигурация' в панели навигации. - + Args: item_name: Название элемента для клика. """ self.navigation_panel.click_sub_item(NavigationPanelLocators.PANEL_MAIN, 1, item_name) - + def click_maintenance_navigation_panel_item(self, item_name: str) -> None: """Кликает по элементу подраздела 'Обслуживание' в панели навигации. - + Args: item_name: Название элемента для клика. """ self.navigation_panel.click_sub_item(NavigationPanelLocators.PANEL_MAIN, 2, item_name) - + def click_user_button(self) -> None: """Кликает по кнопке пользователя.""" self.user_button.click() - + def do_logout(self) -> None: """Выполняет выход из системы.""" self.should_be_user_button() self.click_user_button() self.user_card.click_logout_button() - + def scroll_navigation_panel_up(self) -> None: """Прокручивает панель навигации вверх.""" self.navigation_panel.scroll_up(NavigationPanelLocators.PANEL_SCROLL_CONTAINER) - + def scroll_navigation_panel_down(self) -> None: """Прокручивает панель навигации вниз.""" self.navigation_panel.scroll_down(NavigationPanelLocators.PANEL_SCROLL_CONTAINER) - + # Проверки: def should_be_navigation_panel(self) -> None: """Проверяет наличие панели навигации.""" @@ -83,28 +82,28 @@ class MainPage(BasePage): NavigationPanelLocators.PANEL_MAIN, "Navigation panel is missing" ) - + def should_be_user_button(self) -> None: """Проверяет наличие кнопки пользователя.""" self.user_button.check_presence("User button is missing on event panel") - + def check_navigation_panel_verticall_scrolling(self) -> bool: """Проверяет возможность вертикальной прокрутки панели навигации. - + Returns: bool: True если панель можно прокручивать, иначе False. """ return self.navigation_panel.is_scrollable_vertically( NavigationPanelLocators.PANEL_SCROLL_CONTAINER ) - + def check_navigation_panel_item_visibility(self, item_name: str) -> None: """Проверяет видимость элемента в панели навигации. - + Args: item_name: Название элемента для проверки. """ self.navigation_panel.check_item_visibility( NavigationPanelLocators.PANEL_MAIN, item_name - ) \ No newline at end of file + ) diff --git a/pages/service_status_tab.py b/pages/service_status_tab.py index 402e982..08650dc 100644 --- a/pages/service_status_tab.py +++ b/pages/service_status_tab.py @@ -1,8 +1,8 @@ -from pages.base_page import BasePage +from playwright.sync_api import Page +from locators.table_locators import TableLocators from components.toolbar_component import ToolbarComponent from components.table_component import TableComponent -from locators.table_locators import TableLocators -from playwright.sync_api import Page +from pages.base_page import BasePage class ServiceStatusTab(BasePage): @@ -17,126 +17,126 @@ class ServiceStatusTab(BasePage): def __init__(self, page: Page) -> None: """Инициализация компонентов вкладки 'Статус обслуживания'.""" super().__init__(page) - + self.toolbar = ToolbarComponent(page, "Статус обслуживания") self.services_table = TableComponent(page) - + def get_rows_count(self) -> int: """Возвращает количество строк в таблице сервисов (без учёта заголовка). Returns: int: Количество строк с данными. - + Raises: AssertionError: Если таблица пуста. """ table_content = self.services_table.read(TableLocators.TABLE_WORK_AREA) rows_count = len(table_content) - + if rows_count == 0: assert False, "The contents of the table are missing" - + return rows_count - 1 - + 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: AssertionError: Если таблица пуста или заголовки не соответствуют ожидаемым. """ expected_headers = [ - 'Контейнер', - 'Время создания', - 'Статус', - 'Время работы', - 'Image ID', + 'Контейнер', + 'Время создания', + 'Статус', + 'Время работы', + 'Image ID', 'Image ТЭГ' ] - + table_content = self.services_table.read(TableLocators.TABLE_WORK_AREA) - + if len(table_content) == 0: assert False, "The contents of the table are missing" - + actual_headers = table_content[0] - + self.check_equals( - actual_headers, + actual_headers, expected_headers, f"Expected table headers {expected_headers} are not equal {actual_headers}" ) - + if len(table_content) == 1: assert False, "Table body is missing" - + def check_services_table_verticall_scrolling(self) -> bool: """Проверяет возможность вертикальной прокрутки таблицы. - + Returns: bool: True если прокрутка возможна, иначе False. """ return self.services_table.is_scrollable_vertically( TableLocators.TABLE_SCROLL_CONTAINER - ) - + ) + def check_services_table_first_row_visibility(self) -> None: """Проверяет видимость первой строки таблицы. - + Raises: AssertionError: Если первая строка не видна. """ self.services_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA) - + def check_services_table_last_row_visibility(self) -> None: """Проверяет видимость последней строки таблицы. - + Raises: AssertionError: Если последняя строка не видна. """ self.services_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA) - + def check_services_table_row_highlighting(self, row_index: int) -> None: """Проверяет выделение указанной строки таблицы. - + Args: row_index (int): Индекс проверяемой строки. - + Raises: AssertionError: Если строка не выделена. """ self.services_table.check_row_highlighting( - TableLocators.TABLE_WORK_AREA, + TableLocators.TABLE_WORK_AREA, row_index ) - + def should_be_toolbar(self) -> None: """Проверяет наличие тулбара на вкладке. - + Raises: AssertionError: Если тулбар отсутствует. """ - self.toolbar.check_presence("Toolbar is missing") - + self.toolbar.check_toolbar_presence("Toolbar is missing") + def should_be_services_table(self) -> None: """Проверяет наличие таблицы сервисов. - + Raises: AssertionError: Если таблица отсутствует. """ self.services_table.check_presence( - TableLocators.TABLE_WORK_AREA, + TableLocators.TABLE_WORK_AREA, "Service statuses table is missing" - ) \ No newline at end of file + ) diff --git a/pages/session_tab.py b/pages/session_tab.py index 8fc878b..c10e9e2 100644 --- a/pages/session_tab.py +++ b/pages/session_tab.py @@ -1,12 +1,11 @@ -from pages.base_page import BasePage +from playwright.sync_api import Page, Locator +from locators.table_locators import TableLocators +from locators.button_locators import ButtonLocators from elements.tooltip_button_element import TooltipButton +from data.roles_dict import roles_dict from components.toolbar_component import ToolbarComponent from components.table_component import TableComponent -from locators.button_locators import ButtonLocators -from locators.table_locators import TableLocators -from playwright.sync_api import Page, Locator -from data.roles_dict import roles_dict - +from pages.base_page import BasePage class SessionsTab(BasePage): """Класс для работы с вкладкой 'Сессия'. @@ -20,7 +19,7 @@ class SessionsTab(BasePage): def __init__(self, page: Page) -> None: """Инициализация компонентов вкладки 'Сессия'.""" super().__init__(page) - + self.toolbar = ToolbarComponent(page, "Сессия") self.sessions_table = TableComponent(page) @@ -29,16 +28,16 @@ class SessionsTab(BasePage): Returns: int: Количество строк с данными. - + Raises: AssertionError: Если таблица пуста. """ table_content = self.sessions_table.read(TableLocators.TABLE_WORK_AREA) rows_count = len(table_content) - + if rows_count == 0: assert False, "The contents of the table are missing" - + return rows_count - 1 def get_delete_session_button_from_row(self, row_index: int) -> TooltipButton: @@ -54,11 +53,11 @@ class SessionsTab(BasePage): AssertionError: Если строка не найдена. """ row_locator = self.sessions_table.get_row_locator( - TableLocators.TABLE_WORK_AREA, + 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") @@ -80,33 +79,33 @@ class SessionsTab(BasePage): AssertionError: Если таблица пуста или заголовки не соответствуют. """ expected_headers = [ - 'ID сессии', - 'ID пользователя', - 'Время жизни', - 'Роль', + 'ID сессии', + 'ID пользователя', + 'Время жизни', + 'Роль', 'Адрес' ] - + table_content = self.sessions_table.read(TableLocators.TABLE_WORK_AREA) len_table_content = len(table_content) - + if len_table_content == 0: assert False, "The contents of the table are missing" - + 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: assert False, "Table body is missing" - + 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, "Удалить") @@ -146,7 +145,7 @@ class SessionsTab(BasePage): AssertionError: Если строка не выделена. """ self.sessions_table.check_row_highlighting( - TableLocators.TABLE_WORK_AREA, + TableLocators.TABLE_WORK_AREA, row_index ) @@ -156,7 +155,7 @@ class SessionsTab(BasePage): Raises: AssertionError: Если тулбар отсутствует. """ - self.toolbar.check_presence("Toolbar is missing") + self.toolbar.check_toolbar_presence("Toolbar is missing") def should_be_sessions_table(self) -> None: """Проверяет наличие таблицы сессий. @@ -165,13 +164,13 @@ class SessionsTab(BasePage): AssertionError: Если таблица отсутствует. """ self.sessions_table.check_presence( - TableLocators.TABLE_WORK_AREA, + TableLocators.TABLE_WORK_AREA, "Sessions table is missing" ) def should_be_delete_button_on_sessions_table_row( - self, - row_index: int, + self, + row_index: int, tooltip: str ) -> None: """Проверяет наличие кнопки удаления в строке таблицы. @@ -199,19 +198,19 @@ class SessionsTab(BasePage): 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(): @@ -219,13 +218,13 @@ class SessionsTab(BasePage): 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" - ) \ No newline at end of file + ) diff --git a/pages/users_tab.py b/pages/users_tab.py index 05c07e9..7d688da 100644 --- a/pages/users_tab.py +++ b/pages/users_tab.py @@ -1,14 +1,13 @@ -from pages.base_page import BasePage -from components.alert_component import AlertComponent -from components.toolbar_component import ToolbarComponent -from components.table_component import TableComponent -from modal_windows.modal_add_user import AddUserModalWindow +import re +from playwright.sync_api import Page from modal_windows.modal_edit_user import EditUserModalWindow +from modal_windows.modal_add_user import AddUserModalWindow from locators.table_locators import TableLocators from data.roles_dict import roles_dict -from playwright.sync_api import Page -import re - +from components.toolbar_component import ToolbarComponent +from components.table_component import TableComponent +from components.alert_component import AlertComponent +from pages.base_page import BasePage class UsersTab(BasePage): """Класс для работы с вкладкой 'Пользователи'. @@ -30,12 +29,12 @@ class UsersTab(BasePage): locator_button_2 = self.page.get_by_role("navigation").filter( has_text=re.compile("Пользователи") ).get_by_role("button").nth(1) - + self.toolbar = ToolbarComponent(page, "Пользователи") self.toolbar.add_button(locator_button_1, "edit") self.toolbar.add_button(locator_button_1, "add_user") self.toolbar.add_button(locator_button_2, "close") - + self.users_table = TableComponent(page) self.modal_windows = {} self.success_alert = AlertComponent(page, "success") @@ -51,9 +50,9 @@ class UsersTab(BasePage): AssertionError: Если указан неподдерживаемый тип окна. """ if window_type == "add_user": - self.modal_windows["add_user"] = AddUserModalWindow(self.page) + self.modal_windows["add_user"] = AddUserModalWindow(self.page) elif window_type == "edit_user": - self.modal_windows[title] = EditUserModalWindow(self.page, title) + self.modal_windows[title] = EditUserModalWindow(self.page, title) else: assert False, "Unsupported modal window type" @@ -141,8 +140,8 @@ class UsersTab(BasePage): AssertionError: Если не отображается сообщение об успешном добавлении. """ self.get_modal_window("add_user").new_user(user_data) - self.success_alert.check_presence(' Новый пользователь \n успешно добавлен! ') - self.success_alert.check_absence(' Новый пользователь \n успешно добавлен! ') + self.success_alert.check_alert_presence(' Новый пользователь \n успешно добавлен! ') + self.success_alert.check_alert_absence(' Новый пользователь \n успешно добавлен! ') def delete_user(self, user_name: str) -> None: """Удаляет пользователя. @@ -154,8 +153,8 @@ class UsersTab(BasePage): AssertionError: Если не отображается сообщение об успешном удалении. """ self.get_modal_window(user_name).delete_user() - self.success_alert.check_presence('\nПользователь удалён\n') - self.success_alert.check_absence('\nПользователь удалён\n') + self.success_alert.check_alert_presence('\nПользователь удалён\n') + self.success_alert.check_alert_absence('\nПользователь удалён\n') def edit_user(self, user_name: str, user_data: dict) -> None: """Редактирует данные пользователя. @@ -168,8 +167,8 @@ class UsersTab(BasePage): AssertionError: Если не отображается сообщение об успешном обновлении. """ self.get_modal_window(user_name).edit_user(user_data) - self.success_alert.check_presence('\nОбновление успешно\n') - self.success_alert.check_absence('\nОбновление успешно\n') + self.success_alert.check_alert_presence('\nОбновление успешно\n') + self.success_alert.check_alert_absence('\nОбновление успешно\n') def reset_password(self, user_name: str) -> str: """Сбрасывает пароль пользователя. @@ -182,12 +181,12 @@ class UsersTab(BasePage): """ new_password = "" self.get_modal_window(user_name).reset_password() - - self.success_alert.check_presence("") + + self.success_alert.check_alert_presence("") alert_message = self.success_alert.get_text() if len(alert_message) > 0: new_password = re.findall(r'[\d]+', alert_message)[0] - + return new_password def find_user_in_table(self, name: str, role: str) -> int: @@ -206,9 +205,9 @@ class UsersTab(BasePage): table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA) if len(table_content) == 0: assert False, "The contents of the table are missing" - + del table_content[0] # Удаляем заголовок - + for row_index, user_info in enumerate(table_content): if name in user_info and role in user_info: return row_index @@ -216,14 +215,14 @@ class UsersTab(BasePage): def open_add_user_window(self) -> None: """Открывает окно добавления пользователя. - + Raises: AssertionError: Если кнопки недоступны или окно не открылось. """ if self.toolbar.is_button_not_present("close"): self.toolbar.check_button_presence("edit") self.toolbar.click_button("edit") - + self.toolbar.check_button_presence("add_user") self.toolbar.click_button("add_user") self.add_modal_window("add_user", "") @@ -243,26 +242,26 @@ class UsersTab(BasePage): """ tmp_dict = {"admin": "Администратор", "manager": "Контактное лицо", "operator": "Оператор"} table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA) - + if len(table_content) == 0: - assert False, "The contents of the table are missing" - + assert False, "The contents of the table are missing" + del table_content[0] # Удаляем заголовок - + 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][1] - + 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: @@ -275,10 +274,10 @@ class UsersTab(BasePage): Raises: AssertionError: Если пользователь не найден. """ - row_index = self.find_user_in_table(user_name, role) + row_index = self.find_user_in_table(user_name, role) if row_index == -1: 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() self.add_modal_window("edit_user", user_name) self.get_modal_window(user_name).check_by_window_title() @@ -292,22 +291,22 @@ class UsersTab(BasePage): Raises: AssertionError: Если таблица пуста или заголовки не соответствуют. """ - expected_headers = ['Имя пользователя', 'Роль', 'E-mail', 'Номер для СМС'] + expected_headers = ['Имя пользователя', 'Тип авторизации', 'Роль', 'E-mail', 'Номер для СМС'] table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA) - + if len(table_content) == 0: assert False, "The contents of the table are missing" - + actual_headers = table_content[0] self.check_equals( - actual_headers, + actual_headers, expected_headers, f"Expected table headers {expected_headers} are not equal {actual_headers}" ) - + if len(table_content) == 1: assert False, "Table body is missing" - + if verify: self.verify_users_table_content(table_content) @@ -322,44 +321,44 @@ class UsersTab(BasePage): user_name (str): Имя пользователя role (str): Роль пользователя """ - edit_user_window = self.get_modal_window(user_name) + edit_user_window = self.get_modal_window(user_name) edit_user_window.check_content(user_name, role) def should_be_toolbar(self) -> None: """Проверяет наличие тулбара. - + Raises: AssertionError: Если тулбар или кнопка редактирования отсутствуют. """ - self.toolbar.check_presence("Toolbar is missing") + self.toolbar.check_toolbar_presence("Toolbar is missing") self.toolbar.check_button_presence("edit") def should_be_toolbar_buttons(self) -> None: """Проверяет наличие и функциональность кнопок тулбара. - + Raises: AssertionError: Если кнопки недоступны или имеют некорректные подсказки. """ - self.toolbar.check_button_presence("edit") + self.toolbar.check_button_presence("edit") self.toolbar.check_button_tooltip("edit", "Редактировать") - + self.toolbar.get_button_by_name("edit").click() self.toolbar.check_button_presence("add_user") - self.toolbar.check_button_presence("close") + self.toolbar.check_button_presence("close") self.toolbar.check_button_tooltip("add_user", "Добавить") self.toolbar.check_button_tooltip("close", "Закрыть") - + self.toolbar.get_button_by_name("close").click() self.toolbar.check_button_presence("edit") def should_be_users_table(self) -> None: """Проверяет наличие таблицы пользователей. - + Raises: AssertionError: Если таблица отсутствует. """ self.users_table.check_presence( - TableLocators.TABLE_WORK_AREA, + TableLocators.TABLE_WORK_AREA, "Users table is missing" ) @@ -373,7 +372,7 @@ class UsersTab(BasePage): Raises: AssertionError: Если пользователь не найден. """ - found = self.find_user_in_table(name, role) + found = self.find_user_in_table(name, role) if found == -1: assert False, f"User with name {name} and role {role} has not been found" @@ -387,7 +386,7 @@ class UsersTab(BasePage): Raises: AssertionError: Если пользователь найден. """ - found = self.find_user_in_table(name, role) + found = self.find_user_in_table(name, role) if found != -1: assert False, f"User with name {name} and role {role} has been found" @@ -402,26 +401,31 @@ class UsersTab(BasePage): """ expected_users_list = [] tmp_dict = {"admin": "Администратор", "manager": "Контактное лицо", "operator": "Оператор"} - + query = { - "id": ["/catalogs/user"], + "id": ["/catalogs/user"], "data": { - "namePath": True, + "namePath": True, "children": {"flatten": True} } } - + response = self.send_post_api_request("e-cmdb/api/query", query) response_body = self.get_response_body(response) - + for item in response_body[0]["children"]: user_info = [] user_name = item["name"] - + if user_name in tmp_dict.keys(): item["name"] = tmp_dict[user_name] user_info.append(item["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"] if role in roles_dict.keys(): @@ -429,23 +433,23 @@ class UsersTab(BasePage): user_info.append(item["role"]) else: user_info.append("") - + if item["email"] is not None: user_info.append(item["email"]) else: user_info.append("") - + if item["sms_phone"] is not None: user_info.append(item["sms_phone"]) else: user_info.append("") - + expected_users_list.append(user_info) - + del users_table[0] # Удаляем заголовок - + self.check_lists_equals( - users_table, + users_table, expected_users_list, "Actual users list is not equal expected users list on base db" - ) \ No newline at end of file + ) diff --git a/tests/components/test_json_container.py b/tests/components/test_json_container.py index f7aa2d9..116fff2 100644 --- a/tests/components/test_json_container.py +++ b/tests/components/test_json_container.py @@ -1,8 +1,8 @@ -from pages.login_page import LoginPage -from pages.main_page import MainPage -from pages.license_tab import LicenseTab -from playwright.sync_api import Page import pytest +from playwright.sync_api import Page +from pages.main_page import MainPage +from pages.login_page import LoginPage +from pages.license_tab import LicenseTab class TestJsonContainer: @@ -13,7 +13,7 @@ class TestJsonContainer: """Фикстура для настройки тестового окружения.""" lp = LoginPage(browser) lp.do_login() - + mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") @@ -26,9 +26,9 @@ class TestJsonContainer: is_scrollable = lt.check_json_container_verticall_scrolling() assert is_scrollable, "Should be verticall scrolling" - + lt.scroll_json_container_down() lt.wait_for_timeout(3000) - + lt.scroll_json_container_up() - lt.wait_for_timeout(2000) \ No newline at end of file + lt.wait_for_timeout(2000) diff --git a/tests/components/test_navigation_panel.py b/tests/components/test_navigation_panel.py index 2653cfc..e6587a4 100644 --- a/tests/components/test_navigation_panel.py +++ b/tests/components/test_navigation_panel.py @@ -1,58 +1,57 @@ -from pages.login_page import LoginPage from pages.main_page import MainPage - +from pages.login_page import LoginPage # Запуск с viewport: {'width': 300, 'height': 420} # @pytest.mark.smoke class TestNavigationPanel: """Класс тестов для проверки панели навигации. - + Атрибуты: browser: фикстура для работы с браузером """ - + def test_verticall_scrolling(self, browser): """Тест вертикальной прокрутки панели навигации. - + Аргументы: browser: фикстура для работы с браузером - + Возвращает: None - + Исключения: AssertionError: если панель навигации не поддерживает вертикальную прокрутку """ # Действия: lp = LoginPage(browser) lp.do_login() - + # Мы на главной странице mp = MainPage(browser) - + # Проверки: # Проверяем наличие панели навигации mp.should_be_navigation_panel() - + # Открываем все пункты панели mp.click_main_navigation_panel_item("Настройки") - mp.click_configuration_navigation_panel_item("Аутентификация") - mp.click_configuration_navigation_panel_item("Уведомления") + mp.click_configuration_navigation_panel_item("Аутентификация") + mp.click_configuration_navigation_panel_item("Уведомления") mp.click_configuration_navigation_panel_item("Обслуживание и диагностика") mp.click_configuration_navigation_panel_item("Zero Touch Provisioning") - + # Проверяем возможность вертикальной прокрутки is_scrollable = mp.check_navigation_panel_verticall_scrolling() assert is_scrollable, "Should be vertical scrolling" - + # Действия: # Прокручиваем вверх и проверяем видимость элемента mp.scroll_navigation_panel_up() mp.check_navigation_panel_item_visibility("Панель приборов") mp.wait_for_timeout(3000) - - # Прокручиваем вниз и проверяем видимость элемента + + # Прокручиваем вниз и проверяем видимость элемента Настройки/ZTP/Шаблоны mp.scroll_navigation_panel_down() - mp.check_navigation_panel_item_visibility("Шаблоны") - mp.wait_for_timeout(2000) \ No newline at end of file + mp.check_navigation_panel_item_visibility("Шаблоны_2") + mp.wait_for_timeout(2000) diff --git a/tests/components/test_services_table.py b/tests/components/test_services_table.py index d6da7b2..caee074 100644 --- a/tests/components/test_services_table.py +++ b/tests/components/test_services_table.py @@ -1,9 +1,8 @@ -from pages.login_page import LoginPage -from pages.main_page import MainPage -from pages.service_status_tab import ServiceStatusTab -from playwright.sync_api import Page import pytest - +from playwright.sync_api import Page +from pages.service_status_tab import ServiceStatusTab +from pages.main_page import MainPage +from pages.login_page import LoginPage class TestServiceStatusTable: """Тесты для проверки таблицы статусов сервисов.""" @@ -13,7 +12,7 @@ class TestServiceStatusTable: """Фикстура для настройки тестового окружения.""" lp = LoginPage(browser) lp.do_login() - + mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") @@ -23,17 +22,17 @@ class TestServiceStatusTable: def test_scrolling(self, browser: Page) -> None: """Тест проверки прокрутки таблицы статусов сервисов.""" sst = ServiceStatusTab(browser) - + sst.should_be_services_table() sst.check_services_table_content() - + is_scrollable_vertically = sst.check_services_table_verticall_scrolling() assert is_scrollable_vertically, "Should be vertical scrolling" - + sst.scroll_services_table_down() sst.check_services_table_last_row_visibility() sst.wait_for_timeout(3000) - + sst.scroll_services_table_up() sst.check_services_table_first_row_visibility() - sst.wait_for_timeout(2000) \ No newline at end of file + sst.wait_for_timeout(2000) diff --git a/tests/components/test_user_modal_window.py b/tests/components/test_user_modal_window.py index bd384a0..0bd3a51 100644 --- a/tests/components/test_user_modal_window.py +++ b/tests/components/test_user_modal_window.py @@ -1,9 +1,8 @@ -from pages.login_page import LoginPage -from pages.main_page import MainPage -from pages.users_tab import UsersTab -from playwright.sync_api import Page import pytest - +from playwright.sync_api import Page +from pages.users_tab import UsersTab +from pages.main_page import MainPage +from pages.login_page import LoginPage class TestUsersModalWindow: """Тесты для проверки модальных окон работы с пользователями.""" @@ -13,7 +12,7 @@ class TestUsersModalWindow: """Фикстура для настройки тестового окружения.""" lp = LoginPage(browser) lp.do_login() - + mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") @@ -25,21 +24,21 @@ class TestUsersModalWindow: ut = UsersTab(browser) user_name, role = ut.open_edit_user_page_by_index(0) modal_window = ut.get_modal_window(user_name) - + is_scrollable_vertically = modal_window.check_window_vertical_scrolling() assert is_scrollable_vertically, "Should be vertical scrolling" - + modal_window.scroll_window_down() modal_window.check_button_presence("close") ut.wait_for_timeout(3000) - + modal_window.scroll_window_up() modal_window.check_toolbar_button_presence("close") ut.wait_for_timeout(3000) - + is_scrollable_horizontally = modal_window.check_window_horizontal_scrolling() assert is_scrollable_horizontally, "Should be horizontal scrolling" - + modal_window.scroll_window_right() ut.wait_for_timeout(3000) modal_window.scroll_window_left() @@ -50,22 +49,23 @@ class TestUsersModalWindow: ut = UsersTab(browser) ut.open_add_user_window() modal_window = ut.get_modal_window("add_user") - + is_scrollable_vertically = modal_window.check_window_vertical_scrolling() assert is_scrollable_vertically, "Should be vertical scrolling" - + modal_window.scroll_window_down() modal_window.check_button_presence("close") ut.wait_for_timeout(3000) - + modal_window.scroll_window_up() modal_window.check_toolbar_button_presence("close") ut.wait_for_timeout(3000) - - is_scrollable_horizontally = modal_window.check_window_horizontal_scrolling() - assert is_scrollable_horizontally, "Should be horizontal scrolling" - - modal_window.scroll_window_right() - ut.wait_for_timeout(3000) - modal_window.scroll_window_left() - ut.wait_for_timeout(2000) \ No newline at end of file + + ## Временно закомментарено - для окна добавления пользователя убрали горизонтальный скроллинг - BUG??? + # is_scrollable_horizontally = modal_window.check_window_horizontal_scrolling() + # assert is_scrollable_horizontally, "Should be horizontal scrolling" + + # modal_window.scroll_window_right() + # ut.wait_for_timeout(3000) + # modal_window.scroll_window_left() + # ut.wait_for_timeout(2000) diff --git a/tests/e2e/test_license_tab.py b/tests/e2e/test_license_tab.py index 1cb5de9..d933d63 100644 --- a/tests/e2e/test_license_tab.py +++ b/tests/e2e/test_license_tab.py @@ -1,11 +1,10 @@ -from pages.login_page import LoginPage -from pages.main_page import MainPage -from pages.license_tab import LicenseTab -from playwright.sync_api import Page -import pytest import uuid from typing import List - +import pytest +from playwright.sync_api import Page +from pages.main_page import MainPage +from pages.login_page import LoginPage +from pages.license_tab import LicenseTab class TestLicenseTab: """Тесты для вкладки 'Лицензии'.""" @@ -15,7 +14,7 @@ class TestLicenseTab: """Подготовка тестового окружения.""" lp = LoginPage(browser) lp.do_login() - + mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") @@ -26,7 +25,7 @@ class TestLicenseTab: """Тест содержимого вкладки 'Лицензии'.""" lt = LicenseTab(browser) lt.check_content() - + def test_license_tab_input_form_and_check_alert(self, browser: Page) -> None: """Тест формы ввода лицензии и проверки алертов.""" def gen_test_data() -> List[str]: @@ -34,24 +33,24 @@ class TestLicenseTab: data = [] for i in range(3): data.append(uuid.uuid4().hex) - + lowercase_str = uuid.uuid4().hex data.append(lowercase_str.upper()) - data.append(lowercase_str + "fffffffff") + data.append(lowercase_str + "fffffffff") data.append("0") data.append("000000000000000000000000000000000000000000000000") data.append("-1") - + return data - + lt = LicenseTab(browser) lt.should_be_empty_input_form() - + lt.fill_license_input_form("") lt.should_be_error_alert_window_with_text("Неверный лицензионный ключ") - + data = gen_test_data() - + for data_string in data: - lt.fill_license_input_form(data_string) - lt.should_be_error_alert_window_with_text("Ошибка обновления лицензии") \ No newline at end of file + lt.fill_license_input_form(data_string) + lt.should_be_error_alert_window_with_text("Ошибка обновления лицензии") diff --git a/tests/e2e/test_login.py b/tests/e2e/test_login.py index a59f96a..4fa0c75 100644 --- a/tests/e2e/test_login.py +++ b/tests/e2e/test_login.py @@ -1,26 +1,24 @@ -import pytest -from pages.login_page import LoginPage -from pages.main_page import MainPage from playwright.sync_api import Page - +from pages.main_page import MainPage +from pages.login_page import LoginPage class TestLogin: """Тесты для функционала входа и выхода из системы.""" - + def test_successful_login(self, browser: Page) -> None: """Тест успешного входа в систему.""" lp = LoginPage(browser) lp.do_login() - + def test_unsuccessful_login(self, browser: Page) -> None: """Тест неудачного входа в систему.""" lp = LoginPage(browser) lp.do_unsuccessful_login() - + def test_successful_login_and_logout(self, browser: Page) -> None: """Тест успешного входа и выхода из системы.""" lp = LoginPage(browser) lp.do_login() - + mp = MainPage(browser) - mp.do_logout() \ No newline at end of file + mp.do_logout() diff --git a/tests/e2e/test_service_status_tab.py b/tests/e2e/test_service_status_tab.py index 9b1e826..a52a2d0 100644 --- a/tests/e2e/test_service_status_tab.py +++ b/tests/e2e/test_service_status_tab.py @@ -1,8 +1,7 @@ -from pages.login_page import LoginPage -from pages.main_page import MainPage -from pages.service_status_tab import ServiceStatusTab import pytest - +from pages.service_status_tab import ServiceStatusTab +from pages.main_page import MainPage +from pages.login_page import LoginPage class TestServiceStatusTab: """Набор тестов для вкладки 'Статус обслуживания'. @@ -20,16 +19,16 @@ class TestServiceStatusTab: """ lp = LoginPage(browser) lp.do_login() - + # Переход на главную страницу mp = MainPage(browser) - + # Проверка наличия панели навигации mp.should_be_navigation_panel() - + # Клик по пункту 'Настройки' в главной панели навигации mp.click_main_navigation_panel_item("Настройки") - + # Клик по пункту 'Обслуживание и диагностика' в панели навигации настроек mp.click_configuration_navigation_panel_item("Обслуживание и диагностика") @@ -48,13 +47,13 @@ class TestServiceStatusTab: # Проверка тулбара вкладки sst.should_be_toolbar() - + # Проверка наличия таблицы статусов сервисов sst.should_be_services_table() - + # Проверка содержимого таблицы сервисов sst.check_services_table_content() - + def test_service_status_table_row_highlighting(self, browser): """Тест выделения строк в таблице сервисов. @@ -67,14 +66,14 @@ class TestServiceStatusTab: # Проверка тулбара вкладки sst.should_be_toolbar() - + # Проверка наличия таблицы статусов сервисов sst.should_be_services_table() - + # Получение количества строк в таблице rows_count = sst.get_rows_count() - + # Проверка выделения строк sst.check_services_table_row_highlighting(0) sst.check_services_table_row_highlighting(rows_count - 1) - sst.check_services_table_row_highlighting(int(rows_count / 2)) \ No newline at end of file + sst.check_services_table_row_highlighting(int(rows_count / 2)) diff --git a/tests/e2e/test_sessions_tab.py b/tests/e2e/test_sessions_tab.py index 04928a5..65c3d15 100644 --- a/tests/e2e/test_sessions_tab.py +++ b/tests/e2e/test_sessions_tab.py @@ -1,8 +1,7 @@ -from pages.login_page import LoginPage -from pages.main_page import MainPage -from pages.session_tab import SessionsTab import pytest - +from pages.session_tab import SessionsTab +from pages.main_page import MainPage +from pages.login_page import LoginPage class TestSessionsTab: """Набор тестов для вкладки 'Сеансы'. @@ -21,10 +20,10 @@ class TestSessionsTab: # Авторизация в системе login_page = LoginPage(browser) login_page.do_login() - + # Инициализация главной страницы main_page = MainPage(browser) - + # Проверка и взаимодействие с элементами навигации main_page.should_be_navigation_panel() main_page.click_main_navigation_panel_item("Настройки") @@ -45,6 +44,6 @@ class TestSessionsTab: # Проверка элементов интерфейса sessions_tab.should_be_toolbar() sessions_tab.should_be_sessions_table() - + # Проверка содержимого таблицы с верификацией данных из БД - sessions_tab.check_sessions_table_content(verify=True) \ No newline at end of file + sessions_tab.check_sessions_table_content(verify=True) diff --git a/tests/e2e/test_users_tab.py b/tests/e2e/test_users_tab.py index d762886..d272ce3 100644 --- a/tests/e2e/test_users_tab.py +++ b/tests/e2e/test_users_tab.py @@ -1,10 +1,9 @@ -from pages.login_page import LoginPage -from pages.main_page import MainPage -from pages.users_tab import UsersTab -from playwright.sync_api import Page -import pytest from typing import Dict - +import pytest +from playwright.sync_api import Page +from pages.users_tab import UsersTab +from pages.main_page import MainPage +from pages.login_page import LoginPage class TestUsersTab: """Тесты для вкладки 'Пользователи'.""" @@ -14,7 +13,7 @@ class TestUsersTab: """Подготовка тестового окружения.""" lp = LoginPage(browser) lp.do_login() - + mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") @@ -26,7 +25,7 @@ class TestUsersTab: ut.should_be_toolbar() ut.should_be_users_table() ut.check_users_table_content(True) - + def test_users_tab_toolbar_buttons(self, browser: Page) -> None: """Тест кнопок на панели инструментов.""" ut = UsersTab(browser) @@ -37,12 +36,13 @@ class TestUsersTab: ut = UsersTab(browser) ut.open_add_user_window() ut.check_add_user_window_content() - + def test_add_user_window_close_buttons(self, browser: Page) -> None: """Тест кнопок закрытия окна добавления пользователя.""" ut = UsersTab(browser) - ut.open_add_user_window() + ut.open_add_user_window() ut.close_add_user_window_by_toolbar_button() + ut.open_add_user_window() ut.close_add_user_window() @@ -55,18 +55,18 @@ class TestUsersTab: def test_edit_user_window_close_buttons(self, browser: Page) -> None: """Тест кнопок закрытия окна редактирования пользователя.""" ut = UsersTab(browser) - user_name, role = ut.open_edit_user_page_by_index(0) + user_name, role = ut.open_edit_user_page_by_index(0) ut.close_edit_user_window_by_toolbar_button(user_name) - user_name, role = ut.open_edit_user_page_by_index(0) + user_name, role = ut.open_edit_user_page_by_index(0) ut.close_edit_user_window(user_name) - + def test_add_and_delete_user(self, browser: Page) -> None: """Тест добавления и удаления пользователя.""" - user_data: Dict[str, str] = {"name": "User", "role": "Администратор"} - + user_data: Dict[str, str] = {"name": "User", "role": "Администратор", "password": "987654"} + mp = MainPage(browser) ut = UsersTab(browser) - + ut.open_add_user_window() ut.add_new_user(user_data) mp.click_configuration_navigation_panel_item("Пользователи") @@ -80,21 +80,21 @@ class TestUsersTab: def test_reset_password(self, browser: Page) -> None: """Тест сброса пароля пользователя.""" - user_data: Dict[str, str] = {"name": "autoadmin", "role": "Администратор"} - + user_data: Dict[str, str] = {"name": "autoadmin", "role": "Администратор", "password": "123456"} + mp = MainPage(browser) ut = UsersTab(browser) - + ut.open_add_user_window() ut.add_new_user(user_data) mp.click_configuration_navigation_panel_item("Пользователи") mp.click_configuration_navigation_panel_item("Пользователи") ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) new_password = ut.reset_password(user_data["name"]) - - if len(new_password) == 0: + + if len(new_password) == 0: assert False, "Unsuccessful password reset" - + new_lp = LoginPage(browser) new_lp.do_login(username=user_data["name"], password=new_password) new_mp = MainPage(browser) @@ -111,14 +111,14 @@ class TestUsersTab: mp_1.click_configuration_navigation_panel_item("Пользователи") mp_1.click_configuration_navigation_panel_item("Пользователи") ut_1.should_not_be_user_in_table(user_data["name"], user_data["role"]) - + def test_edit_user_role(self, browser: Page) -> None: """Тест изменения роли пользователя.""" - user_data: Dict[str, str] = {"name": "autooperator", "role": "Оператор"} - + user_data: Dict[str, str] = {"name": "autooperator", "role": "Оператор", "password": "123245"} + mp = MainPage(browser) ut = UsersTab(browser) - + ut.open_add_user_window() ut.add_new_user(user_data) mp.click_configuration_navigation_panel_item("Пользователи") @@ -133,4 +133,4 @@ class TestUsersTab: ut.delete_user(user_data["name"]) mp.click_configuration_navigation_panel_item("Пользователи") mp.click_configuration_navigation_panel_item("Пользователи") - ut.should_not_be_user_in_table(user_data["name"], new_user_data["role"]) \ No newline at end of file + ut.should_not_be_user_in_table(user_data["name"], new_user_data["role"]) diff --git a/tools/fix_python_project.py b/tools/fix_python_project.py index 57018f6..c473c26 100644 --- a/tools/fix_python_project.py +++ b/tools/fix_python_project.py @@ -25,12 +25,12 @@ INIT_TEMPLATE: str = """# Auto-generated by fix_python_project.py class ProjectFixer: """Основной класс для исправления структуры Python-проекта. - + Атрибуты: root_dir (str): Корневая директория проекта. log (List[str]): Список записей лога выполненных операций. """ - + def __init__(self, root_dir: str = '.'): """Инициализирует экземпляр ProjectFixer. @@ -42,7 +42,7 @@ class ProjectFixer: def remove_bom(self, filepath: str) -> bool: """Удаляет BOM-маркер из файла, если он присутствует. - + Обрабатывает все файлы, включая находящиеся в tests/. Args: @@ -57,7 +57,7 @@ class ProjectFixer: 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:]) @@ -69,7 +69,7 @@ class ProjectFixer: def should_skip_init(self, dir_path: str) -> bool: """Проверяет, нужно ли пропустить создание __init__.py в директории. - + Игнорирует служебные папки (tests/, .git/ и др.). Args: @@ -79,7 +79,7 @@ class ProjectFixer: bool: True, если директорию следует пропустить. """ dir_name = os.path.basename(dir_path) - return (dir_name in INIT_IGNORED_DIRS or + return (dir_name in INIT_IGNORED_DIRS or dir_name.startswith('.')) def needs_init_py(self, dir_path: str) -> bool: @@ -93,7 +93,7 @@ class ProjectFixer: """ if self.should_skip_init(dir_path): return False - + try: items = os.listdir(dir_path) has_py_files = any(f.endswith('.py') and f != '__init__.py' for f in items) @@ -149,14 +149,14 @@ class ProjectFixer: 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(f"Подробности сохранены в project_fix.log") \ No newline at end of file + print("Подробности сохранены в project_fix.log") diff --git a/tools/logger.py b/tools/logger.py index fb0d216..7ebb0a3 100644 --- a/tools/logger.py +++ b/tools/logger.py @@ -34,4 +34,4 @@ def get_logger(name: str) -> logging.Logger: logger.addHandler(handler) - return logger \ No newline at end of file + return logger