Playwright — API Practical Playbook
Copy-paste recipes for common API testing scenarios with APIRequestContext.
1) Full CRUD Lifecycle
from playwright.sync_api import APIRequestContext
def test_user_crud_lifecycle(api: APIRequestContext):
# CREATE
resp = api.post("/users", data={"name": "Alice", "email": "alice@test.com"})
assert resp.status == 201
user_id = resp.json()["id"]
# READ
resp = api.get(f"/users/{user_id}")
assert resp.ok
assert resp.json()["name"] == "Alice"
# UPDATE
resp = api.put(f"/users/{user_id}", data={"name": "Alice Updated", "email": "alice@test.com"})
assert resp.ok
assert resp.json()["name"] == "Alice Updated"
# DELETE
resp = api.delete(f"/users/{user_id}")
assert resp.status == 204
# VERIFY DELETED
resp = api.get(f"/users/{user_id}")
assert resp.status == 404
2) Response Schema Validation
from playwright.sync_api import APIRequestContext
REQUIRED_USER_FIELDS = {"id", "name", "email", "role", "created_at"}
def test_user_response_schema(api: APIRequestContext):
resp = api.get("/users/1")
assert resp.ok
body = resp.json()
# verify all required fields are present
missing = REQUIRED_USER_FIELDS - set(body.keys())
assert not missing, f"Missing fields: {missing}"
# verify field types
assert isinstance(body["id"], int)
assert isinstance(body["name"], str)
assert isinstance(body["email"], str)
def test_list_response_schema(api: APIRequestContext):
resp = api.get("/users", params={"page": 1, "per_page": 5})
assert resp.ok
body = resp.json()
# verify list structure
assert isinstance(body["data"], list)
assert isinstance(body["total"], int)
assert len(body["data"]) <= 5
3) Error Response Validation
from playwright.sync_api import APIRequestContext
def test_not_found_returns_404(api: APIRequestContext):
resp = api.get("/users/999999")
assert resp.status == 404
body = resp.json()
assert "error" in body or "detail" in body
def test_invalid_payload_returns_422(api: APIRequestContext):
# missing required "email" field
resp = api.post("/users", data={"name": "Alice"})
assert resp.status == 422
errors = resp.json()
assert any("email" in str(e).lower() for e in errors.get("detail", []))
def test_duplicate_returns_409(api: APIRequestContext):
api.post("/users", data={"name": "Bob", "email": "bob@test.com"})
resp = api.post("/users", data={"name": "Bob2", "email": "bob@test.com"})
assert resp.status == 409
4) Headers and Cookies Validation
from playwright.sync_api import APIRequestContext
def test_rate_limit_headers(api: APIRequestContext):
resp = api.get("/users")
assert resp.ok
assert "x-ratelimit-remaining" in resp.headers
assert int(resp.headers["x-ratelimit-remaining"]) >= 0
def test_set_cookie_on_login(playwright):
ctx = playwright.request.new_context(base_url="https://api.example.com")
resp = ctx.post("/auth/login", data={"email": "a@test.com", "password": "secret"})
assert resp.ok
cookies = ctx.storage_state()["cookies"]
assert any(c["name"] == "session" for c in cookies)
ctx.dispose()
5) Pagination Testing
from playwright.sync_api import APIRequestContext
def test_pagination_basics(api: APIRequestContext):
resp = api.get("/users", params={"page": 1, "per_page": 2})
assert resp.ok
body = resp.json()
assert len(body["data"]) <= 2
assert body["page"] == 1
# second page returns different data
resp2 = api.get("/users", params={"page": 2, "per_page": 2})
body2 = resp2.json()
ids_page1 = {u["id"] for u in body["data"]}
ids_page2 = {u["id"] for u in body2["data"]}
assert ids_page1.isdisjoint(ids_page2), "Pages must not overlap"
def test_empty_page_returns_empty_list(api: APIRequestContext):
resp = api.get("/users", params={"page": 9999, "per_page": 10})
assert resp.ok
assert resp.json()["data"] == []
6) File Upload via API
from pathlib import Path
from playwright.sync_api import APIRequestContext
def test_upload_avatar(api: APIRequestContext, tmp_path: Path):
# create a test file
avatar = tmp_path / "avatar.png"
avatar.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100) # minimal PNG header
resp = api.post("/users/1/avatar", multipart={
"file": {
"name": "avatar.png",
"mimeType": "image/png",
"buffer": avatar.read_bytes(),
},
})
assert resp.ok
assert resp.json()["avatar_url"]
7) Seed via API, Verify in UI
from playwright.sync_api import Page, APIRequestContext, expect
def test_api_created_item_visible_in_ui(api: APIRequestContext, page: Page):
# seed data through API (fast, reliable)
resp = api.post("/items", data={"title": "API Item", "status": "active"})
assert resp.ok
item_id = resp.json()["id"]
# verify it appears in the browser
page.goto("/items")
expect(page.get_by_role("row").filter(has_text="API Item")).to_be_visible()
# cleanup
api.delete(f"/items/{item_id}")
Quick Checklist
- Test the full CRUD cycle — create, read, update, delete, verify deleted.
- Validate response schema — check required fields and types.
- Test error paths — 400, 401, 404, 409, 422.
- Check headers — CORS, rate limits, cache control, cookies.
- Pagination — verify page boundaries, no overlap, empty last page.
- Use
tmp_pathfor test files — auto-cleaned. - Seed data via API before UI tests — faster than UI setup.