Repository: Regikon/homework-4-spring-2025
Pull request #13: Тесты лид-форм
Author: login: Ifelsik
Created: 2025-05-15T03:36:14Z | Merged: 2025-05-15T09:15:48Z

Проходят на костылях
Можете кидать замечания, но вряд ли уже поправлю


Diff:
diff --git a/hw/code/README.md b/hw/code/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/hw/code/test_add_lead_form_decor.py b/hw/code/test_add_lead_form_decor.py
new file mode 100644
index 0000000..c215b2f
--- /dev/null
+++ b/hw/code/test_add_lead_form_decor.py
@@ -0,0 +1,84 @@
+from base_case import BaseCase, UserType
+from ui.pages.advertiser_leadforms_page import LeadFormsPage
+
+import pytest
+
+class TestCreateLeadFormDecorPage(BaseCase):
+    user = UserType.ADVERTISER
+
+    @pytest.fixture(scope="function")
+    def decor_page(self, request):
+        driver = self.driver
+        driver.get(LeadFormsPage.url)
+        return LeadFormsPage(driver).go_to_lead_forms_decor_page()
+
+    def test_lead_form_name_long_error(self, decor_page):
+        decor_page.enter_lead_form_name('a' * 256)
+        assert decor_page.has_lead_form_name_long_error()
+
+    def test_lead_form_name_empty_error(self, decor_page):
+        decor_page.enter_lead_form_name('')
+        assert decor_page.has_lead_form_name_empty_error()
+
+    def test_logo_empty_error(self, decor_page):
+        decor_page.click_continue()
+        assert decor_page.has_logo_empty_error()
+
+    def test_logo_set_up(self, decor_page):
+        decor_page.set_up_logo('./images/logo.jpg')
+        assert decor_page.has_logo()
+    
+    def test_company_name_too_long_error(self, decor_page):
+        decor_page.enter_company_name('a' * 31)
+        assert decor_page.has_company_name_long_error()
+    
+    def test_company_name_empty_error(self, decor_page):
+        decor_page.enter_company_name('')
+        assert decor_page.has_company_name_empty_error()
+
+    def test_header_too_long_error(self, decor_page):
+        decor_page.enter_lead_form_header('a' * 51)
+        assert decor_page.has_lead_form_header_long_error()
+    
+    def test_header_empty_error(self, decor_page):
+        decor_page.enter_lead_form_header('')
+        assert decor_page.has_lead_form_header_empty_error()
+
+    def test_description_too_long_error(self, decor_page):
+        decor_page.enter_lead_form_description('a' * 36)
+        assert decor_page.has_lead_form_description_long_error()
+    
+    def test_description_empty_error(self, decor_page):
+        decor_page.enter_lead_form_description('')
+        assert decor_page.has_lead_form_description_empty_error()
+    
+    # @pytest.mark.skip('skip')
+    def test_cover_set_up(self, decor_page):
+        decor_page.set_up_cover('./images/cover.jpg')
+        assert decor_page.has_cover()
+
+    def test_lead_magnet_sale_zero_error(self, decor_page):
+        decor_page.switch_form_lead_magnet()
+        sale_type = decor_page.sale_locator_types()
+        decor_page.enter_sale_size(sale_type['price'], '0')
+        assert decor_page.has_lead_magnet_sale_zero_error()
+
+    def test_lead_magnet_sale_bigger_100_percent(self, decor_page):
+        decor_page.switch_form_lead_magnet()
+        sale_type = decor_page.sale_locator_types()
+        decor_page.enter_sale_size(sale_type['percent'], '101')
+        assert decor_page.has_lead_magnet_sale_too_big()
+
+    def test_lead_magnet_bonus_empty_error(self, decor_page):
+        decor_page.enter_bonus('')
+        assert decor_page.has_lead_magnet_bonus_empty_error()
+
+    def test_lead_magnet_bonus_long_error(self, decor_page):
+        decor_page.enter_bonus('a' * 31)
+        assert decor_page.has_lead_magnet_bonus_long_error()
+
+    @pytest.mark.skip('skip')
+    def test_go_to_questions_page(self, decor_page):
+        decor_page.click_continue()
+        assert decor_page.has_questions_page()
+    
diff --git a/hw/code/test_add_lead_form_question.py b/hw/code/test_add_lead_form_question.py
new file mode 100644
index 0000000..49301b3
--- /dev/null
+++ b/hw/code/test_add_lead_form_question.py
@@ -0,0 +1,58 @@
+import pytest
+from base_case import BaseCase, UserType
+
+from ui.pages.advertiser_leadforms_page import LeadFormsPage
+from ui.pages.advertiser_lead_form_decor_page import LeadFormDecorPage
+
+class TestCreateLeadFormQuestionPage(BaseCase):
+    user = UserType.ADVERTISER
+
+    QUESTION_TEXT = "Нужны стулья?"
+    ANSWER_1_TEXT = "Да"
+    ANSWER_2_TEXT = "Нет"
+    ANSWER_3_TEXT = "Пропустить"
+    CONTACT_NEW   = "Город"
+    
+    @pytest.fixture(scope='function')
+    def question_page(self, request):
+        driver = self.driver
+        driver.get(LeadFormsPage.url)
+        return LeadFormsPage(driver).\
+            go_to_lead_forms_decor_page().\
+            go_to_questions_page(LeadFormDecorPage.correct_page_data)
+    
+    def test_create_question(self, question_page):
+        question_page.add_question()
+        assert question_page.has_question()
+        question_page.enter_question_text(self.QUESTION_TEXT)
+        assert question_page.has_question_text(self.QUESTION_TEXT)
+        question_page.add_answer_text(1, self.ANSWER_1_TEXT)
+        question_page.add_answer_text(2, self.ANSWER_2_TEXT)
+        assert question_page.has_answer_text(self.ANSWER_1_TEXT)
+        assert question_page.has_answer_text(self.ANSWER_2_TEXT)
+
+    def test_delete_question(self, question_page):
+        question_page.add_question()
+        assert question_page.has_question()
+        question_page.remove_question()
+        assert not question_page.has_question()
+
+    def test_question_switch_type(self, question_page):
+        question_page.add_question()
+        assert question_page.has_question()
+        question_page.switch_question_type_many_answers()
+        assert question_page.has_question_type_many_answers()
+
+    def test_add_new_answer_in_question(self, question_page):
+        question_page.add_question()
+        assert question_page.has_question()
+        question_page.add_answer_input()
+        question_page.add_answer_text(3, self.ANSWER_3_TEXT)
+        assert question_page.has_answer_text(self.ANSWER_3_TEXT)
+
+    def test_add_contact_field_city(self, question_page):
+        question_page.add_contact_field(self.CONTACT_NEW)
+        assert question_page.has_contact_name_on_preview()
+        assert question_page.has_contact_telephone_on_preview()
+        assert question_page.has_contact_city_on_preview()
+
diff --git a/hw/code/test_add_lead_form_result.py b/hw/code/test_add_lead_form_result.py
new file mode 100644
index 0000000..7497d61
--- /dev/null
+++ b/hw/code/test_add_lead_form_result.py
@@ -0,0 +1,61 @@
+import pytest
+from base_case import BaseCase, UserType
+
+from ui.pages.advertiser_leadforms_page import LeadFormsPage
+from ui.pages.advertiser_lead_form_decor_page import LeadFormDecorPage
+
+class TestCreateLeadFormResultPage(BaseCase):
+    user = UserType.ADVERTISER
+
+    @pytest.fixture(scope='function')
+    def result_page(self, request):
+        driver = self.driver
+        driver.get(LeadFormsPage.url)
+        return LeadFormsPage(driver).\
+            go_to_lead_forms_decor_page().\
+            go_to_questions_page(LeadFormDecorPage.correct_page_data).\
+            go_to_result_page()
+
+    # bug with vk ads
+    @pytest.mark.skip('skip')
+    def test_heder_empty(self, result_page):
+        result_page.enter_header('')
+        assert result_page.has_enter_header_empty_error()
+
+    def test_header_long(self, result_page):
+        result_page.enter_header('a' * 26)
+        assert result_page.has_enter_header_long_error()
+    
+    def test_description_long(self, result_page):
+        result_page.enter_description('a' * 161)
+        assert result_page.has_description_long_error()
+    
+    def test_add_site(self, result_page):
+        result_page.click_add_site()
+        result_page.enter_site('uart.site')
+        assert result_page.has_site()
+
+    def test_add_tel(self, result_page):
+        result_page.click_add_tel()
+        result_page.enter_tel('+7952812')
+        assert result_page.has_tel()
+        assert not result_page.has_tel_incorrect_error()
+
+    def test_add_tel_incorrect(self, result_page):
+        result_page.click_add_tel()
+        result_page.enter_tel('123')
+        assert result_page.has_tel_incorrect_error()
+    
+    def test_add_promo(self, result_page):
+        result_page.click_add_promo()
+        result_page.enter_promo('123')
+        assert result_page.has_promo()
+        assert not result_page.has_promo_too_long_error()
+
+    def test_add_promo_too_long(self, result_page):
+        result_page.click_add_promo()
+        result_page.enter_promo('1' * 31)
+        assert result_page.has_promo_too_long_error()
+    
+
+    
diff --git a/hw/code/test_add_lead_form_settings.py b/hw/code/test_add_lead_form_settings.py
new file mode 100644
index 0000000..a279f41
--- /dev/null
+++ b/hw/code/test_add_lead_form_settings.py
@@ -0,0 +1,39 @@
+import time
+import pytest
+from base_case import BaseCase, UserType
+
+from ui.pages.advertiser_leadforms_page import LeadFormsPage
+from ui.pages.advertiser_lead_form_decor_page import LeadFormDecorPage
+
+class TestCreateLeadFormSettingsPage(BaseCase):
+    user = UserType.ADVERTISER
+
+    @pytest.fixture(scope='function')
+    def settings_page(self, request):
+        driver = self.driver
+        driver.get(LeadFormsPage.url)
+        return LeadFormsPage(driver).\
+            go_to_lead_forms_decor_page().\
+            go_to_questions_page(LeadFormDecorPage.correct_page_data).\
+            go_to_result_page().\
+            go_to_settings_page()
+    
+    def test_enter_name_empty(self, settings_page):
+        settings_page.enter_name('')
+        assert settings_page.has_name_empty_error()
+
+    def test_enter_address_empty(self, settings_page):
+        settings_page.enter_address('')
+        assert settings_page.has_address_empty_error()
+    
+    def test_enter_inn_long(self, settings_page):
+        settings_page.enter_inn('1' * 33)
+        assert settings_page.has_inn_long_error()
+
+    def test_save_form(self, settings_page):
+        settings_page.enter_inn('123456789')
+        settings_page.enter_name('Иван Иванов')
+        settings_page.enter_address('Москва, ул. Пушкина, д. 1')
+        time.sleep(1)
+        assert settings_page.is_modal_closed()
+
diff --git a/hw/code/ui/components/base_component.py b/hw/code/ui/components/base_component.py
index f328611..8fd07a5 100644
--- a/hw/code/ui/components/base_component.py
+++ b/hw/code/ui/components/base_component.py
@@ -32,7 +32,7 @@ def find_all(self, locator, timeout=None) -> List[WebElement]:
         return self.driver.find_elements(by=locator[0], value=locator[1])
 
     def click(self, locator, timeout=None):
