⚖️ Старый vs Новый: POM-рефакторинг

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

Здесь показано, как выглядели примеры из лекции (старый стиль) и как они трансформируются в современный POM с BasePage.

⚡ Главные переходы

  • Локаторы в тестах → локаторы в классе страницы
  • driver.find_element(By.ID, "...")page.method()
  • LoginPage без BasePage (из лекции) → LoginPage(BasePage) с общими методами
  • Фикстуры в каждом классе → BaseTest с autouse=True

Переход 1: тесты с голыми локаторами → LoginPage

Из лекции (старое): тест напрямую ищет элементы через driver.find_element.
# tests/test_login.py — ИЗ ЛЕКЦИИ (старый подход)
def test_success_login_valid_data(driver):
    user_name_input_field = driver.find_element(By.ID, "user-name")
    user_name_input_field.send_keys("standard_user")
    password_input_field = driver.find_element(By.ID, "password")
    password_input_field.send_keys("secret_sauce")
    login_button = driver.find_element(By.ID, "login-button")
    login_button.click()
    assert driver.current_url == "https://www.saucedemo.com/inventory.html"

def test_invalid_password(driver):
    user_name_input_field = driver.find_element(By.ID, "user-name")  # дубль
    user_name_input_field.send_keys("standard_user")
    password_input_field = driver.find_element(By.ID, "password")   # дубль
    password_input_field.send_keys("wrong_password")
    login_button = driver.find_element(By.ID, "login-button")        # дубль
    login_button.click()
    error_message = driver.find_element(By.CLASS_NAME, "error-message-container")
    assert "Username and password do not match" in error_message.text
Современно (POM): тест работает с методами страницы, не знает локаторов.
# tests/test_login.py — СОВРЕМЕННЫЙ СТИЛЬ (POM)
from tests.base_test import BaseTest


class TestLogin(BaseTest):
    def test_valid_login(self):
        self.login_page.success_login("standard_user", "secret_sauce")
        assert "inventory.html" in self.driver.current_url

    def test_invalid_password(self):
        self.login_page.open()
        self.login_page.success_login("standard_user", "wrong_password")
        error = self.login_page.get_error_message()
        assert "Username and password do not match" in error

Переход 2: LoginPage без BasePage → LoginPage(BasePage)

Из лекции (старое): LoginPage сам создаёт WebDriverWait и напрямую ищет элементы через wait.until(...).
# pages/login_page.py — ИЗ ЛЕКЦИИ (старое)
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


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")))

    def get_password_input(self):
        return self.wait.until(EC.presence_of_element_located((By.ID, "password")))

    def get_login_button(self):
        return self.wait.until(EC.element_to_be_clickable((By.ID, "login-button")))

    def enter_username(self, username):
        field = self.get_username_input()
        field.clear()
        field.send_keys(username)

    def enter_password(self, password):
        field = self.get_password_input()
        field.clear()
        field.send_keys(password)

    def click_on_login_button(self):
        self.get_login_button().click()
Современно (POM с BasePage): LoginPage наследует общую логику ожидания из BasePage. Локаторы — атрибуты класса.
# pages/login_page.py — СОВРЕМЕННЫЙ СТИЛЬ (с BasePage)
from selenium.webdriver.common.by import By
from pages.base_page import BasePage


class LoginPage(BasePage):
    # Локаторы как атрибуты класса — видны сразу при чтении
    USERNAME_INPUT = (By.ID, "user-name")
    PASSWORD_INPUT = (By.ID, "password")
    LOGIN_BUTTON   = (By.ID, "login-button")
    ERROR_MESSAGE  = (By.CLASS_NAME, "error-message-container")

    URL = "https://www.saucedemo.com/"

    def open(self):
        self.driver.get(self.URL)

    def enter_username(self, username):
        self.type_text(self.USERNAME_INPUT, username)   # из BasePage

    def enter_password(self, password):
        self.type_text(self.PASSWORD_INPUT, password)   # из BasePage

    def click_login(self):
        self.click(self.LOGIN_BUTTON)                   # из BasePage

    def success_login(self, username, password):
        self.enter_username(username)
        self.enter_password(password)
        self.click_login()

    def get_error_message(self):
        return self.get_text(self.ERROR_MESSAGE)        # из BasePage

Переход 3: фикстуры в каждом классе → BaseTest

Из лекции (промежуточный): инициализация driver и страниц повторяется в каждом тестовом классе.
# tests/test_inventory.py — ИЗ ЛЕКЦИИ (промежуточный)
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from pages.inventory_page import InventoryPage
from pages.login_page import LoginPage


class TestInventory:
    @pytest.fixture(scope="class")
    def driver(self):
        d = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
        d.maximize_window()
        d.get("https://www.saucedemo.com/")
        yield d
        d.quit()

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

    @pytest.fixture(scope="class")
    def login_page(self, driver):
        return LoginPage(driver)

    def test_items_amount(self, inventory_page, login_page):
        login_page.success_login("standard_user", "secret_sauce")
        assert inventory_page.get_items_amount() == 6
Современно (BaseTest): инициализация один раз в BaseTest, все классы наследуются от него.
# tests/test_inventory.py — СОВРЕМЕННЫЙ СТИЛЬ (BaseTest)
from tests.base_test import BaseTest


class TestInventory(BaseTest):
    # driver, login_page, inventory_page — уже доступны через BaseTest
    def test_items_amount(self):
        self.login_page.success_login("standard_user", "secret_sauce")
        assert self.inventory_page.get_items_amount() == 6

Переход 4: WebDriverWait в каждом методе → в BasePage

Старое (из задания в лекции): WebDriverWait создаётся прямо в методе.
# Плохо: WebDriverWait создаётся при каждом вызове
class LoginPage:
    def get_username_input(self, driver):
        return WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.ID, "user-name"))
        )
Современно: self.wait создаётся один раз в BasePage.__init__ и переиспользуется.
# Хорошо: self.wait создаётся один раз в BasePage
class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)  # один раз на всю жизнь объекта

    def find(self, locator):
        return self.wait.until(EC.presence_of_element_located(locator))