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

Что такое Auto-Waiting

Auto-Waiting (автоматическое ожидание) — ключевая особенность Playwright, которая кардинально повышает стабильность тестов. Перед тем как выполнить любое действие над элементом (клик, ввод текста, выбор опции и т.д.), Playwright автоматически ожидает, что элемент станет готов к взаимодействию. Вам не нужно писать time.sleep(), WebDriverWait или другие явные ожидания — Playwright делает это за вас.

Ключевое преимущество: Auto-Waiting — одна из главных причин, почему тесты на Playwright реже «flaky» (нестабильны) по сравнению с тестами на Selenium. Playwright ждёт не просто появления элемента, а его полной готовности к взаимодействию.

Actionability Checks (Проверки готовности)

Перед выполнением действия Playwright проверяет пять условий. Элемент должен соответствовать всем этим требованиям:

1. Привязан к DOM (Attached to DOM)

Элемент должен быть частью DOM-дерева. Если элемент был удалён из DOM (например, после динамической перерисовки), Playwright будет ждать, пока он снова появится.

2. Видим (Visible)

Элемент должен быть видимым на странице. Проверяются CSS-свойства: display не равен none, visibility не равен hidden, opacity не равен 0, элемент имеет ненулевые размеры (width и height больше нуля).

3. Стабилен (Stable) — проверка стабильности

Playwright проверяет, что элемент не анимируется и не меняет своё положение. Для этого фреймворк дважды замеряет позицию элемента с интервалом в 500 мс. Если координаты совпали — элемент стабилен. Это критически важно для элементов с анимацией появления, CSS-переходами или динамическим контентом.

4. Не заблокирован (Not obstructed)

Playwright проверяет, что поверх целевого элемента не находится другой элемент, который может перехватить событие клика. Например, модальное окно, всплывающая подсказка или overlay. Если другой элемент перекрывает целевой, Playwright ждёт, пока он исчезнет.

5. Включён (Enabled)

Элемент не должен быть отключён (disabled). Проверяется атрибут disabled и CSS-свойство pointer-events: none. Отключённые поля ввода, кнопки и другие элементы не могут принимать взаимодействие.

Важно: Каждое из этих условий должно выполниться в течение таймаута (по умолчанию 30 секунд). Если хотя бы одно условие не выполнилось за отведённое время, Playwright выбрасывает исключение с подробным описанием, какая именно проверка не прошла и каково было состояние элемента.

Таймауты по умолчанию

Playwright использует следующие таймауты по умолчанию:

ТаймаутЗначение по умолчаниюОписание
timeout (глобальный)30000 мс (30 с)Таймаут для всех действий и утверждений
assertions_timeout10000 мс (10 с)Таймаут для утверждений (expect)
navigation_timeout30000 мс (30 с)Таймаут для навигаций (page.goto, page.reload)
global_timeoutнет (0)Максимальное время выполнения всего теста

Настройка таймаутов

Глобальная настройка через set_default_timeout()

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context()

    # Установка глобального таймаута для всех действий (60 секунд)
    context.set_default_timeout(60000)

    page = context.new_page()
    page.goto("https://example.com")

    # Для одной страницы
    page.set_default_timeout(45000)

Настройка через pytest-playwright

# conftest.py — настройка таймаутов для всех тестов
import pytest

def pytest_collection_modifyitems(items):
    # Можно настроить таймаут через маркер
    for item in items:
        item.add_marker(pytest.mark.timeout(120))

# pytest.ini или pyproject.toml:
# [tool.pytest.ini_options]
# timeout = 120
# Настройка через конфигурацию playwright в pytest
# pytest.ini
# [pytest]
# playwright_default_timeout = 60000

Таймаут для конкретного действия

# Таймаут только для этого клика (10 секунд вместо 30)
page.get_by_role("button").click(timeout=10000)

# Таймаут для fill
page.get_by_label("Email").fill("user@example.com", timeout=5000)

Настройка таймаутов для утверждений

# Установка таймаута утверждений для теста
page.set_default_timeout(15000)

# Или для конкретного expect
expect(page.get_by_text("Success")).to_be_visible(timeout=5000)

# Ожидание определённого URL с таймаутом
expect(page).to_have_url("https://example.com/dashboard", timeout=20000)

Явные ожидания (Explicit Waits)

Несмотря на Auto-Waiting, иногда требуются явные ожидания — когда нужно дождаться определённого состояния страницы или элемента.

locator.wait_for() — Ожидание состояния элемента

# Ожидание появления элемента (по умолчанию visible)
page.get_by_text("Loading complete").wait_for()

# Ожидание определённого состояния
page.get_by_role("button").wait_for(state="visible")
page.get_by_role("button").wait_for(state="attached")
page.get_by_role("button").wait_for(state="hidden")
page.get_by_role("button").wait_for(state="detached")

# С кастомным таймаутом
page.get_by_text("Completed").wait_for(timeout=15000)

page.wait_for_load_state() — Ожидание состояния загрузки

# Ожидание полной загрузки страницы
page.wait_for_load_state()
page.wait_for_load_state("load")
page.wait_for_load_state("domcontentloaded")
page.wait_for_load_state("networkidle")  # Нет сетевых запросов более 500 мс

# Ожидание после клика, который вызывает навигацию
page.get_by_text("Go to dashboard").click()
page.wait_for_load_state("networkidle")

page.wait_for_url() — Ожидание URL

# Ожидание конкретного URL
page.wait_for_url("https://example.com/dashboard")

# Ожидание URL по паттерну (glob)
page.wait_for_url("**/dashboard")