-        self.find(locator, timeout=timeout)
+        elem = self.find(locator, timeout=timeout)
         elem = self.wait(timeout).until(EC.element_to_be_clickable(locator))
         elem.click()
 
@@ -44,10 +44,19 @@ def has_element(self, locator, timeout=None) -> bool:
         except Exception:
             return False
 
+    def input_write(self, locator, text):
+        input = self.find(locator)
+        input.clear()
+        input.send_keys(text)
+        input.submit()
+    
+    def input_write_without_submit(self, locator, text):
+        input = self.find(locator)
+        input.clear()
+        input.send_keys(text)
     def wait_till_element_disappears(self, locator, timeout=5):
         started = time.time()
         while time.time() - started < timeout:
             if not self.has_element(locator, POLL_FREQUENCY):
                 return True
         raise RuntimeError("Element still on the page")
-
diff --git a/hw/code/ui/components/mediateka_component.py b/hw/code/ui/components/mediateka_component.py
new file mode 100644
index 0000000..3e3aa53
--- /dev/null
+++ b/hw/code/ui/components/mediateka_component.py
@@ -0,0 +1,26 @@
+import time
+from ui.components.base_component import BaseComponent
+from ui.locators.mediateka_locators import MediaLoaderLocators
+
+import os
+
+class MediaLoader(BaseComponent):
+    def __init__(self, driver):
+        super().__init__(driver)
+        self.locators = MediaLoaderLocators()
+
+    def upload_image(self, path):
+        file_input = self.find(self.locators.INPUT__LOAD_IMAGE)
+        file_input.send_keys(os.path.abspath(path))
+        file_input.submit()
+        
+    
+    def select_image(self, name):
+        image_locator = self.locators.image(name)
+        self.click(image_locator)
+        time.sleep(3) # it's really necessary
+        self.click(self.locators.BUTTON__SAVE)
+
+    def close_modal(self):
+        self.click(self.locators.BUTTON__CLOSE)
+
diff --git a/hw/code/ui/locators/lead_form_page_locators.py b/hw/code/ui/locators/lead_form_page_locators.py
new file mode 100644
index 0000000..a6797fb
--- /dev/null
+++ b/hw/code/ui/locators/lead_form_page_locators.py
@@ -0,0 +1,142 @@
+from selenium.webdriver.common.by import By
+
+class LeadFormDecorPageLocators:
+    INPUT__NAME = (By.XPATH, "//input[@placeholder='Название лид-формы']")
+    INPUT__NAME_ALERT_TOO_LONG = (By.XPATH, "//span[@role='alert' and contains(., 'Сократите текст')]")
+    INPUT__NAME_ALERT_EMPTY = (By.XPATH, "//span[@role='alert' and contains(., 'Нужно заполнить')]")
+
+    INPUT__COMPANY = (By.XPATH, "//input[@placeholder='Название компании']")
+    INPUT__COMPANY_ALERT_EMPTY = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Название компании')]]//div[contains(., 'Нужно заполнить')]"
+    )
+    INPUT__COMPANY_ALERT_TOO_LONG = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Название компании')]]//div[contains(., 'Сократите')]"
+    )
+
+    INPUT__HEADER = (By.XPATH, "//input[@placeholder='Текст заголовка']")
+    INPUT__HEADER_ALERT_EMPTY = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Заголовок')]]//div[contains(., 'Нужно заполнить')]"
+    )
+    INPUT__HEADER_ALERT_TOO_LONG = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Заголовок')]]//div[contains(., 'Сократите')]"
+    )
+
+    INPUT__DESCRIPTION = (By.XPATH, "//input[@placeholder='Введите описание']")
+    INPUT__DESCRIPTION_ALERT_EMPTY = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Описание')]]//div[contains(., 'Нужно заполнить')]"
+    )
+    INPUT__DESCRIPTION_ALERT_TOO_LONG = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Описание')]]//div[contains(., 'Сократите')]"
+    )
+
+    INPUT__LOGO_UPLOAD = (By.XPATH, "//div[@data-testid='set-global-image']")
+    INPUT__LOGO_CHANGE = (By.XPATH, "//button[@data-testid='change-image']")
+    INPUT__LOGO_EMPTY_ALERT = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Логотип']]//div[contains(., 'Нужно заполнить')]"
+    )
+
+    SWITCH__CREATE_LENDING = (By.CSS_SELECTOR, ".vkuiSwitch")
+    BUTTON__EDIT_LENDING = (
+        By.XPATH,
+        "//*[contains(@class, 'vkuiButton__in') and contains(., 'Редактировать')]"
+    )
+    INPUT__LENDING_COMPANY = (By.XPATH, "//input[@placeholder='Введите название компании']")
+    INPUT__LENDING_HEADER = (By.XPATH, "//textarea[@placeholder='Введите заголовок']")
+    BUTTON__LENDING_ADD_BLOCK = (
+        By.XPATH,
+        "//*[contains(@class, 'vkuiButton__in') and contains(., 'Добавить блок')]"
+    )
+    BUTTON__LENDING_ADD_TEXT = (
+        By.XPATH,
+        "//*[contains(@class, 'AddBlockTooltip_option') and contains(., 'Текст')]"
+    )
+    INPUT__LENDING_WRITE_TEXT = (
+        By.XPATH,
+        "//*[contains(@class, 'TextBlock_inputField')]"
+    )
+    BUTTON__SAVE_LANDING = (
+        By.XPATH,
+        "//*[contains(@class, 'vkuiButton__in') and contains(., 'Сохранить')]"
+    )
+
+    BUTTON__SELECT_LEAD_MAGNET = (
+        By.XPATH,
+        "//label[contains(@class, 'vkuiSegmentedControlOption')][.//span[text()='Лид-магнит']]"
+    )
+    INPUT__SALE = (
+        By.XPATH,
+        "//input[@value='500']"
+    )
+    BUTTON__SWITCH_RUBLE = (
+        By.XPATH,
+        "//*[contains(@class, 'vkuiSegmentedControlOption')][.//*[contains(.,'₽')]]"
+    )
+    BUTTON__SWITCH_PERCENT = (
+        By.XPATH,
+        "//*[contains(@class, 'vkuiSegmentedControlOption')][.//*[contains(.,'%')]]"
+    )
+    INPUT__SALE_ALERT_ZERO = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Размер скидки')]]//div[contains(., 'больше нуля')]"
+    )
+    INPUT__SALE_ALERT_TOO_BIG = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Размер скидки')]]//div[contains(., '100%')]"
+    )
+    SWITCH__BONUS = (
+        By.XPATH,
+        "//label[contains(@class, 'vkuiRadio')][.//input[@value='bonus']]"
+    )
+    INPUT__BONUS = (By.XPATH, "//input[@placeholder='Бонус']")
+    INPUT__BONUS_ALERT_EMPTY = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Описание бонуса')]]//div[contains(., 'Нужно заполнить')]"
+    )
+    INPUT__BONUS_ALERT_TOO_LONG = (
+        By.XPATH,
+        "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Описание бонуса')]]//div[contains(., 'Сократите')]"
+    )
+
+    INPUT__COVER_UPLOAD = (
+        By.XPATH,
+        "//span[contains(@class, 'vkuiButton__in')][.//span[contains(., 'Выбрать из медиатеки')]]"
+    )
+    HAS_COVER = (By.XPATH, "//div[contains(@class, 'AdMediaPreview_item')]")
+
+    BUTTON__CONTINUE = (
+        By.XPATH,
+        "//*[contains(@class, 'vkuiButton__in') and contains(., 'Продолжить')]"
+    )
+
+class LeadFormPageLocators:
+    BUTTON__CREATE_NEW = (By.XPATH, "//button[contains(., 'Создать лид-форму')]")
+    
+    BUTTON__ADD_QUESTION = (
+        By.XPATH,
+        "//*[contains(@class, 'vkuiButton__in') and contains(., 'Добавить вопрос')]"
+    )
+    INPUT__QUESTION_TEXT = (
+        By.XPATH,
+        "//textarea[@placeholder='Напишите вопрос']"
+    )
+    INPUT_QUESTION_ANSWER_1 = (
+        By.XPATH,
+        "(//input[@placeholder='Введите ответ'])[1]"
+    )
+    INPUT_QUESTION_ANSWER_2 = (
+        By.XPATH,
+        "(//input[@placeholder='Введите ответ'])[2]"
+    )
+    BUTTON_QUESTION_REMOVE = (
+        By.XPATH,
+        "//button[@aria-label='Remove']"
+    )
+
+
diff --git a/hw/code/ui/locators/lead_form_questions_locators.py b/hw/code/ui/locators/lead_form_questions_locators.py
new file mode 100644
index 0000000..87c342d
--- /dev/null
+++ b/hw/code/ui/locators/lead_form_questions_locators.py
@@ -0,0 +1,41 @@
+from selenium.webdriver.common.by import By
+
+
+class LeadFormQuestionsLocators:
+    ADD_QUESTION = (By.XPATH, "//span[contains(@class, 'vkuiButton')][.//*[contains(., 'Добавить вопрос')]]")
+    REMOVE_QUESTION = (By.XPATH, "//button[@aria-label='Remove']")
+    QUESTION_EXISTS = (By.XPATH, "//div[contains(@class, 'Question_question__')]")
+    QUESTION_TEXT = (By.XPATH, "//textarea[@placeholder='Напишите вопрос']")
+
+    @staticmethod
+    def HAS_QUESTION_TEXT_ON_PREVIEW(text: str):
+        return (By.XPATH, f"//div[contains(@class, 'OnePageContentBlock')]//*[text()='{text}']")
+
+    @staticmethod
+    def ANSWER(index: int):
+        return (By.XPATH, f"(//input[@placeholder='Введите ответ'])[{index}]")
+    
+    @staticmethod
+    def HAS_ANSWER_ON_PREVIEW(text: str):
+        return (By.XPATH, f"//div[contains(@class, 'OnePageContentBlock')]//div[text()='{text}']")
+    
+    QUESTION_TYPE = (By.XPATH, "//span[contains(., 'Тип вопроса')]//div[contains(@class, 'HintSelector')]")
+    QUESTION_TYPE_MANY_ANSWERS = (By.XPATH, "//div[@role='button'][.//*[contains(., 'Выбор нескольких ответов')]]")
+    HAS_QUESTION_TYPE_MANY_ANSWERS_ON_PREVIEW = (By.XPATH, "//div[contains(@class, 'OnePageContentBlock')]//input[@type='checkbox']")
+
+    ADD_ANSWER = (By.XPATH, "//span[@class='vkuiButton__in'][.//span[text()='Добавить ответ']]")
+
+    ADD_CONTACTS = (By.XPATH, "//span[@class='vkuiButton__in'][.//span[text()='Добавить контактные данные']]")
+    
+    @staticmethod
+    def SELECT_CONTACT(text: str):
+        return (By.XPATH, f"//label[contains(@class, 'vkuiCheckbox')][.//span[text()='{text}']]")
+    CONFIRM_CONTACTS = (By.XPATH, "//span[@class='vkuiButton__in'][.//span[text()='Добавить']]")
+    HAS_CONTACTS_NAME_ON_PREVIEW = (By.XPATH, "//input[@placeholder='Введите имя']")
+    HAS_CONTACTS_CITY_ON_PREVIEW = (By.XPATH, "//input[@placeholder='Введите город']")
+    HAS_CONTACTS_TELEPHONE_ON_PREVIEW = (By.XPATH, "//input[@placeholder='+7']")
+
+    CONTINUE = (By.XPATH, "//span[@class='vkuiButton__in'][.//span[text()='Продолжить']]")
+    
+
+
diff --git a/hw/code/ui/locators/lead_form_result_page_locators.py b/hw/code/ui/locators/lead_form_result_page_locators.py
new file mode 100644
index 0000000..ecc8cee
--- /dev/null
+++ b/hw/code/ui/locators/lead_form_result_page_locators.py
@@ -0,0 +1,26 @@
+from selenium.webdriver.common.by import By
+
+
+class LeadFormResultPageLocators:
+    ADD_SITE  = (By.XPATH, "//div[@role='button'][.//*[contains(., 'сайт')]]")
+    SITE = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Ссылка на сайт']]//input")
+    HAS_SITE = (By.XPATH, "//span[text()='Перейти на сайт']")
+
+    ADD_TEL   = (By.XPATH, "//div[@role='button'][.//*[contains(., 'телефон')]]")
+    TEL = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Телефон для заказа']]//input")
+    HAS_TEL = (By.XPATH, "//span[@class='vkuiButton__in'][.//*[contains(@class, 'phone')]]")
+    TEL_INCORRECT = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Телефон для заказа']]//*[contains(., 'Телефон должен')]")
+
+    ADD_PROMO = (By.XPATH, "//div[@role='button'][.//*[contains(., 'промокод')]]")
+    PROMO = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Промокод']]//input")
+    HAS_PROMO = (By.XPATH, "//span[contains(@class, 'vkuiFormField')][.//*[@aria-label='Скопировать']]")
+    PROMO_TOO_LONG = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Промокод']]//*[text()='Сократите текст']")
+
+    HEADER  = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Заголовок']]//input")
+    HEADER_EMPTY = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Заголовок']]//*[text()='Нужно заполнить']")
+    HEADER_TOO_LONG = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Заголовок']]//*[text()='Сократите текст']")
+    
+    DESCRIPTION  = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Описание']]//input")
+    DESCRIPTION_TOO_LONG = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[text()='Описание']]//*[text()='Сократите текст']")
+
+    CONTINUE = (By.XPATH, "//span[@class='vkuiButton__in'][.//span[text()='Продолжить']]")
diff --git a/hw/code/ui/locators/lead_form_settings_page.py b/hw/code/ui/locators/lead_form_settings_page.py
new file mode 100644
index 0000000..6b0cc79
--- /dev/null
+++ b/hw/code/ui/locators/lead_form_settings_page.py
@@ -0,0 +1,14 @@
+from selenium.webdriver.common.by import By
+
+
+class LeadFormSettingsPageLocators:
+    ADDRESS = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Адрес')]]//input")
+    ADDRESS_EMPTY = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Адрес')]]//*[contains(., 'Нужно заполнить')]")
+
+    NAME = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Фамилия')]]//input")
+    NAME_EMPTY = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'Фамилия')]]//*[contains(., 'Нужно заполнить')]")
+
+    INN = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'ИНН')]]//input")
+    INN_LONG = (By.XPATH, "//div[contains(@class, 'vkuiFormItem')][.//*[contains(., 'ИНН')]]//*[contains(., 'Сократите')]")
+
+    FORM = (By.XPATH, "//div[contains(@class, 'ModalRoot_componentWrapper')]")
diff --git a/hw/code/ui/locators/mediateka_locators.py b/hw/code/ui/locators/mediateka_locators.py
new file mode 100644
index 0000000..ced4921
--- /dev/null
+++ b/hw/code/ui/locators/mediateka_locators.py
@@ -0,0 +1,21 @@
+from selenium.webdriver.common.by import By
+
+class MediaLoaderLocators:
+    BUTTON__CLOSE = (
+        By.XPATH,
+        "//div[contains(@class, 'ModalRoot')][contains(., 'Медиатека')]//button[@aria-label='Close']"
+    )
+    INPUT__LOAD_IMAGE = (
+        By.XPATH,
+        "//div[contains(@class, 'LocalFileSelector_topContent')]//input"
+    )
+    BUTTON__SAVE = (
+        By.XPATH,
+        "//button[.//span[contains(text(), 'Сохранить')]]"
+    )
+
+    def image(self, name) -> tuple:
+        return (
+            By.XPATH,
+            f"//div[contains(@class, 'ImageItem_imageItem')][.//span[contains(., '{name}')]]"
+        )
diff --git a/hw/code/ui/pages/advertiser_lead_form_decor_page.py b/hw/code/ui/pages/advertiser_lead_form_decor_page.py
new file mode 100644
index 0000000..57c1445
--- /dev/null
+++ b/hw/code/ui/pages/advertiser_lead_form_decor_page.py
@@ -0,0 +1,130 @@
+from dataclasses import dataclass
+import os
+import time
+from ui.pages.advertiser_lead_form_question_page import LeadFormQuestionPage
+from ui.locators.lead_form_page_locators import LeadFormDecorPageLocators
+from ui.pages.base_page import BasePage
+
+from ui.components.mediateka_component import MediaLoader
+
+@dataclass
+class CorrectDecorPageData:
+    name: str
+    logo: str
+    company: str
+    header: str
+    description: str
+
+class LeadFormDecorPage(BasePage):
+    locators = LeadFormDecorPageLocators
+    url = 'https://ads.vk.com/hq/leadads/leadforms'
+
+    correct_page_data = correct_page_data = CorrectDecorPageData(
+        name='Моя лид-форма',
+        logo='./images/logo.jpg',
+        company='Рога и копыта',
+        header='Стулья',
+        description='Продаём стулья'
+    )
+
+    @staticmethod
+    def sale_locator_types():
+        return {
+            'price': LeadFormDecorPageLocators.BUTTON__SWITCH_RUBLE,
+            'percent': LeadFormDecorPageLocators.BUTTON__SWITCH_PERCENT
+        }
+
+    def enter_lead_form_name(self, name):
+        self.input_write(self.locators.INPUT__NAME, name)
+
+    def has_lead_form_name_long_error(self):
+        return self.has_element(self.locators.INPUT__NAME_ALERT_TOO_LONG)
+    
+    def has_lead_form_name_empty_error(self):
+        return self.has_element(self.locators.INPUT__NAME_ALERT_EMPTY)
+    
+    def enter_company_name(self, name):
+        self.input_write(self.locators.INPUT__COMPANY, name)
+
+    def has_company_name_long_error(self):
+        return self.has_element(self.locators.INPUT__COMPANY_ALERT_TOO_LONG)
+    
+    def has_company_name_empty_error(self):
+        return self.has_element(self.locators.INPUT__COMPANY_ALERT_EMPTY)
+
+    def enter_lead_form_header(self, header):
+        self.input_write(self.locators.INPUT__HEADER, header)
+
+    def has_lead_form_header_long_error(self):
+        return self.has_element(self.locators.INPUT__HEADER_ALERT_TOO_LONG)
+    
+    def has_lead_form_header_empty_error(self):
+        return self.has_element(self.locators.INPUT__HEADER_ALERT_EMPTY)
+
+    def enter_lead_form_description(self, description):
+        self.input_write(self.locators.INPUT__DESCRIPTION, description)
+
+    def has_lead_form_description_long_error(self):
+        return self.has_element(self.locators.INPUT__DESCRIPTION_ALERT_TOO_LONG)
+    
+    def has_lead_form_description_empty_error(self):
+        return self.has_element(self.locators.INPUT__DESCRIPTION_ALERT_EMPTY)
+
+    def set_up_logo(self, path):
+        self.click(self.locators.INPUT__LOGO_UPLOAD)
+        media_loader = MediaLoader(self.driver)
+        media_loader.upload_image(path)
+        filename = os.path.basename(path)
+        print(filename)
+        media_loader.select_image(filename)
+
+    def has_logo(self):
+        return self.has_element(self.locators.INPUT__LOGO_CHANGE)
+
+    def has_logo_empty_error(self):
+        return self.has_element(self.locators.INPUT__LOGO_EMPTY_ALERT)
+
+    def set_up_cover(self, path):
+        self.click(self.locators.INPUT__COVER_UPLOAD)
+        media_loader = MediaLoader(self.driver)
+        media_loader.upload_image(path)
+        media_loader.select_image(os.path.basename(path))
+
+    def has_cover(self):
+        return self.has_element(self.locators.HAS_COVER)
+    
+    def switch_form_lead_magnet(self):
+        self.click(self.locators.BUTTON__SELECT_LEAD_MAGNET)
+
+    def enter_sale_size(self, type_locator, value):
+        self.click(type_locator)
+        self.input_write(self.locators.INPUT__SALE, value)
+    
+    def has_lead_magnet_sale_zero_error(self):
+        return self.has_element(self.locators.INPUT__SALE_ALERT_ZERO)
+
+    def has_lead_magnet_sale_too_big(self):
+        return self.has_element(self.locators.INPUT__SALE_ALERT_TOO_BIG)
+    
+    def enter_bonus(self, value):
+        self.switch_form_lead_magnet()
+        self.click(self.locators.SWITCH__BONUS)
+        self.input_write(self.locators.INPUT__BONUS, value)
+
+    def has_lead_magnet_bonus_empty_error(self):
+        return self.has_element(self.locators.INPUT__BONUS_ALERT_EMPTY)
+    
+    def has_lead_magnet_bonus_long_error(self):
+        return self.has_element(self.locators.INPUT__BONUS_ALERT_TOO_LONG)
+
+    def click_continue(self):
+        self.click(self.locators.BUTTON__CONTINUE)
+
+    def go_to_questions_page(self, data: CorrectDecorPageData):
+        self.enter_lead_form_name(data.name)
+        self.enter_company_name(data.company)
+        self.enter_lead_form_header(data.header)
+        self.enter_lead_form_description(data.description)
+        self.set_up_logo(data.logo)
+        self.click_continue()
+        return LeadFormQuestionPage(self.driver)  
diff --git a/hw/code/ui/pages/advertiser_lead_form_question_page.py b/hw/code/ui/pages/advertiser_lead_form_question_page.py
new file mode 100644
index 0000000..4a5ece6
--- /dev/null
+++ b/hw/code/ui/pages/advertiser_lead_form_question_page.py
@@ -0,0 +1,62 @@
+import pytest
+from ui.pages.advertiser_lead_form_result_page import LeadFormResultPage
+from ui.pages.base_page import BasePage
+from ui.locators.lead_form_questions_locators import LeadFormQuestionsLocators
+
+
+class LeadFormQuestionPage(BasePage):
+    url = 'https://ads.vk.com/hq/leadads/leadforms'
+    locators = LeadFormQuestionsLocators()
+
+    def add_question(self):
+        self.click(self.locators.ADD_QUESTION)
+    
+    def has_question(self):
+        return self.has_element(self.locators.QUESTION_EXISTS)
+    
+    def enter_question_text(self, text):
+        input = self.find(self.locators.QUESTION_TEXT)
+        input.clear()
+        input.send_keys(text)
+
+    def has_question_text(self, text):
+        return self.has_element(self.locators.HAS_QUESTION_TEXT_ON_PREVIEW(text))
+    
+    def add_answer_text(self, id: int, text: str):
+        input = self.find(self.locators.ANSWER(id))
+        input.clear()
+        input.send_keys(text)
+    
+    def has_answer_text(self, text):
+        return self.has_element(self.locators.HAS_ANSWER_ON_PREVIEW(text))
+    
+    def switch_question_type_many_answers(self):
+        self.click(self.locators.QUESTION_TYPE)
+        self.click(self.locators.QUESTION_TYPE_MANY_ANSWERS)
+
+    def has_question_type_many_answers(self):
+        return self.has_element(self.locators.HAS_QUESTION_TYPE_MANY_ANSWERS_ON_PREVIEW)
+    
+    def add_answer_input(self):
+        self.click(self.locators.ADD_ANSWER)
+
+    def remove_question(self):
+        self.click(self.locators.REMOVE_QUESTION)
+
+    def add_contact_field(self, text):
+        self.click(self.locators.ADD_CONTACTS)
+        self.click(self.locators.SELECT_CONTACT(text))
+        self.click(self.locators.CONFIRM_CONTACTS)
+    
+    def has_contact_name_on_preview(self):
+        return self.has_element(self.locators.HAS_CONTACTS_NAME_ON_PREVIEW)
+    
+    def has_contact_telephone_on_preview(self):
+        return self.has_element(self.locators.HAS_CONTACTS_TELEPHONE_ON_PREVIEW)
+    
+    def has_contact_city_on_preview(self):
+        return self.has_element(self.locators.HAS_CONTACTS_CITY_ON_PREVIEW)
+    
+    def go_to_result_page(self):
+        self.click(self.locators.CONTINUE)
+        return LeadFormResultPage(self.driver)
diff --git a/hw/code/ui/pages/advertiser_lead_form_result_page.py b/hw/code/ui/pages/advertiser_lead_form_result_page.py
new file mode 100644
index 0000000..73de4ed
--- /dev/null
+++ b/hw/code/ui/pages/advertiser_lead_form_result_page.py
@@ -0,0 +1,66 @@
+from ui.locators.lead_form_result_page_locators import LeadFormResultPageLocators
+from ui.pages.base_page import BasePage
+from ui.pages.advertiser_lead_form_settings import LeadFormSettingsPage
+
+
+class LeadFormResultPage(BasePage):
+    url = 'https://ads.vk.com/hq/leadads/leadforms'
+    locators = LeadFormResultPageLocators
+
+    def enter_header(self, header: str):
+        self.input_write_without_submit(self.locators.HEADER, header)
+        self.click(self.locators.CONTINUE)
+
+    def has_enter_header_empty_error(self):
+        return self.has_element(self.locators.HEADER_EMPTY)
+    
+    def has_enter_header_long_error(self):
+        return self.has_element(self.locators.HEADER_TOO_LONG)
+    
+    def enter_description(self, description: str):
+        self.input_write_without_submit(self.locators.DESCRIPTION, description)
+        self.click(self.locators.CONTINUE)
+
+
+    def has_description_long_error(self):
+        return self.has_element(self.locators.DESCRIPTION_TOO_LONG)
+
+    def click_add_site(self):
+        self.click(self.locators.ADD_SITE)
+    
+    def enter_site(self, link: str):
+        self.input_write_without_submit(self.locators.SITE, link)
+
+    def has_site(self):
+        return self.has_element(self.locators.HAS_SITE)
+
+    def click_add_tel(self):
+        self.click(self.locators.ADD_TEL)
+    
+    def enter_tel(self, tel: str):
+        self.input_write_without_submit(self.locators.TEL, tel)
+        self.click(self.locators.CONTINUE)
+
+
+    def has_tel(self):
+        return self.has_element(self.locators.HAS_TEL)
+
+    def has_tel_incorrect_error(self):
+        return self.has_element(self.locators.TEL_INCORRECT)
+
+    def click_add_promo(self):
+        self.click(self.locators.ADD_PROMO)
+    
+    def enter_promo(self, promo: str):
+        self.input_write_without_submit(self.locators.PROMO, promo)
+        self.click(self.locators.CONTINUE)
+    
+    def has_promo(self):
+        return self.has_element(self.locators.HAS_PROMO)
+
+    def has_promo_too_long_error(self):
+        return self.has_element(self.locators.PROMO_TOO_LONG)
+
+    def go_to_settings_page(self):
+        self.click(self.locators.CONTINUE)
+        return LeadFormSettingsPage(self.driver)
diff --git a/hw/code/ui/pages/advertiser_lead_form_settings.py b/hw/code/ui/pages/advertiser_lead_form_settings.py
new file mode 100644
index 0000000..1203b5b
--- /dev/null
+++ b/hw/code/ui/pages/advertiser_lead_form_settings.py
@@ -0,0 +1,28 @@
+from ui.locators.lead_form_settings_page import LeadFormSettingsPageLocators
+from ui.pages.base_page import BasePage
+
+
+class LeadFormSettingsPage(BasePage):
+    url = 'https://ads.vk.com/hq/leadads/leadforms'
+    locators = LeadFormSettingsPageLocators
+
+    def enter_name(self, name):
+        self.input_write(self.locators.NAME, name)
+
+    def has_name_empty_error(self):
+        return self.has_element(self.locators.NAME_EMPTY)
+    
+    def enter_address(self, address):
+        self.input_write(self.locators.ADDRESS, address)
+
+    def has_address_empty_error(self):
+        return self.has_element(self.locators.ADDRESS_EMPTY)
+
+    def enter_inn(self, inn):
+        self.input_write(self.locators.INN, inn)
+
+    def has_inn_long_error(self):
+        return self.has_element(self.locators.INN_LONG)
+    
+    def is_modal_closed(self):
+        return not self.has_element(self.locators.FORM)
diff --git a/hw/code/ui/pages/advertiser_leadforms_page.py b/hw/code/ui/pages/advertiser_leadforms_page.py
new file mode 100644
index 0000000..5b9771d
--- /dev/null
+++ b/hw/code/ui/pages/advertiser_leadforms_page.py
@@ -0,0 +1,12 @@
+from ui.pages.base_page import BasePage
+from ui.locators.lead_form_page_locators import LeadFormPageLocators
+from ui.pages.advertiser_lead_form_decor_page import LeadFormDecorPage
+
+class LeadFormsPage(BasePage):
+    locators = LeadFormPageLocators
+    url = 'https://ads.vk.com/hq/leadads/leadforms'
+
+    def go_to_lead_forms_decor_page(self) -> LeadFormDecorPage:
+        self.click(self.locators.BUTTON__CREATE_NEW)
+        return LeadFormDecorPage(self.driver)
+        
\ No newline at end of file
diff --git a/images/cover.jpg b/images/cover.jpg
new file mode 100644
index 0000000..fad81a3
Binary files /dev/null and b/images/cover.jpg differ
diff --git a/images/logo.jpg b/images/logo.jpg
new file mode 100644
index 0000000..a405e14
Binary files /dev/null and b/images/logo.jpg differ


