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

Введение в тестирование API

Playwright предоставляет встроенные возможности для тестирования REST API через объекты APIRequestContext. Вы можете выполнять HTTP-запросы напрямую, без использования браузера, что позволяет тестировать API как отдельно, так и в сочетании с UI-тестами. API-запросы можно выполнять через page.request, context.request или изолированный APIRequestContext.

Важно: API-тесты в Playwright выполняются в том же контексте, что и браузерные тесты, что упрощает передачу кук, заголовков аутентификации и других данных между UI и API.

Способы отправки запросов

page.request — запросы от лица страницы

Объект page.request автоматически использует куки и заголовки текущей страницы. Это удобно для выполнения API-запросов в контексте авторизованного пользователя.

response = page.request.get("https://api.example.com/users")
response = page.request.post("https://api.example.com/users", json={"name": "Иван"})

context.request — запросы от лица контекста

Объект context.request использует куки и настройки конкретного браузерного контекста.

response = context.request.get("https://api.example.com/data")
response = context.request.post("https://api.example.com/data", data="raw body content")

APIRequestContext — изолированный контекст API

Для полной изоляции можно создать отдельный контекст API через request.new_context(). Это полезно, когда нужно отправить запрос без браузерного контекста или с собственными настройками аутентификации.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    api_context = p.request.new_context(
        base_url="https://api.example.com",
        extra_headers={"X-API-Key": "my-secret-key"}
    )
    response = api_context.get("/users")
    print(response.json())

HTTP-методы

request.get() — GET-запрос

# Простой GET-запрос
response = page.request.get("https://api.example.com/users")

# GET с параметрами запроса
response = page.request.get("https://api.example.com/users", params={
    "page": 1,
    "limit": 10,
    "sort": "name"
})

# GET с заголовками
response = page.request.get(
    "https://api.example.com/protected",
    headers={"Authorization": "Bearer token123"}
)

request.post() — POST-запрос

# POST с JSON-телом
response = page.request.post("https://api.example.com/users", json={
    "name": "Иван Петров",
    "email": "ivan@example.com",
    "role": "admin"
})

# POST с form-данными
response = page.request.post("https://api.example.com/login", form={
    "username": "admin",
    "password": "secret"
})

# POST с multipart/form-data (загрузка файла)
response = page.request.post("https://api.example.com/upload", multipart={
    "file": {"name": "report.pdf", "mimeType": "application/pdf", "buffer": b"PDF content"}
})

# POST с raw-телом
response = page.request.post("https://api.example.com/data", data="raw string body")

request.put() — PUT-запрос

# Обновление ресурса
response = page.request.put("https://api.example.com/users/42", json={
    "name": "Иван Петров (обновлено)",
    "email": "ivan.new@example.com"
})

# PUT с заголовками
response = page.request.put(
    "https://api.example.com/users/42",
    data='{"name": "Обновление"}',
    headers={"Content-Type": "application/json"}
)

request.patch() — PATCH-запрос

# Частичное обновление ресурса
response = page.request.patch("https://api.example.com/users/42", json={
    "email": "updated@example.com"
})

request.delete() — DELETE-запрос

# Удаление ресурса
response = page.request.delete("https://api.example.com/users/42")

# DELETE с телом запроса
response = page.request.delete("https://api.example.com/users/batch", json={
    "ids": [1, 2, 3]
})

request.fetch() — универсальный метод

Метод fetch() позволяет выполнить запрос с полным контролем над всеми параметрами. Это универсальная замена для всех отдельных HTTP-методов.

# GET-запрос через fetch
response = page.request.fetch("https://api.example.com/users")

# POST с JSON через fetch
response = page.request.fetch(
    "https://api.example.com/users",
    method="POST",
    headers={"Content-Type": "application/json"},
    json={"name": "Новый пользователь"}
)

# Полный кастомный запрос через fetch
response = page.request.fetch(
    "https://api.example.com/data",
    method="PUT",
    headers={"Authorization": "Bearer token", "X-Custom": "value"},
    params={"version": "2"},
    data='{"key": "value"}',
    timeout=30000
)

Работа с ответом (APIResponse)

Объект APIResponse предоставляет множество методов для проверки и разбора ответа сервера.

Статус и заголовки

response = page.request.get("https://api.example.com/users")

# Код статуса
print(response.status)       # 200
print(response.ok)           # True (200-299)
print(response.status_text)  # "OK"

# Проверка статуса
assert response.status == 200
assert response.ok
assert response.status == 201  # для создания ресурса
assert response.status == 204  # для успешного удаления

# Заголовки ответа
print(response.headers)
print(response.headers["content-type"])
print(response.headers.get("x-request-id"))
print(response.headers_array())  # все заголовки с дубликатами

Тело ответа

# JSON (автоматический парсинг)
data = response.json()
print(data["name"])
print(data["items"][0])

# Текст
text = response.text()
print(text)

# Бинарные данные (байты)
body = response.body()
with open("downloaded.pdf", "wb") as f:
    f.write(body)

Дополнительные методы APIResponse

# URL после возможных редиректов
print(response.url)

# Размер тела в байтах
print(response.body_size)  # int

# Проверка, завершён ли ответ (все данные получены)
assert response.finished()  # True после полной загрузки тела

Безопасность и TLS

response.security_details() — информация о TLS

Возвращает информацию о TLS-соединении: версия протокола, шифр, сертификат.

response = page.request.get("https://api.example.com/users")
security = response.security_details()

if security:
    print(security["protocol"])     # "TLS 1.3"
    print(security["cipher"])      # "AES-256-GCM"
    print(security["subjectName"]) # "api.example.com"
    print(security["issuer"])      # "Let's Encrypt"
    print(security["validFrom"])   # timestamp
    print(security["validTo"])     # timestamp

