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

Обзор утверждений в Playwright

Playwright предоставляет мощную систему утверждений через функцию expect(). Все утверждения автоматически повторяются до достижения таймаута — это означает, что проверка будет успешной, как только условие будет выполнено, без необходимости писать явные ожидания. Это ключевое отличие от традиционных assert-библиотек: Playwright ждёт, пока состояние станет ожидаемым.

Важно: Все утверждения в Playwright являются асинхронными и используют механизм автоматического повторного запроса (auto-retry). По умолчанию таймаут составляет 5 секунд, но его можно изменить глобально или для конкретного утверждения.

Утверждения для страницы (Page)

expect(page).to_have_title()

Проверяет заголовок страницы (содержимое тега <title>).

from playwright.sync_api import expect

expect(page).to_have_title("Главная страница")
expect(page).to_have_title(re.compile(r"Страница \d+"))
expect(page).to_have_title("Точный заголовок", exact=True)
expect(page).to_have_title("Медленная загрузка", timeout=10000)

expect(page).to_have_url()

Проверяет текущий URL страницы.

expect(page).to_have_url("https://example.com/dashboard")
expect(page).to_have_url(re.compile(r"example\.com/\d+"))
expect(page).to_have_url("**/dashboard")   # glob-паттерн
expect(page).to_have_url("https://example.com", timeout=5000)

Утверждения для локаторов (Locator)

to_be_visible() / to_be_hidden()

Проверяют видимость элемента на странице.

expect(page.get_by_role("button", name="Submit")).to_be_visible()
expect(page.get_by_role("dialog")).to_be_hidden()
expect(page.get_by_text("Спасибо")).to_be_visible(timeout=3000)
expect(page.locator(".spinner")).to_be_hidden(timeout=10000)

to_be_enabled() / to_be_disabled()

Проверяют, включён или отключён элемент (атрибут disabled).

expect(page.get_by_role("button", name="Отправить")).to_be_enabled()
expect(page.get_by_placeholder("Введите имя")).to_be_enabled()
expect(page.get_by_role("button", name="Удалить")).to_be_disabled()
expect(page.get_by_role("textbox", name="Email")).to_be_enabled(timeout=5000)

to_be_checked()

Проверяет, что чекбокс или радиокнопка отмечены.

expect(page.get_by_role("checkbox", name="Согласен")).to_be_checked()
expect(page.get_by_role("radio", name="Вариант 1")).to_be_checked()
expect(page.get_by_role("checkbox")).not_.to_be_checked()  # не отмечен

to_be_focused()

Проверяет, что элемент находится в фокусе.

expect(page.get_by_label("Имя")).to_be_focused()
page.get_by_label("Email").focus()
expect(page.get_by_label("Email")).to_be_focused()

to_be_empty()

Проверяет, что элемент пуст (не содержит дочерних узлов или текста).

expect(page.get_by_role("listitem").first).to_be_empty()
expect(page.locator(".notifications")).to_be_empty()
expect(page.get_by_role("textbox")).to_be_empty()  # пустое поле ввода

to_have_text() / to_contain_text()

to_have_text() проверяет текстовое содержимое элемента. Поддерживает точное совпадение и частичное. to_contain_text() проверяет, что элемент содержит указанный текст где-либо внутри себя.

# Точное совпадение текста
expect(page.get_by_role("heading", name="Добро пожаловать")).to_have_text("Добро пожаловать")

# Частичное совпадение (по умолчанию)
expect(page.get_by_role("button")).to_have_text("Отправить")

# Совпадение с регулярным выражением
expect(page.locator(".price")).to_have_text(re.compile(r"\d+\.\d{2} руб"))

# Точное совпадение (exact=True игнорирует пробелы по краям)
expect(page.get_by_role("heading")).to_have_text("Заголовок", exact=True)

# to_contain_text — текст содержится где-то внутри элемента
expect(page.locator(".card")).to_contain_text("специальное предложение")

# Проверка нескольких элементов по тексту
expect(page.get_by_role("listitem")).to_have_text(["Пункт 1", "Пункт 2", "Пункт 3"])

# Использование регулярных выражений в списке
expect(page.get_by_role("row")).to_have_text([
    re.compile(r"ID-\d+"),
    "Активен",
    re.compile(r"\d{2}\.\d{2}\.\d{4}")
])

to_have_value() / to_have_values()

Проверяют значение поля ввода (value) или выбранные опции в <select multiple>.

# Проверка значения поля ввода
expect(page.get_by_label("Email")).to_have_value("user@example.com")

# Проверка выбранного значения в select
expect(page.get_by_role("combobox", name="Страна")).to_have_value("RU")

# Проверка множественного выбора (select multiple)
expect(page.get_by_role("listbox")).to_have_values(["red", "blue"])

# С регулярным выражением
expect(page.get_by_label("Телефон")).to_have_value(re.compile(r"^\+7 \d{3}"))