# Ожидание URL по регулярному выражению
page.wait_for_url(re.compile(r"/dashboard/\d+"))

# С таймаутом
page.wait_for_url("**/success", timeout=10000)

page.wait_for_selector() — Ожидание селектора

# Ожидание появления элемента по CSS-селектору
page.wait_for_selector(".loading-spinner")

# Ожидание исчезновения
page.wait_for_selector(".loading-spinner", state="hidden")

# С таймаутом
page.wait_for_selector(".success-message", timeout=20000)

page.wait_for_function() — Ожидание JS-выражения

# Ожидание выполнения JavaScript-условия
page.wait_for_function("window.app.ready === true")

# С аргументами
page.wait_for_function(
    "(selector) => document.querySelector(selector) !== null",
    arg="#my-element"
)

# С таймаутом
page.wait_for_function("document.title === 'Dashboard'", timeout=5000)

page.wait_for_event() — Ожидание события

# Ожидание определённого события
with page.expect_event("download") as event_info:
    page.get_by_text("Download report").click()
download = event_info.value

# Ожидание появления диалога
with page.expect_event("dialog") as dialog_info:
    page.get_by_role("button", name="Delete").click()
dialog = dialog_info.value
dialog.accept()

Ожидание нескольких событий

# Ожидание двух параллельных событий
with page.expect_event("requestfinished") as req_info:
    with page.expect_event("download") as dl_info:
        page.get_by_role("button", name="Export").click()
request = req_info.value
download = dl_info.value

Совет: Используйте page.wait_for_load_state("networkidle") с осторожностью. Некоторые современные SPA-приложения могут поддерживать постоянные WebSocket-соединения, из-за чего networkidle никогда не наступит. В таких случаях лучше использовать locator.wait_for() с конкретным элементом.

Как обойти Auto-Waiting: force=True

В некоторых ситуациях вам может потребоваться выполнить действие без проверок готовности. Для этого используется параметр force=True. Он полностью отключает все пять проверок actionability.

# Принудительный клик без ожиданий
page.get_by_role("button").click(force=True)

# Принудительная отметка чекбокса
page.get_by_role("checkbox").check(force=True)

# Принудительная очистка поля
page.get_by_label("Name").fill("test", force=True)

Внимание: Используйте force=True только когда вы точно понимаете, почему элемент не проходит проверки actionability. Чаще всего проблема решается корректным ожиданием, а не принудительным действием. Злоупотребление force=True может привести к flaky-тестам.

Проверка стабильности элемента (Stability Checks)

Playwright проверяет стабильность элемента, дважды замеряя его положение с интервалом 500 мс. Если координаты совпадают — элемент считается стабильным. Это особенно важно для:

  • Анимированных элементов — элементы, которые появляются с CSS-анимацией (fadeIn, slideDown и т.д.)
  • Динамического контента — контент, который подгружается и меняет размеры/положение элементов на странице
  • Элементов с CSS-переходами — transition на transform, top, left и другие позиционирующие свойства

Пример проблемы, которую решает проверка стабильности:

# Без Auto-Waiting этот тест мог бы упасть, если бы
# меню появлялось с анимацией slideDown
page.get_by_role("button", name="Меню").click()

# Playwright подождёт, пока анимация закончится и пункт меню
# займёт своё финальное положение
page.get_by_role("menuitem", name="Настройки").click()

Стратегии ожидания в сложных сценариях

Комбинирование явных ожиданий

def test_complex_form(page):
    page.goto("https://example.com/form")

    # Ждём загрузки формы
    page.wait_for_load_state("networkidle")

    # Ждём появления поля ввода
    page.get_by_label("Username").wait_for(state="visible")
    page.get_by_label("Username").fill("test_user")

    # Отправляем форму и ждём URL
    page.get_by_role("button", name="Submit").click()
    page.wait_for_url("**/success", timeout=15000)

    # Ждём появления сообщения об успехе
    success_message = page.get_by_text("Форма успешно отправлена")
    success_message.wait_for(timeout=5000)

    # Утверждение с ожиданием
    expect(success_message).to_be_visible()

Ожидание с пользовательским условием

def test_wait_for_custom_condition(page):
    page.goto("https://example.com")

    # Ожидание, когда определённый элемент получит конкретный текст
    page.wait_for_function(
        "() => document.querySelector('.status').textContent === 'Готово'"
    )

    # Ожидание, когда счётчик достигнет определённого значения
    page.wait_for_function(
        "() => window.counter >= 100",
        timeout=30000
    )

Auto-Waiting и асинхронный API

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()

        await page.goto("https://example.com")

        # Auto-Waiting работает и в асинхронном режиме
        await page.get_by_role("button").click()

        # Явные ожидания
        await page.wait_for_load_state("networkidle")
        await page.get_by_text("Success").wait_for()

        await browser.close()

asyncio.run(main())

Примечание: Playwright использует концепцию «умных» ожиданий. Он не ждёт фиксированное время, а постоянно опрашивает состояние элемента с короткими интервалами. Как только элемент становится готовым, действие выполняется немедленно. Это делает тесты одновременно и стабильными, и быстрыми.

Заключение

Auto-Waiting — это не просто удобная функция, а фундаментальная часть философии Playwright. В сочетании с явными ожиданиями (wait_for(), wait_for_load_state(), wait_for_url()) и гибкой настройкой таймаутов, вы получаете полный контроль над синхронизацией тестов. Используйте force=True только в крайних случаях, и ваши тесты будут стабильными, быстрыми и надёжными.