Seed state via request, then verify the result in the browser. API call and UI check tell one story, so they live together.
page.request carries the page's cookies alongUse the `request` fixture to hit HTTP endpoints directly without a browser.
Playwright is known for browser-based end-to-end testing, but it ships with an HTTP client, too. The request object makes plain HTTP calls and returns an APIResponse you can assert on. It works without a page or browser context.
Use it when:
request vs page.route()You met page.route() in the network lesson. It intercepts calls the browser is about to make. request is the opposite. Your test makes the call itself without a browser context.
request provides methods for the common HTTP operations: get, head, post, put, delete, etc.
test("hits the products endpoint", async ({ request }) => { const response = await request.get("/api/products/"); await expect(response).toBeOK(); const products = await response.json(); expect(products.length).toBeGreaterThan(0);});
There are some things to note:
baseURL from playwright.config still applies.expect(response).toBeOK() passes for any 2xx status and is much friendlier than expect(response.ok()).toBeTruthy().await response.json() reads the response body. There's also .text() and .body() if you want to fiddle with raw bytes.request vs page.requestThe request object isn't the only way to make HTTP calls. There's also page.request. What's the difference?
Both request objects use the same HTTP client but with a different context. To pick one, ask yourself: does this call need to share state with the browser?
request (the test-level fixture) launches no browser and keeps its own storage. Use it in standalone API suites where the response doesn't depend on a logged-in user.page.request is bound to the page's BrowserContext. Every session the browser logged in to and every cookie page.goto set comes along. Use it whenever an API call has to see or set cookies the page also touches.// Standalone API test: no browser, separate session contexttest("products endpoint", async ({ request }) => { const response = await request.get("/api/products/"); await expect(response).toBeOK();});// Mixed flow: log in via UI, then hit the API with shared cookiestest("authenticated checkout", async ({ page }) => { await page.goto("/login"); // … make a request with the current browser state and all existing cookies const response = await page.request.post("/api/checkout/", { /* ... */ }); await expect(response).toBeOK();});
The post, put, patch, and delete methods accept an options object with data (JSON), form (URL-encoded), multipart (file uploads), and headers.
const response = await request.post("/api/checkout/", {
data: {
email: "test@example.com",
/* …rest of the payload */
},
headers: { "x-test": "playwright" },
});
Assert the products response shape
/api/products/ returns an array of Shopify products. Each product has
a priceRange.minVariantPrice with an amount and a currencyCode.
request.get("/api/products/") to hit the endpoint.priceRange.minVariantPrice.amount.test("every product has a price", async ({ request }) => {
const response = await request.get("/api/products/");
await expect(response).toBeOK();
const products = await response.json();
for (const product of products) {
expect(Number(product.priceRange.minVariantPrice.amount)).toBeGreaterThan(0);
}
});
There are two common patterns:
Seed state via request, then verify the result in the browser. API call and UI check tell one story, so they live together.
page.request carries the page's cookies alongName them *.api.spec.ts and give them their own project. That project can skip the browser launch for a real speed win.
The Playwright API testing guide covers authentication, request context reuse, and mocking the API layer for UI tests. Worth a read once you've shipped your first few.
Probe the checkout endpoint without logging in
POST /api/checkout/ requires a name cookie. Without one, it returns
401 Unauthorized. That's a quick test and a perfect fit for request because it needs no
browser and no setup.
POST to the checkout with no body.response.status() is 401.Log in via API, see your name in the navbar
POST /api/login/ accepts { name } and sets a name cookie. Skip
the login form entirely: hit the endpoint, then navigate and watch
the page render as if you'd already signed in.
Use page.request so the cookie lands on the page's context.
page.goto("/") and assert the welcome banner shows the right name.Speed up your setup steps
Now that you know about /api/login/, you can speed up all your *.setup.ts
files. Making an API call instead of spinning up a browser saves your tests time and resources.