to_have_attribute()

Проверяет значение атрибута элемента.

expect(page.get_by_role("link", name="Подробнее")).to_have_attribute("href", "/details")
expect(page.get_by_role("img", name="Лого")).to_have_attribute("src", re.compile(r"logo.*\.png"))
expect(page.get_by_test_id("submit-btn")).to_have_attribute("type", "submit")
expect(page.locator("input")).to_have_attribute("placeholder", "Введите email")

to_have_class()

Проверяет CSS-классы элемента.

expect(page.get_by_role("button")).to_have_class("btn btn-primary")
expect(page.get_by_role("button")).to_have_class(re.compile(r"btn-.*"))
expect(page.locator(".nav-item")).to_have_class(["nav-item", "active"])

to_have_count()

Проверяет количество элементов, соответствующих локатору.

expect(page.get_by_role("listitem")).to_have_count(5)
expect(page.get_by_role("row")).to_have_count(11)   # 10 строк + заголовок
expect(page.get_by_role("button")).to_have_count(3, timeout=10000)
expect(page.locator(".error-message")).to_have_count(0)  # нет ошибок

to_have_css()

Проверяет CSS-свойства элемента.

expect(page.get_by_role("button", name="Submit")).to_have_css("background-color", "rgb(0, 123, 255)")
expect(page.get_by_role("heading")).to_have_css("font-size", "24px")
expect(page.locator(".error")).to_have_css("color", re.compile(r"^rgb\(255,"))
expect(page.get_by_test_id("modal")).to_have_css("display", "none")

to_have_id()

Проверяет значение атрибута id элемента.

expect(page.get_by_role("button")).to_have_id("submit-btn")
expect(page.locator("header")).to_have_id("main-header")
expect(page.get_by_role("form")).to_have_id(re.compile(r"form-\d+"))

to_have_js_property()

Проверяет JavaScript-свойство DOM-элемента.

expect(page.get_by_role("textbox")).to_have_js_property("type", "email")
expect(page.locator("video")).to_have_js_property("paused", True)
expect(page.get_by_role("button")).to_have_js_property("disabled", False)

# Проверка сложного свойства
expect(page.locator("progress")).to_have_js_property("value", 42)

Soft assertions — мягкие утверждения

По умолчанию, если утверждение не проходит, тест немедленно завершается ошибкой. Мягкие утверждения (soft assertions) позволяют продолжить выполнение теста и собрать все ошибки, а затем сообщить о них в конце.

# Мягкое утверждение — тест продолжается даже при ошибке
expect(page.get_by_role("heading")).to_be_visible()
expect(page.get_by_text("Добро пожаловать")).to_be_visible(timeout=5000)  # мягкая проверка

# Использование expect.with_options
expect.configure(timeout=10000)
expect(page.get_by_text("Загрузка завершена")).to_be_visible()

Мягкие утверждения полезны при проверке множества независимых условий на одной странице — вы получите полный список проблем, а не только первую ошибку.

Таймауты утверждений

По умолчанию все утверждения имеют таймаут 5 секунд. Таймаут можно изменить:

  • Глобально — в конфигурации тестов Playwright
  • Для всех expect — через expect.set_options(timeout=10000)
  • Для конкретного утверждения — параметром timeout
# Глобальная настройка таймаута для всех утверждений
expect.configure(timeout=10000)

# Таймаут для конкретного утверждения (переопределяет глобальный)
expect(page.get_by_text("Медленно загружаемый текст")).to_be_visible(timeout=15000)

# Таймаут в конфигурации playwright (playwright.config)
# expect: { timeout: 10000 }

expect.poll() — Повторная проверка с интервалом

expect.poll() позволяет повторно выполнять функцию или получать значение до тех пор, пока оно не удовлетворит условию. Это полезно для проверок, которые не вписываются в стандартные утверждения.

import re
from playwright.sync_api import expect

# Проверка с интервалом: ждём, пока значение счётчика не станет 100
expect.poll(
    lambda: page.locator(".counter").text_content(),
    timeout=10000,
    interval=500
).to_have_text("100")

# Проверка с регулярным выражением
expect.poll(
    lambda: page.evaluate("document.title"),
    timeout=5000
).to_have_text(re.compile(r"Готово \d+%"))

# Проверка JS-свойства с интервалом
expect.poll(
    lambda: page.evaluate("window.loadingProgress"),
    interval=200,
    timeout=30000
).to_have_value(100)

# poll с кастомной функцией проверки
expect.poll(
    lambda: page.locator(".status").get_attribute("data-state"),
    timeout=5000
).to_be("loaded")  # to_be для произвольных значений

Совет: expect.poll() принимает параметры timeout (максимальное время ожидания) и interval (интервал между попытками, по умолчанию 100 мс). Функция выполняется повторно, пока не будет достигнут таймаут или условие не будет выполнено.

Сравнение скриншотов

