From b4f20aae4c2edd763f64a176a440c92744b6c011 Mon Sep 17 00:00:00 2001 From: nsubbot Date: Fri, 14 Nov 2025 14:44:45 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=B0=20=D0=B1?= =?UTF-8?q?=D0=BE=D0=BA=D0=BE=D0=B2=D0=BE=D0=B9=20=D0=BF=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/date_picker_component.py | 232 +++++++++++++ components/events_container_component.py | 13 + components_derived/date_input_component.py | 165 ++++++++++ .../sidebar_filter_component.py | 105 ++++++ locators/date_picker_locators.py | 21 ++ tests/components/{ => scrolling}/run.bat | 0 .../{ => scrolling}/test_json_container.py | 0 .../{ => scrolling}/test_navigation_panel.py | 0 .../{ => scrolling}/test_services_table.py | 0 .../{ => scrolling}/test_session_settings.py | 0 .../{ => scrolling}/test_user_modal_window.py | 0 .../components/unit_tests/test_date_input.py | 285 ++++++++++++++++ .../components/unit_tests/test_date_picker.py | 309 ++++++++++++++++++ 13 files changed, 1130 insertions(+) create mode 100644 components/date_picker_component.py create mode 100644 components_derived/date_input_component.py create mode 100644 components_derived/sidebar_filter_component.py create mode 100644 locators/date_picker_locators.py rename tests/components/{ => scrolling}/run.bat (100%) rename tests/components/{ => scrolling}/test_json_container.py (100%) rename tests/components/{ => scrolling}/test_navigation_panel.py (100%) rename tests/components/{ => scrolling}/test_services_table.py (100%) rename tests/components/{ => scrolling}/test_session_settings.py (100%) rename tests/components/{ => scrolling}/test_user_modal_window.py (100%) create mode 100644 tests/components/unit_tests/test_date_input.py create mode 100644 tests/components/unit_tests/test_date_picker.py diff --git a/components/date_picker_component.py b/components/date_picker_component.py new file mode 100644 index 0000000..20f3f15 --- /dev/null +++ b/components/date_picker_component.py @@ -0,0 +1,232 @@ +"""Модуль компонента средства выбора даты. + +Содержит класс для работы с средством выбора даты через Playwright. +""" + +import datetime +from playwright.sync_api import Page +from tools.logger import get_logger +from locators.date_picker_locators import DatePickerLocators +from elements.text_element import Text +from elements.button_element import Button +from components.base_component import BaseComponent + +logger = get_logger("DATE_PICKER") + + +class DatePickerComponent(BaseComponent): + """Компонент средства выбора даты. + + Предоставляет методы для взаимодействия с элементами средства выбора даты. + """ + + def __init__(self, page: Page): + """Инициализирует компонент средства выбора даты. + + Args: + page: Экземпляр страницы Playwright. + """ + + super().__init__(page) + + self.date_picker_year_button = Button(page, + DatePickerLocators.DATE_PICKER_TITLE_BUTTON_YEAR, + "date_picker_year_button") + + loc = page.locator(DatePickerLocators.DATE_PICKER_HEADER).\ + get_by_role("button").locator("i.mdi-chevron-left") + self.prev_month_button = Button(page, loc, "prev_month_button") + + loc = page.locator(DatePickerLocators.DATE_PICKER_HEADER).\ + get_by_role("button").locator("i.mdi-chevron-right") + self.next_month_button = Button(page, loc, "next_month_button") + + self.current_month_year = Text(page, + DatePickerLocators.DATE_PICKER_HEADER_VALUE, + "current_month_year") + + # Действия: + def click_prev_month_button(self) -> None: + """ Нажатие на кнопку перехода к предыдущему месяцу (стрелочка влево).""" + + self.prev_month_button.click() + + def click_next_month_button(self) -> None: + """ Нажатие на кнопку перехода к следующему месяцу (стрелочка вправо).""" + + self.next_month_button.click() + + def click_year_button(self) -> None: + """ Нажатие на кнопку текущего значения года в заголовке средства выбора даты.""" + + self.date_picker_year_button.click() + + def get_year(self) -> str: + """ Получение текущего значения года из заголовка средства выбора даты.""" + + return self.date_picker_year_button.get_text(0).strip() + + def get_month_year(self) -> str: + """ Получение текущего значения месяца и года.""" + + return self.current_month_year.get_text(0).strip() + + def get_day(self) -> str: + """ Получение текущего номера дня.""" + + days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS) + day_button_locator = days_table_locator.locator("//td/button[contains(@class, 'accent--text')]") + + return day_button_locator.text_content().strip() + + def select_day(self, day: str) -> None: + """ Выбор дня месяца по его номеру.""" + + visible = False + + days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS) + days_table_locator.wait_for(timeout=300) + day_button_locator = days_table_locator.locator("//td").get_by_role("button", name=day) + visible = day_button_locator.is_visible() + if visible: + day_button_locator.click() + else: + assert False, f"Day number {day} is missing on date picker days table" + + def select_year_and_month(self, year: str, month: str) -> None: + """ Выбор года и месяца по заданным значениям. Значения должны быть в формате гггг и мм""" + + try: + month_index = int(month) + except ValueError: + assert False, f"Incorrect month value {month} for selection" + + assert month_index >= 1, f"Incorrect month value {month} for selection" + assert month_index <= 12, f"Incorrect month value {month} for selection" + + current_year = int(self.get_year()) + if int(year) > current_year + 100 or int(year) < current_year - 100: + assert False, f"Incorrect year value {year} for selection" + + self.click_year_button() + + years_list = self.page.locator(DatePickerLocators.DATE_PICKER_LIST_YEARS) + years_list.wait_for(timeout=300) + year_locator = f"{DatePickerLocators.DATE_PICKER_LIST_YEARS} li:has-text(\"{year}\")" + self.page.locator(year_locator).click() + + months_table = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_MONTHS) + months_table.wait_for(timeout=300) + month_button = months_table.locator("//td").get_by_role("button").nth(month_index-1) + month_button.click() + + + # Проверки: + def check_prev_month_button_visibility(self) -> None: + """ Проверка видимости кнопки перехода к предыдущему месяцу (стрелочка влево).""" + + self.prev_month_button.check_visibility("Prev Month Button is missing") + + def check_next_month_button_visibility(self) -> None: + """ Проверка видимости кнопки перехода к следующему месяцу (стрелочка вправо).""" + + self.next_month_button.check_visibility("Next Month Button is missing") + + def check_year_button_visibility(self) -> None: + """ Проверка видимости кнопки текущего значения года в заголовке средства выбора даты.""" + + self.date_picker_year_button.check_visibility("Current Year Button is missing") + + def check_content(self) -> None: + """Проверка состава компонент средства выбора даты.""" + + month_dict = {"1":"январь", + "2":"февраль", + "3":"март", + "4":"апрель", + "5":"май", + "6":"июнь", + "7":"июль", + "8":"август", + "9":"сентябрь", + "10":"октябрь", + "11":"ноябрь", + "12":"декабрь"} + days_per_month = {"1":31, + "2":28, + "3":31, + "4":30, + "5":31, + "6":30, + "7":31, + "8":31, + "9":30, + "10":31, + "11":30, + "12":31} + expected_weekdays = ["В", "П", "В", "С", "Ч", "П", "С"] + + expected_date = datetime.datetime.now() + + # Проверка наличия кнопки текущего значения года в заголовке средства выбора даты + self.check_year_button_visibility() + + actual_year = self.get_year() + assert actual_year == str(expected_date.year), \ + f"Expected year {expected_date.year} is not equal actual year {actual_year} on date picker title" + + # Проверка наличия кнопок перехода к предыдущему/следующему месяцу + self.check_prev_month_button_visibility() + self.check_next_month_button_visibility() + + # Проверка наличия и правильности текущей даты + self.current_month_year.check_visibility("Month and Year values is missing on date picker body") + + y = expected_date.year + m = str(expected_date.strftime("%m")) + expected_month_year = f"{month_dict[m]} {y} г." + actual_month_year = self.get_month_year() + assert actual_month_year == expected_month_year, \ + f"Expected value {expected_month_year} is not equal actual value {actual_month_year} on date picker body" + + expected_day = str(expected_date.strftime("%d")) + actual_day = self.get_day() + assert actual_day == expected_day, \ + f"Expected day {expected_day} is not equal actual day {actual_day} on date picker body" + + # Проверка таблицы дней + days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS) + headers = days_table_locator.locator("th").all() + actual_weekdays = [] + for header in headers: + actual_weekdays.append(header.text_content()) + + assert actual_weekdays == expected_weekdays, \ + f"Expected weekdays list {expected_weekdays} is not equal actual weekdays list {actual_weekdays} on date picker body" + + rows = days_table_locator.locator("//tbody/tr") + days_counter = 0 + for i in range(rows.count()): + row = rows.nth(i) + cells = row.locator("td") + for j in range(cells.count()): + cell_text = cells.nth(j).inner_text() + if cell_text != "": + days_counter += 1 + + expected_day_counter = days_per_month[m] + if m == "2" and self._is_leap_year(expected_date.year): + expected_day_counter = 29 + + assert days_counter == expected_day_counter, f"Incorrect amount of days {days_counter} on date picker body" + + def _is_leap_year(self, year: str) -> bool: + """Проверка является ли год високосным.""" + + if int(year) % 400 == 0: + return True + if int(year) % 100 == 0: + return False + if int(year) % 4 == 0: + return True + return False diff --git a/components/events_container_component.py b/components/events_container_component.py index 8bce8d6..8caebc8 100644 --- a/components/events_container_component.py +++ b/components/events_container_component.py @@ -9,6 +9,7 @@ from elements.tab_button_element import TabButton from elements.button_element import Button from components.toolbar_component import ToolbarComponent from components.table_component import TableComponent +from components.sidebar_filter_component import SidebarFilterComponent from components.base_component import BaseComponent logger = get_logger("EVENTS_CONTANER") @@ -63,6 +64,10 @@ class EventsContainerComponent(BaseComponent): locator("xpath=..").get_by_role("button").nth(2) self.data_set_number = Button(page, loc, "data_set_number") + self.sidebar_filter_locator = self.container_locator.locator("//aside//div[@class='scrollarea__container']") + self.sidebar_filter = SidebarFilterComponent(self.page, + self.sidebar_filter_locator) + # Действия: def add_tab_to_toolbar(self, locator: str | Locator, name: str) -> None: """Добавление кнопки типа v-tabs к тулбару""" @@ -90,6 +95,14 @@ class EventsContainerComponent(BaseComponent): self.last_page.click() + def click_filter_button(self) -> SidebarFilterComponent: + """Нажатие кнопки перехода на первую сраницу""" + + self.toolbar.click_button("filter_button") + self.sidebar_filter.check_visibility(self.sidebar_filter_locator, + "Filter sidebar is missing") + return self.sidebar_filter + def click_event_table_header_arrow(self, index: int) -> None: """ Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы diff --git a/components_derived/date_input_component.py b/components_derived/date_input_component.py new file mode 100644 index 0000000..875ee93 --- /dev/null +++ b/components_derived/date_input_component.py @@ -0,0 +1,165 @@ +"""Модуль компонента задания даты и времени. + +Содержит класс для работы с компонентом задания даты и времени через Playwright. +""" + +from playwright.sync_api import Page, Locator, expect +from tools.logger import get_logger +# from elements.text_element import Text +from elements.text_input_element import TextInput +from elements.button_element import Button +from components.date_picker_component import DatePickerComponent +from components.base_component import BaseComponent + +logger = get_logger("DATE_INPUT") + + +class DateInput(BaseComponent): + """Компонент задания даты и времени. + + Предоставляет методы для взаимодействия с элементами компонентом задания даты и времени. + """ + + def __init__(self, page: Page, locator: str | Locator): + """Инициализирует компонент задания даты и времени. + + Args: + page: Экземпляр страницы Playwright. + locator: Локатор формы ввода даты и времени (строка или объект Locator) + """ + + super().__init__(page) + + self.date_input_locator = self.get_locator(locator) + + self.switch_mode_button = Button(page, + self.date_input_locator.get_by_role("button"), + "switch_mode_button") + + loc = self.date_input_locator.get_by_placeholder("дд.мм.гггг") + self.date_input_field = TextInput(page, loc, "date_input") + + loc = self.date_input_locator.get_by_placeholder("00:00") + self.time_input_field = TextInput(page, loc, "time_input") + + self.date_picker = DatePickerComponent(self.page) + + # Действия: + def click_switch_mode_button(self) -> None: + """ Нажатие на кнопку переключения режимов ввода (текстовый или календарь).""" + + self.switch_mode_button.click() + + def get_date_picker(self) -> DatePickerComponent: + """ Возвращает экземпляр компонента средства выбора даты. """ + + return self.date_picker + + def get_date_field_value(self) -> str: + """ Возвращает текущее значение поля ввода даты. """ + + return self.date_input_field.get_input_value() + + def get_time_field_value(self) -> str: + """ Возвращает текущее значение поля ввода времени. """ + + return self.time_input_field.get_input_value() + + def input_date(self, date: str) -> None: + """ Ввод даты в формате дд.мм.гггг """ + + day, month, year = date.split('.') + assert len(day) == 2, "Incorrect day format: should be 'dd.mm.yyyy'" + assert len(month) == 2, "Incorrect month format: should be 'dd.mm.yyyy'" + assert len(year) == 4, "Incorrect year format: should be 'dd.mm.yyyy'" + + try: + _ = int(day) + except ValueError: + assert False, f"Incorrect day value {day} for selection" + + try: + _ = int(month) + except ValueError: + assert False, f"Incorrect month value {month} for selection" + + try: + _ = int(year) + except ValueError: + assert False, f"Incorrect year value {year} for selection" + + if self.is_text_input_mode(): + self.date_input_field.check_editable_input("Text field for date input should be editable") + self.date_input_field.clear() + self.date_input_field.input_value(date) + else: + self.date_picker.select_year_and_month(year, month) + self.date_picker.select_day(day) + + def input_time(self, time: str) -> None: + """ Ввод даты в формате чч:мм """ + + hours, minutes = time.split(':') + assert len(hours) == 2, "Incorrect time format: should be 'hh:mm'" + assert len(minutes) == 2, "Incorrect time format: should be 'hh:mm'" + + try: + _ = int(hours) + except ValueError: + assert False, f"Incorrect hours value {hours} for selection" + + try: + _ = int(minutes) + except ValueError: + assert False, f"Incorrect minutes value {minutes} for selection" + + self.time_input_field.check_editable_input("Text field for date input should be editable") + self.time_input_field.clear() + self.time_input_field.input_value(time) + + # Проверки: + def check_content(self, label: str) -> None: + """Проверка состава компонента ввода даты.""" + + self.check_switch_mode_button_visibility() + + label_locator = self.date_input_locator.get_by_label(label) + expect(label_locator).to_be_visible() + + self.date_input_field.check_visibility("Text field for date input is missing") + self.date_input_field.check_empty_input("Text field for date input should be empty") + + self.click_switch_mode_button() + self.page.wait_for_timeout(300) + self.click_switch_mode_button() + self.page.wait_for_timeout(300) + + self.date_picker.check_content() + + self.click_switch_mode_button() + self.click_switch_mode_button() + + self.page.wait_for_timeout(300) + self.input_date("11.11.2011") + + label_locator = self.date_input_locator.get_by_label("Время") + expect(label_locator).to_be_visible() + self.time_input_field.check_visibility("Text field for time input is missing") + current_time_value = self.get_time_field_value() + assert current_time_value == "00:00", \ + "Should be empty time input field" + + def check_switch_mode_button_visibility(self) -> None: + """ Проверка видимости кнопки переключения режимов ввода.""" + + self.switch_mode_button.check_visibility("Switch Mode Button is missing") + + def is_text_input_mode(self) -> bool: + """ Проверка текстового режима ввода.""" + + result = False + + inner_text = self.switch_mode_button.get_text(0).strip() + if inner_text == "keyboard": + result = True + return result diff --git a/components_derived/sidebar_filter_component.py b/components_derived/sidebar_filter_component.py new file mode 100644 index 0000000..6812b89 --- /dev/null +++ b/components_derived/sidebar_filter_component.py @@ -0,0 +1,105 @@ +"""Модуль компонента боковой панели формы ввода полей фильтрации отображения данных в панели событий. Содержит класс для работы +с формами ввода, их элементами и проверками.""" + +from playwright.sync_api import Page, Locator +from tools.logger import get_logger +from elements.button_element import Button +from components_derived.date_input_component import DateInput +from components.toolbar_component import ToolbarComponent +from components.base_component import BaseComponent + +logger = get_logger("SIDEBAR_FILTER") + + +class SidebarFilterComponent(BaseComponent): + """Компонент боковой панели формы ввода полей фильтрации отображения данных в панели событий. Предоставляет методы для + взаимодействия с формой, ее содержимым и проверок.""" + + def __init__(self, page: Page, locator: str | Locator): + """Инициализирует компонент боковой панели формы ввода параметров фильтрации. + + Args: + page: Экземпляр страницы Playwright + locator: Локатор контейнера (строка или объект Locator) для которого вызывается панель + """ + super().__init__(page) + + self.sidebar_locator = self.get_locator(locator) + + self.toolbar = ToolbarComponent(page, "Настройки и параметры") + + # Поля ввода даты начала и даты окончания события + self.start_time_filter = DateInput(page, + self.sidebar_locator.locator( + "//div[contains(@class, 'scrollarea__body')]/div").nth(0)) + self.finish_time_filter = DateInput(page, + self.sidebar_locator.locator( + "//div[contains(@class, 'scrollarea__body')]/div").nth(1)) + + # Поля задания параметров фильтрации (произвольное количество) + self.filter_items = {} + + # Кнопки задания/сброса параметров фильтрации + self.apply_button = Button(page, + self.sidebar_locator.get_by_role("button").filter(has_text='Применить Фильтры'), + "apply_button") + self.reset_button = Button(page, + self.sidebar_locator.get_by_role("button").filter(has_text='Сбросить Фильтры'), + "reset_button") + + # Действия: + def add_filter_item(self, name: str, item: object) -> None: + """Добавляет поле задания параметров фильтрации по заданному имени.""" + + self.filter_items[name] = item + + def get_filter_item(self, name: str) -> object | None: + """Возвращает поле задания параметров фильтрации по заданному или None, если не найдено.""" + + return self.filter_items.get(name) + + def get_start_time_filter(self) -> DateInput: + """Возвращает поле задания параметров фильтрации даты начала.""" + + return self.start_time_filter + + def get_finish_time_filter(self) -> DateInput: + """Возвращает поле задания параметров фильтрации даты окончания.""" + + return self.finish_time_filter + + def click_apply_button(self) -> None: + """Клик по кнопке применения фильтра.""" + + self.apply_button.click() + + def click_reset_button(self) -> None: + """Клик по кнопке сброса фильтра.""" + + self.reset_button.click() + + # Проверки: + def check_vertical_scrolling(self, locator: str| Locator) -> bool: + """Проверяет возможность вертикальной прокрутки формы.""" + + return self.is_scrollable_vertically(locator) + + def check_apply_button_visibility(self) -> None: + """Проверяет наличие кнопки применения фильтра.""" + + self.apply_button.check_visibility("Apply Filter Button is missing") + + def check_reset_button_visibility(self) -> None: + """Проверяет наличие кнопки сброса фильтра.""" + + self.reset_button.check_visibility("Reset Filter Button is missing") + + def should_be_toolbar(self) -> None: + """Проверяет наличие тулбара. + + Raises: + AssertionError: Если тулбар отсутствует. + """ + + self.toolbar.check_toolbar_presence_by_locator_and_title(self.sidebar_locator, + "Sidebar Filter form toolbar is missing") diff --git a/locators/date_picker_locators.py b/locators/date_picker_locators.py new file mode 100644 index 0000000..12884e2 --- /dev/null +++ b/locators/date_picker_locators.py @@ -0,0 +1,21 @@ +"""Модуль date_picker_locators содержит локаторы полей ввода даты. + +Класс DatePickerLocators предоставляет XPath/CSS локаторы для взаимодействия +с полями ввода даты. +""" + +class DatePickerLocators: + """Локаторы для полей ввода на странице. + + Содержит XPath/CSS локаторы для: + DATE_PICKER (str): контейнер средства выбора даты + + """ + + DATE_PICKER = "div.menuable__content__active > div.v-picker" + DATE_PICKER_TITLE_BUTTON_YEAR = "//div[contains(@class,'v-date-picker-title__year')]" + DATE_PICKER_HEADER = "//div[contains(@class, 'v-date-picker-header')]" + DATE_PICKER_HEADER_VALUE = "//div[contains(@class, 'v-date-picker-header__value')]" + DATE_PICKER_TABLE_DAYS = "//div[contains(@class, 'v-date-picker-table--date')]" + DATE_PICKER_LIST_YEARS = "ul.v-date-picker-years" + DATE_PICKER_TABLE_MONTHS = "//div[contains(@class, 'v-date-picker-table--month')]" diff --git a/tests/components/run.bat b/tests/components/scrolling/run.bat similarity index 100% rename from tests/components/run.bat rename to tests/components/scrolling/run.bat diff --git a/tests/components/test_json_container.py b/tests/components/scrolling/test_json_container.py similarity index 100% rename from tests/components/test_json_container.py rename to tests/components/scrolling/test_json_container.py diff --git a/tests/components/test_navigation_panel.py b/tests/components/scrolling/test_navigation_panel.py similarity index 100% rename from tests/components/test_navigation_panel.py rename to tests/components/scrolling/test_navigation_panel.py diff --git a/tests/components/test_services_table.py b/tests/components/scrolling/test_services_table.py similarity index 100% rename from tests/components/test_services_table.py rename to tests/components/scrolling/test_services_table.py diff --git a/tests/components/test_session_settings.py b/tests/components/scrolling/test_session_settings.py similarity index 100% rename from tests/components/test_session_settings.py rename to tests/components/scrolling/test_session_settings.py diff --git a/tests/components/test_user_modal_window.py b/tests/components/scrolling/test_user_modal_window.py similarity index 100% rename from tests/components/test_user_modal_window.py rename to tests/components/scrolling/test_user_modal_window.py diff --git a/tests/components/unit_tests/test_date_input.py b/tests/components/unit_tests/test_date_input.py new file mode 100644 index 0000000..495d517 --- /dev/null +++ b/tests/components/unit_tests/test_date_input.py @@ -0,0 +1,285 @@ +"""Модуль тестов для проверки операций с компонентом ввода даты. + +Содержит тесты для для проверки операций с компонентом ввода даты. +""" + +import pytest +from playwright.sync_api import Page +from pages.main_page import MainPage +from pages.login_page import LoginPage + + +# @pytest.mark.smoke +class TestDateInputComponent: + """Класс тестов для проверки операций с компонентом ввода даты. + + Атрибуты: + browser: Фикстура для работы с браузером. + """ + + # @pytest.mark.develop + def test_date_input_content(self, browser: Page) -> None: + """Проверяет содержимое компонента ввода даты. +put + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + date_input.check_content("Дата начала") + + # @pytest.mark.develop + def test_date_input_by_text_positive(self, browser: Page) -> None: + """Проверяет возможность задания даты с помощью текстового ввода для корректного входного значения. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + if date_input.is_text_input_mode(): + # выбираем 15 января 2024, проверяем результат + expected_date = "15.01.2024" + date_input.input_date(expected_date) + browser.wait_for_timeout(300) + actual_date = date_input.get_date_field_value() + assert expected_date == actual_date, \ + f"Expected date {expected_date} is not equal actual date {actual_date}" + else: + assert False, "Should be text date input mode" + + # @pytest.mark.develop + def test_date_input_by_text_negative(self, browser: Page) -> None: + """Проверяет возможность задания даты с помощью текстового ввода для некорректных входных значений. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + if date_input.is_text_input_mode(): + try: + date_input.input_date("1.01.2020") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect day format: should be 'dd.mm.yyyy'" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + try: + date_input.input_date("ff.01.2020") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect day value ff for selection" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + try: + date_input.input_date("01.1.2020") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect month format: should be 'dd.mm.yyyy'" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + try: + date_input.input_date("01.o1.2020") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect month value o1 for selection" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + try: + date_input.input_date("01.01.20") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect year format: should be 'dd.mm.yyyy'" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + try: + date_input.input_date("01.01.2o2o") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect year value 2o2o for selection" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + else: + assert False, "Should be text date input mode" + + # @pytest.mark.develop + def test_time_input_after_text_positive(self, browser: Page) -> None: + """Проверяет возможность задания времени для корректного входного значения. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + if date_input.is_text_input_mode(): + # выбираем 15 января 2024 10:11, проверяем результат + date_input.input_date("15.01.2024") + browser.wait_for_timeout(300) + + expected_time = "10:11" + date_input.input_time(expected_time) + + actual_time = date_input.get_time_field_value() + assert expected_time == actual_time, \ + f"Expected date {expected_time} is not equal actual date {actual_time}" + else: + assert False, "Should be text date input mode" + + # @pytest.mark.develop + def test_time_input_after_date_picker_positive(self, browser: Page) -> None: + """Проверяет возможность задания времени для корректного входного значения. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + if date_input.is_text_input_mode(): + assert False, "Should be date input mode by date picker" + else: + # выбираем 16 января 2024 08:11, проверяем результат + date_input.input_date("16.01.2024") + browser.wait_for_timeout(300) + + expected_time = "08:11" + date_input.input_time(expected_time) + + actual_time = date_input.get_time_field_value() + assert expected_time == actual_time, \ + f"Expected date {expected_time} is not equal actual date {actual_time}" + + # @pytest.mark.develop + def test_time_input_negative(self, browser: Page) -> None: + """Проверяет возможность задания времени для некорректного входного значения. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + if date_input.is_text_input_mode(): + # выбираем 15 января 2024 10:11, проверяем результат + date_input.input_date("15.01.2024") + browser.wait_for_timeout(300) + + try: + date_input.time_date("1:01") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect time format: should be 'hh:mm'" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + try: + date_input.input_date("o2.01") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect hours value o2 for selection" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + try: + date_input.input_time("01:1") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect time format: should be 'hh:mm'" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + try: + date_input.input_date("01:o1") + except AssertionError as e: + actual_err = f"{e}" + expected_err = "Incorrect minutes value o1 for selection" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + else: + assert False, "Should be text date input mode" diff --git a/tests/components/unit_tests/test_date_picker.py b/tests/components/unit_tests/test_date_picker.py new file mode 100644 index 0000000..552b534 --- /dev/null +++ b/tests/components/unit_tests/test_date_picker.py @@ -0,0 +1,309 @@ +"""Модуль тестов компонента средства выбора даты. + +Содержит тесты для проверки функциональности +компонента средства выбора даты. +""" + +import pytest +from playwright.sync_api import Page +from pages.main_page import MainPage +from pages.login_page import LoginPage + + +# @pytest.mark.smoke +class TestDatePickerComponent: + """Класс тестов для проверки компонента средства выбора даты. + + Атрибуты: + browser: Фикстура для работы с браузером. + """ + + # @pytest.mark.develop + def test_date_picker_content(self, browser: Page) -> None: + """Проверяет содержимое компонента средства выбора даты. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + date_input.check_switch_mode_button_visibility() + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + date_picker = date_input.get_date_picker() + date_picker.check_content() + + # @pytest.mark.develop + def test_month_year_selection(self, browser: Page) -> None: + """Проверяет изменение текущего значения месяца и года с помощью кнопок перехода (стрелочек). + + Args: + browser: Экземпляр страницы Playwright. + """ + + months = ["январь", "февраль", "март", "апрель", "май", "июнь", + "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"] + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + date_input.check_switch_mode_button_visibility() + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + date_picker = date_input.get_date_picker() + + # Проверка наличия кнопок перехода к предыдущему/следующему месяцу + date_picker.check_prev_month_button_visibility() + date_picker.check_next_month_button_visibility() + + current_month_year = date_picker.get_month_year() + start_month, start_year, tmp = current_month_year.split(" ") + + # нажатие на кнопки перехода к предыдущему месяцу (стрелочка влево) и проверка значений + date_picker.click_prev_month_button() + browser.wait_for_timeout(300) + current_month_year = date_picker.get_month_year() + prev_month, prev_year, tmp = current_month_year.split(" ") + + num = months.index(start_month) + expected_month = months[num-1] + expected_year = start_year + + if start_month == "январь": + expected_month = "декабрь" + expected_year = str(int(start_year) - 1) + assert prev_month == expected_month, \ + f"Expected value {expected_month} is not equal actual value {prev_month} on date picker body" + assert prev_year == expected_year, \ + f"Expected value {expected_year} is not equal actual value {prev_year} on date picker body" + + # нажатие на кнопки перехода к следующему месяцу (стрелочка вправо) и проверка возврата к началу + date_picker.click_next_month_button() + browser.wait_for_timeout(300) + current_month_year = date_picker.get_month_year() + next_month, next_year, tmp = current_month_year.split(" ") + + assert next_month == start_month, \ + f"Expected value {start_month} is not equal actual value {next_month} on date picker body" + assert next_year == start_year, \ + f"Expected value {start_year} is not equal actual value {next_year} on date picker body" + + # нажатие на кнопки перехода к следующему месяцу (стрелочка вправо) и проверка значений + date_picker.click_next_month_button() + browser.wait_for_timeout(300) + current_month_year = date_picker.get_month_year() + next_month, next_year, tmp = current_month_year.split(" ") + + num = months.index(start_month) + expected_month = months[num+1] + expected_year = start_year + + if start_month == "декабрь": + expected_month = "январь" + expected_year = str(int(start_year) + 1) + + assert next_month == expected_month, \ + f"Expected value {expected_month} is not equal actual value {next_month} on date picker body" + assert next_year == expected_year, \ + f"Expected value {expected_year} is not equal actual value {next_year} on date picker body" + + # нажатие на кнопки перехода к предыдущему месяцу (стрелочка влево) и проверка возврата к началу + date_picker.click_prev_month_button() + browser.wait_for_timeout(300) + current_month_year = date_picker.get_month_year() + prev_month, prev_year, tmp = current_month_year.split(" ") + + assert prev_month == start_month, \ + f"Expected value {start_month} is not equal actual value {prev_month} on date picker body" + assert prev_year == start_year, \ + f"Expected value {start_year} is not equal actual value {prev_year} on date picker body" + + # @pytest.mark.develop + def test_day_selection_positive(self, browser: Page) -> None: + """Проверяет возможность задания корректного значения дня месяца. + + Args: + browser: Экземпляр страницы Playwright. + """ + + months = ["январь", "февраль", "март", "апрель", "май", "июнь", + "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"] + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + date_input.check_switch_mode_button_visibility() + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + date_picker = date_input.get_date_picker() + current_month_year = date_picker.get_month_year() + current_month, current_year, tmp = current_month_year.split(" ") + + # выбираем 15 число текущего года и месяца, проверяем результат + date_picker.select_day("15") + browser.wait_for_timeout(300) + + month_num = months.index(current_month) + 1 + expected_date = f"15.{month_num}.{current_year}" + actual_date = date_input.get_date_field_value() + assert expected_date == actual_date, \ + f"Expected date {expected_date} is not equal actual date {actual_date}" + + # @pytest.mark.develop + def test_day_selection_negative(self, browser: Page) -> None: + """Проверяет возможность задания некорректного значения дня месяца. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + date_input.check_switch_mode_button_visibility() + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + date_picker = date_input.get_date_picker() + + # выбор некорректных значений + incorrect_values = ["0", "32", "ff"] + for value in incorrect_values: + try: + date_picker.select_day(value) + except AssertionError as e: + actual_err = f"{e}" + expected_err =f"Day number {value} is missing on date picker days table" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + # @pytest.mark.develop + def test_year_and_month_selection_positive(self, browser: Page) -> None: + """Проверяет возможность задания корректных значений года и месяца. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + date_input.check_switch_mode_button_visibility() + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + date_picker = date_input.get_date_picker() + + # выбираем 15 января 2024, проверяем результат + date_picker.select_year_and_month("2024", "01") + browser.wait_for_timeout(300) + date_picker.select_day("15") + browser.wait_for_timeout(300) + + expected_date = "15.01.2024" + actual_date = date_input.get_date_field_value() + assert expected_date == actual_date, \ + f"Expected date {expected_date} is not equal actual date {actual_date}" + + # @pytest.mark.develop + def test_year_and_month_selection_negative(self, browser: Page) -> None: + """Проверяет возможность задания некорректных значений года и месяца. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + date_input = sidebar_filter.get_start_time_filter() + date_input.check_switch_mode_button_visibility() + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + date_input.click_switch_mode_button() + browser.wait_for_timeout(500) + + date_picker = date_input.get_date_picker() + + # выбор некорректных значений + incorrect_years = ["0", "2222", "1212"] + for value in incorrect_years: + try: + date_picker.select_year_and_month(value, "01") + except AssertionError as e: + actual_err = f"{e}" + expected_err =f"Incorrect year value {value} for selection" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" + + incorrect_months = ["june", "aaaa", "0", "14"] + for value in incorrect_months: + try: + date_picker.select_year_and_month("2012", value) + except AssertionError as e: + actual_err = f"{e}" + expected_err =f"Incorrect month value {value} for selection" + assert expected_err == actual_err, \ + f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" From 42a813180f3a2a91481639c7b6eed045c582943d Mon Sep 17 00:00:00 2001 From: nsubbot Date: Tue, 18 Nov 2025 10:46:45 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D0=B2=D1=8B=D0=B1?= =?UTF-8?q?=D0=BE=D1=80=D0=B0=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- components/events_container_component.py | 7 +- .../container_system_log_events.py | 21 ++++ components_derived/selection_bar_component.py | 75 +++++++++++++ .../sidebar_filter_component.py | 24 +++- locators/selection_bar_locators.py | 16 +++ .../unit_tests/test_selection_bar.py | 106 ++++++++++++++++++ 7 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 components_derived/selection_bar_component.py create mode 100644 locators/selection_bar_locators.py create mode 100644 tests/components/unit_tests/test_selection_bar.py diff --git a/.env b/.env index f4ddf1d..7571ece 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -ENV=develop +ENV=test AUTH_LOGIN = admin AUTH_PASSWORD = enodemon-admin diff --git a/components/events_container_component.py b/components/events_container_component.py index 8caebc8..1a34f97 100644 --- a/components/events_container_component.py +++ b/components/events_container_component.py @@ -7,9 +7,9 @@ from locators.toolbar_locators import ToolbarLocators from elements.tooltip_button_element import TooltipButton from elements.tab_button_element import TabButton from elements.button_element import Button +from components_derived.sidebar_filter_component import SidebarFilterComponent from components.toolbar_component import ToolbarComponent from components.table_component import TableComponent -from components.sidebar_filter_component import SidebarFilterComponent from components.base_component import BaseComponent logger = get_logger("EVENTS_CONTANER") @@ -156,6 +156,11 @@ class EventsContainerComponent(BaseComponent): loc = self.container_locator.locator(self.table_locator) return self.events_table.get_rows_count(loc) + + def get_sidebar_filter(self) -> SidebarFilterComponent: + """Возвращает боковую панель фильтрации.""" + + return self.sidebar_filter def get_toolbar_filter_button(self) -> Button: """Возвращает кнопку фильтрации.""" diff --git a/components_derived/container_system_log_events.py b/components_derived/container_system_log_events.py index 9bcb829..f930ad3 100644 --- a/components_derived/container_system_log_events.py +++ b/components_derived/container_system_log_events.py @@ -31,6 +31,12 @@ class SystemLogEventsContainer(EventsContainerComponent): super().__init__(page, locator) self.add_tab_to_toolbar(ToolbarLocators.TABS, "events") + + sidebar_filter = self.get_sidebar_filter() + sidebar_filter.add_filtering_parameter("filter_type", "Тип") + sidebar_filter.add_filtering_parameter("filter_strictness", "Строгость") + sidebar_filter.add_filtering_parameter("filter_host", "Хост") + # Действия: # Проверки: def check_content(self) -> None: @@ -58,3 +64,18 @@ class SystemLogEventsContainer(EventsContainerComponent): assert False, "Table body is missing" self.should_be_pagination_buttons() + + sidebar_filter = self.click_filter_button() + sidebar_filter.check_content() + + filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type") + filter_type_title = filter_type_bar.get_filter_parameter_title() + assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing" + + filter_strictness_bar = sidebar_filter.get_filtering_parameter("filter_strictness") + filter_strictness_title = filter_strictness_bar.get_filter_parameter_title() + assert filter_strictness_title == "Строгость", "Filtering parameter bar 'Строгость' is missing" + + filter_host_bar = sidebar_filter.get_filtering_parameter("filter_host") + filter_host_title = filter_host_bar.get_filter_parameter_title() + assert filter_host_title == "Хост", "Filtering parameter bar 'Хост' is missing" diff --git a/components_derived/selection_bar_component.py b/components_derived/selection_bar_component.py new file mode 100644 index 0000000..f8f371c --- /dev/null +++ b/components_derived/selection_bar_component.py @@ -0,0 +1,75 @@ +"""Модуль компонента панели выбора значения. + +Содержит класс для работы с компонентом панели выбора значения через Playwright. +""" + +from playwright.sync_api import Page, Locator, expect +from tools.logger import get_logger +from locators.selection_bar_locators import SelectionBarLocators +from components.dropdown_list_component import DropdownList +from components.base_component import BaseComponent + +logger = get_logger("FILTER_PARAMETER_BAR") + + +class SelectionBarComponent(BaseComponent): + """Компонент панели выбора значения. + + Предоставляет методы для взаимодействия с элементами компонента панели выбора значения. + """ + + def __init__(self, page: Page, locator: str | Locator): + """Инициализирует компонент панели выбора значения. + + Args: + page: Экземпляр страницы Playwright. + locator: Локатор панели выбора значения (строка или объект Locator) + """ + + super().__init__(page) + + # Локатор панели параметра фильтрации + self.selection_bar_locator = self.get_locator(locator) + + # При нажатии на панель появляется выпадающий список с параметрами фильтрации для выбора + self.selected_values_list = DropdownList(self.page) + + # Действия: + def clear_selections(self) -> None: + """ Удаление ранее выбранных значений """ + + selected_values = self.get_selected_values() + if len(selected_values) > 0: + clear_button_locator = self.selection_bar_locator.\ + locator(SelectionBarLocators.CLEAR_SELECTION_BUTTON) + clear_button_locator.click() + + + def get_selection_bar_title(self) -> str: + """ Возвращает название панели выбора значения """ + + title_locator = self.selection_bar_locator.locator("//label") + return title_locator.text_content() + + def get_selected_values(self) -> []: + """ Возвращает список выбранных значений """ + + selected_values_locator = self.selection_bar_locator.\ + locator(SelectionBarLocators.PARAMETERS_SELECTED) + + selected_values = selected_values_locator.all_inner_texts() + return selected_values[0].splitlines() + + def open_values_list(self) -> None: + """ Открытие выпадающего списка путем нажатия на панель выбора значения """ + + expect(self.selection_bar_locator).to_be_visible() + self.selection_bar_locator.click() + + def select_value(self, name: str) -> None: + """ Выбор значения из списка """ + + self.selected_values_list.check_item_with_text(name) + self.selected_values_list.click_item_with_text(name) + + # Проверки: diff --git a/components_derived/sidebar_filter_component.py b/components_derived/sidebar_filter_component.py index 6812b89..a047516 100644 --- a/components_derived/sidebar_filter_component.py +++ b/components_derived/sidebar_filter_component.py @@ -4,6 +4,7 @@ from playwright.sync_api import Page, Locator from tools.logger import get_logger from elements.button_element import Button +from components_derived.selection_bar_component import SelectionBarComponent from components_derived.date_input_component import DateInput from components.toolbar_component import ToolbarComponent from components.base_component import BaseComponent @@ -37,7 +38,7 @@ class SidebarFilterComponent(BaseComponent): "//div[contains(@class, 'scrollarea__body')]/div").nth(1)) # Поля задания параметров фильтрации (произвольное количество) - self.filter_items = {} + self.filtering_parameters = {} # Кнопки задания/сброса параметров фильтрации self.apply_button = Button(page, @@ -48,15 +49,17 @@ class SidebarFilterComponent(BaseComponent): "reset_button") # Действия: - def add_filter_item(self, name: str, item: object) -> None: + def add_filtering_parameter(self, name: str, title: str) -> None: """Добавляет поле задания параметров фильтрации по заданному имени.""" - self.filter_items[name] = item + loc = self.sidebar_locator.locator("//div[contains(@class, 'scrollarea__body')]").\ + get_by_role("combobox").filter(has_text=title) + self.filtering_parameters[name] = SelectionBarComponent(self.page, loc) - def get_filter_item(self, name: str) -> object | None: + def get_filtering_parameter(self, name: str) -> SelectionBarComponent | None: """Возвращает поле задания параметров фильтрации по заданному или None, если не найдено.""" - return self.filter_items.get(name) + return self.filtering_parameters.get(name) def get_start_time_filter(self) -> DateInput: """Возвращает поле задания параметров фильтрации даты начала.""" @@ -79,6 +82,17 @@ class SidebarFilterComponent(BaseComponent): self.reset_button.click() # Проверки: + def check_content(self) -> None: + """Проверяет наличие постоянных полей панели параметров фильтрации.""" + + self.should_be_toolbar() + self.start_time_filter.check_content("Дата начала") + self.finish_time_filter.check_content("Дата окончания") + + self.check_apply_button_visibility() + self.check_reset_button_visibility() + + def check_vertical_scrolling(self, locator: str| Locator) -> bool: """Проверяет возможность вертикальной прокрутки формы.""" diff --git a/locators/selection_bar_locators.py b/locators/selection_bar_locators.py new file mode 100644 index 0000000..7b595d2 --- /dev/null +++ b/locators/selection_bar_locators.py @@ -0,0 +1,16 @@ +"""Модуль selection_bar_locators содержит локаторы компонента панели выбора значения. + +Класс SelectionBarLocators предоставляет XPath/CSS локаторы для взаимодействия +с компонентом панели выбора значения. +""" + +class SelectionBarLocators: + """Локаторы для компонента панели выбора значения. + + Содержит XPath/CSS локаторы для: + + """ + + OPEN_PARAMETERS_LIST_BUTTON = "div.v-input__icon--append" + CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear" + PARAMETERS_SELECTED = "div.v-select__selections" diff --git a/tests/components/unit_tests/test_selection_bar.py b/tests/components/unit_tests/test_selection_bar.py new file mode 100644 index 0000000..30ccc2e --- /dev/null +++ b/tests/components/unit_tests/test_selection_bar.py @@ -0,0 +1,106 @@ +"""Модуль тестов для проверки операций с компонентом панели выбора значения. + +Содержит тесты для для проверки операций с компонентом панели выбора значения. +""" + +import pytest +from playwright.sync_api import Page +from pages.main_page import MainPage +from pages.login_page import LoginPage + + +# @pytest.mark.smoke +class TestSelectionBarComponent: + """Класс тестов для проверки операций с компонентом панели выбора значения на примере + панели выбора значения 'Тип' в боковой панели фильтрации вкладки 'Системный журнал' + панели событий. + + Атрибуты: + browser: Фикстура для работы с браузером. + """ + + # @pytest.mark.develop + def test_selection_bar_content(self, browser: Page) -> None: + """Проверяет содержимое компонента панели выбора значения. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + sidebar_filter.add_filtering_parameter("filter_type", "Тип") + filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type") + filter_type_title = filter_type_bar.get_selection_bar_title() + assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing" + + # @pytest.mark.develop + def test_selection_values(self, browser: Page) -> None: + """Проверяет возможность выбора значений. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + sidebar_filter.add_filtering_parameter("filter_type", "Тип") + filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type") + + filter_type_bar.open_values_list() + filter_type_bar.select_value("e-nms") + selected_values = filter_type_bar.get_selected_values() + assert len(selected_values) > 0, "No values has been selected" + assert selected_values[0] == "e-nms", f"Got unexpected selected value {selected_values[0]}" + + filter_type_bar.open_values_list() + filter_type_bar.select_value("Технический учет") + selected_values = filter_type_bar.get_selected_values() + assert len(selected_values) > 0, "No values has been selected" + assert selected_values[0] == "e-nms", f"Got unexpected selected value {selected_values[0]}" + assert selected_values[1] == "Технический учет", f"Got unexpected selected value {selected_values[1]}" + + # @pytest.mark.develop + def test_clear_selected_values(self, browser: Page) -> None: + """Проверяет возможность удаления ранее выбранных значений. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + mp.should_be_event_panel() + + system_log_events_container = mp.click_events_panel_system_log_tab() + sidebar_filter = system_log_events_container.click_filter_button() + browser.wait_for_timeout(500) + + sidebar_filter.add_filtering_parameter("filter_type", "Тип") + filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type") + filter_type_bar.open_values_list() + filter_type_bar.select_value("e-nms") + browser.wait_for_timeout(300) + + filter_type_bar.clear_selections() + selected_values = filter_type_bar.get_selected_values() + assert len(selected_values) == 0, "Selected values should be deleted" + \ No newline at end of file From 6c6f8c18f2d3987b12bbe45180dde25c2ef3242a Mon Sep 17 00:00:00 2001 From: nsubbot Date: Thu, 20 Nov 2025 14:11:34 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B4=D0=B0=D1=87=D0=B8=20=D1=8D=D0=BB=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20=D0=B2=D1=8B=D0=BF=D0=B2=D0=B4?= =?UTF-8?q?=D0=B0=D1=8E=D1=89=D0=B5=D0=B3=D0=BE=20=D1=81=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BE=D0=BC=D0=BF?= =?UTF-8?q?=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/events_container_component.py | 4 +- .../container_system_log_events.py | 16 +++--- components_derived/selection_bar_component.py | 56 +++++++++++++------ .../sidebar_filter_component.py | 20 +++---- locators/selection_bar_locators.py | 8 ++- .../unit_tests/test_selection_bar.py | 19 +++---- 6 files changed, 73 insertions(+), 50 deletions(-) diff --git a/components/events_container_component.py b/components/events_container_component.py index 1a34f97..58013b5 100644 --- a/components/events_container_component.py +++ b/components/events_container_component.py @@ -156,10 +156,10 @@ class EventsContainerComponent(BaseComponent): loc = self.container_locator.locator(self.table_locator) return self.events_table.get_rows_count(loc) - + def get_sidebar_filter(self) -> SidebarFilterComponent: """Возвращает боковую панель фильтрации.""" - + return self.sidebar_filter def get_toolbar_filter_button(self) -> Button: diff --git a/components_derived/container_system_log_events.py b/components_derived/container_system_log_events.py index f930ad3..d654dc2 100644 --- a/components_derived/container_system_log_events.py +++ b/components_derived/container_system_log_events.py @@ -9,9 +9,7 @@ from tools.logger import get_logger from locators.toolbar_locators import ToolbarLocators from components.events_container_component import EventsContainerComponent - - -logger = get_logger("USER_SETTINGS_DIALOG") +logger = get_logger("SYSTEM_LOG_EVENTS_CONTAINER") class SystemLogEventsContainer(EventsContainerComponent): @@ -31,12 +29,12 @@ class SystemLogEventsContainer(EventsContainerComponent): super().__init__(page, locator) self.add_tab_to_toolbar(ToolbarLocators.TABS, "events") - + sidebar_filter = self.get_sidebar_filter() sidebar_filter.add_filtering_parameter("filter_type", "Тип") sidebar_filter.add_filtering_parameter("filter_strictness", "Строгость") sidebar_filter.add_filtering_parameter("filter_host", "Хост") - + # Действия: # Проверки: def check_content(self) -> None: @@ -64,18 +62,18 @@ class SystemLogEventsContainer(EventsContainerComponent): assert False, "Table body is missing" self.should_be_pagination_buttons() - + sidebar_filter = self.click_filter_button() sidebar_filter.check_content() - + filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type") filter_type_title = filter_type_bar.get_filter_parameter_title() assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing" - + filter_strictness_bar = sidebar_filter.get_filtering_parameter("filter_strictness") filter_strictness_title = filter_strictness_bar.get_filter_parameter_title() assert filter_strictness_title == "Строгость", "Filtering parameter bar 'Строгость' is missing" - + filter_host_bar = sidebar_filter.get_filtering_parameter("filter_host") filter_host_title = filter_host_bar.get_filter_parameter_title() assert filter_host_title == "Хост", "Filtering parameter bar 'Хост' is missing" diff --git a/components_derived/selection_bar_component.py b/components_derived/selection_bar_component.py index f8f371c..1f1f515 100644 --- a/components_derived/selection_bar_component.py +++ b/components_derived/selection_bar_component.py @@ -9,7 +9,7 @@ from locators.selection_bar_locators import SelectionBarLocators from components.dropdown_list_component import DropdownList from components.base_component import BaseComponent -logger = get_logger("FILTER_PARAMETER_BAR") +logger = get_logger("SELECTION_BAR") class SelectionBarComponent(BaseComponent): @@ -30,46 +30,66 @@ class SelectionBarComponent(BaseComponent): # Локатор панели параметра фильтрации self.selection_bar_locator = self.get_locator(locator) - + # При нажатии на панель появляется выпадающий список с параметрами фильтрации для выбора self.selected_values_list = DropdownList(self.page) # Действия: def clear_selections(self) -> None: """ Удаление ранее выбранных значений """ - + selected_values = self.get_selected_values() if len(selected_values) > 0: clear_button_locator = self.selection_bar_locator.\ locator(SelectionBarLocators.CLEAR_SELECTION_BUTTON) clear_button_locator.click() - - + + def get_available_options(self) -> list[str]: + """Возвращает список всех доступных опций из выпадающего списка """ + + logger.info("Получение списка доступных опций из выпадающего списка...") + + # Открываем выпадающий список + self.open_values_list() + + # Ждем появления списка + self.page.wait_for_timeout(1000) + + # Получаем все элементы списка + options = self.selected_values_list.get_item_names(SelectionBarLocators.LIST_ITEMS) + + # Закрываем список (кликаем вне его) + self.page.mouse.click(10, 10) + self.page.wait_for_timeout(500) + + logger.info(f"Найдено доступных опций: {len(options)} - {options}") + return options + def get_selection_bar_title(self) -> str: """ Возвращает название панели выбора значения """ - - title_locator = self.selection_bar_locator.locator("//label") + + title_locator = self.selection_bar_locator.locator("//label") return title_locator.text_content() - - def get_selected_values(self) -> []: + + def get_selected_values(self) -> list[str]: """ Возвращает список выбранных значений """ - + selected_values_locator = self.selection_bar_locator.\ locator(SelectionBarLocators.PARAMETERS_SELECTED) - + selected_values = selected_values_locator.all_inner_texts() return selected_values[0].splitlines() - + def open_values_list(self) -> None: """ Открытие выпадающего списка путем нажатия на панель выбора значения """ - - expect(self.selection_bar_locator).to_be_visible() - self.selection_bar_locator.click() - + + expect(self.selection_bar_locator).to_be_visible() + self.selection_bar_locator.click() + def select_value(self, name: str) -> None: """ Выбор значения из списка """ - + self.selected_values_list.check_item_with_text(name) self.selected_values_list.click_item_with_text(name) - + # Проверки: diff --git a/components_derived/sidebar_filter_component.py b/components_derived/sidebar_filter_component.py index a047516..6eb6196 100644 --- a/components_derived/sidebar_filter_component.py +++ b/components_derived/sidebar_filter_component.py @@ -1,20 +1,20 @@ -"""Модуль компонента боковой панели формы ввода полей фильтрации отображения данных в панели событий. Содержит класс для работы -с формами ввода, их элементами и проверками.""" +"""Модуль компонента боковой панели формы ввода полей фильтрации отображения данных в панели событий. Содержит класс +для работы с формами ввода, их элементами и проверками.""" from playwright.sync_api import Page, Locator from tools.logger import get_logger from elements.button_element import Button -from components_derived.selection_bar_component import SelectionBarComponent -from components_derived.date_input_component import DateInput from components.toolbar_component import ToolbarComponent from components.base_component import BaseComponent +from components_derived.selection_bar_component import SelectionBarComponent +from components_derived.date_input_component import DateInput logger = get_logger("SIDEBAR_FILTER") class SidebarFilterComponent(BaseComponent): - """Компонент боковой панели формы ввода полей фильтрации отображения данных в панели событий. Предоставляет методы для - взаимодействия с формой, ее содержимым и проверок.""" + """Компонент боковой панели формы ввода полей фильтрации отображения данных в панели событий. Предоставляет методы + для взаимодействия с формой, ее содержимым и проверок.""" def __init__(self, page: Page, locator: str | Locator): """Инициализирует компонент боковой панели формы ввода параметров фильтрации. @@ -84,15 +84,15 @@ class SidebarFilterComponent(BaseComponent): # Проверки: def check_content(self) -> None: """Проверяет наличие постоянных полей панели параметров фильтрации.""" - + self.should_be_toolbar() self.start_time_filter.check_content("Дата начала") self.finish_time_filter.check_content("Дата окончания") - + self.check_apply_button_visibility() self.check_reset_button_visibility() - - + + def check_vertical_scrolling(self, locator: str| Locator) -> bool: """Проверяет возможность вертикальной прокрутки формы.""" diff --git a/locators/selection_bar_locators.py b/locators/selection_bar_locators.py index 7b595d2..d49d1c0 100644 --- a/locators/selection_bar_locators.py +++ b/locators/selection_bar_locators.py @@ -8,9 +8,15 @@ class SelectionBarLocators: """Локаторы для компонента панели выбора значения. Содержит XPath/CSS локаторы для: - + - Кнопок открытия и очистки + - Выбранных значений + - Элементов выпадающего списка """ OPEN_PARAMETERS_LIST_BUTTON = "div.v-input__icon--append" CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear" PARAMETERS_SELECTED = "div.v-select__selections" + + # Локаторы для элементов выпадающего списка + LISTBOX = "//div[@role='listbox']" + LIST_ITEMS = "//div[@role='listbox']//div[@role='listitem']" diff --git a/tests/components/unit_tests/test_selection_bar.py b/tests/components/unit_tests/test_selection_bar.py index 30ccc2e..1b05adb 100644 --- a/tests/components/unit_tests/test_selection_bar.py +++ b/tests/components/unit_tests/test_selection_bar.py @@ -36,12 +36,12 @@ class TestSelectionBarComponent: system_log_events_container = mp.click_events_panel_system_log_tab() sidebar_filter = system_log_events_container.click_filter_button() browser.wait_for_timeout(500) - + sidebar_filter.add_filtering_parameter("filter_type", "Тип") filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type") filter_type_title = filter_type_bar.get_selection_bar_title() assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing" - + # @pytest.mark.develop def test_selection_values(self, browser: Page) -> None: """Проверяет возможность выбора значений. @@ -59,23 +59,23 @@ class TestSelectionBarComponent: system_log_events_container = mp.click_events_panel_system_log_tab() sidebar_filter = system_log_events_container.click_filter_button() browser.wait_for_timeout(500) - + sidebar_filter.add_filtering_parameter("filter_type", "Тип") filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type") - + filter_type_bar.open_values_list() filter_type_bar.select_value("e-nms") selected_values = filter_type_bar.get_selected_values() assert len(selected_values) > 0, "No values has been selected" assert selected_values[0] == "e-nms", f"Got unexpected selected value {selected_values[0]}" - + filter_type_bar.open_values_list() filter_type_bar.select_value("Технический учет") selected_values = filter_type_bar.get_selected_values() assert len(selected_values) > 0, "No values has been selected" assert selected_values[0] == "e-nms", f"Got unexpected selected value {selected_values[0]}" assert selected_values[1] == "Технический учет", f"Got unexpected selected value {selected_values[1]}" - + # @pytest.mark.develop def test_clear_selected_values(self, browser: Page) -> None: """Проверяет возможность удаления ранее выбранных значений. @@ -93,14 +93,13 @@ class TestSelectionBarComponent: system_log_events_container = mp.click_events_panel_system_log_tab() sidebar_filter = system_log_events_container.click_filter_button() browser.wait_for_timeout(500) - + sidebar_filter.add_filtering_parameter("filter_type", "Тип") filter_type_bar = sidebar_filter.get_filtering_parameter("filter_type") filter_type_bar.open_values_list() filter_type_bar.select_value("e-nms") browser.wait_for_timeout(300) - - filter_type_bar.clear_selections() + + filter_type_bar.clear_selections() selected_values = filter_type_bar.get_selected_values() assert len(selected_values) == 0, "Selected values should be deleted" - \ No newline at end of file