intermediate19 min

APIRequestContext: API Calls Without a Browser

Making real HTTP requests directly from a Playwright test — no browser, no page, no rendering — and why that's often the faster, more reliable way to set up test state.

What you'll learn

  • Explain what APIRequestContext is and how it differs from browser-driven network traffic
  • Use APIRequestContext to set up test data before a UI test runs
  • Decide when a scenario is better tested purely at the API level than through the UI

Prerequisites

Explanation

APIRequestContext (const api = await request.newContext(); await api.post("/api/courses", { data: {...} })) makes real HTTP requests directly, without launching a browser, opening a page, or rendering any HTML at all — it's Playwright's own built-in HTTP client, sharing the same library the browser-driven page object uses under the hood for its own network activity, but usable completely on its own. This is a genuinely different tool from everything covered so far in this course: no browser process, no page, no DOM, no rendering — just a request and a response.

The practical payoff is test setup speed and reliability: seeding the exact data a UI test needs (creating a course, enrolling a learner, marking a lesson complete) by clicking through the UI is slow and adds unrelated failure surface — if the UI's own enrollment flow has a bug, every single test that depends on "a learner is already enrolled" as a starting condition breaks too, even tests that have nothing to do with enrollment. Setting up that same state via a direct APIRequestContext call is faster and decouples a test's actual subject (what it's meant to verify) from unrelated parts of the application the test doesn't care about.

The decision rule for API-only versus UI testing: if a scenario's real value is verifying what a user sees and can do (does the enrolled-course badge render, can the learner click through to the lesson), it belongs in a browser-driven UI test. If a scenario's real value is verifying backend behavior (does the API reject an invalid payload, does a duplicate-enrollment attempt correctly return a 409, is the response shape correct) with no meaningful UI-rendering component to check, testing it purely through APIRequestContext is both faster and a more direct, honest test of the actual thing being verified — routing every scenario through the UI regardless of what it's actually testing is a common, needless source of slow, brittle test suites.

Example

Modeling APIRequestContext's role as pure HTTP setup, decoupled from any UI, and the decision rule for choosing it.

// A simplified stand-in for Playwright's real request.newContext() -- models the SHAPE
// of API-only requests, not real network calls.
class FakeApiRequestContext {
  constructor() { this.records = []; }
  post(path, options) {
    this.records.push({ method: "POST", path, body: options.data });
    return { status: 201, body: { id: this.records.length, ...options.data } };
  }
}

function shouldTestAtApiLevel(scenario) {
  const apiLevelReasons = ["invalid-payload-rejection", "duplicate-conflict-status", "response-shape"];
  return apiLevelReasons.includes(scenario);
}

const api = new FakeApiRequestContext();
const enrollment = api.post("/api/enrollments", { data: { learnerId: 1, courseId: 10 } });
console.log(enrollment); // { status: 201, body: { id: 1, learnerId: 1, courseId: 10 } } -- test setup, no browser involved

console.log(shouldTestAtApiLevel("invalid-payload-rejection")); // true -- backend behavior, no UI-rendering component
console.log(shouldTestAtApiLevel("enrolled-badge-renders"));    // false -- this is genuinely a UI concern

Try it yourself

Call api.post twice and confirm each call gets a distinct, incrementing id in its response body.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Write seedEnrollment(apiClient, learnerId, courseId) that calls apiClient.post('/api/enrollments', { data: { learnerId, courseId } }) and returns just the created enrollment's id from the response. Then write shouldTestAtApiLevel(scenario) returning true for 'invalid-payload-rejection', 'duplicate-conflict-status', or 'response-shape'; false otherwise.

Checks: seedEnrollment correctly extracts the created id · recognizes backend validation as API-level testing · recognizes a UI-rendering concern as NOT API-level testing

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Write seedMultipleEnrollments(apiClient, pairs) where pairs is an array of [learnerId, courseId] tuples. Call apiClient.post once per pair, and return an array of all the created ids, IN ORDER. This models efficiently seeding several pieces of test state via API calls before a UI test runs.

Checks: seeds multiple enrollments in order and returns their ids · handles an empty pairs list

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Setting up all test data by clicking through the UI, even for tests whose actual subject has nothing to do with that setup flow -- this couples every test to the reliability of an unrelated UI flow, and is far slower than a direct API call.
  • Testing backend-only concerns (input validation, status codes, response shape) exclusively through the UI when there's no meaningful rendering to actually verify -- this is slower and less direct than exercising the API surface itself.
  • Assuming APIRequestContext requires a browser or page to be open -- it doesn't; it's a standalone HTTP client that can be used with no browser involved at all.

Knowledge check

Knowledge check

1. What does APIRequestContext let a Playwright test do?
2. Why is seeding test data via APIRequestContext often preferable to clicking through the UI to create it?
3. A scenario verifies that the API returns a 409 status for a duplicate enrollment attempt, with no UI-rendering aspect being tested. Where does this scenario best belong?

Takeaway

APIRequestContext makes real HTTP requests with no browser or rendering involved — use it to seed test state fast and to test backend-only behavior directly, reserving browser-driven UI tests for scenarios whose real value is what a user actually sees and does.

Summary

APIRequestContext is Playwright's standalone HTTP client, usable with no browser or page. It's the right tool for fast test-data setup and for testing backend behavior (validation, status codes, response shape) that has no meaningful UI-rendering component to verify.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.