✅ Решения: Page Object Model
⚡ Ключевые паттерны из решений
TestInventory(BaseTest)— наследование и доступ кself.inventory_pageget_item_price(item_name)— XPath с ancestorget_items_prices()— список всех цен в корзине- Drag & Drop: переключение в iframe → ActionChains → default_content
Решение 1: TestInventory
# tests/test_inventory.py
from tests.base_test import BaseTest
class TestInventory(BaseTest):
def test_items_amount(self):
self.login_page.open()
self.login_page.success_login("standard_user", "secret_sauce")
assert self.inventory_page.get_items_amount() == 6, \
"Количество товаров не совпадает."
def test_all_items_are_displayed(self):
self.login_page.open()
self.login_page.success_login("standard_user", "secret_sauce")
assert self.inventory_page.all_items_are_displayed(), \
"Не все товары отображаются."
def test_all_items_names_are_displayed(self):
self.login_page.open()
self.login_page.success_login("standard_user", "secret_sauce")
assert self.inventory_page.all_items_names_are_displayed(), \
"Не все названия товаров отображаются."
def test_all_item_names_are_not_empty(self):
self.login_page.open()
self.login_page.success_login("standard_user", "secret_sauce")
assert self.inventory_page.all_item_names_are_not_empty(), \
"Есть товары с пустыми названиями."
def test_all_item_names_contains_sauce_labs(self):
self.login_page.open()
self.login_page.success_login("standard_user", "secret_sauce")
assert self.inventory_page.all_item_names_contains_sauce_labs(), \
"Не все товары начинаются с 'Sauce Labs'."
Логика: каждый тест вызывает
open() + success_login() для начала с чистого состояния. При scope="class" драйвер создаётся один раз, но логин выполняется в каждом тесте, чтобы гарантировать нахождение на странице инвентаря.
Решение 2: Сравнение цен трёх товаров (из лекции)
Сначала метод для получения нескольких цен из корзины в CartPage:
# pages/cart_page.py (фрагмент)
from selenium.webdriver.common.by import By
from pages.base_page import BasePage
class CartPage(BasePage):
CHECKOUT_BUTTON = (By.ID, "checkout")
ITEM_PRICES = (By.CLASS_NAME, "inventory_item_price")
def get_items_prices(self):
"""Возвращает список строк цен всех товаров в корзине."""
return [el.text for el in self.find_all(self.ITEM_PRICES)]
def proceed_to_checkout(self):
self.click(self.CHECKOUT_BUTTON)
Тест:
# tests/test_prices.py
from tests.base_test import BaseTest
class TestAllItemsCost(BaseTest):
def test_all_items_cost_are_correct(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", \
"URL страницы после входа неверен."
# 3. Запоминаем цены на странице Inventory
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 item_cost in items_cost_from_inventory:
print(f"Цена товара: {item_cost}")
# 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_items_prices()
# 8. Сравниваем
assert items_cost_from_inventory == items_cost_from_cart, \
"Цены в корзине не совпадают с ценами на странице Inventory."
Логика:
get_item_price() находит цену через XPath с ancestor, get_items_prices() возвращает список по CLASS_NAME. Порядок добавления товаров в корзину соответствует порядку в списке цен.
Решение 3: Drag & Drop через POM
# pages/drag_and_drop_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver import ActionChains
from pages.base_page import BasePage
class DragAndDropPage(BasePage):
"""Page Object для страницы drag-and-drop на globalsqa.com."""
URL = "https://www.globalsqa.com/demo-site/draganddrop/"
# iframe содержит всё взаимодействие
IFRAME = (By.CSS_SELECTOR, "iframe[src*='draganddrop']")
PHOTOS = (By.CSS_SELECTOR, "#gallery li")
TRASH_AREA = (By.ID, "trash")
TRASH_PHOTOS = (By.CSS_SELECTOR, "#trash li")
def open(self):
self.driver.get(self.URL)
def switch_to_iframe(self):
"""Переключается внутрь iframe с drag-and-drop содержимым."""
iframe = self.find(self.IFRAME)
self.driver.switch_to.frame(iframe)
def switch_to_default(self):
self.driver.switch_to.default_content()
def get_photos_count(self):
"""Возвращает количество фото в галерее (без корзины)."""
return len(self.find_all(self.PHOTOS))
def get_trash_photos_count(self):
"""Возвращает количество фото в корзине."""
return len(self.find_all(self.TRASH_PHOTOS))
def drag_first_photo_to_trash(self):
"""Перетаскивает первую фото из галереи в корзину."""
first_photo = self.find_all(self.PHOTOS)[0]
trash = self.find(self.TRASH_AREA)
ActionChains(self.driver)\
.drag_and_drop(first_photo, trash)\
.perform()
# tests/test_drag_and_drop.py
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from pages.drag_and_drop_page import DragAndDropPage
class TestDragAndDrop:
@pytest.fixture(scope="class", autouse=True)
def setup(self):
self.driver = webdriver.Chrome(
service=ChromeService(ChromeDriverManager().install())
)
self.driver.maximize_window()
self.page = DragAndDropPage(self.driver)
yield
self.driver.quit()
def test_drag_photo_to_trash(self):
self.page.open()
# Переключаемся в iframe
self.page.switch_to_iframe()
# Перетаскиваем первую фото
self.page.drag_first_photo_to_trash()
# Проверяем корзину: должна быть 1 фото
assert self.page.get_trash_photos_count() == 1, \
"В корзине должна быть 1 фотография"
# Проверяем галерею: должно остаться 3 фото
assert self.page.get_photos_count() == 3, \
"В галерее должно остаться 3 фотографии"
# Возвращаемся из iframe
self.page.switch_to_default()
Примечание: drag & drop через
ActionChains может не работать на некоторых сайтах из-за JavaScript-перехвата событий. В таких случаях используют JS-эмуляцию.
⚠️ Проверить: если ActionChains.drag_and_drop() не работает на globalsqa.com, попробуйте метод через move_to_element + click_and_hold + move_to_element + release, или JS-подход.