expect(page).to_have_screenshot() делает скриншот страницы или элемента и сравнивает его с эталонным изображением. Это мощный инструмент для визуального регрессионного тестирования.

# Сравнение полного скриншота страницы с эталоном
expect(page).to_have_screenshot()

# Сравнение скриншота с именованным эталоном
expect(page).to_have_screenshot("login-page.png")

# Сравнение скриншота конкретного элемента
expect(page.get_by_role("form", name="Login")).to_have_screenshot()

# Скриншот с маскированием динамических областей
expect(page).to_have_screenshot(mask=[page.locator(".timestamp")])

# Скриншот с опциями
expect(page).to_have_screenshot(
    "dashboard.png",
    full_page=True,
    animations="disabled",
    timeout=15000,
    max_diff_pixel_ratio=0.01   # допускается 1% различий
)

# Скриншот с маскированным элементом
expect(page).to_have_screenshot(mask=[page.locator(".banner")], mask_color="#FF0000")

# Скриншот элемента с обрезкой (clip)
expect(page.locator(".chart")).to_have_screenshot(
    "chart.png",
    max_diff_pixels=100
)

# Обновление эталонов (режим записи)
# При запуске с --update-snapshots все скриншоты будут перезаписаны
expect(page).to_have_screenshot("new-reference.png")

Важно: Скриншоты по умолчанию сохраняются в директории test-results или в кастомной папке, указанной в конфигурации. При первом запуске эталонный скриншот создаётся автоматически. При последующих запусках происходит сравнение с эталоном. Для обновления эталонов используйте флаг --update-snapshots.

Параметры to_have_screenshot()

ПараметрОписание
nameИмя файла эталонного скриншота
full_pageСкриншот всей страницы (включая прокручиваемую часть)
clipОбрезка: {"x": 0, "y": 0, "width": 100, "height": 100}
maskСписок локаторов для маскирования
mask_colorЦвет маски (по умолчанию #FF00FF)
animationsОбработка анимации: "disabled", "allow"
max_diff_pixel_ratioМаксимальная доля отличающихся пикселей (0.0–1.0)
max_diff_pixelsМаксимальное количество отличающихся пикселей
thresholdПорог сравнения пикселей (0–1, по умолчанию 0.2)
timeoutТаймаут ожидания (мс)

Практические примеры

Полный набор проверок страницы

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

    # Проверки страницы
    expect(page).to_have_title("Панель управления")
    expect(page).to_have_url(re.compile(r"example\.com/dashboard"))

    # Проверки видимости элементов
    expect(page.get_by_role("heading", name="Добро пожаловать")).to_be_visible()
    expect(page.get_by_text("Загрузка...")).to_be_hidden()

    # Проверки состояния
    expect(page.get_by_role("button", name="Сохранить")).to_be_enabled()
    expect(page.get_by_role("button", name="Удалить")).to_be_disabled()

    # Проверка формы
    expect(page.get_by_label("Email")).to_have_value("admin@example.com")
    expect(page.get_by_label("Email")).to_be_focused()

    # Проверка списка
    expect(page.get_by_role("listitem")).to_have_count(5)
    expect(page.get_by_role("listitem").first).to_have_text("Задача #1")

Мягкие проверки в одном тесте

def test_multiple_assertions(page: Page):
    page.goto("https://example.com/profile")

    # Собираем все ошибки, не прерывая тест
    errors = []

    try:
        expect(page.get_by_role("heading", name="Профиль")).to_be_visible(timeout=3000)
    except AssertionError as e:
        errors.append(f"Заголовок не виден: {e}")

    try:
        expect(page.get_by_label("Имя")).to_have_value("Иван")
    except AssertionError as e:
        errors.append(f"Имя не совпадает: {e}")

    if errors:
        raise AssertionError("\n".join(errors))

Сравнение скриншотов в CI

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

    # Маскируем динамические элементы (дата, время, ID сессии)
    expect(page).to_have_screenshot(
        "home-page.png",
        mask=[
            page.locator(".session-id"),
            page.locator(".current-date"),
            page.locator(".random-banner")
        ],
        max_diff_pixel_ratio=0.005  # допускаем 0.5% различий
    )

Примечание: Playwright использует pixelmatch для сравнения скриншотов. При первом запуске тестов со скриншотами эталонные изображения создаются автоматически. Для обновления эталонов запустите тесты с флагом --update-snapshots или -u.

Заключение

Утверждения Playwright — это мощная и гибкая система проверок, которая автоматически ожидает выполнения условий. Основные преимущества:

  • Автоматический повтор — утверждения повторяются до достижения таймаута
  • Читаемые сообщения об ошибках — понятные описания, что пошло не так
  • Мягкие проверки — можно собрать все ошибки без остановки теста
  • expect.poll() — для кастомных проверок с интервалом
  • Сравнение скриншотов — визуальное регрессионное тестирование
  • Гибкая настройка таймаутов — глобально, для всех expect или для конкретного утверждения