Тестирование API
Введение
Playwright может быть использован для доступа к REST API вашего приложения.
Иногда может возникнуть необходимость отправить запросы на сервер напрямую из Python, не загружая страницу и не выполняя в ней js-код. Несколько примеров, когда это может быть полезно:
- Тестирование API вашего сервера.
- Подготовка состояния на стороне сервера перед посещением веб-приложения в тесте.
- Проверка постусловий на стороне сервера после выполнения некоторых действий в браузере.
Все это можно достичь с помощью методов APIRequestContext.
Следующие примеры опираются на пакет pytest-playwright
, который добавляет фикстуры Playwright в тестовый фреймворк Pytest.
Написание теста API
APIRequestContext может отправлять все виды HTTP(S) запросов по сети.
Следующий пример демонстрирует, как использовать Playwright для тестирования создания задач через GitHub API. Набор тестов будет выполнять следующие действия:
- Создавать новый репозиторий перед запуском тестов.
- Создавать несколько задач и проверять состояние сервера.
- Удалять репозиторий после выполнения тестов.
Настройка
GitHub API требует авторизации, поэтому мы настроим токен один раз для всех тестов. Заодно мы также установим baseURL
, чтобы упростить тесты.
import os
from typing import Generator
import pytest
from playwright.sync_api import Playwright, APIRequestContext
GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")
assert GITHUB_API_TOKEN, "GITHUB_API_TOKEN is not set"
@pytest.fixture(scope="session")
def api_request_context(
playwright: Playwright,
) -> Generator[APIRequestContext, None, None]:
headers = {
# Устанавливаем этот заголовок согласно рекомендациям GitHub.
"Accept": "application/vnd.github.v3+json",
# Добавляем токен авторизации ко всем запросам.
# Предполагается, что личный токен доступа доступен в окружении.
"Authorization": f"token {GITHUB_API_TOKEN}",
}
request_context = playwright.request.new_context(
base_url="https://api.github.com", extra_http_headers=headers
)
yield request_context
request_context.dispose()
Написание тестов
Теперь, когда мы инициализировали объект запроса, мы можем добавить несколько тестов, которые будут создавать новые задачи в репозитории.
import os
from typing import Generator
import pytest
from playwright.sync_api import Playwright, APIRequestContext
GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")
assert GITHUB_API_TOKEN, "GITHUB_API_TOKEN is not set"
GITHUB_USER = os.getenv("GITHUB_USER")
assert GITHUB_USER, "GITHUB_USER is not set"
GITHUB_REPO = "test"
# ...
def test_should_create_bug_report(api_request_context: APIRequestContext) -> None:
data = {
"title": "[Bug] report 1",
"body": "Bug description",
}
new_issue = api_request_context.post(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data)
assert new_issue.ok
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issues_response = issues.json()
issue = list(filter(lambda issue: issue["title"] == "[Bug] report 1", issues_response))[0]
assert issue
assert issue["body"] == "Bug description"
def test_should_create_feature_request(api_request_context: APIRequestContext) -> None:
data = {
"title": "[Feature] request 1",
"body": "Feature description",
}
new_issue = api_request_context.post(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data)
assert new_issue.ok
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issues_response = issues.json()
issue = list(filter(lambda issue: issue["title"] == "[Feature] request 1", issues_response))[0]
assert issue
assert issue["body"] == "Feature description"
Настройка и завершение
Эти тесты предполагают, что репозиторий существует. Вероятно, вы захотите создать новый перед запуском тестов и удалить его после. Используйте фикстуру сессии для этого. Часть перед yield
выполняется до всех тестов, а после — после всех.
# ...
@pytest.fixture(scope="session", autouse=True)
def create_test_repository(
api_request_context: APIRequestContext,
) -> Generator[None, None, None]:
# Перед всеми тестами
new_repo = api_request_context.post("/user/repos", data={"name": GITHUB_REPO})
assert new_repo.ok
yield
# После всех тестов
deleted_repo = api_request_context.delete(f"/repos/{GITHUB_USER}/{GITHUB_REPO}")
assert deleted_repo.ok
Полный пример теста
Вот полный пример теста API:
from enum import auto
import os
from typing import Generator
import pytest
from playwright.sync_api import Playwright, Page, APIRequestContext, expect
GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")
assert GITHUB_API_TOKEN, "GITHUB_API_TOKEN is not set"
GITHUB_USER = os.getenv("GITHUB_USER")
assert GITHUB_USER, "GITHUB_USER is not set"
GITHUB_REPO = "test"
@pytest.fixture(scope="session")
def api_request_context(
playwright: Playwright,
) -> Generator[APIRequestContext, None, None]:
headers = {
# Устанавливаем этот заголовок согласно рекомендациям GitHub.
"Accept": "application/vnd.github.v3+json",
# Добавляем токен авторизации ко всем запросам.
# Предполагается, что личный токен доступа доступен в окружении.
"Authorization": f"token {GITHUB_API_TOKEN}",
}
request_context = playwright.request.new_context(
base_url="https://api.github.com", extra_http_headers=headers
)
yield request_context
request_context.dispose()
@pytest.fixture(scope="session", autouse=True)
def create_test_repository(
api_request_context: APIRequestContext,
) -> Generator[None, None, None]:
# Перед всеми тестами
new_repo = api_request_context.post("/user/repos", data={"name": GITHUB_REPO})
assert new_repo.ok
yield
# После всех тестов
deleted_repo = api_request_context.delete(f"/repos/{GITHUB_USER}/{GITHUB_REPO}")
assert deleted_repo.ok
def test_should_create_bug_report(api_request_context: APIRequestContext) -> None:
data = {
"title": "[Bug] report 1",
"body": "Bug description",
}
new_issue = api_request_context.post(
f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data
)
assert new_issue.ok
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issues_response = issues.json()
issue = list(
filter(lambda issue: issue["title"] == "[Bug] report 1", issues_response)
)[0]
assert issue
assert issue["body"] == "Bug description"
def test_should_create_feature_request(api_request_context: APIRequestContext) -> None:
data = {
"title": "[Feature] request 1",
"body": "Feature description",
}
new_issue = api_request_context.post(
f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data
)
assert new_issue.ok
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issues_response = issues.json()
issue = list(
filter(lambda issue: issue["title"] == "[Feature] request 1", issues_response)
)[0]
assert issue
assert issue["body"] == "Feature description"
Подготовка состояния сервера через API вызовы
Следующий тест создает новую задачу через API, а затем переходит к списку всех задач в проекте, чтобы проверить, что она появляется в начале списка. Проверка выполняется с использованием LocatorAssertions.
def test_last_created_issue_should_be_first_in_the_list(api_request_context: APIRequestContext, page: Page) -> None:
def create_issue(title: str) -> None:
data = {
"title": title,
"body": "Feature description",
}
new_issue = api_request_context.post(
f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data
)
assert new_issue.ok
create_issue("[Feature] request 1")
create_issue("[Feature] request 2")
page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")
first_issue = page.locator("a[data-hovercard-type='issue']").first
expect(first_issue).to_have_text("[Feature] request 2")
Проверка состояния сервера после выполнения действий пользователя
Следующий тест создает новую задачу через пользовательский интерфейс в браузере, а затем проверяет через API, была ли она создана:
def test_last_created_issue_should_be_on_the_server(api_request_context: APIRequestContext, page: Page) -> None:
page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")
page.locator("text=New issue").click()
page.locator("[aria-label='Title']").fill("Bug report 1")
page.locator("[aria-label='Comment body']").fill("Bug description")
page.locator("text=Submit new issue").click()
issue_id = page.url.split("/")[-1]
new_issue = api_request_context.get(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues/{issue_id}")
assert new_issue.ok
assert new_issue.json()["title"] == "[Bug] report 1"
assert new_issue.json()["body"] == "Bug description"
Повторное использование состояния аутентификации
Веб-приложения используют аутентификацию на основе куки или токенов, где аутентифицированное состояние хранится как cookies. Playwright предоставляет метод api_request_context.storage_state(), который может быть использован для получения состояния хранилища из аутентифицированного контекста, а затем создания новых контекстов с этим состоянием.
Состояние хранилища взаимозаменяемо между BrowserContext и APIRequestContext. Вы можете использовать его для входа через API вызовы, а затем создать новый контекст с уже установленными куки. Следующий фрагмент кода получает состояние из аутентифицированного APIRequestContext и создает новый BrowserContext с этим состоянием.
request_context = playwright.request.new_context(http_credentials={"username": "test", "password": "test"})
request_context.get("https://api.example.com/login")
# Сохраните состояние хранилища в переменную.
state = request_context.storage_state()
# Создайте новый контекст с сохраненным состоянием хранилища.
context = browser.new_context(storage_state=state)