Расширяемость (Extensibility)
Кастомные селекторы, скрипты инициализации, перехват запросов, API-hooks, плагины для pytest-playwright и расширение стандартного поведения Playwright.
Кастомные селекторы
Playwright позволяет регистрировать собственные обработчики селекторов через playwright.selectors.register(). Это полезно, когда стандартные локаторы не подходят для вашей специфической разметки.
from playwright.sync_api import sync_playwright def data_qa_selector(attribute, value): """Кастомный селектор для data-qa атрибутов""" return f'[data-qa="{value}"]' with sync_playwright() as p: # Регистрация кастомного селектора p.selectors.register("qa", data_qa_selector) browser = p.chromium.launch() page = browser.new_page() page.goto("https://example.com") # Использование: page.locator("qa=submit-btn") page.locator("qa=submit-btn").click()
Механизм работы: Функция-обработчик принимает три параметра: (attribute, value, selector). Она должна вернуть строку с CSS-селектором или None, если селектор не подходит. Зарегистрированный селектор доступен во всех браузерах и контекстах.
Регистрация на основе get_by_text
# Кастомный селектор для элементов, содержащих иконку def icon_selector(attribute, value): return f'[class*="icon-{value}"]' with sync_playwright() as p: p.selectors.register("icon", icon_selector) page = p.chromium.launch().new_page() page.goto("https://example.com") # Используем кастомный селектор в цепочке с get_by_text # page.get_by_text("Настройки").locator("icon=settings") page.get_by_text("Настройки").click()
Кастомные скрипты инициализации
Методы context.add_init_script() и page.add_init_script() позволяют внедрить JavaScript-код на страницу до загрузки любых скриптов приложения. Это идеально для настройки тестового окружения, мокирования API, изменения времени или переопределения глобальных объектов.
context.add_init_script()
Скрипт выполняется на каждой странице внутри контекста перед загрузкой контента.
with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() # Внедрение скрипта для мокирования геолокации context.add_init_script(""" navigator.geolocation.getCurrentPosition = (success) => { success({ coords: { latitude: 55.7558, longitude: 37.6173 } }); }; """) # Мокирование времени context.add_init_script(""" Date.now = () => 1700000000000; """) page = context.new_page() page.goto("https://example.com")
page.add_init_script() с файлом
Вы также можете загрузить скрипт из внешнего файла.
# inject.js — содержимое файла // Переопределение консольных методов для сбора логов const originalLog = console.log; console.log = function(...args) { window.__testLogs = window.__testLogs || []; window.__testLogs.push(args); originalLog.apply(console, args); };
# В тесте page.add_init_script(path="inject.js") page.goto("https://example.com") page.evaluate("console.log('тестовое сообщение')") logs = page.evaluate("window.__testLogs") print(logs) # [['тестовое сообщение']]
Применения: add_init_script часто используется для мокирования сторонних сервисов (аналитика, реклама, виджеты), подмены API-эндпоинтов, эмуляции определённого состояния приложения и сбора метрик.
Кастомная обработка запросов: page.route()
Метод page.route() позволяет перехватывать и модифицировать HTTP-запросы. Это мощный инструмент для мокирования API, блокировки нежелательных ресурсов и тестирования граничных случаев.
def handle_request(route): """Обработчик запросов: блокируем аналитику, мокаем API""" url = route.request.url if "analytics" in url: route.abort() elif "/api/users" in url: route.fulfill( status=200, content_type="application/json", body='{"users": [{"id": 1, "name": "Тестовый"}]}' ) else: route.continue_() page.route("**/*", handle_request)
Пример: блокировка изображений для ускорения тестов
page.route("**/*.{png,jpg,jpeg,gif,svg,webp}", lambda route: route.abort())
Пример: эмуляция сетевых ошибок
import asyncio async def timeout_handler(route): await asyncio.sleep(30) # Имитация таймаута route.abort() # Для 1% запросов имитируем network error def error_handler(route): route.abort("connectionrefused")
API hooks: playwright.on("browser", handler)
Playwright предоставляет события для отслеживания создания браузеров, контекстов и страниц. Это позволяет внедрять кастомную логику на каждом этапе жизненного цикла.
from playwright.sync_api import sync_playwright def on_browser(browser): print(f"Браузер запущен: {browser.browser_type.name}") def on_context(context): print("Новый контекст создан") # Автоматически добавляем init script в каждый контекст context.add_init_script("console.log('init')") with sync_playwright() as p: p.on("browser", on_browser) p.on("context", on_context) browser = p.chromium.launch() context = browser.new_context()
Создание плагинов для pytest-playwright
pytest-playwright основан на системе хуков pytest. Вы можете создавать плагины, которые расширяют его функциональность, используя стандартный механизм pytest-плагинов.
# plugin.py — простой плагин для логирования import pytest from datetime import datetime @pytest.hookimpl(tryfirst=True) def pytest_runtest_protocol(item, nextitem): print(f"[{datetime.now()}] Запуск: {item.name}") @pytest.hookimpl def pytest_runtest_makereport(item, call): if call.when == "call" and call.excinfo is not None: print(f"[{datetime.now()}] ПАДЕНИЕ: {item.name}") # Получаем page из теста page = item.funcargs.get("page") if page: page.screenshot(path=f"screenshots/{item.name}_failed.png")
# conftest.py — регистрация плагина pytest_plugins = ["plugin"]
Плагины pytest и pytest-playwright могут переопределять фикстуры, добавлять новые CLI-опции, обрабатывать события тестового прогона и интегрироваться с отчётами. Это основа для создания корпоративных фреймворков тестирования.
Расширение фикстур pytest
Вы можете создавать свои фикстуры, расширяющие или оборачивающие стандартные фикстуры pytest-playwright.
# conftest.py import pytest from playwright.sync_api import BrowserContext @pytest.fixture def authenticated_context(context: BrowserContext): "Фикстура с предустановленной авторизацией" page = context.new_page() page.goto("https://example.com/login") page.get_by_label("Email").fill("admin@example.com") page.get_by_label("Пароль").fill("admin123") page.get_by_role("button", name="Войти").click() # Сохраняем состояние аутентификации context.storage_state(path="auth.json") page.close() # Создаём новый контекст с сохранённым состоянием with context.browser.new_context(storage_state="auth.json") as new_context: yield new_context @pytest.fixture def tracking_page(page): "Фикстура с блокировкой трекеров" page.route("**/*.google-analytics.com/**", lambda route: route.abort()) yield page
Кастомные таймауты и retry-логика
Playwright позволяет настраивать таймауты глобально, на уровне контекста или для отдельных действий. Вы также можете реализовать свою retry-логику для неустойчивых элементов.
import functools import time def retry(max_attempts=3, delay=1.0): "Декоратор для повторных попыток" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): last_exc = None for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: last_exc = e if attempt < max_attempts - 1: time.sleep(delay) raise last_exc return wrapper return decorator # Использование @retry(max_attempts=5, delay=0.5) def click_with_retry(locator): locator.click() # Глобальная настройка таймаутов page.set_default_timeout(15000) # 15 секунд для всех действий # Таймаут для конкретного действия page.get_by_role("button", name="Submit").click(timeout=30000) # 30 секунд
Важно: Использование собственной retry-логики может маскировать реальные проблемы с тестами. Playwright уже имеет встроенное автоматическое ожидание (auto-waiting) для большинства действий. Применяйте retry только когда стандартного ожидания недостаточно (например, при анимациях перехода между страницами).
Переопределение поведения по умолчанию
Playwright позволяет переопределить практически любое поведение по умолчанию через фикстуры pytest-playwright.
# conftest.py — переопределение browser_context_args @pytest.fixture(scope="session") def browser_context_args(browser_context_args): return { **browser_context_args, "viewport": {"width": 1440, "height": 900}, "locale": "de-DE", "timezone_id": "Europe/Berlin", "permissions": ["geolocation"], "geolocation": {"latitude": 52.52, "longitude": 13.405}, } # Переопределение контекста для всех тестов @pytest.fixture(scope="session") def browser_context_args(browser_context_args): return { "ignore_https_errors": True, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Custom/1.0", "extra_http_headers": {"X-Test-Run": "true"}, } # Переопределение браузера @pytest.fixture(scope="session") def browser(playwright): return playwright.chromium.launch( headless=True, args=["--disable-gpu", "--no-sandbox"], )
Заключение
Playwright предоставляет множество точек расширения: от кастомных селекторов и скриптов инициализации до полноценных плагинов для pytest. Используйте эти возможности для адаптации инструмента под специфические требования вашего проекта: мокирование внешних сервисов, блокировка нежелательных ресурсов, автоматическая авторизация и кастомная обработка ошибок.