The request fixture
Playwright Test gives you a request fixture in every test — it's an APIRequestContext that can make GET, POST, PUT, DELETE and other HTTP requests. It shares cookies with the test's browser context, so if you're logged in via the UI, the API calls are also authenticated.
import { test, expect } from '@playwright/test'
test('GET /api/orders returns list', async ({ request }) => {
const response = await request.get('/api/orders')
expect(response.status()).toBe(200)
const body = await response.json()
expect(body.orders).toBeInstanceOf(Array)
expect(body.total).toBeGreaterThanOrEqual(0)
})
test('POST /api/orders creates an order', async ({ request }) => {
const response = await request.post('/api/orders', {
data: {
item: 'Laptop',
quantity: 1,
customerId: 'cust-001',
},
})
expect(response.status()).toBe(201)
const order = await response.json()
expect(order.id).toBeTruthy()
expect(order.status).toBe('pending')
})
Seed data before UI tests
The most practical use: create test data via API, then verify it in the UI. This avoids clicking through forms just to set up a starting state. The UI test focuses on what it's actually testing.
test('order appears in dashboard after creation', async ({ request, page }) => {
// Створюємо замовлення через API (швидко, без форм)
const res = await request.post('/api/orders', {
data: { item: 'Keyboard', quantity: 2 },
})
const { id: orderId } = await res.json()
// Перевіряємо в UI що воно з'явилось
await page.goto('/dashboard')
await expect(page.getByRole('row').filter({ hasText: orderId })).toBeVisible()
})
test('deleting from UI removes from API', async ({ request, page }) => {
// Seed через API
const res = await request.post('/api/orders', {
data: { item: 'Mouse', quantity: 1 },
})
const { id: orderId } = await res.json()
// Видаляємо через UI
await page.goto('/orders')
await page.getByRole('row').filter({ hasText: orderId })
.getByRole('button', { name: 'Delete' }).click()
await page.getByRole('button', { name: 'Confirm' }).click()
// Перевіряємо що API повертає 404
const checkRes = await request.get(`/api/orders/${orderId}`)
expect(checkRes.status()).toBe(404)
})
Configure base URL
Set baseURL in the config and you can use relative paths in all requests — both in page.goto() and in request.get(). This makes it easy to switch between local, staging, and production environments.
// playwright.config.ts
export default defineConfig({
use: {
baseURL: 'https://app.example.com',
},
})
// В тесті — відносні шляхи
test('api and ui share base url', async ({ request, page }) => {
await page.goto('/orders') // → https://app.example.com/orders
const res = await request.get('/api/orders') // → https://app.example.com/api/orders
})
API context without a browser
For pure API tests — no browser needed at all — use request.newContext() to create a standalone APIRequestContext. This is faster and uses fewer resources than launching a full browser context.
import { test, expect, request } from '@playwright/test'
test('create and fetch order via API only', async () => {
const apiCtx = await request.newContext({
baseURL: 'https://app.example.com',
extraHTTPHeaders: {
'Authorization': `Bearer ${process.env.API_TOKEN}`,
},
})
const createRes = await apiCtx.post('/api/orders', {
data: { item: 'Monitor', quantity: 1 },
})
expect(createRes.status()).toBe(201)
const { id } = await createRes.json()
const getRes = await apiCtx.get(`/api/orders/${id}`)
expect(getRes.status()).toBe(200)
await apiCtx.dispose()
})