response.server_addr() — адрес сервера

Возвращает IP-адрес и порт сервера, который обработал запрос.

response = page.request.get("https://api.example.com/users")
addr = response.server_addr()
if addr:
    print(addr["ipAddress"])  # "93.184.216.34"
    print(addr["port"])       # 443

Параметры запроса: заголовки, куки и query-параметры

# Отправка кастомных заголовков
response = page.request.get(
    "https://api.example.com/users",
    headers={
        "Authorization": "Bearer eyJhbGciOiJIUzI1Ni...",
        "X-Request-ID": "abc-123",
        "Accept-Language": "ru-RU"
    }
)

# Query-параметры (URL-параметры)
response = page.request.get("https://api.example.com/search", params={
    "q": "playwright",
    "page": 2,
    "sort": "desc",
    "filter": "active"
})

# Отправка кук
response = page.request.get(
    "https://api.example.com/profile",
    headers={"Cookie": "session_id=abc123; theme=dark"}
)

request.storage_state() — состояние для аутентификации

Позволяет получить куки, localStorage и sessionStorage после выполнения запросов. Это полезно для сохранения состояния аутентификации и повторного использования в других тестах.

# Выполнение входа и сохранение состояния
response = page.request.post("https://example.com/api/login", json={
    "username": "admin",
    "password": "password123"
})
assert response.ok

# Сохранение состояния (куки, токены)
state = page.request.storage_state()
with open("auth-state.json", "w") as f:
    f.write("json.dumps(state)")

# Использование сохранённого состояния в новом контексте
context = browser.new_context(storage_state="auth-state.json")
page = context.new_page()

# Теперь пользователь уже авторизован
page.goto("https://example.com/dashboard")

Совет: Используйте storage_state() для ускорения тестов — выполняйте вход один раз через API (это быстрее, чем UI), сохраняйте состояние и переиспользуйте его в последующих тестах. Это также позволяет избежать многократного ввода логина/пароля.

Полный пример CRUD-теста API

from playwright.sync_api import sync_playwright

class TestUsersAPI:

    base_url = "https://api.example.com"
    created_user_id = None

    def setup_method(self):
        self.playwright = sync_playwright().start()
        self.api_context = self.playwright.request.new_context(
            base_url=self.base_url,
            extra_headers={"Authorization": "Bearer test-token"}
        )

    def teardown_method(self):
        self.api_context.dispose()
        self.playwright.stop()

    def test_01_create_user(self):
        # CREATE (POST) — создание нового пользователя
        response = self.api_context.post("/users", json={
            "name": "Иван Петров",
            "email": "ivan@example.com",
            "role": "user"
        })

        assert response.status == 201
        data = response.json()
        self.created_user_id = data["id"]
        assert data["name"] == "Иван Петров"
        assert data["email"] == "ivan@example.com"

    def test_02_get_user(self):
        # READ (GET) — получение созданного пользователя
        assert self.created_user_id is not None

        response = self.api_context.get(f"/users/{self.created_user_id}")

        assert response.status == 200
        assert response.ok
        data = response.json()
        assert data["id"] == self.created_user_id
        assert data["name"] == "Иван Петров"

    def test_03_update_user(self):
        # UPDATE (PUT) — обновление пользователя
        response = self.api_context.put(f"/users/{self.created_user_id}", json={
            "name": "Иван Петров (обновлено)",
            "email": "ivan.updated@example.com"
        })

        assert response.status == 200
        data = response.json()
        assert data["email"] == "ivan.updated@example.com"

    def test_04_list_users(self):
        # LIST (GET) — получение списка пользователей
        response = self.api_context.get("/users", params={
            "page": 1,
            "limit": 10
        })

        assert response.status == 200
        data = response.json()
        assert "items" in data
        assert len(data["items"]) > 0

    def test_05_delete_user(self):
        # DELETE — удаление пользователя
        response = self.api_context.delete(f"/users/{self.created_user_id}")

        assert response.status == 204

        # Проверка, что пользователь действительно удалён
        response = self.api_context.get(f"/users/{self.created_user_id}")
        assert response.status == 404

Тестирование API в сочетании с UI

Одна из сильных сторон Playwright — возможность комбинировать API-запросы с UI-тестами. Например, можно подготовить данные через API, а затем проверить их отображение в браузере.

def test_create_user_via_api_check_ui(page, context):
    # Шаг 1: Создание пользователя через API
    api_context = context.request
    response = api_context.post("https://example.com/api/users", json={
        "name": "Тестовый пользователь",
        "email": "test@example.com"
    })
    assert response.ok
    user_id = response.json()["id"]

    # Шаг 2: Проверка в UI, что пользователь отображается
    page.goto("https://example.com/users")
    expect(page.get_by_text("Тестовый пользователь")).to_be_visible()
    expect(page.get_by_text("test@example.com")).to_be_visible()

    # Шаг 3: Очистка — удаление пользователя через API
    api_context.delete(f"https://example.com/api/users/{user_id}")

Совет: Используйте API для подготовки данных (сев-ап) и очистки (тир-даун) в тестах. Это быстрее и надёжнее, чем создавать данные через UI. Кроме того, API-запросы не зависят от состояния интерфейса.

Заключение

Тестирование API в Playwright Python — это полноценный инструмент, который позволяет:

  • Выполнять все стандартные HTTP-методы (GET, POST, PUT, PATCH, DELETE)
  • Работать с различными форматами тела запроса (JSON, form, multipart, raw)
  • Проверять статусы, заголовки и тело ответа
  • Получать информацию о TLS-соединении и адресе сервера
  • Использовать изолированный API-контекст или контекст страницы/браузера
  • Сохранять состояние аутентификации через storage_state()
  • Комбинировать API-запросы с UI-тестами для эффективного тестирования