Reviews:
user: login: Regikon
body: В целом даже неплохо, минификсы ток нужны. Пару утечек sleep можно через асинхронные ожидания профиксить, могу попробовать поправить
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-15T06:32:10Z
user: login: LizaSemyan
body: картинки супер, слипы грустно но понимаемо
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-15T06:54:18Z

Review comments:
diff_hunk: @@ -0,0 +1,83 @@
+from base_case import BaseCase, UserType
+from ui.pages.advertiser_leadforms_page import LeadFormsPage
+
+import pytest
+
+class TestCreateLeadFormDecorPage(BaseCase):
+    user = UserType.ADVERTISER
+
+    @pytest.fixture(scope="function")
+    def decor_page(self, request):
+        driver = self.driver
+        driver.get(LeadFormsPage.url)
+        return LeadFormsPage(driver).go_to_lead_forms_decor_page()
+
+    def test_lead_form_name_long_error(self, decor_page):
+        decor_page.enter_lead_form_name('a' * 256)
+        assert decor_page.has_lead_form_name_long_error()
+
+    def test_lead_form_name_empty_error(self, decor_page):
+        decor_page.enter_lead_form_name('')
+        assert decor_page.has_lead_form_name_empty_error()
+
+    def test_logo_empty_error(self, decor_page):
+        decor_page.click_continue()
+        assert decor_page.has_logo_empty_error()
+
+    def test_logo_set_up(self, decor_page):
+        decor_page.set_up_logo('./images/logo.jpg')
+        assert decor_page.has_logo()
+    
+    def test_company_name_too_long_error(self, decor_page):
+        decor_page.enter_company_name('a' * 31)
+        assert decor_page.has_company_name_long_error()
+    
+    def test_company_name_empty_error(self, decor_page):
+        decor_page.enter_company_name('')
+        assert decor_page.has_company_name_empty_error()
+
+    def test_header_too_long_error(self, decor_page):
+        decor_page.enter_lead_form_header('a' * 51)
+        assert decor_page.has_lead_form_header_long_error()
+    
+    def test_header_empty_error(self, decor_page):
+        decor_page.enter_lead_form_header('')
+        assert decor_page.has_lead_form_header_empty_error()
+
+    def test_description_too_long_error(self, decor_page):
+        decor_page.enter_lead_form_description('a' * 36)
+        assert decor_page.has_lead_form_description_long_error()
+    
+    def test_description_empty_error(self, decor_page):
+        decor_page.enter_lead_form_description('')
+        assert decor_page.has_lead_form_description_empty_error()
+    
+    # @pytest.mark.skip('skip')
+    def test_cover_set_up(self, decor_page):
+        decor_page.set_up_cover('./images/cover.jpg')
+        assert decor_page.has_cover()
+
+    def test_lead_magnet_sale_zero_error(self, decor_page):
+        decor_page.switch_form_lead_magnet()
+        sale_type = decor_page.sale_locator_types()
+        decor_page.enter_sale_size(sale_type['price'], '0')
+        assert decor_page.has_lead_magnet_sale_zero_error()
+
+    def test_lead_magnet_sale_bigger_100_percent(self, decor_page):
+        decor_page.switch_form_lead_magnet()
+        sale_type = decor_page.sale_locator_types()
+        decor_page.enter_sale_size(sale_type['percent'], '101')
+        assert decor_page.has_lead_magnet_sale_too_big()
+
+    def test_lead_magnet_bonus_empty_error(self, decor_page):
+        decor_page.enter_bonus('')
+        assert decor_page.has_lead_magnet_bonus_empty_error()
+
+    def test_lead_magnet_bonus_long_error(self, decor_page):
+        decor_page.enter_bonus('a' * 31)
+        assert decor_page.has_lead_magnet_bonus_long_error()
+
+    # def test_go_to_questions_page(self, decor_page):
+    #     decor_page.click_continue()
+    #     assert decor_page.has_questions_page()
path: hw/code/test_add_lead_form_decor.py
user: login: Regikon
body: В требованиях есть бан на закоменченный код, надо будет убрать
created_at: 2025-05-15T06:27:05Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,39 @@
+import time
+import pytest
+from base_case import BaseCase, UserType
+
+from ui.pages.advertiser_leadforms_page import LeadFormsPage
+from ui.pages.advertiser_lead_form_decor_page import LeadFormDecorPage
+
+class TestCreateLeadFormSettingsPage(BaseCase):
+    user = UserType.ADVERTISER
+
+    @pytest.fixture(scope='function')
+    def settings_page(self, request):
+        driver = self.driver
+        driver.get(LeadFormsPage.url)
+        return LeadFormsPage(driver).\
+            go_to_lead_forms_decor_page().\
+            go_to_questions_page(LeadFormDecorPage.correct_page_data).\
+            go_to_result_page().\
+            go_to_settings_page()
+    
+    def test_enter_name_empty(self, settings_page):
+        settings_page.enter_name('')
+        assert settings_page.has_name_empty_error()
+
+    def test_enter_address_empty(self, settings_page):
+        settings_page.enter_address('')
+        assert settings_page.has_address_empty_error()
+    
+    def test_enter_inn_long(self, settings_page):
+        settings_page.enter_inn('1' * 33)
+        assert settings_page.has_inn_long_error()
+
+    def test_save_form(self, settings_page):
+        settings_page.enter_inn('123456789')
+        settings_page.enter_name('Иван Иванов')
+        settings_page.enter_address('Москва, ул. Пушкина, д. 1')
+        time.sleep(1)
+        assert settings_page.is_modal_closed()
path: hw/code/test_add_lead_form_settings.py
user: login: Regikon
body: Я с такой штукой тоже сталкивался, добавил в base page метод на проверку, что элемент исчез, можно использовать, чтобы задетектить, что модалка закрылась с таймаутом
created_at: 2025-05-15T06:29:25Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,27 @@
+import time
+from ui.components.base_component import BaseComponent
+from ui.locators.mediateka_locators import MediaLoaderLocators
+
+import os
+
+class MediaLoader(BaseComponent):
+    def __init__(self, driver):
+        super().__init__(driver)
+        self.locators = MediaLoaderLocators()
+
+    def upload_image(self, path):
+        file_input = self.find(self.locators.INPUT__LOAD_IMAGE)
+        file_input.send_keys(os.path.abspath(path))
+        file_input.submit()
+        
+    
+    def select_image(self, name):
+        image_locator = self.locators.image(name)
+        print(image_locator)
+        self.click(image_locator)
+        time.sleep(3) # it's really necessary
path: hw/code/ui/components/mediateka_component.py
user: login: Regikon
body: А зачем? У click под капотом же тоже есть таймаут?
created_at: 2025-05-15T06:30:27Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,23 @@
+from selenium.webdriver.common.by import By
+
+class MediaLoaderLocators:
+    BUTTON__CLOSE = (
+        By.XPATH,
+        "//div[contains(@class, 'ModalRoot')][contains(., 'Медиатека')]//button[@aria-label='Close']"
+    )
+    INPUT__LOAD_IMAGE = (
+        By.XPATH,
+        "//div[contains(@class, 'LocalFileSelector_topContent')]//input"
+    )
+    BUTTON__SAVE = (
+        By.XPATH,
+        "//button[.//span[contains(text(), 'Сохранить')]]"
+    )
+
+    def image(self, name) -> tuple:
+        return (
+            By.XPATH,
+            f"//div[contains(@class, 'ImageItem_imageItem')][.//span[contains(., '{name}')]]"
+        )
+
+    # TEST_LOGO = (By.XPATH, "//div[contains(@class, 'ImageItem_imageItem')][.//span[contains(., 'logo.jpg')]]")
path: hw/code/ui/locators/mediateka_locators.py
user: login: Regikon
body: Комент просочился
created_at: 2025-05-15T06:30:57Z
author_association: OWNER

PR comments:
user: login: Regikon
created_at: 2025-05-15T06:41:09Z
body: Немного профиксил, остальное можно будет после слива моей ветки, там хелпер методы есть чтобы не делать слипы
author_association: OWNER
user: login: Regikon
created_at: 2025-05-15T09:15:49Z
body: Сливаю, замечания поправим отдельной веткой
author_association: OWNER