When should a Playwright test create its own APIRequestContext with playwright.request.newContext()?
answer
- The fixture is scoped to one test
- Hooks need a longer-lived client
- Different identity, different context
- Responses buffer until disposal
- Whoever creates it disposes it
basics
~20 sBuild one when the fixture's per-test lifetime or its configured options do not fit: shared setup in beforeAll, a different base URL or token, a captured session, or a deliberately isolated cookie jar. Then dispose it yourself when the work is done.
solid answer
~40 sThe `request` fixture is created per test and disposed with it, and it carries whatever the `use` block configured. Create your own with `playwright.request.newContext(options)` when that does not fit: setup shared by a whole file in `beforeAll`, a second identity such as an admin token, a different `baseURL`, or a context seeded with `storageState` so it acts as a signed-in user. You then own its lifetime. Every response an `APIRequestContext` returns is buffered in memory until the context is disposed, so call `await apiContext.dispose()` in `afterAll`; after disposal any method on it throws. Do not keep the `request` fixture taken in `beforeAll` — Playwright disposes it when the hook ends and reports that the fixture cannot be reused in a test.
code
typescript · 16 linesimport { test, expect, type APIRequestContext } from '@playwright/test';
let seed: APIRequestContext;
test.beforeAll(async ({ playwright }) => {
seed = await playwright.request.newContext({
baseURL: 'https://weather.example.com',
extraHTTPHeaders: { Authorization: `Bearer ${process.env.SEED_TOKEN}` },
});
const city = await seed.post('/api/cities', { data: { name: 'Bergen' } });
await expect(city).toBeOK();
});
test.afterAll(async () => {
await seed.dispose();
});go deeper
Know that the request fixture is enough for a normal test, and that anything you create yourself is also yours to close when the work is finished.
Explain the per-test lifetime of the fixture, why a context taken in beforeAll is already disposed by the time a test runs, and what dispose actually releases.
Choose the scope deliberately — file-level seeding, a second identity, a captured session — and pair every creation with a disposal so a long run does not accumulate buffered responses.
Decide where shared HTTP clients live in a suite: fixtures with owned lifetimes rather than module-level variables, so scope and teardown are visible to everyone who reads a test.
The `request` fixture covers the common case, and most suites never need anything else. The cases where it does not fit are all about **lifetime** or **identity**. ## What the fixture gives you for free One isolated `APIRequestContext` per test, configured from the project's `use` options, created lazily and disposed when the test finishes. Disposal is not cosmetic: every response the client returned is buffered in memory so that `body()` stays readable, and the buffers are released only on disposal. Because the runner does it for you, a per-test client never accumulates anything. ## The beforeAll boundary A fixture instance belongs to the scope that requested it. Take `request` in `test.beforeAll` and Playwright disposes it when the hook completes, so a test that saved the reference into a file-level variable meets a disposed context. Playwright says so explicitly rather than failing obscurely: the message states the fixture from `beforeAll` cannot be reused in a test, and recommends either using `{ request }` inside the test or creating an `APIRequestContext` manually and disposing it in `afterAll`. That recommendation is the main reason to reach for `newContext()`. ## Reasons to build your own - **File-level or suite-level setup**, where one seed serves many tests and repeating it per test is waste. - **A second identity** — an admin or service token on `extraHTTPHeaders` while the fixture stays on the ordinary user's configuration. - **A different target** — a `baseURL` pointing at the third-party forecast provider rather than your own service. - **A signed-in session**, by passing `storageState` (a path, or the value from `browserContext.storageState()` or `apiRequestContext.storageState()`), which is how an isolated client acts as a real user. - **Different transport settings** for one job: `ignoreHTTPSErrors`, `httpCredentials`, `proxy`, `timeout`, `clientCertificates`. - **Deliberate isolation inside a browser test**, when the call must not carry the page's cookies. ## Disposal is your job Once you construct it, nothing disposes it for you. The pattern is symmetrical: 1. Create it in `test.beforeAll(async ({ playwright }) => ...)` and store it in a module-level variable. 2. Use it from the tests in that file. 3. `await apiContext.dispose()` in `test.afterAll`. Skipping step 3 keeps every buffered response alive for as long as the worker process lives, which on a long file of large payloads is a real memory cost, and leaves an unclosed client behind. `dispose()` also accepts a `reason`, which is reported to any request interrupted by the disposal — useful when a teardown races an in-flight call, because the failure then names the teardown rather than surfacing as an anonymous socket error. | Concern | request fixture | Your own context | |---|---|---| | Lifetime | The test | Whatever scope you write | | Options | The `use` block | The options you pass | | Disposal | Automatic | Yours, explicitly | | Usable across tests | No | Yes, within the file or worker scope you gave it | ## Where to put it `beforeAll` plus a module variable is the documented shape and is easy to read, but a custom fixture is often better: it keeps creation and disposal in one place, gives the context a name that appears in the report, and lets tests declare the dependency instead of relying on a file-level variable existing. Either way, the rule is the same — one owner, one disposal. ## Signals you got it wrong - An error saying a context is disposed, usually a fixture kept from `beforeAll`. - Worker memory that climbs across a long file of API-heavy tests. - A `beforeAll` seed that runs once but is silently wasted because each test re-seeds anyway. - Two contexts alive with the same configuration, which means one of them should have been the fixture. As of Playwright 1.63 the constructor options include `baseURL`, `extraHTTPHeaders`, `storageState`, `httpCredentials`, `ignoreHTTPSErrors`, `proxy`, `userAgent`, `clientCertificates`, `timeout`, `maxRedirects` and `failOnStatusCode`.
- What does Playwright do if a test uses the request fixture that a beforeAll hook obtained?It fails with a message saying the `{ request }` fixture from `beforeAll` cannot be reused in a test, because the runner disposed it when the hook ended. The fix is either to take `{ request }` inside the test, or to create an `APIRequestContext` manually in `beforeAll` and dispose it in `afterAll`.
- Why does an APIRequestContext need disposing at all?Because every response it returned is held in memory so that `body()`, `text()` and `json()` remain readable afterwards. `dispose()` discards those buffers and the client's resources, and any method called on it afterwards throws. Passing a `reason` makes requests interrupted by the disposal report why they were cut off.
- How would you give a manually created context the session of an already signed-in user?Pass `storageState` — a path written earlier, or the object returned by `browserContext.storageState()` or `apiRequestContext.storageState()`. The cookies then travel with every call the context makes. For a bearer-token scheme there is nothing to carry in cookies, so set the token on `extraHTTPHeaders` instead.
saying these in an interview costs you the question
- Keeps the request fixture from beforeAll and reuses it in tests
- Never disposes a manually created request context
- Thinks the runner cleans up contexts it did not create
- Creates a new context per call instead of per scope
- Calls a method on a context after disposing it