commit 51123880b9a2b052cea5a7a5628ea61e9e32cb4d Author: Radislav Date: Fri Jul 4 09:44:15 2025 +0300 Initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..73a7825 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +ENV=test +AUTH_LOGIN = admin +AUTH_PASSWORD = admin diff --git a/Locators/__pycache__/base_page.cpython-313.pyc b/Locators/__pycache__/base_page.cpython-313.pyc new file mode 100644 index 0000000..167c119 Binary files /dev/null and b/Locators/__pycache__/base_page.cpython-313.pyc differ diff --git a/Locators/__pycache__/configuration_page.cpython-313.pyc b/Locators/__pycache__/configuration_page.cpython-313.pyc new file mode 100644 index 0000000..fa960c8 Binary files /dev/null and b/Locators/__pycache__/configuration_page.cpython-313.pyc differ diff --git a/Locators/__pycache__/login.cpython-313.pyc b/Locators/__pycache__/login.cpython-313.pyc new file mode 100644 index 0000000..f5d1b64 Binary files /dev/null and b/Locators/__pycache__/login.cpython-313.pyc differ diff --git a/Locators/__pycache__/main_page.cpython-313.pyc b/Locators/__pycache__/main_page.cpython-313.pyc new file mode 100644 index 0000000..e9ad9a5 Binary files /dev/null and b/Locators/__pycache__/main_page.cpython-313.pyc differ diff --git a/Locators/__pycache__/session_locators.cpython-313.pyc b/Locators/__pycache__/session_locators.cpython-313.pyc new file mode 100644 index 0000000..b63d4a0 Binary files /dev/null and b/Locators/__pycache__/session_locators.cpython-313.pyc differ diff --git a/Locators/__pycache__/users_tab.cpython-313.pyc b/Locators/__pycache__/users_tab.cpython-313.pyc new file mode 100644 index 0000000..495bbaa Binary files /dev/null and b/Locators/__pycache__/users_tab.cpython-313.pyc differ diff --git a/Locators/base_page.py b/Locators/base_page.py new file mode 100644 index 0000000..65aec12 --- /dev/null +++ b/Locators/base_page.py @@ -0,0 +1,12 @@ +class BasePageLocators(): + TABLE_HEADERS_CELLS = "//thead[contains(@class,'scrolltable__header')]/tr[contains(@class,'scrolltable__row')]/th" + TABLE_BODY = "//tbody[contains(@class,'scrolltable__body')]/tr[contains(@class,'scrolltable__row')]" + + ALERT_WINDOW = "//div[@role='alert']" + ALERT_WINDOW_TEXT_ERROR = "//div[@class='v-alert error']/div" + ALERT_WINDOW_TEXT_SUCCESS = "//div[@class='v-alert success']/div" + ALERT_WINDOW_TEXT_INFO = "//div[@class='v-alert info']/div" + ALERT_WINDOW_TEXT_WARNING = "//div[@class='v-alert warning']/div" + + TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]" + \ No newline at end of file diff --git a/Locators/configuration_page.py b/Locators/configuration_page.py new file mode 100644 index 0000000..acf2dc0 --- /dev/null +++ b/Locators/configuration_page.py @@ -0,0 +1,20 @@ +class ConfigurationPageLocators(): + CONFIG_NAVIGATION_PANEL = "//ul/li[3]/div[@class='v-expansion-panel__body']" + + CONFIG_NAVIGATION_PANEL_USER_BUTTON = "//ul/li[3]/div[@class='v-expansion-panel__body']//div[contains(@class,'v-treeview-node--click')]" + CONFIG_NAVIGATION_PANEL_NOTIFICATION_BUTTON = "//ul/li[3]/div[@class='v-expansion-panel__body']//div[contains(@class,'v-treeview-node--click')][2]" + CONFIG_NAVIGATION_PANEL_MAINTENANCE_BUTTON = "//ul/li[3]/div[@class='v-expansion-panel__body']//div[contains(@class,'v-treeview-node--click')][3]" + CONFIG_NAVIGATION_PANEL_ZTP_BUTTON = "//ul/li[3]/div[@class='v-expansion-panel__body']//div[contains(@class,'v-treeview-node--click')][4]" + + MAINTENANCE_NAVIGATION_PANEL = \ + "//ul/li[3]/div[@class='v-expansion-panel__body']//div[contains(@class,'v-treeview-node--click')][3]/div[@class='v-treeview-node__children']" + MAINTENANCE_NAVIGATION_PANEL_SESSION_BUTTON = \ + "//ul/li[3]/div[@class='v-expansion-panel__body']//div[contains(@class,'v-treeview-node--click')][3]/div[@class='v-treeview-node__children']//div[contains(@class,'v-treeview-node--click')]" + MAINTENANCE_NAVIGATION_PANEL_SERVICE_STATUS_BUTTON = \ + "//ul/li[3]/div[@class='v-expansion-panel__body']//div[contains(@class,'v-treeview-node--click')][3]/div[@class='v-treeview-node__children']//div[contains(@class,'v-treeview-node--click')][2]" + MAINTENANCE_NAVIGATION_PANEL_LICENSING_BUTTON = \ + "//ul/li[3]/div[@class='v-expansion-panel__body']//div[contains(@class,'v-treeview-node--click')][3]/div[@class='v-treeview-node__children']//div[contains(@class,'v-treeview-node--click')][3]" + + WORK_AREA_TITLE = "//div[@class ='v-toolbar__title']/span" + WORK_AREA_TABLE = "//div[@class='scrollarea__body']/div/div/div/table" + \ No newline at end of file diff --git a/Locators/license_tab.py b/Locators/license_tab.py new file mode 100644 index 0000000..3da8556 --- /dev/null +++ b/Locators/license_tab.py @@ -0,0 +1,5 @@ +class LicenseTabLocators: + JSON_ELEMENT = "//div[@class='jv-node']" + LICENSE_TITLE = "//span[@class='title']" + LICENSE_TITLE_DEVICE_ID = "//span[@class='title text_select']" + LICENSE_INPUT_FORM_TEXTAREA = "//div[contains(@class,'v-input')]/div[@class='v-input__control']//div[@class='v-text-field__slot']/textarea" diff --git a/Locators/login.py b/Locators/login.py new file mode 100644 index 0000000..f5e20b6 --- /dev/null +++ b/Locators/login.py @@ -0,0 +1,4 @@ +class LoginPageLocators: + USERNAME_INPUT = "//input[@type='text']" + PASSWORD_INPUT = "//input[@type='password']" + LOGIN_BTN = "//button[@type='button']" diff --git a/Locators/main_page.py b/Locators/main_page.py new file mode 100644 index 0000000..02cf5bf --- /dev/null +++ b/Locators/main_page.py @@ -0,0 +1,14 @@ +class MainPageLocators: + NAVIGATION_PANEL = "//ul" + NAVIGATION_PANEL_DASHBOARD_BUTTON = "//ul/li[1]/div" + NAVIGATION_PANEL_TOPOLOGY_BUTTON = "//ul/li[2]/div" + NAVIGATION_PANEL_CONFIGURATION_BUTTON = "//ul/li[3]/div" + + NAVIGATION_PANEL_DASHBOARD_BUTTON_HEADER = "//ul/li[1]" + NAVIGATION_PANEL_TOPOLOGY_BUTTON_HEADER = "//ul/li[2]" + NAVIGATION_PANEL_CONFIGURATION_BUTTON_HEADER = "//ul/li[3]" + + + CURRENT_USER_BUTTON = "#app > div.application--wrap > div > div.layout.white > nav > div > div:nth-child(3) > div > div > div > button:nth-child(3)" + CURRENT_USER_WINDOW = "//div[@class='v-card__text']" + \ No newline at end of file diff --git a/Locators/session_locators.py b/Locators/session_locators.py new file mode 100644 index 0000000..322213e --- /dev/null +++ b/Locators/session_locators.py @@ -0,0 +1,31 @@ +class SessionLocators(): + + # Область работы + #TABLE_BODY = "//div[contains(@class, 'scrollarea__body')]//table[@class='scrolltable__container']" + TABLE_BODY = "//div[@class='scrollarea__body']/div/div/div/table" + TEXT_TITLE = "//div[@class ='v-toolbar__title']/span" + + TABLE_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body') and .//table[@class='scrolltable__container']]" # Контейнер со скроллом + TABLE_ROWS = f"{TABLE_SCROLL_CONTAINER}//table/tbody/tr" + #TABLE_SCROLL_CONTAINER = "div.layout.white.column.fill-height" + + # Ячейки таблицы + TABLE_HEADER_CELL_SESSION_ID = "//div[@class='scrollarea__body']//td[1]/div/div" + TABLE_HEADER_CELL_USER_ID = "//div[@class='scrollarea__body']//td[2]/div/div" + TABLE_HEADER_CELL_LIFETIME = "//div[@class='scrollarea__body']//td[3]//div/div" + TABLE_HEADER_CELL_ROLE = "//div[@class='scrollarea__body']//td[4]//div/div" + TABLE_HEADER_CELL_ADDRESS = "//div[@class='scrollarea__body']//td[5]//div/div" + + # Кнопки + BUTTON_DELETE_SESSION = "button.v-btn--icon svg[fill='#4caf50']" # "//div[@class='scrollarea__body']//td[5]//button" + + # Модальное окно + MODAL_WINDOW = "div.v-dialog--active" # Основное модальное окно + MODAL_TITLE = "//*[@id='app']/div[2]/div/div[2]/div[1]" # Заголовк модального окна + #MODAL_SCROLL_CONTAINER_HORIZONTAL = "div.v-dialog--active div.v-card__text" # Горизонтальный скролл + + + MODAL_CLOSE_BUTTON = "//*[@id='app']/div[2]/div/div[1]" # Кнопка закрытия "Х" + MODAL_CANCEL_BUTTON = "div.v-dialog--active button:not(.red--text)" # Кнопка "Отмена" + MODAL_DELETE_BUTTON = "div.v-dialog--active button.red--text" # Кнопка "Удалить" + #SUCCESS_MESSAGE = "селектор сообщения об успешном удалении" # опционально diff --git a/Locators/users_tab.py b/Locators/users_tab.py new file mode 100644 index 0000000..2064b77 --- /dev/null +++ b/Locators/users_tab.py @@ -0,0 +1,14 @@ +class UsersTabLocators: + TOOLBAR_EDIT_BUTTON = "xpath=(.//*[normalize-space(text()) and normalize-space(.)='Пользователи'])[2]/following::*[name()='svg'][1]" + TOOLBAR_ADD_USER_BUTTON = "xpath=(.//*[normalize-space(text()) and normalize-space(.)='Пользователи'])[2]/following::*[name()='svg'][1]" + TOOLBAR_CLOSE_BUTTON = "xpath=(.//*[normalize-space(text()) and normalize-space(.)='Пользователи'])[2]/following::*[name()='svg'][2]" + + USER_DATA_WORK_AREA_TITLE = "//div[@class ='v-toolbar__title']/span[contains(@class,'body-2')]" + ADD_USER_WORK_AREA_CLOSE_BUTTON = "xpath=(.//*[normalize-space(text()) and normalize-space(.)='Добавить нового пользователя'])[1]/following::*[name()='svg'][1]" + + + USER_DATA_INPUT_FORM = "//form[@class='v-form']" + USER_DATA_INPUT_FORM_ROLES_MENU = "//div[contains(@class, 'menuable__content__active')]" + USER_DATA_INPUT_FORM_NOTIFICATION_LABEL = "//label[contains(@class,'v-label')]/span" + + USER_ACTION_CONFIRMATION_DIALOG = "//div[contains(@class, 'v-dialog--active')]//h3" diff --git a/__pycache__/conftest.cpython-313-pytest-8.3.5.pyc b/__pycache__/conftest.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 0000000..3aa0cd8 Binary files /dev/null and b/__pycache__/conftest.cpython-313-pytest-8.3.5.pyc differ diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..ebcb6b5 --- /dev/null +++ b/conftest.py @@ -0,0 +1,7 @@ +from dotenv import load_dotenv + +load_dotenv() + +pytest_plugins = [ + 'fixtures.pages' +] \ No newline at end of file diff --git a/data/__pycache__/assertions.cpython-313.pyc b/data/__pycache__/assertions.cpython-313.pyc new file mode 100644 index 0000000..72e9852 Binary files /dev/null and b/data/__pycache__/assertions.cpython-313.pyc differ diff --git a/data/__pycache__/constants.cpython-313.pyc b/data/__pycache__/constants.cpython-313.pyc new file mode 100644 index 0000000..4c65347 Binary files /dev/null and b/data/__pycache__/constants.cpython-313.pyc differ diff --git a/data/__pycache__/environment.cpython-313.pyc b/data/__pycache__/environment.cpython-313.pyc new file mode 100644 index 0000000..a54056b Binary files /dev/null and b/data/__pycache__/environment.cpython-313.pyc differ diff --git a/data/assertions.py b/data/assertions.py new file mode 100644 index 0000000..51340c8 --- /dev/null +++ b/data/assertions.py @@ -0,0 +1,107 @@ +from playwright.sync_api import Page +from data.environment import host +from playwright.sync_api import expect +from pages.base_page import BasePage +from Locators.base_page import BasePageLocators + +import jsondiff + +class Assertions(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + + def check_URL(self, uri, msg): + expect(self.page).to_have_url(f"{host.get_base_url()}{uri}", timeout=60000), msg + + def have_text(self, locator, text: str, msg): #элемент имеет текст + loc = self.page.locator(locator) + expect(loc).to_have_text(text), msg + + def have_title(self, locator, title: str, msg): + loc = self.page.locator(locator) + expect(loc).to_have_text(title), msg + + def check_presence(self, locator, msg): + loc = self.page.locator(locator) + expect(loc).to_be_visible(visible=True, timeout=12000), msg + + def check_button_presence_with_text(self, text, msg): + loc = self.page.get_by_role("button", name=text) + expect(loc).to_be_visible(visible=True, timeout=12000), msg + + def check_absence(self, locator, msg): + loc = self.page.locator(locator) + expect(loc).to_be_hidden(timeout=700), msg + + def check_absence_after_period(self, locator, period, msg): + loc = self.page.locator(locator) + expect(loc).to_be_hidden(timeout=period), msg + + def check_empty_input_area(self, input_area, msg): + expect(input_area).to_be_empty(), msg + + def check_menu_item_with_text(self, text, msg): + count = self.page.get_by_role("listitem", name=text).count() + assert count != 1, msg + + def check_equals(self, actual, expected, msg): + assert actual == expected, msg + + def check_not_equals(self, actual, expected, msg): + assert actual != expected, msg + + def check_json_equals(self, actual, expected, msg): + diff = jsondiff.diff(expected, actual, syntax='symmetric') + assert len(diff) == 0, f"{msg}. DIFF is {diff}" + + def check_lists_equals(self, actual, expected, msg): + def compare_lists(list1, list2): + if len(list1) != len(list2): + return False + for item1, item2 in zip(list1, list2): + if isinstance(item1, list) and isinstance(item2, list): + if not compare_lists(item1, item2): + return False + elif item1 != item2: + return False + return True + + assert compare_lists(actual, expected), msg + + def check_url_content(self, uri,msg): + assert f"{uri}" in self.page.url, msg + + def check_element_active(self, locator, msg): + element_handle = self.page.wait_for_selector(locator) + class_names = element_handle.get_attribute('class') + assert 'active' in class_names, msg + + def check_alert_window_with_text(self, alert_type, text): + self.check_presence(BasePageLocators.ALERT_WINDOW, "No alert window for action notification") + + if alert_type == "error": + self.have_text(BasePageLocators.ALERT_WINDOW_TEXT_ERROR, text, \ + "Unexpected error message in alert window") + elif alert_type == "success": + self.have_text(BasePageLocators.ALERT_WINDOW_TEXT_SUCCESS, text, \ + "Unexpected message about success action in alert window") + elif alert_type == "info": + self.have_text(BasePageLocators.ALERT_WINDOW_TEXT_WARNING, text, \ + "Unexpected info message in alert window") + elif alert_type == "warning": + self.have_text(BasePageLocators.ALERT_WINDOW_TEXT_WARNING, text, \ + "Unexpected warning message about success action in alert window") + else: + assert False, "Unsupported type of alert window" + self.check_absence_after_period(BasePageLocators.ALERT_WINDOW, 30000, \ + "Alert window for action notification should disappear") + + def check_tooltip_with_text(self, locator, text): + self.page.locator(locator).hover() + tooltip = self.page.locator(BasePageLocators.TOOLTIP) + assert tooltip.text_content().strip() == text, "Unexpected tooltip text" + + def check_confirmation_dialog_with_title(self, locator, title): + loc = self.page.locator(locator) + expect(loc).to_contain_text(title), f"Confirmation dialog window with title {title} is not present" + \ No newline at end of file diff --git a/data/constants.py b/data/constants.py new file mode 100644 index 0000000..e733910 --- /dev/null +++ b/data/constants.py @@ -0,0 +1,9 @@ +import os + +class Constants: + try: + 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 diff --git a/data/environment.py b/data/environment.py new file mode 100644 index 0000000..4c8e089 --- /dev/null +++ b/data/environment.py @@ -0,0 +1,31 @@ +import os + +class Environment: + TEST = 'test' + DEVELOP = 'develop' + + URLS = { + TEST: 'http://192.168.2.76/', + DEVELOP: 'http://192.168.50.69/' + } + + def __init__(self): + try: + self.env = os.getenv('ENV') + self.access_token = "" + except KeyError: + self.env = self.TEST + + def get_base_url(self): + if self.env in self.URLS: + return self.URLS[self.env] + else: + raise Exception(f"Unknown value of ENV variable {self.env}") + + def set_access_token(self, token): + self.token = token + + def get_access_token(self): + return self.token + +host = Environment() diff --git a/fixtures/__pycache__/pages.cpython-313-pytest-8.3.5.pyc b/fixtures/__pycache__/pages.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 0000000..22668b2 Binary files /dev/null and b/fixtures/__pycache__/pages.cpython-313-pytest-8.3.5.pyc differ diff --git a/fixtures/pages.py b/fixtures/pages.py new file mode 100644 index 0000000..dca3e66 --- /dev/null +++ b/fixtures/pages.py @@ -0,0 +1,95 @@ +import pytest +from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright +import os + + + +def pytest_addoption(parser): + """Пользовательские опции командной строки""" + parser.addoption('--bn', action='store', default="chrome", help="Choose browser: chrome, remote_chrome or firefox") + parser.addoption('--h', action='store', default=False, help='Choose headless: True or False') + parser.addoption('--s', action='store', default={'width': 1520, 'height': 380}, 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, help='Choose slow_mo for robot action') + parser.addoption('--t', action='store', default=60000, help='Choose timeout') + parser.addoption('--l', action='store', default='ru-RU', help='Choose locale') + # parser.addini('qs_to_api_token', default=os.getenv("QASE_TOKEN"), help='Qase app token') + + + +@pytest.fixture(scope='class') +def browser(request) -> Page: + playwright = sync_playwright().start() + if request.config.getoption("bn") == 'remote_chrome': + browser = get_remote_chrome(playwright, request) + context = get_context(browser, request, 'remote') + page_data = context.new_page() + elif request.config.getoption("bn") == 'firefox': + browser = get_firefox_browser(playwright, request) + context = get_context(browser, request, 'local') + page_data = context.new_page() + elif request.config.getoption("bn") == 'chrome': + browser = get_chrome_browser(playwright, request) + context = get_context(browser, request, 'local') + page_data = context.new_page() + else: + 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() + browser.close() + playwright.stop() + + +def get_firefox_browser(playwright, request) -> Browser: + return playwright.firefox.launch( + headless=request.config.getoption("h"), + slow_mo=request.config.getoption("slow"), + ) + + +def get_chrome_browser(playwright, request) -> Browser: + return playwright.chromium.launch( + headless=request.config.getoption("h"), + slow_mo=request.config.getoption("slow"), + args=['--s'] + ) + +def get_remote_chrome(playwright, request) -> Browser: + return playwright.chromium.launch( + headless=True, + slow_mo=request.config.getoption("slow") + ) + + +def get_context(browser, request, start) -> BrowserContext: + if start == 'local': + context = browser.new_context( + # no_viewport=True, + viewport=request.config.getoption('s'), + locale=request.config.getoption('l') + ) + context.set_default_timeout( + timeout=request.config.getoption('t') + ) + # context.add_cookies([{'url': 'https://example.ru', 'name': 'ab_test', 'value': 'd'}]) добавляем куки, если нужны + return context + + elif start == 'remote': + context = browser.new_context( + viewport=request.config.getoption('s'), + locale=request.config.getoption('l') + ) + context.set_default_timeout( + timeout=request.config.getoption('t') + ) + # context.add_cookies([{'url': 'https://example.ru', 'name': 'ab_test', 'value': 'd'}]) добавляем куки, если нужны + return context + + + +@pytest.fixture(scope="function") +def return_back(browser): + browser.go_back() \ No newline at end of file diff --git a/fixtures/pages.py.or b/fixtures/pages.py.or new file mode 100644 index 0000000..8361343 --- /dev/null +++ b/fixtures/pages.py.or @@ -0,0 +1,93 @@ +import pytest +from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright +import os + + + +def pytest_addoption(parser): + """Пользовательские опции командной строки""" + parser.addoption('--bn', action='store', default="chrome", help="Choose browser: chrome, remote_chrome or firefox") + parser.addoption('--h', action='store', default=False, help='Choose headless: True or False') + parser.addoption('--s', action='store', default={'width': 1920, 'height': 1080}, help='Size window: width,height') + parser.addoption('--slow', action='store', default=200, help='Choose slow_mo for robot action') + parser.addoption('--t', action='store', default=60000, help='Choose timeout') + parser.addoption('--l', action='store', default='ru-RU', help='Choose locale') + # parser.addini('qs_to_api_token', default=os.getenv("QASE_TOKEN"), help='Qase app token') + + + +@pytest.fixture(scope='class') +def browser(request) -> Page: + playwright = sync_playwright().start() + if request.config.getoption("bn") == 'remote_chrome': + browser = get_remote_chrome(playwright, request) + context = get_context(browser, request, 'remote') + page_data = context.new_page() + elif request.config.getoption("bn") == 'firefox': + browser = get_firefox_browser(playwright, request) + context = get_context(browser, request, 'local') + page_data = context.new_page() + elif request.config.getoption("bn") == 'chrome': + browser = get_chrome_browser(playwright, request) + context = get_context(browser, request, 'local') + page_data = context.new_page() + else: + 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() + browser.close() + playwright.stop() + + +def get_firefox_browser(playwright, request) -> Browser: + return playwright.firefox.launch( + headless=request.config.getoption("h"), + slow_mo=request.config.getoption("slow"), + ) + + +def get_chrome_browser(playwright, request) -> Browser: + return playwright.chromium.launch( + headless=request.config.getoption("h"), + slow_mo=request.config.getoption("slow"), + args=['--start-maximized'] + ) + +def get_remote_chrome(playwright, request) -> Browser: + return playwright.chromium.launch( + headless=True, + slow_mo=request.config.getoption("slow") + ) + + +def get_context(browser, request, start) -> BrowserContext: + if start == 'local': + context = browser.new_context( + no_viewport=True, + locale=request.config.getoption('l') + ) + context.set_default_timeout( + timeout=request.config.getoption('t') + ) + # context.add_cookies([{'url': 'https://example.ru', 'name': 'ab_test', 'value': 'd'}]) добавляем куки, если нужны + return context + + elif start == 'remote': + context = browser.new_context( + viewport=request.config.getoption('s'), + locale=request.config.getoption('l') + ) + context.set_default_timeout( + timeout=request.config.getoption('t') + ) + # context.add_cookies([{'url': 'https://example.ru', 'name': 'ab_test', 'value': 'd'}]) добавляем куки, если нужны + return context + + + +@pytest.fixture(scope="function") +def return_back(browser): + browser.go_back() \ No newline at end of file diff --git a/pages/__pycache__/base_page.cpython-313.pyc b/pages/__pycache__/base_page.cpython-313.pyc new file mode 100644 index 0000000..360b8c7 Binary files /dev/null and b/pages/__pycache__/base_page.cpython-313.pyc differ diff --git a/pages/__pycache__/configuration_page.cpython-313.pyc b/pages/__pycache__/configuration_page.cpython-313.pyc new file mode 100644 index 0000000..9a661ec Binary files /dev/null and b/pages/__pycache__/configuration_page.cpython-313.pyc differ diff --git a/pages/__pycache__/login_page.cpython-313.pyc b/pages/__pycache__/login_page.cpython-313.pyc new file mode 100644 index 0000000..99b3449 Binary files /dev/null and b/pages/__pycache__/login_page.cpython-313.pyc differ diff --git a/pages/__pycache__/main_page.cpython-313.pyc b/pages/__pycache__/main_page.cpython-313.pyc new file mode 100644 index 0000000..8901742 Binary files /dev/null and b/pages/__pycache__/main_page.cpython-313.pyc differ diff --git a/pages/__pycache__/session_tab.cpython-313.pyc b/pages/__pycache__/session_tab.cpython-313.pyc new file mode 100644 index 0000000..c680e99 Binary files /dev/null and b/pages/__pycache__/session_tab.cpython-313.pyc differ diff --git a/pages/__pycache__/users_tab.cpython-313.pyc b/pages/__pycache__/users_tab.cpython-313.pyc new file mode 100644 index 0000000..32cf998 Binary files /dev/null and b/pages/__pycache__/users_tab.cpython-313.pyc differ diff --git a/pages/base_page.py b/pages/base_page.py new file mode 100644 index 0000000..0cf5c12 --- /dev/null +++ b/pages/base_page.py @@ -0,0 +1,296 @@ +from playwright.sync_api import Page, TimeoutError, Response, APIRequestContext, expect +from data.environment import host +from Locators.base_page import BasePageLocators +import time +import json + +class BasePage: + def __init__(self, page: Page): + self.page = page + self.token = "" + + def open(self, uri) -> Response | None: + return self.page.goto(f"{host.get_base_url()}{uri}", wait_until='domcontentloaded') + + def get_api_request_context(self) -> APIRequestContext: + return self.page.context.request + + #return page url + def current_url(self) -> str: + return self.page.url + + # click on element + def click(self, locator: str) -> None: + self.page.click(locator) + + # input in field + def input(self, locator: str, data: str) -> None: + self.page.locator(locator).fill(data) + + # clear input field + def clear_input(self, locator: str) -> None: + self.page.locator(locator).press('Control+A') + self.page.locator(locator).press('Backspace') + + def page_reload(self) ->None: + self.page.reload() + + # wait for element + def wait_for_element(self, locator, timeout=12000) -> None: + self.page.wait_for_selector(locator, timeout=timeout) + + # wait for all elements + def wait_for_all_elements(self, locator, timeout=5000): + elements = self.page.query_selector_all(locator) + + for element in elements: + self.page.wait_for_selector(locator, timeout=timeout) + + return elements + + # if element presents then ok + def is_element_present(self, locator: str, timeout: int = 5000) -> bool: + try: + self.page.wait_for_selector(locator, timeout=timeout) + except TimeoutError: + return False + return True + + # if element is not present then ok + def is_element_NOT_presence(self, locator: str, timeout: int = 5000) -> bool: + try: + self.page.wait_for_selector(locator, timeout=timeout) + except TimeoutError: + return True + return False + + # get text. If we have once locator then index is 0 + def get_text(self, locator: str, index: int) -> str: + return self.page.locator(locator).nth(index).text_content() + + # Traverse the table + def read_table_data(self, locator) -> []: + table_data = [] + + table = self.page.locator(locator) + + # read table header + header_cells = table.locator(BasePageLocators.TABLE_HEADERS_CELLS) + header_data = [] + for h in range(header_cells.count()): + header_cell_text = header_cells.nth(h).inner_text() + header_data.append(header_cell_text) + table_data.append(header_data) + + # read table cells by rows + rows = table.locator(BasePageLocators.TABLE_BODY) + for i in range(rows.count()): + row = rows.nth(i) + cells = row.locator("td") + row_data = [] + for j in range(cells.count()): + cell_text = cells.nth(j).inner_text() + row_data.append(cell_text) + table_data.append(row_data) + + return table_data + + # send get request to api + def send_get_api_request(self, uri): + api_request_context = self.get_api_request_context() + token = host.get_access_token() + headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"} + response = api_request_context.get(f"{host.get_base_url()}{uri}", headers = headers) + return response + + # send get request to api + def send_post_api_request(self, uri, payload): + api_request_context = self.get_api_request_context() + token = host.get_access_token() + headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"} + response = api_request_context.post(f"{host.get_base_url()}{uri}", headers = headers, data=payload) + return response + + # get response body + def get_response_body(self, response): + try: + response_body = response.json() + except json.JSONDecodeError: + print("Failed to decode JSON response") + return None + return response_body + + def should_be_horizontal_scroll(self) -> None: + """ + Проверяет горизонтальный скролл с плавной прокруткой. + """ + modal_selector = "div.v-dialog.v-dialog--active" + modal = self.page.locator(modal_selector).first + + try: + # Ожидаем появления окна + modal.wait_for(state="visible", timeout=5000) + + # Получаем размеры через page.evaluate + scroll_data = self.page.evaluate("""() => { + const modal = document.querySelector('div.v-dialog.v-dialog--active'); + return { + scrollWidth: modal.scrollWidth, + clientWidth: modal.clientWidth, + scrollLeft: modal.scrollLeft + }; + }""") + + print(f"Окно: Общая ширина: {scroll_data['scrollWidth']}px, " + f"Видимая область: {scroll_data['clientWidth']}px, " + f"Начальная позиция: {scroll_data['scrollLeft']}px") + + if scroll_data['scrollWidth'] > scroll_data['clientWidth']: + print("Тестируем скролл...") + + # Плавный скролл вправо + self.page.evaluate("""() => { + const modal = document.querySelector('div.v-dialog.v-dialog--active'); + return new Promise(resolve => { + const target = modal.scrollWidth - modal.clientWidth; + const duration = 1000; + const start = modal.scrollLeft; + const startTime = performance.now(); + + function scrollStep(timestamp) { + const progress = (timestamp - startTime) / duration; + modal.scrollLeft = start + (target - start) * Math.min(progress, 1); + if (progress < 1) { + window.requestAnimationFrame(scrollStep); + } else { + resolve(); + } + } + window.requestAnimationFrame(scrollStep); + }); + }""") + + # Проверяем конечную позицию + final_scroll = self.page.evaluate("""() => { + return document.querySelector('div.v-dialog.v-dialog--active').scrollLeft; + }""") + print("Успешно проскроллили вправо") + + # Плавный скролл влево + self.page.evaluate("""() => { + const modal = document.querySelector('div.v-dialog.v-dialog--active'); + return new Promise(resolve => { + const duration = 1000; + const start = modal.scrollLeft; + const startTime = performance.now(); + + function scrollStep(timestamp) { + const progress = (timestamp - startTime) / duration; + modal.scrollLeft = start - start * Math.min(progress, 1); + if (progress < 1) { + window.requestAnimationFrame(scrollStep); + } else { + resolve(); + } + } + window.requestAnimationFrame(scrollStep); + }); + }""") + + # Проверяем возврат в начало + final_scroll = self.page.evaluate("""() => { + return document.querySelector('div.v-dialog.v-dialog--active').scrollLeft; + }""") + assert final_scroll == 0, "Не удалось вернуться в начальную позицию скролла" + print("Успешно проскроллили влево") + else: + print("Окно не требует горизонтального скролла.") + + except Exception as e: + print(f"Ошибка при проверке скролла окна: {str(e)}") + raise + + def should_be_vertical_scroll(self, wrapper_selector: str = "div.scrolltable__wrapper") -> None: + """ + Проверяет вертикальный скролл в указанном контейнере. + Args: + wrapper_selector: CSS-селектор скроллируемого контейнера. + По умолчанию: "div.scrolltable__wrapper". + """ + # Ожидаем загрузки таблицы + table_wrapper = self.page.locator(wrapper_selector).first + table_wrapper.wait_for(state="visible", timeout=10000) + + try: + # Пытаемся найти скроллируемый элемент с небольшим таймаутом + scrollable_element = table_wrapper.locator("tbody.scrolltable__scroll").first + scrollable_element.wait_for(state="attached", timeout=3000) + + # Проверяем параметры скролла + scroll_height = scrollable_element.evaluate("el => el.scrollHeight") + client_height = scrollable_element.evaluate("el => el.clientHeight") + scroll_data = scrollable_element.evaluate("el => el.scrollLeft") + + + print(f"Окно: Общая ширина: {scroll_height}px, " + f"Видимая область: {client_height}px, " + f"Начальная позиция: {scroll_data}px") + + if scroll_height > client_height: + print("Тестируем скролл...") + + # 1. Плавный скролл вниз + scrollable_element.evaluate("""el => { + const target = el.scrollHeight; + const duration = 1000; + const start = el.scrollTop; + const startTime = performance.now(); + + function scrollStep(timestamp) { + const progress = (timestamp - startTime) / duration; + el.scrollTop = start + (target - start) * Math.min(progress, 1); + if (progress < 1) { + window.requestAnimationFrame(scrollStep); + } + } + window.requestAnimationFrame(scrollStep); + }""") + time.sleep(1.5) # Ожидаем завершения скролла + + # Проверяем последнюю строку + last_row = table_wrapper.locator("tbody tr").last + expect(last_row).to_be_visible() + print("Успешно проскроллили вниз") + + # 2. Плавный скролл вверх + scrollable_element.evaluate("""el => { + const duration = 1000; + const start = el.scrollTop; + const startTime = performance.now(); + + function scrollStep(timestamp) { + const progress = (timestamp - startTime) / duration; + el.scrollTop = start - start * Math.min(progress, 1); + if (progress < 1) { + window.requestAnimationFrame(scrollStep); + } + } + window.requestAnimationFrame(scrollStep); + }""") + time.sleep(1.5) # Ожидаем завершения скролла + + # Проверяем первую строку + first_row = table_wrapper.locator("tbody tr").first + expect(first_row).to_be_visible() + print("Успешно проскроллили вверх") + else: + print("Весь контент виден без скролла") + + except Exception as e: + print(f"Элемент для скролла не найден или не скроллится: {e}") + # Проверяем, есть ли хотя бы строки в таблице + rows = table_wrapper.locator("tbody tr") + if rows.count() > 0: + print(f"Найдено {rows.count()} строк в таблице без скролла") + else: + print("Таблица пуста или не найдена") \ No newline at end of file diff --git a/pages/configuration_page.py b/pages/configuration_page.py new file mode 100644 index 0000000..d12b6f8 --- /dev/null +++ b/pages/configuration_page.py @@ -0,0 +1,49 @@ +from pages.base_page import BasePage +from Locators.configuration_page import ConfigurationPageLocators +from data.assertions import Assertions +from playwright.sync_api import Page + +import json + +class ConfigurationPage(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertion = Assertions(page) + + def should_be_configuration_navigation_panel(self): + self.assertion.check_presence(ConfigurationPageLocators.CONFIG_NAVIGATION_PANEL, \ + "Configuration navigation panel is not present on the Configuration page") + def should_be_maintenance_navigation_panel(self): + self.assertion.check_presence(ConfigurationPageLocators.MAINTENANCE_NAVIGATION_PANEL, \ + "Maintenance navigation panel is not present on the Configuration page") + + def click_configuration_navigation_panel_item(self, item_name): + button_locator = None + + if item_name == "users": + button_locator = ConfigurationPageLocators.CONFIG_NAVIGATION_PANEL_USER_BUTTON + elif item_name == "notification": + button_locator = ConfigurationPageLocators.CONFIG_NAVIGATION_PANEL_NOTIFICATION_BUTTON + elif item_name == "maintenance": + button_locator = ConfigurationPageLocators.CONFIG_NAVIGATION_PANEL_MAINTENANCE_BUTTON + elif item_name == "ztp": + button_locator = ConfigurationPageLocators.CONFIG_NAVIGATION_PANEL_ZTP_BUTTON + else: + assert False, "Unsupported configuration navigation panel item" + + self.click(button_locator) + + def click_maintenance_navigation_panel_item(self, item_name): + button_locator = None + + if item_name == "session": + button_locator = ConfigurationPageLocators.MAINTENANCE_NAVIGATION_PANEL_SESSION_BUTTON + elif item_name == "service_status": + button_locator = ConfigurationPageLocators.MAINTENANCE_NAVIGATION_PANEL_SERVICE_STATUS_BUTTON + elif item_name == "licensing": + button_locator = ConfigurationPageLocators.MAINTENANCE_NAVIGATION_PANEL_LICENSING_BUTTON + else: + assert False, "Unsupported configuration navigation panel item" + + self.click(button_locator) + \ No newline at end of file diff --git a/pages/email_tab.py b/pages/email_tab.py new file mode 100644 index 0000000..23c4fe9 --- /dev/null +++ b/pages/email_tab.py @@ -0,0 +1,9 @@ +from pages.base_page import BasePage +# from Locators.email_tab import EmailTabLocators +from data.assertions import Assertions +from playwright.sync_api import Page + +class EmailTab(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertion = Assertions(page) diff --git a/pages/license_tab.py b/pages/license_tab.py new file mode 100644 index 0000000..67c48b1 --- /dev/null +++ b/pages/license_tab.py @@ -0,0 +1,102 @@ +from pages.base_page import BasePage +from Locators.configuration_page import ConfigurationPageLocators +from Locators.license_tab import LicenseTabLocators +from data.assertions import Assertions +from playwright.sync_api import Page + +import json + +class LicenseTab(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertion = Assertions(page) + + def fill_license_input_form(self, value): + button_text = "Обновить лицензию" + + self.clear_input(LicenseTabLocators.LICENSE_INPUT_FORM_TEXTAREA) + self.input(LicenseTabLocators.LICENSE_INPUT_FORM_TEXTAREA, value) + + self.page.get_by_role("button", name=button_text).click() + + def should_be_error_alert_window_with_text(self, text): + self.assertion.check_alert_window_with_text("error", text) + + def should_be_license_work_area(self): + self.assertion.have_title(ConfigurationPageLocators.WORK_AREA_TITLE, "Лицензирование", \ + "Expected work area page title is not equal real title") + self.should_be_json_content() + self.should_be_license_input_form() + + def should_be_license_input_form(self): + # get form title and compare with cmdb value + form_title = "Идентификатор:" + title = self.get_text(LicenseTabLocators.LICENSE_TITLE, 0) + self.assertion.check_equals(title.strip(), form_title, \ + f"Expected input form title {title} is not equal {form_title}") + + device_id = self.get_text(LicenseTabLocators.LICENSE_TITLE_DEVICE_ID, 0).strip() + + response = self.send_get_api_request("e-cmdb/api/lic/deviceid") + response_body = self.get_response_body(response) + + self.assertion.check_equals(device_id, response_body["deviceId"], \ + f"Expected ID value {device_id} is not equal {response_body['deviceId']}") + + # check input form presence + self.assertion.check_presence(LicenseTabLocators.LICENSE_INPUT_FORM_TEXTAREA, \ + "License input form is not present on the License work area") + # check input form button + button_text = "Обновить лицензию" + self.assertion.check_button_presence_with_text(button_text, f"License input form button with text {button_text} is not present") + + def should_be_json_content(self): + def format_json_string(json_string): + substrings = json_string.splitlines() + + formatted_string_list = [] + + last_substring = substrings.pop() + + for substring in substrings: + if substring.find(':') == -1: + if substring == '{': + formatted_string_list.append(substring) + elif substring == '}': + s1 = formatted_string_list.pop() + formatted_string_list.append(s1.rstrip(',')) + formatted_string_list.append(substring+ ',') + 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) + + ## read json_content from work area + json_string = self.page.locator(LicenseTabLocators.JSON_ELEMENT).inner_text() + formatted_json_string = format_json_string(json_string) + expected_json_data = json.loads(formatted_json_string) + + # 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.assertion.check_json_equals(expected_json_data, response_body, \ + "Expected json content is not equal actual:") diff --git a/pages/login_page.py b/pages/login_page.py new file mode 100644 index 0000000..0cb736b --- /dev/null +++ b/pages/login_page.py @@ -0,0 +1,59 @@ +from pages.base_page import BasePage +from data.constants import Constants +from Locators.login import LoginPageLocators +from data.assertions import Assertions +from data.environment import host +from playwright.sync_api import Page + +class LoginPage(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.token = "" + self.assertion = Assertions(page) + + def do_login(self, username: str = None, password: str = None): + """Выполняет вход в систему. + Если username/password не указаны, использует значения из Constants""" + def handle_response(response): + if "login" in response.url: + response_body = self.get_response_body(response) + 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.clear_input(LoginPageLocators.USERNAME_INPUT) + self.input(LoginPageLocators.USERNAME_INPUT, actual_username) + + self.clear_input(LoginPageLocators.PASSWORD_INPUT) + self.input(LoginPageLocators.PASSWORD_INPUT, actual_password) + + self.click(LoginPageLocators.LOGIN_BTN) + + self.assertion.check_URL("dashboard", "An unexpected page has been opened") + + def do_unsuccessful_login(self, username: str = "someuser", password: str = "password"): + """Выполняет попытку входа с неверными учетными данными. + Можно передать свои неверные данные или использовать значения по умолчанию""" + incorrect_credentials_text = "Неверная пара логин/пароль" + + self.open("") + + self.clear_input(LoginPageLocators.USERNAME_INPUT) + self.input(LoginPageLocators.USERNAME_INPUT, username) + + self.clear_input(LoginPageLocators.PASSWORD_INPUT) + self.input(LoginPageLocators.PASSWORD_INPUT, password) + + self.click(LoginPageLocators.LOGIN_BTN) + + self.assertion.check_alert_window_with_text("error", incorrect_credentials_text) + + \ No newline at end of file diff --git a/pages/main_page.py b/pages/main_page.py new file mode 100644 index 0000000..cb42352 --- /dev/null +++ b/pages/main_page.py @@ -0,0 +1,42 @@ +from pages.base_page import BasePage +from Locators.main_page import MainPageLocators +from data.assertions import Assertions +from playwright.sync_api import Page + +class MainPage(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertion = Assertions(page) + + def should_be_navigation_panel(self): + self.assertion.check_presence(MainPageLocators.NAVIGATION_PANEL, "Navigation panel is not present") + + def click_main_navigation_panel_item(self, item_name): + button_locator = None + header_locator = None + + if item_name == "dashboard": + button_locator = MainPageLocators.NAVIGATION_PANEL_DASHBOARD_BUTTON + header_locator = MainPageLocators.NAVIGATION_PANEL_DASHBOARD_BUTTON_HEADER + elif item_name == "topology": + button_locator = MainPageLocators.NAVIGATION_PANEL_TOPOLOGY_BUTTON + header_locator = MainPageLocators.NAVIGATION_PANEL_TOPOLOGY_BUTTON_HEADER + elif item_name == "configuration": + button_locator = MainPageLocators.NAVIGATION_PANEL_CONFIGURATION_BUTTON + header_locator = MainPageLocators.NAVIGATION_PANEL_CONFIGURATION_BUTTON_HEADER + else: + assert False, "Unsupported main navigation panel item" + + self.click(button_locator) + self.assertion.check_URL(item_name, "An unexpected page has been opened") + self.assertion.check_element_active(header_locator, f"{item_name} button is not active") + + def do_logout(self): + self.click(MainPageLocators.CURRENT_USER_BUTTON) + + self.assertion.check_presence(MainPageLocators.CURRENT_USER_WINDOW, "Window with current user data has not been opened") + + logout_button_text = "Выйти" + self.page.get_by_role("button", name=logout_button_text).click() + + self.assertion.check_URL("login", "An unexpected page has been opened") diff --git a/pages/service_status_tab.py b/pages/service_status_tab.py new file mode 100644 index 0000000..4162504 --- /dev/null +++ b/pages/service_status_tab.py @@ -0,0 +1,27 @@ +from pages.base_page import BasePage +from Locators.configuration_page import ConfigurationPageLocators +from data.assertions import Assertions +from playwright.sync_api import Page + +class ServiceStatusTab(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertion = Assertions(page) + + def should_be_service_status_work_area(self): + self.assertion.have_title(ConfigurationPageLocators.WORK_AREA_TITLE, "Статус обслуживания", \ + "Expected work area page title is not equal real title") + + self.should_be_service_status_table() + + + def should_be_service_status_table(self): + headers = ['Контейнер', 'Время создания', 'Статус', 'Время работы', 'Image ID', 'Image ТЭГ'] + + service_status_table = self.read_table_data(ConfigurationPageLocators.WORK_AREA_TABLE) + + service_status_table_headers = service_status_table[0] + self.assertion.check_equals(service_status_table_headers, headers, \ + f"Expected table headers {service_status_table_headers} are not equal {headers}") + self.assertion.check_not_equals(len(service_status_table) - 1, 0, \ + "Service status table is empty") diff --git a/pages/session_tab.py b/pages/session_tab.py new file mode 100644 index 0000000..a3f32f6 --- /dev/null +++ b/pages/session_tab.py @@ -0,0 +1,221 @@ +from pages.base_page import BasePage +from Locators.session_locators import SessionLocators +from data.assertions import Assertions +from playwright.sync_api import Page, expect +import time + +class SessionTab(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertions = Assertions(page) + self.expected_headers = ['ID сессии', 'ID пользователя', 'Время жизни', 'Роль', 'Адрес'] + + def should_be_session_table(self) -> None: + """ Проверка таблицы сессий """ + + self.assertions.have_title( + SessionLocators.TEXT_TITLE, + 'Сессия', + "Заголовок страницы не соответствует ожидаемому" + ) + + # Проверка наименования заголовков таблицы сессий + session_table_data = self.read_table_data(SessionLocators.TABLE_BODY) + actual_headers = session_table_data[0] + + self.assertions.check_equals(actual_headers, self.expected_headers, "Заголовки таблицы не соответствуют ожидаемым") + + # Проверка, что таблица не пустая + self.assertions.check_not_equals(len(session_table_data) - 1, 0, "Таблица сессий пуста") + + # Проверка наличия всех необходимых колонок + required_columns = [ + SessionLocators.TABLE_HEADER_CELL_SESSION_ID, + SessionLocators.TABLE_HEADER_CELL_USER_ID, + SessionLocators.TABLE_HEADER_CELL_LIFETIME, + SessionLocators.TABLE_HEADER_CELL_ROLE, + SessionLocators.TABLE_HEADER_CELL_ADDRESS + ] + + for locator in required_columns: + self.is_element_present(locator, 1000) + + + def should_be_session_table_data_vs_api(self) -> None: + """ Проверка соответствие данных в таблице сессий с данными из API """ + ROLE_MAPPING = { + 'user': 'Пользователь', + 'administrator': 'Администратор' + } + + # Получение данных из UI + ui_session_id = self.get_text(SessionLocators.TABLE_HEADER_CELL_SESSION_ID, 0).strip() + ui_session_userId = self.get_text(SessionLocators.TABLE_HEADER_CELL_USER_ID, 0).strip() + ui_session_roles = self.get_text(SessionLocators.TABLE_HEADER_CELL_ROLE, 0).strip() + ui_session_ip = self.get_text(SessionLocators.TABLE_HEADER_CELL_ADDRESS, 0).strip() + + # Получение данных из API + response = self.send_get_api_request("e-nms/auth/sessions") + response_body = self.get_response_body(response) + + api_session_id = response_body[0].get('id') + api_session_userId = response_body[0].get('userId') + api_session_roles = response_body[0].get('roles', []) + api_session_ip = response_body[0].get('ip') + + # Преобразование и сортировка ролей из API + translated_roles = [ROLE_MAPPING.get(role.lower(), role) for role in api_session_roles] + api_session_roles_str = ', '.join(sorted(translated_roles)) + + # Сортировка ролей из UI для сравнения + ui_roles_sorted = ', '.join(sorted(role.strip() for role in ui_session_roles.split(','))) + + # Проверки соответствия данных + self.assertions.check_equals(ui_session_id, api_session_id, "ID сессии не совпадает") + self.assertions.check_equals(ui_session_userId, api_session_userId, "ID пользователя не совпадает") + self.assertions.check_equals(ui_roles_sorted, api_session_roles_str, "Роли не совпадают") + self.assertions.check_equals(ui_session_ip, api_session_ip, "IP-адрес не совпадает") + + # Логирование для отладки + #print(f"Session ID (UI/API): {ui_session_id}/{api_session_id}") + #print(f"User ID (UI/API): {ui_session_userId}/{api_session_userId}") + #print(f"Roles (UI/API): {ui_session_roles}/{api_session_roles}") + #print(f"IP Address (UI/API): {ui_session_ip}/{api_session_ip}") + + def should_be_new_session_added(self) -> None: + """ + Проверка добавленных записей в таблицу сессий + """ + + def should_be_vertical_scroll_session(self) -> None: + """ Проверка вертикального скролла таблицы сессий """ + + self.should_be_vertical_scroll(SessionLocators.TABLE_SCROLL_CONTAINER) + + + def open_last_session_modal(self) -> None: + """Открывает модальное окно удаления через последнюю строку таблицы сессий""" + print("\n=== Открытие модального окна через последнюю строку ===") + + # Ожидаем загрузки таблицы + self.page.wait_for_selector(SessionLocators.TABLE_BODY, state="visible", timeout=5000) + print("✓ Таблица сессий загружена") + + # Получаем все строки таблицы + rows = self.page.locator(SessionLocators.TABLE_ROWS) + row_count = rows.count() + + if row_count == 0: + raise AssertionError("Таблица сессий пуста, нечего удалять!") + + print(f"Всего строк в таблице: {row_count}") + + # Берём последнюю строку + last_row = rows.last + print("✓ Последняя строка получена") + + # Прокручиваем к последней строке (если нужно) + last_row.scroll_into_view_if_needed() + + # Локатор кнопки удаления в последней строке + delete_button = last_row.locator(SessionLocators.BUTTON_DELETE_SESSION) + + # Проверяем и кликаем кнопку удаления + expect(delete_button).to_be_visible(timeout=5000) + print("Нажимаем кнопку удаления в последней строке...") + delete_button.click() + + # Проверяем появление модального окна + modal = self.page.locator(SessionLocators.MODAL_WINDOW) + expect(modal).to_be_visible(timeout=5000) + print("✓ Модальное окно удаления успешно открыто") + + # Проверка заголовка окна + if hasattr(SessionLocators, 'MODAL_TITLE'): + modal_title = self.page.locator(SessionLocators.MODAL_TITLE) + expect(modal_title).to_contain_text("Удаление") + print("✓ Заголовок модального окна корректен") + + print("=== Модальное окно через последнюю строку открыто успешно ===\n") + + def should_be_horizontal_scroll_session_modal(self) -> None: + """ Проверка горизонтального скролла модального окна """ + self.should_be_horizontal_scroll() + + + def should_be_close_modal_window_by_button(self, button_type: str) -> None: + """ + Проверка кнопок модального окна подтверждения удаления сессии с детальным логированием + :param button_type: Тип кнопки ('close', 'cancel', 'delete') + """ + print(f"\n=== Начало проверки модального окна для кнопки '{button_type}' ===") + + # 1. Проверка видимости модального окна перед действиями + modal = self.page.locator(SessionLocators.MODAL_WINDOW) + expect(modal).to_be_visible() + print("✓ Модальное окно отображается перед взаимодействием") + + if button_type == 'close': + # 2. Нажатие кнопки закрытия (X) + print("Нажимаем кнопку закрытия (X)...") + close_button = self.page.locator(SessionLocators.MODAL_CLOSE_BUTTON) + close_button.click() + + # 3. Проверка закрытия окна + expect(modal).not_to_be_visible() + print("✓ Проверяем, что окно закрылось после нажатия 'X'") + + elif button_type == 'cancel': + # 2. Нажатие кнопки "Отмена" + print("Нажимаем кнопку 'Отмена'...") + cancel_button = self.page.locator(SessionLocators.MODAL_CANCEL_BUTTON) + cancel_button.click() + + # 3. Проверка закрытия окна + expect(modal).not_to_be_visible() + print("✓ Проверяем, что окно закрылось после нажатия 'Отмена'") + + elif button_type == 'delete': + # 2. Подготовка к удалению + rows_before = self.page.locator(SessionLocators.TABLE_ROWS).count() + print(f"Количество строк до удаления: {rows_before}") + + # 3. Нажатие кнопки "Удалить" + print("Нажимаем кнопку 'Удалить'...") + delete_button = self.page.locator(SessionLocators.MODAL_DELETE_BUTTON) + delete_button.click() + """ + # 4. Проверка сообщения об успехе + success_msg = self.page.locator(SessionLocators.SUCCESS_MESSAGE) + expect(success_msg).to_be_visible(timeout=5000) + expect(success_msg).to_contain_text("Сессия успешно удалена") + print("✓ Сообщение об успешном удалении отображается") + """ + # 5. Проверка закрытия окна + expect(modal).not_to_be_visible() + print("✓ Проверяем, что окно закрылось после нажатия 'Удалить'") + """ + # 6. Проверка исчезновения сообщения (если нужно) + expect(success_msg).not_to_be_visible(timeout=10000) + print("✓ Сообщение автоматически скрылось (если предусмотрено UI)") + """ + # 7. Проверка изменения таблицы + rows_after = self.page.locator(SessionLocators.TABLE_ROWS).count() + self.assertions.check_equals(rows_after, rows_before - 1, + "Количество строк не изменилось после удаления") + print(f"✓ Количество строк после удаления: {rows_after} (уменьшилось на 1)") + + print(f"=== Проверка для кнопки '{button_type}' успешно завершена ===\n") + + + def delete_all_created_sessions(self, username) -> None: + """ Проверка удаления созданных ссесий """ + + def delete_current_session(self) -> None: + """ Проверка удаления текущей сессии """ + + + + + + \ No newline at end of file diff --git a/pages/users_tab.py b/pages/users_tab.py new file mode 100644 index 0000000..2f909a0 --- /dev/null +++ b/pages/users_tab.py @@ -0,0 +1,432 @@ +from pages.base_page import BasePage +from Locators.base_page import BasePageLocators +from Locators.configuration_page import ConfigurationPageLocators +from Locators.users_tab import UsersTabLocators +from data.assertions import Assertions +from playwright.sync_api import Page + +import re + +class UsersTab(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertion = Assertions(page) + self.role_dict = {"administrator": "Администратор", + "manager":"Контактное лицо", + "operator":"Оператор", + "inform_secur_user" : "Специалист информационной безопасности"} + + def add_new_user(self, user_data): + fields = user_data.keys() + + self.assertion.check_presence(UsersTabLocators.USER_DATA_INPUT_FORM, \ + "Input form for add new user is not present") + + ## input user name + if "name" in fields: + loc = "xpath=div[2]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["name"]) + + + ## input user role + if "role" in fields: + loc = "xpath=div[3]/div[2]/div/div/div/div/div[1]" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).click() + + self.assertion.check_presence(UsersTabLocators.USER_DATA_INPUT_FORM_ROLES_MENU, \ + "Roles drop-down menu is not present") + role = user_data["role"] + self.assertion.check_menu_item_with_text(role, f"No menu item with text: {role}") + self.page.get_by_role("listitem").filter(has_text=role).click() + + ## input password + if "password" in fields: + loc = "xpath=div[4]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["password"]) + + ## input commentary + if "commentary" in fields: + loc = "xpath=div[5]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["commentary"]) + + ## input e-mail + if "email" in fields: + loc = "xpath=div[6]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["email"]) + + ## input phone number for sms + if "phone_number" in fields: + loc = "xpath=div[7]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["phone_number"]) + + ##to be done: checkbox + + ## click add user button + add_button_text = "Добавить" + self.assertion.check_button_presence_with_text(add_button_text, f"Add user input form button with text {add_button_text} is not present") + self.page.get_by_role("button", name=add_button_text).click() + + ##check add user confirmation dialog + confirm_add_button_text = " Добавить " + self.assertion.check_confirmation_dialog_with_title(UsersTabLocators.USER_ACTION_CONFIRMATION_DIALOG, "Добавить нового пользователя") + self.page.get_by_role("button", name=confirm_add_button_text).first.click() + + ## check message about successfull user addition + self.assertion.check_alert_window_with_text("success", ' Новый пользователь \n успешно добавлен! ') + + + def close_user_window_by_toolbar_button(self, window_title): + BUTTON_CLOSE_ON_TOOLBAR = f"xpath=(.//*[normalize-space(text()) and normalize-space(.)='{window_title}'])[1]/following::*[name()='svg'][1]" + self.assertion.check_presence(BUTTON_CLOSE_ON_TOOLBAR, \ + "Close button is not presenet on toolbar") + self.click(BUTTON_CLOSE_ON_TOOLBAR) + + self.assertion.check_absence(UsersTabLocators.USER_DATA_WORK_AREA_TITLE, \ + f"{window_title} window should be closed") + + def close_user_window(self, window_title): + close_button_text = "Закрыть" + self.assertion.check_button_presence_with_text(close_button_text, f"Edit user input form button with text {close_button_text} is not present") + self.page.get_by_role("button", name=close_button_text).click() + + self.assertion.check_absence(UsersTabLocators.USER_DATA_WORK_AREA_TITLE, \ + f"{window_title} window should be closed") + + def delete_user(self): + remove_button_text = "Удалить" + self.assertion.check_button_presence_with_text(remove_button_text, f"Edit user input form button with text {remove_button_text} is not present") + self.page.get_by_role("button", name=remove_button_text).click() + + ##check add user confirmation dialog + confirm_remove_button_text = " Удалить " + self.assertion.check_confirmation_dialog_with_title(UsersTabLocators.USER_ACTION_CONFIRMATION_DIALOG, "Удаление") + self.page.get_by_role("button", name=confirm_remove_button_text).first.click() + + ## check message about successfull user deletion + self.assertion.check_alert_window_with_text("success", '\nПользователь удалён\n') + + def edit_user(self, user_data): + fields = user_data.keys() + + self.assertion.check_presence(UsersTabLocators.USER_DATA_INPUT_FORM, \ + "Input form for edit user is not present") + + ## input user name + if "name" in fields: + loc = "xpath=div[2]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["name"]) + + + ## input user role + if "role" in fields: + loc = "xpath=div[3]/div[2]/div/div/div/div/div[1]" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).click() + + self.assertion.check_presence(UsersTabLocators.USER_DATA_INPUT_FORM_ROLES_MENU, \ + "Roles drop-down menu is not present") + role = user_data["role"] + self.assertion.check_menu_item_with_text(role, f"No menu item with text: {role}") + self.page.get_by_role("listitem").filter(has_text=role).click() + + ## input commentary + if "commentary" in fields: + loc = "xpath=div[5]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["commentary"]) + + ## input e-mail + if "email" in fields: + loc = "xpath=div[5]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["email"]) + + ## input phone number for sms + if "phone_number" in fields: + loc = "xpath=div[6]/div[2]/div/div/div/div/input" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).fill(user_data["phone_number"]) + + ##to be done: checkbox + + ## click add user button + add_button_text = "Сохранить" + self.assertion.check_button_presence_with_text(add_button_text, f"Add user input form button with text {add_button_text} is not present") + self.page.get_by_role("button", name=add_button_text).click() + + ##check add user confirmation dialog + confirm_add_button_text = " Сохранить " + self.assertion.check_confirmation_dialog_with_title(UsersTabLocators.USER_ACTION_CONFIRMATION_DIALOG, "Сохранение") + self.page.get_by_role("button", name=confirm_add_button_text).first.click() + + ## check message about successfull user addition. Temporarily without translation + self.assertion.check_alert_window_with_text("success", '\nupdate success\n') + + + def open_add_user_page(self): + self.assertion.check_presence(UsersTabLocators.TOOLBAR_EDIT_BUTTON, \ + "Edit button is not presenet on toolbar") + self.click(UsersTabLocators.TOOLBAR_EDIT_BUTTON) + self.assertion.check_presence(UsersTabLocators.TOOLBAR_ADD_USER_BUTTON, \ + "Add User button is not presenet on toolbar") + + self.click(UsersTabLocators.TOOLBAR_ADD_USER_BUTTON) + + # check that new work area with title has been opened + self.assertion.have_title(UsersTabLocators.USER_DATA_WORK_AREA_TITLE, "Добавить нового пользователя", \ + "Expected work area page title is not equal real title") + + def open_edit_user_page_by_index(self, row_index): + ## temporarily + tmp_dict = {"admin":"Администратор", "manager":"Контактное лицо", "operator":"Оператор"} + + users_table = self.read_table_data(ConfigurationPageLocators.WORK_AREA_TABLE) + self.assertion.check_not_equals(len(users_table) - 1, 0, "Users table is empty") + + # remove header + del users_table[0] + + # check row_index + if row_index > len(users_table): + assert False, "Row_index is out of range" + + # get user name and role + user_name = users_table[row_index][0] + for key, val in tmp_dict.items(): + if user_name == val: + user_name = key + + role = users_table[row_index][2] + + # click to found table row + self.page.locator(ConfigurationPageLocators.WORK_AREA_TABLE).locator(BasePageLocators.TABLE_BODY).nth(row_index).click() + + # check that edit user work area with user name title has been opened + self.assertion.have_title(UsersTabLocators.USER_DATA_WORK_AREA_TITLE, user_name, \ + "Expected edit user work area page title is not equal real title") + return user_name, role + + def reset_password(self): + new_password = "" + + reset_password_button_text = "Сбросить пароль" + self.assertion.check_button_presence_with_text(reset_password_button_text, f"Edit user input form button with text {reset_password_button_text} is not present") + self.page.get_by_role("button", name=reset_password_button_text).click() + + alert_message = self.get_text(BasePageLocators.ALERT_WINDOW_TEXT_SUCCESS, 0) + if len(alert_message) > 0: + new_password = re.findall(r'[\d]+', alert_message)[0] + + return new_password + + def open_edit_user_page_by_name(self, name, role): + row_index = self.find_user_in_table(name, role) + if row_index == -1: + assert False, f"User with name {name} and role {role} has not been found" + + # click to found table row + self.page.locator(ConfigurationPageLocators.WORK_AREA_TABLE).locator(BasePageLocators.TABLE_BODY).nth(row_index).click() + + # check that edit user work area with user name title has been opened + self.assertion.have_title(UsersTabLocators.USER_DATA_WORK_AREA_TITLE, name, \ + "Expected edit user work area page title is not equal real title") + + def should_be_users_work_area(self): + self.assertion.have_title(ConfigurationPageLocators.WORK_AREA_TITLE, "Пользователи", \ + "Expected work area page title is not equal real title") + + self.assertion.check_presence(UsersTabLocators.TOOLBAR_EDIT_BUTTON, \ + "Edit button is not presenet on toolbar") + self.should_be_users_table() + + def should_be_users_page_toolbar_buttons(self): + self.assertion.check_presence(UsersTabLocators.TOOLBAR_EDIT_BUTTON, \ + "Edit button is not presenet on toolbar") + self.assertion.check_tooltip_with_text(UsersTabLocators.TOOLBAR_EDIT_BUTTON, "Редактировать") + + self.click(UsersTabLocators.TOOLBAR_EDIT_BUTTON) + self.assertion.check_presence(UsersTabLocators.TOOLBAR_ADD_USER_BUTTON, \ + "Add User button is not presenet on toolbar") + self.assertion.check_presence(UsersTabLocators.TOOLBAR_CLOSE_BUTTON, \ + "Close button is not presenet on toolbar") + + self.assertion.check_tooltip_with_text(UsersTabLocators.TOOLBAR_ADD_USER_BUTTON, "Добавить") + self.assertion.check_tooltip_with_text(UsersTabLocators.TOOLBAR_CLOSE_BUTTON, "Закрыть") + + self.click(UsersTabLocators.TOOLBAR_CLOSE_BUTTON) + self.assertion.check_presence(UsersTabLocators.TOOLBAR_EDIT_BUTTON, \ + "Edit button is not presenet on toolbar") + + def should_be_add_user_work_area(self): + self.assertion.have_title(UsersTabLocators.USER_DATA_WORK_AREA_TITLE, "Добавить нового пользователя", \ + "Expected add user window title is not equal real title") + self.assertion.check_presence(UsersTabLocators.ADD_USER_WORK_AREA_CLOSE_BUTTON, \ + "Close button is not presenet on toolbar") + self.assertion.check_tooltip_with_text(UsersTabLocators.ADD_USER_WORK_AREA_CLOSE_BUTTON, "Закрыть") + + # check input form + self.should_be_add_user_input_form() + + # check input form buttons + add_button_text = "Добавить" + self.assertion.check_button_presence_with_text(add_button_text, f"Add user input form button with text {add_button_text} is not present") + + close_button_text = "Закрыть" + self.assertion.check_button_presence_with_text(close_button_text, f"Add user input form button with text {close_button_text} is not present") + + def should_be_edit_user_work_area(self, user_name, role): + EDIT_USER_WORK_AREA_CLOSE_BUTTON = f"xpath=(.//*[normalize-space(text()) and normalize-space(.)='{user_name}'])[1]/following::*[name()='svg'][1]" + + self.assertion.have_title(UsersTabLocators.USER_DATA_WORK_AREA_TITLE, f"{user_name}", \ + "Expected edit user window title is not equal real title") + + self.assertion.check_presence(EDIT_USER_WORK_AREA_CLOSE_BUTTON, \ + "Close button is not presenet on toolbar") + self.assertion.check_tooltip_with_text(EDIT_USER_WORK_AREA_CLOSE_BUTTON, "Закрыть") + + # check edit user input form + self.should_be_edit_user_input_form(user_name, role) + + # check input form buttons + save_button_text = "Сохранить" + self.assertion.check_button_presence_with_text(save_button_text, f"Edit user input form button with text {save_button_text} is not present") + + remove_button_text = "Удалить" + self.assertion.check_button_presence_with_text(remove_button_text, f"Edit user input form button with text {remove_button_text} is not present") + + reset_password_button_text = "Сбросить пароль" + self.assertion.check_button_presence_with_text(reset_password_button_text, f"Edit user input form button with text {reset_password_button_text} is not present") + + close_button_text = "Закрыть" + self.assertion.check_button_presence_with_text(close_button_text, f"Edit user input form button with text {close_button_text} is not present") + + def should_be_users_table(self): + headers = ['Имя пользователя', 'hash_password', 'Роль', 'E-mail', 'Номер для СМС'] + + users_table = self.read_table_data(ConfigurationPageLocators.WORK_AREA_TABLE) + + users_table_headers = users_table[0] + self.assertion.check_equals(users_table_headers, headers, \ + f"Expected table headers {users_table_headers} are not equal {headers}") + self.assertion.check_not_equals(len(users_table) - 1, 0, \ + "Users table is empty") + self.verify_users_table_content(users_table) + + def should_be_add_user_input_form(self): + self.assertion.check_presence(UsersTabLocators.USER_DATA_INPUT_FORM, \ + "Input form for add new user is not present") + + n = 7 + for i in range (1, n + 1): + loc = f"xpath=div[{i}]/div[2]/div/div/div/div/input" + input_area = self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc) + if i == 1: + checked = self.page.get_by_role("checkbox").first.is_checked() + self.assertion.check_equals(checked, False, "Checkbox is checked by default") + + loc = f"xpath={UsersTabLocators.USER_DATA_INPUT_FORM}/div[1]/div[2]/div/div/div{UsersTabLocators.USER_DATA_INPUT_FORM_NOTIFICATION_LABEL}" + self.assertion.have_text(loc, "ad", \ + "Label for ad is not present") + elif i == 3: + loc = f"xpath=div[{i}]/div[2]/div/div/div/div/div[1]" + self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).click() + + self.assertion.check_presence(UsersTabLocators.USER_DATA_INPUT_FORM_ROLES_MENU, \ + "Roles drop-down menu is not present") + roles = self.role_dict.values() + + for role in roles: + self.assertion.check_menu_item_with_text(role, f"No menu item with text: {role}") + else: + self.assertion.check_empty_input_area(input_area, "No empty input area") + + checked = self.page.get_by_role("checkbox").nth(1).is_checked() + self.assertion.check_equals(checked, False, "Checkbox is checked by default") + + loc = f"xpath={UsersTabLocators.USER_DATA_INPUT_FORM}/div[8]/div/div/div/div{UsersTabLocators.USER_DATA_INPUT_FORM_NOTIFICATION_LABEL}" + self.assertion.have_text(loc, "Подписка на Push-уведомления", \ + "Label for push-notifications subscribtion is not present") + + def should_be_edit_user_input_form(self, user_name, role): + self.assertion.check_presence(UsersTabLocators.USER_DATA_INPUT_FORM, \ + "Input form for edit user is not present") + + loc = "xpath=div[2]/div[2]/div/div/div/div/input" + text_value = self.page.locator(UsersTabLocators.USER_DATA_INPUT_FORM).locator(loc).input_value() + + + self.assertion.check_equals(text_value, user_name, "Expected user name is not equal real user name") + + self.assertion.check_menu_item_with_text(role, f"No menu item with text: {role}") + + def should_be_user_in_table(self, 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" + + def should_not_be_user_in_table(self, 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" + + def find_user_in_table(self, name, role): + users_table = self.read_table_data(ConfigurationPageLocators.WORK_AREA_TABLE) + self.assertion.check_not_equals(len(users_table) - 1, 0, "Users table is empty") + + # remove header + del users_table[0] + + not_found_index = -1 + row_index = 0 + + for user_info in users_table: + if name in user_info and role in user_info: + return row_index + row_index += 1 + return not_found_index + + def verify_users_table_content(self, users_table): + expected_users_list = [] + + ## temporarily + tmp_dict = {"admin":"Администратор", "manager":"Контактное лицо", "operator":"Оператор"} + + query = {"id": ["/catalogs/user"], "data": {"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 = [] + + ## temporarily + user_name = item["name"] + if user_name in tmp_dict.keys(): + item["name"] = tmp_dict[user_name] + + user_info.append(item["name"]) + if item["password"] is not None: + user_info.append(item["password"]) + else: + user_info.append("") + if item["role"] is not None: + role = item["role"] + if role in self.role_dict.keys(): + item["role"] = self.role_dict[role] + 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) + + # remove header + del users_table[0] + + self.assertion.check_lists_equals(users_table, expected_users_list, \ + "Actual users list is not equal users list from db") + + diff --git a/pages/ztp_configuration_tab.py b/pages/ztp_configuration_tab.py new file mode 100644 index 0000000..e756a82 --- /dev/null +++ b/pages/ztp_configuration_tab.py @@ -0,0 +1,9 @@ +from pages.base_page import BasePage +# from Locators.ztp_tab import ZTPTabLocators +from data.assertions import Assertions +from playwright.sync_api import Page + +class ZTPConfigurationTab(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertion = Assertions(page) diff --git a/pages/ztp_templates_tab.py b/pages/ztp_templates_tab.py new file mode 100644 index 0000000..ce1809e --- /dev/null +++ b/pages/ztp_templates_tab.py @@ -0,0 +1,9 @@ +from pages.base_page import BasePage +# from Locators.ztp_tab import ZTPTabLocators +from data.assertions import Assertions +from playwright.sync_api import Page + +class ZTPTemplatesTab(BasePage): + def __init__(self, page: Page) -> None: + super().__init__(page) + self.assertion = Assertions(page) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..e167e42 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +markers = + develop: current test development +addopts = -v -s \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2a57ba0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +pytest +playwright +requests +qase-pytest==4.2.0 +python-dotenv +jsondiff + diff --git a/tests/__pycache__/test_session_tab.cpython-313-pytest-8.3.5.pyc b/tests/__pycache__/test_session_tab.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 0000000..c8f64fb Binary files /dev/null and b/tests/__pycache__/test_session_tab.cpython-313-pytest-8.3.5.pyc differ diff --git a/tests/test_license_tab.py b/tests/test_license_tab.py new file mode 100644 index 0000000..5d36c78 --- /dev/null +++ b/tests/test_license_tab.py @@ -0,0 +1,99 @@ +import pytest +from pages.login_page import LoginPage +from pages.main_page import MainPage +from pages.configuration_page import ConfigurationPage +from pages.license_tab import LicenseTab + +import uuid + +## @pytest.mark.smoke +class TestLicenseTab: + def test_license_tab_content(self, browser): + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Technical Maintenance button in configuration navigation panel + cp.click_configuration_navigation_panel_item("maintenance") + + # check Maintenance navigation panel presence + cp.should_be_maintenance_navigation_panel() + + # click to Service Status button in configuration navigation panel + cp.click_maintenance_navigation_panel_item("licensing") + + # license tab has been opened + lt = LicenseTab(browser) + + # check service status work area + lt.should_be_license_work_area() + + def test_license_tab_input_form_and_check_alert(self, browser): + def gen_test_data(): + 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("0") + data.append("000000000000000000000000000000000000000000000000") + data.append("-1") + + return data + + + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Technical Maintenance button in configuration navigation panel + cp.click_configuration_navigation_panel_item("maintenance") + + # check Maintenance navigation panel presence + cp.should_be_maintenance_navigation_panel() + + # click to Service Status button in configuration navigation panel + cp.click_maintenance_navigation_panel_item("licensing") + + # license tab has been opened + lt = LicenseTab(browser) + + # check that license input form presents + lt.should_be_license_input_form() + + # input empty string + lt.fill_license_input_form("") + # check alert window + lt.should_be_error_alert_window_with_text("Неверный лицензионный ключ") + + data = gen_test_data() + + for data_string in data: + # input incorrect value + lt.fill_license_input_form(data_string) + # check alert window + lt.should_be_error_alert_window_with_text("Ошибка обновления лицензии") diff --git a/tests/test_login.py b/tests/test_login.py new file mode 100644 index 0000000..df7ee64 --- /dev/null +++ b/tests/test_login.py @@ -0,0 +1,23 @@ +import pytest +from pages.login_page import LoginPage +from pages.main_page import MainPage + +## @pytest.mark.smoke +class TestLogin: + def test_successful_login(self, browser): + lp = LoginPage(browser) + lp.do_login() + + def test_unsuccessful_login(self, browser): + lp = LoginPage(browser) + lp.do_unsuccessful_login() + + def test_successful_login_and_logout(self, browser): + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # do logout + mp.do_logout() diff --git a/tests/test_scroll_tab.py b/tests/test_scroll_tab.py new file mode 100644 index 0000000..beffbfd --- /dev/null +++ b/tests/test_scroll_tab.py @@ -0,0 +1,54 @@ +import pytest +import time +from pages.login_page import LoginPage +from pages.main_page import MainPage +from pages.configuration_page import ConfigurationPage +from pages.session_tab import SessionTab +from pages.scroll_tab import ScrollTab + + +## @pytest.mark.smoke +class TestSessionTab: + def test_session_tab_content(self, browser): + lp1 = LoginPage(browser) + lp1.do_login() + #''' + users = [f"User{i}" for i in range(1, 9)] # Генерация списка пользователей + + for username in users: + login_page = LoginPage(browser) + login_page.do_login(username=username, password="admin") + #''' + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + sc = ScrollTab(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + sc.check_vertical_scroll("//*[@id='app']/div[150]/div/div[1]/div/div[1]/div/div[2]/div/div/div/div/div/div/div") + + # click to Technical Maintenance button in configuration navigation panel + cp.click_configuration_navigation_panel_item("maintenance") + cp.should_be_maintenance_navigation_panel() + + # click to Session button in configuration navigation panel + cp.click_maintenance_navigation_panel_item("session") + sc.check_vertical_scroll("div.layout.white.column.fill-height") + + + # click to Service Status button in configuration navigation panel + cp.click_maintenance_navigation_panel_item("service_status") + sc.check_vertical_scroll("//*[@id='app']/div[151]/div/div[1]/div/div[1]/div/div[2]/div/div/div/div/div/div/div") + + + + diff --git a/tests/test_service_status_tab.py b/tests/test_service_status_tab.py new file mode 100644 index 0000000..2c1e85d --- /dev/null +++ b/tests/test_service_status_tab.py @@ -0,0 +1,39 @@ +import pytest +from pages.login_page import LoginPage +from pages.main_page import MainPage +from pages.configuration_page import ConfigurationPage +from pages.service_status_tab import ServiceStatusTab + +## @pytest.mark.smoke +class TestServiceStatusTab: + def test_service_status_tab_content(self, browser): + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Technical Maintenance button in configuration navigation panel + cp.click_configuration_navigation_panel_item("maintenance") + + # check Maintenance navigation panel presence + cp.should_be_maintenance_navigation_panel() + + # click to Service Status button in configuration navigation panel + cp.click_maintenance_navigation_panel_item("service_status") + + # service status tab has been opened + sst = ServiceStatusTab(browser) + + # check service status work area + sst.should_be_service_status_work_area() + diff --git a/tests/test_session_tab.py b/tests/test_session_tab.py new file mode 100644 index 0000000..9134b67 --- /dev/null +++ b/tests/test_session_tab.py @@ -0,0 +1,162 @@ +from pages.login_page import LoginPage +from pages.main_page import MainPage +from pages.users_tab import UsersTab +from pages.configuration_page import ConfigurationPage +from pages.session_tab import SessionTab + +import pytest + +## @pytest.mark.smoke # Закомментированная метка для smoke-тестов +class TestSessionTab: + + def test_add_users(self, browser): + """Тест создания 9 пользователей User1..User9 с ролью Администратор""" + # Переходим на страницу авторизации + lp = LoginPage(browser) + # Выполняем вход в систему (по умолчанию) + lp.do_login() + + # Переходим на главную страницу + mp = MainPage(browser) + + # Проверяем наличие панели навигации + mp.should_be_navigation_panel() + + # Нажимаем на кнопку "Configuration" в главной панели навигации + mp.click_main_navigation_panel_item("configuration") + + # Переходим на страницу конфигурации + cp = ConfigurationPage(browser) + + # Нажимаем вкладку "Users" в панели навигации конфигурации + cp.click_configuration_navigation_panel_item("users") + + # Переходим на вкладку пользователей + ut = UsersTab(browser) + + # Создаем 9 пользователей + for i in range(1, 10): + user_data = { + "name": f"User{i}", # Генерируем имя пользователя + "role": "Администратор", # Устанавливаем роль + "password": "admin" # Пароль для всех пользователей + } + ut.open_add_user_page() # Открываем страницу добавления пользователя + ut.add_new_user(user_data) # Добавляем пользователя с заданными данными + + # Двойной клик по вкладке Users ( баг ) + cp.click_configuration_navigation_panel_item("users") + cp.click_configuration_navigation_panel_item("users") + + # Проверяем, что пользователь появился в таблице + ut.should_be_user_in_table(user_data["name"], user_data["role"]) + + def test_login_users(self, browser): + """Тест входа под созданными пользователями (создание 9 сессий)""" + + # Генерируем список имен пользователей + users = [f"User{i}" for i in range(1, 9)] + + # Выполняем вход в систему 9 созданными пользователями + for username in users: + login_page = LoginPage(browser) + login_page.do_login(username=username, password="admin") + + + def test_session_tab_content(self, browser): + """Тест проверки содержимого вкладки сессий""" + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + + # Проверяем наличие панели навигации + mp.should_be_navigation_panel() + + # Нажимаем на кнопку "Configuration" в главной панели навигации + mp.click_main_navigation_panel_item("configuration") + + # Переходим на страницу конфигурации + cp = ConfigurationPage(browser) + + # Нажимаем на кнопку "Technical Maintenance" в панели навигации конфигурации + cp.click_configuration_navigation_panel_item("maintenance") + + # Проверяем наличие панели навигации Maintenance + cp.should_be_maintenance_navigation_panel() + + # Нажимаем на вкладку "Session" в панели навигации Maintenanc + cp.click_maintenance_navigation_panel_item("session") + + # Работаем с вкладкой сессий + st = SessionTab(browser) + + # Проверяем наличие таблицы сессий + st.should_be_session_table() + + # Проверяем соответствие данных в таблице данным из API + st.should_be_session_table_data_vs_api() + + #st.should_be_new_session_added() требуется разработать + + # Проверяем наличие вертикальной прокрутки в таблице сессий + st.should_be_vertical_scroll_session() + + # Открываем модальное окно последней сессии + st.open_last_session_modal() + + # Проверяем наличие горизонтальной прокрутки в модальном окне + st.should_be_horizontal_scroll_session_modal() + + # Закрываем модальное окно кнопкой "close" + st.should_be_close_modal_window_by_button("close") + + # Закрываем модальное окно кнопкой "cancel" + st.open_last_session_modal() + st.should_be_close_modal_window_by_button("cancel") + + # Закрываем модальное окно кнопкой "delete" + st.open_last_session_modal() + st.should_be_close_modal_window_by_button("delete") + + + def test_delete_users(self, browser): + """Тест удаления созданных пользователей (в обратном порядке)""" + lp = LoginPage(browser) + lp.do_login() + + # Переходим на главную страницу + mp = MainPage(browser) + + # Проверяем наличие панели навигации + mp.should_be_navigation_panel() + + # Нажимаем на кнопку "Configuration" в главной панели навигации + mp.click_main_navigation_panel_item("configuration") + + # Нажимаем на страницу конфигурации + cp = ConfigurationPage(browser) + + # Нажимаем на вкладку "Users" в панели навигации конфигурации + cp.click_configuration_navigation_panel_item("users") + + # Работаем с вкладкой пользователей + ut = UsersTab(browser) + + # Удаляем пользователей в обратном порядке (от User8 до User1) + for i in range(9, 0, -1): + user_data = { + "name": f"User{i}", + "role": "Администратор", + } + ut.open_edit_user_page_by_name(user_data["name"], user_data["role"]) + ut.delete_user() + + # Двойной клик по вкладке Users (возможно, для обновления списка) + cp.click_configuration_navigation_panel_item("users") + cp.click_configuration_navigation_panel_item("users") + + # Проверяем отсутствие последнего удаленного пользователя в таблице + ut.should_not_be_user_in_table(user_data["name"], user_data["role"]) + + \ No newline at end of file diff --git a/tests/test_users_tab.py b/tests/test_users_tab.py new file mode 100644 index 0000000..4acf500 --- /dev/null +++ b/tests/test_users_tab.py @@ -0,0 +1,334 @@ +import pytest +from pages.login_page import LoginPage +from pages.main_page import MainPage +from pages.configuration_page import ConfigurationPage +from pages.users_tab import UsersTab + +class TestUsersTab: + def test_users_tab_content(self, browser): + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut = UsersTab(browser) + + # check users work area + ut.should_be_users_work_area() + + def test_users_tab_toolbar_buttons(self, browser): + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut = UsersTab(browser) + + ut.should_be_users_page_toolbar_buttons() + + def test_add_user_window_content(self, browser): + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut = UsersTab(browser) + + # open add new user page + ut.open_add_user_page() + + # check add user work area + ut.should_be_add_user_work_area() + + def test_edit_user_window_content(self, browser): + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut = UsersTab(browser) + + # open edit user page + user_name, role = ut.open_edit_user_page_by_index(0) + + # check edit user work area + ut.should_be_edit_user_work_area(user_name, role) + + def test_edit_user_window_close_buttons(self, browser): + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut = UsersTab(browser) + + # open edit user page + user_name, role = ut.open_edit_user_page_by_index(0) + ut.close_user_window_by_toolbar_button(user_name) + + # open edit user page + user_name, role = ut.open_edit_user_page_by_index(0) + ut.close_user_window(user_name) + + def test_add_and_delete_user(self, browser): + user_data = {"name": "User", "role": "Администратор", "password": "admin"} + + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut = UsersTab(browser) + + # open add new user page + ut.open_add_user_page() + + # create new user + ut.add_new_user(user_data) + + # click to Users button in configuration navigation panel to update users list (two times is bug?) + cp.click_configuration_navigation_panel_item("users") + cp.click_configuration_navigation_panel_item("users") + + # check that new user has been added + ut.should_be_user_in_table(user_data["name"], user_data["role"]) + + # open edit user page and delete user + ut.open_edit_user_page_by_name(user_data["name"], user_data["role"]) + ut.delete_user() + + # click to Users button in configuration navigation panel to update users list (two times is bug?) + cp.click_configuration_navigation_panel_item("users") + cp.click_configuration_navigation_panel_item("users") + + # check user abcense in users list + ut.should_not_be_user_in_table(user_data["name"], user_data["role"]) + + def test_reset_password(self, browser): + user_data = {"name": "autoadmin", "role": "Администратор", "password": "12345"} + + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut = UsersTab(browser) + + # open add new user page + ut.open_add_user_page() + + # create new user + ut.add_new_user(user_data) + + # click to Users button in configuration navigation panel to update users list (two times is bug?) + cp.click_configuration_navigation_panel_item("users") + cp.click_configuration_navigation_panel_item("users") + + # check that new user has been added + ut.should_be_user_in_table(user_data["name"], user_data["role"]) + + # open edit user page + ut.open_edit_user_page_by_name(user_data["name"], user_data["role"]) + + new_password = ut.reset_password() + + 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) + + # we are on main page + new_mp = MainPage(browser) + + # do logout + new_mp.do_logout() + + lp_1 = LoginPage(browser) + lp_1.do_login() + + # we are on main page + mp_1 = MainPage(browser) + + # click to Configuration button in main navigation panel + mp_1.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp_1 = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp_1.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut_1 = UsersTab(browser) + + # open edit user page and delete user + ut_1.open_edit_user_page_by_name(user_data["name"], user_data["role"]) + ut_1.delete_user() + + # click to Users button in configuration navigation panel to update users list (two times is bug?) + cp_1.click_configuration_navigation_panel_item("users") + cp_1.click_configuration_navigation_panel_item("users") + + # check user abcense in users list + ut_1.should_not_be_user_in_table(user_data["name"], user_data["role"]) + + def test_edit_user_role(self, browser): + user_data = {"name": "autooperator", "role": "Оператор", "password": "auto123@"} + + lp = LoginPage(browser) + lp.do_login() + + # we are on main page + mp = MainPage(browser) + + # check navigation panel presence + mp.should_be_navigation_panel() + + # click to Configuration button in main navigation panel + mp.click_main_navigation_panel_item("configuration") + + # we are on configuration page + cp = ConfigurationPage(browser) + + # click to Users button in configuration navigation panel + cp.click_configuration_navigation_panel_item("users") + + # users tab has been opened + ut = UsersTab(browser) + + # open add new user page + ut.open_add_user_page() + + # create new user + ut.add_new_user(user_data) + + # click to Users button in configuration navigation panel to update users list (two times is bug?) + cp.click_configuration_navigation_panel_item("users") + cp.click_configuration_navigation_panel_item("users") + + # check that new user has been added + ut.should_be_user_in_table(user_data["name"], user_data["role"]) + + # open edit user page and delete user + ut.open_edit_user_page_by_name(user_data["name"], user_data["role"]) + + new_user_data = {} + new_user_data["role"] = "Контактное лицо" + ut.edit_user(new_user_data) + + # click to Users button in configuration navigation panel to update users list (two times is bug?) + cp.click_configuration_navigation_panel_item("users") + cp.click_configuration_navigation_panel_item("users") + + # check that new user has been added + ut.should_be_user_in_table(user_data["name"], new_user_data["role"]) + + # open edit user page and delete user + ut.open_edit_user_page_by_name(user_data["name"], new_user_data["role"]) + ut.delete_user() + + # click to Users button in configuration navigation panel to update users list (two times is bug?) + cp.click_configuration_navigation_panel_item("users") + cp.click_configuration_navigation_panel_item("users") + + # check user abcense in users list + ut.should_not_be_user_in_table(user_data["name"], new_user_data["role"]) +