Playwright Python — справочник На главный сайт

Кастомные селекторы

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. Используйте эти возможности для адаптации инструмента под специфические требования вашего проекта: мокирование внешних сервисов, блокировка нежелательных ресурсов, автоматическая авторизация и кастомная обработка ошибок.