✅ Решения: ответы на вопросы самопроверки

← К оглавлению урока

⚡ Краткие ответы

  • POM: паттерн, где каждая страница = класс; локаторы и методы — в классе; тесты вызывают только методы
  • Ошибки: инициализация элементов в __init__, локаторы в тестах, нет WebDriverWait, нет __init__.py
  • Динамические элементы: WebDriverWait + EC.presence_of_element_located
  • Упрощение: BasePage с общими методами, BaseTest с autouse=True

Ответ 1: Что такое POM

Из лекции: Page Object Model (POM) — шаблон проектирования, используемый в автоматизированном тестировании для организации кода. В POM для каждой страницы веб-приложения создаётся отдельный класс, содержащий локаторы элементов и методы для взаимодействия с ними.

Зачем нужен:
  • Снижение дублирования кода — локаторы и методы хранятся в одном месте
  • Упрощение поддержки — изменения на странице требуют редактирования только одного класса
  • Улучшение читаемости — тесты работают с методами, а не с сырыми локаторами

Ответ 2: Организация POM-проекта

Правильная структура (из лекции):
/tests
    test_login.py
    test_inventory.py
    test_cart.py
/pages
    login_page.py
    inventory_page.py
    cart_page.py
    checkout_page.py
Файл pages/ содержит классы Page Object (LoginPage, InventoryPage, CartPage). Локаторы хранятся внутри соответствующего Page Object. Методы взаимодействия выполняют действия с элементами. Тесты хранятся отдельно в tests/ и используют Page Object для сценариев.

Ответ 3: Основные ошибки POM

Из лекции — три ключевые ошибки:
  1. Инициализация элементов в __init__:
    # Плохо — элемент ищется при создании объекта (страница может не загрузиться)
    class LoginPage:
        def __init__(self, driver):
            self.username_input = driver.find_element(By.ID, "user-name")  # Ошибка!
    
    # Хорошо — элемент ищется при вызове метода
    def get_username_input(self):
        return self.driver.find_element(By.ID, "user-name")
  2. Хранение сложной логики в тестах: в тестах должны использоваться методы Page Object, а не find_element().
  3. Неиспользование WebDriverWait: если элементы загружаются динамически, нужно использовать явные ожидания, а не find_element() напрямую.

Ответ 4: Динамически изменяющиеся элементы

Из лекции: если элемент может загружаться с задержкой, используй WebDriverWait вместо find_element():
# pages/login_page.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By


class LoginPage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def get_username_input(self):
        return self.wait.until(
            EC.presence_of_element_located((By.ID, "user-name"))
        )
В современном POM с BasePage это уже встроено в find(locator).

Ответ 5: Упрощение POM в больших проектах

Из лекции: два способа
  1. BasePage — общий класс, от которого наследуются все Page Object:
    # pages/base_page.py
    class BasePage:
        def __init__(self, driver):
            self.driver = driver
            self.wait = WebDriverWait(driver, 10)
    
        def click(self, locator):
            self.wait.until(EC.element_to_be_clickable(locator)).click()
    
        def type_text(self, locator, text):
            field = self.find(locator)
            field.clear()
            field.send_keys(text)
    
    # pages/login_page.py
    class LoginPage(BasePage):
        USERNAME_INPUT = (By.ID, "user-name")
    
        def enter_username(self, username):
            self.type_text(self.USERNAME_INPUT, username)  # из BasePage
  2. BaseTest — базовый тестовый класс с фикстурой autouse=True: вместо инициализации driver в каждом классе создаём один BaseTest, от которого наследуются все тестовые классы.

Ответ 6: StaleElementReferenceException

Проблема: элемент ищется в __init__ при создании объекта страницы. Если страница перезагружается или DOM обновляется (например, AJAX), ссылка на элемент устаревает (StaleElementReferenceException). Метод enter_username обращается к уже несуществующей ссылке.

Решение: искать элемент в момент использования (в методе), а не при инициализации класса.

Ответ 7: Локаторы в тесте

Проблема: в методе test_valid тест напрямую использует driver.find_element(By.ID, ...) — это нарушает принцип POM. Если id="user-name" изменится, нужно обновить каждый тест. При наличии 20 тестов это становится катастрофой.

Решение: вынести поиск элементов и действия в класс LoginPage, а тест пусть вызывает только login_page.success_login().

Ответ 8: Отсутствие __init__.py

Результат: Python не воспринимает директорию pages/ как пакет. При попытке from pages.login_page import LoginPage возникнет ModuleNotFoundError: No module named 'pages'.

Решение: создать пустой файл pages/__init__.py и tests/__init__.py.

Ответ 9: Тест сравнения цен (полное решение)

# tests/test_all_items_cost.py
from tests.base_test import BaseTest


class TestAllItemsCost(BaseTest):
    def test_three_items_prices_match_cart(self):
        # 1. Открытие и авторизация
        self.login_page.open()
        self.login_page.success_login("standard_user", "secret_sauce")

        # 2. Проверка URL
        assert self.driver.current_url == "https://www.saucedemo.com/inventory.html"

        # 3. Цены на странице инвентаря
        items_cost_from_inventory = [
            self.inventory_page.get_item_price("Sauce Labs Backpack"),
            self.inventory_page.get_item_price("Sauce Labs Bike Light"),
            self.inventory_page.get_item_price("Sauce Labs Bolt T-Shirt"),
        ]
        # 4. Вывод цен в консоль
        for price in items_cost_from_inventory:
            print(f"Цена товара: {price}")

        # 5. Добавление в корзину
        self.inventory_page.add_item_to_cart("Sauce Labs Backpack")
        self.inventory_page.add_item_to_cart("Sauce Labs Bike Light")
        self.inventory_page.add_item_to_cart("Sauce Labs Bolt T-Shirt")

        # 6. Переход в корзину
        self.inventory_page.go_to_cart()

        # 7. Цены в корзине
        items_cost_from_cart = [
            self.cart_page.get_cart_item_price("Sauce Labs Backpack"),
            self.cart_page.get_cart_item_price("Sauce Labs Bike Light"),
            self.cart_page.get_cart_item_price("Sauce Labs Bolt T-Shirt"),
        ]

        # 8. Сравнение
        assert items_cost_from_inventory == items_cost_from_cart, (
            "Цены в корзине не совпадают с ценами на странице Inventory"
        )

Ответ 10: scope="function" vs scope="class"

scope="function" (по умолчанию): новый браузер перед каждым тестом → чистое состояние → медленнее. Используй, когда тесты изменяют состояние (добавляют товары, меняют URL) и должны быть независимы.

scope="class": один браузер на весь класс тестов → быстрее. Используй, когда тесты в классе не влияют друг на друга и не оставляют «грязного» состояния браузера.

Ответ 11: autouse=True

autouse=True означает, что фикстура запускается автоматически для всех тестов в её области (scope), без явного указания в параметрах теста. В BaseTest:
class BaseTest:
    @pytest.fixture(scope="class", autouse=True)
    def setup(self):
        self.driver = ...
        yield
        self.driver.quit()
Каждый класс, унаследованный от BaseTest, автоматически получает инициализированный driver без явного указания setup в аргументах тестовых методов.

Ответ 12: Проблема "грязного состояния"

При scope="class" браузер используется всеми тестами класса. Если test_1 добавил товар в корзину и не удалил его, test_2 начнёт работу с непустой корзиной — это «грязное состояние».

Решения:
  1. Каждый тест сам восстанавливает нужное состояние (открывает страницу заново через login_page.open())
  2. Использовать scope="function" — каждый тест получает чистый браузер
  3. В конце каждого теста явно убирать за собой (удалять добавленные товары)