✅ Решения: ответы на вопросы самопроверки
⚡ Краткие ответы
- 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
Из лекции — три ключевые ошибки:
-
Инициализация элементов в
__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") -
Хранение сложной логики в тестах: в тестах должны использоваться методы Page Object, а не
find_element(). -
Неиспользование 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 в больших проектах
Из лекции: два способа
-
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 -
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": один браузер на весь класс тестов → быстрее. Используй, когда тесты в классе не влияют друг на друга и не оставляют «грязного» состояния браузера.
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 начнёт работу с непустой корзиной — это «грязное состояние».
Решения:
- Каждый тест сам восстанавливает нужное состояние (открывает страницу заново через
login_page.open()) - Использовать
scope="function"— каждый тест получает чистый браузер - В конце каждого теста явно убирать за собой (удалять добавленные товары)