Playwright API Testing 101 โ€” A Practical Guide to APIRequestContext for GET and POST

Playwright is best known for browser automation, but it also has API testing built in. With APIRequestContext, you can write GET, POST, PUT, and DELETE tests without installing anything extra. This article covers how to get started with API testing using nothing but Playwright’s own built-in feature โ€” from basic requests to combining it with your UI tests.

It’s easy to assume API testing always means reaching for a separate library. If you’re already working in a pytest-playwright setup, you can start writing API tests without adding requests at all.

๐Ÿ“Œ Who This Article Is For

  • Anyone comfortable with Playwright browser automation who hasn’t written an API test yet
  • Anyone curious about the difference between Python’s requests library and Playwright’s API testing
  • Anyone who wants to combine UI tests and API tests in the same test code
  • Anyone who wants to start simple, with basic GET and POST requests

โœ… What You’ll Learn

  • How to write basic requests using Playwright’s APIRequestContext
  • How to verify response status, headers, and JSON body
  • The practical benefits of combining UI tests and API tests

๐Ÿ‘ค About the Author

Written by Yoshi, a QA and test automation engineer with over 15 years of hands-on experience implementing both UI and API tests with Playwright, and designing test suites that combine the two.

๐Ÿ“Œ Key Takeaways

  • With a pytest-playwright setup, Playwright’s APIRequestContext lets you write GET, POST, PUT, and DELETE tests without adding requests
  • Sharing cookies and session state with the browser is a strength Playwright’s API testing has that the requests library doesn’t
  • A test like “log in via UI, then verify via API” can happen entirely within a single shared session

Working on a Playwright-based UI test project, you’ll sometimes run into situations like these:

  • You need to set up a large volume of test data via API as a precondition for UI tests
  • You want to verify the result of a UI action precisely, by checking the API response
  • You’d rather not add a separate library (like requests) purely for API testing

Playwright’s APIRequestContext feature lets you handle all of this with the same tool, inside the same test code.

Where This Article Fits

This is an introductory article for anyone writing their first Playwright API test, focused on the basics of making GET, POST, and similar requests. More advanced, production-oriented topics โ€” authentication flows, token retrieval, security verification โ€” are covered in a separate, dedicated article.

๐Ÿ’ก Want to go further? Authentication flows, token retrieval, CRUD operations, and security verification are covered in depth in our Japanese-language article “Playwright + Python ใง่ช่จผใƒ•ใƒญใƒผAPIใƒ†ใ‚นใƒˆใ‚’่‡ชๅ‹•ๅŒ–ใ™ใ‚‹ๆ–นๆณ•” (Automating Authentication-Flow API Tests with Playwright and Python โ€” Token Retrieval, CRUD, and Security Verification). Reading this article first will give you the basics you need to follow along.

requests vs. Playwright’s APIRequestContext

The requests library is the standard choice for API testing in Python, but there are good reasons to reach for Playwright’s own API testing feature instead when you’re already working in a Playwright-based project.

ConsiderationrequestsPlaywright APIRequestContext
Extra installationRequired (pip install requests)Not required (built into pytest-playwright’s APIRequestContext)
Ease of writing simple API testsโ—Ž (simple, well-documented)โ—‹
Sharing browser cookies/sessionโœ• (you’d have to pass it through yourself)โœ… (shareable via context.request)
Integration with UI testsTends to become a separate tool with separate managementโœ… (lives inside the same test file)
๐Ÿ’ก Which to pick: If you’re writing a large, systematic suite of API-only tests, requests‘s simplicity is a genuine advantage. But if you want to combine API tests with UI tests, or reuse a browser’s logged-in state to call an API, Playwright’s APIRequestContext is the better fit.

โ‘  A Basic GET Request

Here’s the basic form, using the playwright fixture that pytest-playwright provides, to send an independent API request.

from playwright.sync_api import Playwright

def test_get_user(playwright: Playwright) -> None:
    # Create a dedicated API request context with a base_url
    request_context = playwright.request.new_context(
        base_url="https://jsonplaceholder.typicode.com"
    )

    response = request_context.get("/users/1")

    assert response.status == 200
    assert response.ok  # True for any status in the 200โ€“299 range

    body = response.json()
    assert body["id"] == 1

    # Explicitly dispose of the context once you're done with it
    request_context.dispose()
โš ๏ธ Note: Forgetting to call request_context.dispose() leaves the request context’s resources hanging around. Creating and disposing of it through a fixture, rather than managing it manually every time, helps prevent forgetting this step.

Reusing it via a fixture

# conftest.py
import pytest
from playwright.sync_api import Playwright, APIRequestContext

@pytest.fixture
def api_context(playwright: Playwright):
    context = playwright.request.new_context(
        base_url="https://jsonplaceholder.typicode.com"
    )
    yield context
    context.dispose()  # Automatically disposed after the test finishes
# test_users.py
def test_get_user(api_context):
    response = api_context.get("/users/1")
    assert response.status == 200

โ‘ก POST, PUT, and DELETE Requests

Other HTTP methods can be called just as intuitively from the same APIRequestContext.

def test_create_user(api_context):
    # POST: create a new resource
    response = api_context.post("/users", data={
        "name": "Taro Yamada",
        "email": "taro@example.com",
    })
    assert response.status == 201
    created = response.json()
    assert created["name"] == "Taro Yamada"


def test_update_user(api_context):
    # PUT: full update
    response = api_context.put("/users/1", data={
        "name": "Updated Name",
        "email": "updated@example.com",
    })
    assert response.status == 200


def test_delete_user(api_context):
    # DELETE: remove a resource
    response = api_context.delete("/users/1")
    assert response.status == 200
