description: Test HTTP APIs with the request fixture, share cookies between API and browser contexts, and use the API+UI hybrid pattern for fast deterministic E2E. tldr: The request fixture shares the browser context's cookie jar — use it to log in via API then drive UI as an authenticated user. The killer pattern: create state via JSON:API (fast), drive UI for the feature under test, verify via API (authoritative).
API Testing
When to Use
Use the
requestfixture for HTTP contract tests and for setting up state via API before driving UI. Use the API+UI hybrid when you need both deterministic state and user-journey coverage.
Decision
| Test value | Approach |
|---|---|
| HTTP contract (status, JSON shape, headers) | Pure API — no browser; 10–100× faster |
| User journey (forms, JS-rendered components, navigation) | UI-only E2E |
| Both: verify a feature end-to-end with deterministic state | API + UI hybrid |
Pattern
// Basic request fixture usage
test('order API', async ({ request }) => {
const get = await request.get('/api/orders/42');
await expect(get).toBeOK(); // 200–299
expect(get.status()).toBe(200);
const body = await get.json();
expect(body.total).toBe(123.45);
await request.post('/api/orders', {
data: { items: [{ sku: 'X', qty: 2 }] }, // auto-serializes to JSON
headers: { Authorization: 'Bearer …' },
});
});
Cookie sharing between API and UI
test('login via API, drive UI as authed user', async ({ request, page }) => {
// Login via API — Set-Cookie ends up in the shared jar
const res = await request.post('/user/login?_format=json', {
data: { name: 'admin', pass: 'admin' },
});
await expect(res).toBeOK();
// page is now authenticated
await page.goto('/admin');
await expect(page.getByRole('heading', { name: 'Administration' })).toBeVisible();
});
API + UI hybrid (the killer pattern)
test('publishing a node updates JSON:API', async ({ request, page }) => {
// 1. Set up state via API (fast)
const created = await request.post('/jsonapi/node/article', { /* ... */ });
const nuid = (await created.json()).data.id;
// 2. Drive UI for the actual feature under test
await page.goto(`/node/${nuid}/edit`);
await page.getByLabel('Published').check();
await page.getByRole('button', { name: 'Save' }).click();
// 3. Verify via API (cheap, deterministic)
const verify = await request.get(`/jsonapi/node/article/${nuid}`);
const body = await verify.json();
expect(body.data.attributes.status).toBe(true);
});
Common Mistakes
- Wrong: Using UI for state setup that could be done via API — slow tests
- Wrong: Asserting only via UI when API verification would be more authoritative
- Wrong: Spinning up
requestfor every assertion instead of using the shared fixture
See Also
- Drupal & DDEV Patterns — JSON:API + UI hybrid applied to Drupal entity CRUD
- Network Mocking — intercepting API calls for determinism
- Reference: Playwright API Testing