Implementation:Microsoft Playwright APIRequestContext Fetch
| Knowledge Sources | |
|---|---|
| Domains | API_Testing, HTTP, REST |
| Last Updated | 2026-02-11 00:00 GMT |
Overview
Concrete tool for executing HTTP requests (GET, POST, PUT, DELETE, PATCH, HEAD) against REST APIs with full control over headers, body, and request options, provided by the Playwright library.
Description
The APIRequestContext provides six convenience methods -- get(), post(), put(), delete(), patch(), and head() -- that all delegate to the underlying fetch() method. This design ensures consistent behavior across all HTTP methods while providing ergonomic, method-specific entry points.
The request lifecycle proceeds through two layers:
Client layer (packages/playwright-core/src/client/fetch.ts:L124-170):
- Each convenience method sets the appropriate HTTP method and calls the internal
_innerFetch()method (L172-265). _innerFetch()validates that mutually exclusive body parameters (data,form,multipart) are not combined.- It resolves relative URLs against the context's
baseURL. - It serializes the body according to the parameter used: JSON for
data, URL-encoded forform, multipart/form-data formultipart. - The serialized request is sent to the server layer via the Playwright protocol channel.
Server layer (packages/playwright-core/src/server/fetch.ts:L146-548):
- The
_sendRequest()method (L285-548) handles the actual HTTP communication using Node.js built-inhttp/httpsmodules. - It follows redirects (HTTP 301, 302, 303, 307, 308) up to the configured
maxRedirectslimit. - It handles HTTP 401 authentication challenges by replaying the request with credentials.
- It decompresses
gzip,br(Brotli), anddeflateresponse bodies. - It enforces timeouts and manages connection lifecycle.
Usage
Use these methods whenever you need to make HTTP requests in API tests. Use the specific convenience method matching the desired HTTP verb for clarity, or use fetch() directly when the method needs to be dynamically determined.
Code Reference
Source Location
- Repository: playwright
- Client methods:
packages/playwright-core/src/client/fetch.ts:L124-170 - Client _innerFetch:
packages/playwright-core/src/client/fetch.ts:L172-265 - Server _sendRequest:
packages/playwright-core/src/server/fetch.ts:L285-548
Signature
// Convenience methods (all delegate to fetch())
request.get(url: string, options?: FetchOptions): Promise<APIResponse>;
request.post(url: string, options?: FetchOptions): Promise<APIResponse>;
request.put(url: string, options?: FetchOptions): Promise<APIResponse>;
request.delete(url: string, options?: FetchOptions): Promise<APIResponse>;
request.patch(url: string, options?: FetchOptions): Promise<APIResponse>;
request.head(url: string, options?: FetchOptions): Promise<APIResponse>;
// Generic fetch method
request.fetch(urlOrRequest: string | Request, options?: FetchOptions): Promise<APIResponse>;
// FetchOptions type
interface FetchOptions {
params?: { [key: string]: string | number | boolean };
method?: string;
headers?: { [key: string]: string };
data?: string | Buffer | Serializable;
form?: { [key: string]: string | number | boolean };
multipart?: {
[key: string]: string | number | boolean | ReadStream | {
name: string;
mimeType: string;
buffer: Buffer;
};
};
timeout?: number;
failOnStatusCode?: boolean;
ignoreHTTPSErrors?: boolean;
maxRedirects?: number;
maxRetries?: number;
}
Import
import { test, expect } from '@playwright/test';
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| url | string |
Yes | The target URL. If relative, it is resolved against the context's baseURL. Query parameters can be included directly in the URL or via the params option.
|
| options.params | number | boolean } | No | Query parameters appended to the URL. Values are URL-encoded automatically. |
| options.method | string |
No | HTTP method override. Only used with fetch(); convenience methods set this automatically.
|
| options.headers | { [key: string]: string } |
No | Request-specific headers merged with context defaults. Per-request headers take precedence over context headers. |
| options.data | Buffer | Serializable | No | Request body. Objects are serialized to JSON; strings and Buffers are sent as-is. Mutually exclusive with form and multipart.
|
| options.form | number | boolean } | No | Form-encoded body. Mutually exclusive with data and multipart.
|
| options.multipart | object |
No | Multipart form data for file uploads. Mutually exclusive with data and form.
|
| options.timeout | number |
No | Request timeout in milliseconds. Defaults to 30000 (30 seconds). |
| options.failOnStatusCode | boolean |
No | If true, throws an error for non-2xx/3xx responses. Defaults to false.
|
| options.ignoreHTTPSErrors | boolean |
No | Whether to ignore HTTPS certificate errors for this request. Defaults to the context setting. |
| options.maxRedirects | number |
No | Maximum number of HTTP redirects to follow. Defaults to 20. Set to 0 to not follow redirects. |
| options.maxRetries | number |
No | Maximum number of times to retry the request on transient failures. Defaults to 0. |
Outputs
| Name | Type | Description |
|---|---|---|
| response | Promise<APIResponse> |
The API response object. Provides status() for the HTTP status code, ok() for a boolean success check (200-299), headers() for response headers, json() for parsed JSON body, text() for string body, and body() for raw Buffer body.
|
Usage Examples
Basic Example
import { test, expect } from '@playwright/test';
test.use({
baseURL: 'https://api.example.com',
});
test('CRUD operations on users API', async ({ request }) => {
// CREATE - POST a new user
const createResponse = await request.post('/api/users', {
data: {
name: 'Alice Johnson',
email: 'alice@example.com',
},
});
expect(createResponse.ok()).toBeTruthy();
const created = await createResponse.json();
const userId = created.id;
// READ - GET the created user
const getResponse = await request.get(`/api/users/${userId}`);
expect(getResponse.ok()).toBeTruthy();
const user = await getResponse.json();
expect(user.name).toBe('Alice Johnson');
// UPDATE - PUT to replace user data
const putResponse = await request.put(`/api/users/${userId}`, {
data: {
name: 'Alice Smith',
email: 'alice.smith@example.com',
},
});
expect(putResponse.ok()).toBeTruthy();
// PARTIAL UPDATE - PATCH to update a single field
const patchResponse = await request.patch(`/api/users/${userId}`, {
data: { email: 'alice.new@example.com' },
});
expect(patchResponse.ok()).toBeTruthy();
// DELETE - Remove the user
const deleteResponse = await request.delete(`/api/users/${userId}`);
expect(deleteResponse.ok()).toBeTruthy();
});
Example: Query Parameters and Custom Headers
import { test, expect } from '@playwright/test';
test('search with query params and pagination', async ({ request }) => {
const response = await request.get('/api/products', {
params: {
search: 'laptop',
page: 1,
limit: 20,
sort: 'price_asc',
},
headers: {
'X-Request-ID': 'test-search-001',
},
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
expect(data.results.length).toBeLessThanOrEqual(20);
});
Example: Using fetch() with Dynamic Method
import { test, expect } from '@playwright/test';
test('generic fetch with dynamic method', async ({ request }) => {
const methods = ['GET', 'HEAD'];
for (const method of methods) {
const response = await request.fetch('/api/health', {
method,
});
expect(response.status()).toBe(200);
}
});
Example: Handling Redirects
import { test, expect } from '@playwright/test';
test('follow redirects up to limit', async ({ request }) => {
// Follow up to 5 redirects
const response = await request.get('/api/legacy-endpoint', {
maxRedirects: 5,
});
expect(response.ok()).toBeTruthy();
});
test('do not follow redirects', async ({ request }) => {
const response = await request.get('/api/redirect-endpoint', {
maxRedirects: 0,
});
expect(response.status()).toBe(301);
});