๐Ÿ” From the Field: Passing a Python dictionary to data= automatically gets serialized to JSON by Playwright and sent as the request body. Not having to manually set the Content-Type header is a nice bit of convenience compared to the requests library. If you need to send an application/x-www-form-urlencoded (form-encoded) request instead, use form= in place of data= โ€” pick whichever fits your use case.

โ‘ข Verifying the Response

Here’s a summary of the main properties and methods you can pull off a response object.

Property / MethodWhat it gives you
response.statusThe HTTP status code (as an integer)
response.okA boolean for whether the status is in the 200s
response.headersThe response headers, as a dictionary
response.json()The response body, parsed as JSON
response.text()The response body as a plain string
def test_response_details(api_context):
    response = api_context.get("/users/1")

    # Status code
    assert response.status == 200

    # Partial-match header check (an exact match tends to fail once a charset is added)
    content_type = response.headers.get("content-type", "")
    assert "application/json" in content_type

    # Confirm required fields are present
    body = response.json()
    for field in ["id", "name", "email"]:
        assert field in body, f"'{field}' is missing from the response"

โ‘ฃ Combining UI and API Tests

The biggest strength of Playwright’s API testing is that the browser context and the API request context can share cookies and session state. You get this by using context.request โ€” the request property that belongs to your browser context โ€” instead of playwright.request.

Browser context
Logs in via page.goto()
cookies
shared
context.request
Calls the API with the same cookies
โš ๏ธ Note: By contrast, playwright.request.new_context() creates a context that’s independent of the browser context, so cookies are not shared (compare it with the code below).
def test_ui_login_then_verify_via_api(page, context):
    # โ‘  Log in via the UI
    page.goto("/login")
    page.get_by_label("Email").fill("test@example.com")
    page.get_by_label("Password").fill("Password123")
    page.get_by_role("button", name="Log in").click()
    page.wait_for_url("**/dashboard")

    # โ‘ก Use the same session (cookies) to check the profile via the API
    # context.request shares cookies with the browser
    response = context.request.get("/api/profile")
    assert response.status == 200
    profile = response.json()
    assert profile["email"] == "test@example.com"
๐Ÿ’ก Practical Tip: This pattern lets you naturally write a test that backs up a UI action’s result with the API response, not just what’s visible on screen. It also works the other way around โ€” seeding test data via the API first, then verifying it through the UI.

Common Mistakes Seen in Practice

โ‘  Confusing playwright.request with context.request

playwright.request.new_context() creates a fresh, independent context, while context.request shares cookies with your browser context. Mixing these up based on your actual goal (an isolated API test vs. one tied to your UI session) leads to a session state you didn’t expect, and can cost real time in investigation.

โ‘ก Forgetting to call request_context.dispose()

When you’re managing a context created with playwright.request.new_context() manually, forgetting dispose() leaves its resources lingering. Designing it around a fixture’s yield pattern, so disposal happens automatically, is the safer approach.

โ‘ข Using an exact-match header check that breaks unexpectedly

Checking with response.headers["content-type"] == "application/json" fails in practice whenever a charset gets appended, like application/json; charset=utf-8. A partial match using in is the safer check.

โ‘ฃ Sticking with Playwright’s APIRequestContext even when it’s purely API testing

If you don’t need any UI integration and you’re writing a large, purely API-focused test suite, forcing yourself to stick with Playwright often means more code than the simpler requests library would need. Choosing the right tool for the job matters here too.

FAQ

Q. Should I use requests or Playwright’s API testing?

If you’re writing a large, systematic suite of purely API-focused tests, the simplicity of requests is a real advantage. If you want to write API tests alongside UI tests in the same project, or reuse a browser’s login session, Playwright’s APIRequestContext is the better fit. Choose based on your project’s primary goal.

Q. What’s the difference between playwright.request and context.request?

playwright.request.new_context() creates a brand-new, fully independent API request context. context.request, on the other hand, lets you send API requests while sharing the cookies and session state already held by an existing browser context. Use the latter when you want your UI and API activity to work together.

Q. How should I validate a response’s schema?

The basic approach is checking each field’s type with isinstance() against the dictionary you get from response.json(). For stricter schema validation, you might consider pairing it with a dedicated library like jsonschema.

Q. Do I have to set base_url when creating a new_context?

No, base_url isn’t required. If you skip it, you’ll need to specify the full URL on every request. If you’re sending multiple requests to the same API, setting base_url up front keeps your code noticeably cleaner.

Q. Can base_url be managed through an environment variable?

Yes. Instead of hardcoding a string like base_url="https://api.example.com", reading it from an environment variable โ€” e.g. os.environ["API_BASE_URL"] โ€” lets you switch between staging and production without touching your test code. Managing this through a .env file with python-dotenv is also a common pattern.

Q. What if I need to test an API that requires authentication?

Token retrieval, attaching authentication headers, and other authentication-flow topics are outside the scope of this article. Our related article on automating authentication-flow API tests with Playwright and Python covers this in depth โ€” check that out for the details.

๐Ÿ“‹ Summary

  • With a pytest-playwright setup, Playwright’s APIRequestContext lets you write GET, POST, PUT, and DELETE tests without adding requests
  • context.request lets you call an API while sharing the browser’s logged-in session
  • In practice: reach for requests when it’s purely API testing, and Playwright when you need UI integration
  • More advanced topics like authentication flows are covered in a related article

If you’re already using Playwright for UI tests, you don’t need to learn a new tool just to test APIs. Start with a simple GET request and try weaving it into your existing test code.

Copied title and URL