🐛 Типичные ошибки: Page Object Model

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

⚡ Топ-5 ошибок POM

  1. Нет __init__.py в pages/ModuleNotFoundError
  2. Локаторы прямо в тестах, а не в классе страницы → нарушение POM
  3. scope="class", но тесты зависят друг от друга → падает второй тест
  4. Фикстура в методе класса без self как первого аргумента
  5. login_page.error_message.text — обращение к атрибуту, которого нет

Ошибка 1: ModuleNotFoundError — нет __init__.py

Симптом: ModuleNotFoundError: No module named 'pages'
# Ошибка: файл __init__.py отсутствует
saucedemo_tests/
├── pages/
│   ├── login_page.py     # ← нет __init__.py!
│   └── inventory_page.py

# Импорт упадёт:
from pages.login_page import LoginPage  # ModuleNotFoundError

Исправление: создайте пустые файлы __init__.py:

saucedemo_tests/
├── pages/
│   ├── __init__.py       # ← создаём (пустой файл)
│   ├── login_page.py
│   └── inventory_page.py
└── tests/
    ├── __init__.py       # ← тоже создаём
    └── test_login.py

В PowerShell:

New-Item -Path "pages\__init__.py" -ItemType File
New-Item -Path "tests\__init__.py" -ItemType File

Ошибка 2: Локаторы в тестах (нарушение POM)

Симптом: тест содержит driver.find_element(By.ID, ...) — POM не используется.
# Неправильно: локатор в тесте
def test_login(self):
    self.driver.find_element(By.ID, "user-name").send_keys("standard_user")  # ❌

Исправление: любой поиск элемента — только в классе страницы:

# Правильно: вызов метода страницы
def test_login(self):
    self.login_page.enter_username("standard_user")  # ✅

Ошибка 3: scope="class" при зависимых тестах

Симптом: второй тест падает с AssertionError или NoSuchElementException, хотя код правильный.
# Неправильно: тесты зависят — второй видит состояние первого
class TestInventory(BaseTest):  # scope="class" → один браузер на класс

    def test_add_to_cart(self):
        self.login_page.success_login("standard_user", "secret_sauce")
        self.inventory_page.add_item_to_cart("Sauce Labs Backpack")
        # Корзина теперь не пуста!

    def test_cart_empty_at_start(self):
        # ПАДАЕТ: корзина не пуста из-за предыдущего теста
        self.login_page.success_login("standard_user", "secret_sauce")
        assert self.inventory_page.get_cart_count() == 0  # ❌ упадёт

Исправление — вариант A: каждый тест начинает с чистого URL:

def test_cart_empty_at_start(self):
    self.login_page.open()  # открываем страницу заново — корзина пустая
    self.login_page.success_login("standard_user", "secret_sauce")
    assert self.inventory_page.get_cart_count() == 0  # ✅

Исправление — вариант B: использовать scope="function" если тесты действительно должны быть независимы:

# conftest.py
@pytest.fixture(scope="function")  # новый браузер для каждого теста
def driver():
    ...

Ошибка 4: Фикстура в классе без self

Симптом: fixture 'driver' not found или фикстура не получает self.
# Неправильно: фикстура класса без self
class TestInventory:
    @pytest.fixture(scope="class")
    def driver():        # ❌ — нет self!
        d = webdriver.Chrome(...)
        yield d
        d.quit()

    @pytest.fixture(scope="class")
    def inventory_page(driver):  # ❌ — нет self, не видит self.driver
        return InventoryPage(driver)

Исправление: первый аргумент метода — всегда self:

class TestInventory:
    @pytest.fixture(scope="class")
    def driver(self):        # ✅
        d = webdriver.Chrome(...)
        yield d
        d.quit()

    @pytest.fixture(scope="class")
    def inventory_page(self, driver):  # ✅
        return InventoryPage(driver)

Ошибка 5: Обращение к несуществующему атрибуту страницы

Симптом: AttributeError: 'LoginPage' object has no attribute 'error_message'
# Неправильно: в тесте обращаются к атрибуту, которого нет в LoginPage
def test_invalid_password(driver):
    login_page = LoginPage(driver)
    login_page.enter_username("standard_user")
    login_page.enter_password("wrong")
    login_page.click_login()
    assert "do not match" in login_page.error_message.text  # ❌ нет такого свойства!

Исправление: добавить метод get_error_message() в класс страницы:

# pages/login_page.py
def get_error_message(self):
    return self.get_text(self.ERROR_MESSAGE)  # ✅

# tests/test_login.py
assert "do not match" in login_page.get_error_message()  # ✅

Ошибка 6: Неправильный XPath в методах InventoryPage

Симптом: NoSuchElementException при попытке получить цену или добавить товар.
# Неправильно: имя товара не совпадает точно
inventory_page.get_item_price("Backpack")         # ❌ — нет такого текста
inventory_page.add_item_to_cart("Bolt T-Shirt")   # ❌ — должно быть "Sauce Labs Bolt T-Shirt"

Исправление: имя товара в XPath должно совпадать с текстом элемента страницы точно:

inventory_page.get_item_price("Sauce Labs Backpack")          # ✅
inventory_page.add_item_to_cart("Sauce Labs Bolt T-Shirt")    # ✅

Ошибка 7: conftest.py в неправильной директории

Симптом: фикстура driver из conftest.py не видна в тестах.
# Неправильно: conftest.py внутри tests/, а тесты его не видят
saucedemo_tests/
└── tests/
    ├── conftest.py      # ← pytest ищет здесь — OK для тестов в этой папке
    └── test_login.py

Правило: conftest.py работает для тестов в своей директории и всех вложенных. Для проекта лучше разместить его в корне:

saucedemo_tests/
├── conftest.py          # ✅ виден всем тестам в проекте
├── pages/
└── tests/
    └── test_login.py