Playwright API Testing: Your Personal Messenger Service
Imagine you’re running a messenger service in your neighborhood. People give you letters, you deliver them to the right house, and you bring back replies. That’s exactly what API testing does with websites!
The Big Picture
When websites talk to each other, they send messages (requests) and receive replies (responses). Playwright lets you be the messenger and check if everything is working correctly!
graph TD A["Your Test"] -->|Send Request| B["API Server"] B -->|Send Response| A A -->|Check Everything| C["Test Passes!"]
1. APIRequestContext: Your Messenger Toolkit
Think of APIRequestContext as your messenger bag. It has everything you need to send and receive messages!
What Is It?
It’s a special tool Playwright gives you to talk directly to websites without opening a browser. Like calling someone instead of visiting their house!
How to Get Your Bag
// Get your messenger toolkit
const { request } = require('@playwright/test');
// Create your messenger context
const apiContext = await request.newContext({
baseURL: 'https://api.example.com'
});
Real Example
const apiContext = await request.newContext({
baseURL: 'https://jsonplaceholder.typicode.com',
extraHTTPHeaders: {
'Accept': 'application/json'
}
});
// Now you're ready to send messages!
Why it’s cool: You can test if a website’s “back door” (API) works without loading any pictures or buttons!
2. HTTP Request Methods: Types of Messages
Just like there are different types of mail (letters, packages, postcards), there are different types of API messages!
The Main Five
| Method | What It Does | Real Life Example |
|---|---|---|
| GET | “Show me something” | Looking at a menu |
| POST | “Here’s something new” | Ordering food |
| PUT | “Replace this completely” | Rewriting your order |
| PATCH | “Change just this part” | “No onions please” |
| DELETE | “Remove this” | Canceling your order |
Code Examples
// GET - "Show me user #1"
const user = await apiContext.get('/users/1');
// POST - "Create a new user"
const newUser = await apiContext.post('/users', {
data: { name: 'Sam', job: 'tester' }
});
// PUT - "Replace user #1 completely"
const updated = await apiContext.put('/users/1', {
data: { name: 'Sam', job: 'developer' }
});
// PATCH - "Just update the job"
const patched = await apiContext.patch('/users/1', {
data: { job: 'architect' }
});
// DELETE - "Remove user #1"
const deleted = await apiContext.delete('/users/1');
Memory Trick:
- GET = Grab info
- POST = Put something new
- DELETE = Destroy something
3. Request Headers: The Envelope Details
Headers are like the information written on an envelope. Who’s it from? What’s inside? How should it be handled?
Common Headers
| Header | What It Means | Example |
|---|---|---|
Content-Type |
“What’s inside” | application/json |
Authorization |
“Here’s my ID card” | Bearer token123 |
Accept |
“Send me this format” | application/json |
User-Agent |
“I’m this type of browser” | PlaywrightTest/1.0 |
Adding Headers
// Method 1: In the context (for all requests)
const apiContext = await request.newContext({
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
'Authorization': 'Bearer my-secret-token',
'Content-Type': 'application/json'
}
});
// Method 2: Per request (for one request only)
const response = await apiContext.get('/secret-data', {
headers: {
'X-Custom-Header': 'special-value'
}
});
Think of it like: Putting a “FRAGILE” sticker on a package!
4. Request Body: The Actual Message
The body is like the letter inside the envelope. It’s the actual content you’re sending!
When Do You Need a Body?
- GET/DELETE: Usually no body (you’re asking or removing)
- POST/PUT/PATCH: Almost always have a body (you’re sending data)
Sending Different Types of Data
// JSON data (most common)
await apiContext.post('/users', {
data: {
name: 'Alex',
email: 'alex@example.com',
age: 25
}
});
// Form data (like filling out a form)
await apiContext.post('/login', {
form: {
username: 'alex',
password: 'secret123'
}
});
// File upload
await apiContext.post('/upload', {
multipart: {
file: {
name: 'photo.jpg',
mimeType: 'image/jpeg',
buffer: fileBuffer
}
}
});
Simple Rule: data for JSON, form for forms, multipart for files!
5. Response Handling: Reading the Reply
When you send a letter, you get a reply back. The response is that reply, and you need to read it!
What’s in a Response?
graph TD A["Response"] --> B["Status Code"] A --> C["Headers"] A --> D["Body"] B --> E["200 = Success!"] B --> F["404 = Not Found"] C --> G["Info about the reply"] D --> H["The actual data"]
Reading Responses
const response = await apiContext.get('/users/1');
// Get the status code
const status = response.status();
console.log(status); // 200
// Get the body as JSON
const data = await response.json();
console.log(data.name); // "Alex"
// Get the body as text
const text = await response.text();
// Check if it was successful
const ok = response.ok(); // true if 200-299
Complete Example
const response = await apiContext.post('/users', {
data: { name: 'Taylor', job: 'artist' }
});
// Check everything!
expect(response.status()).toBe(201);
expect(response.ok()).toBeTruthy();
const user = await response.json();
expect(user.name).toBe('Taylor');
expect(user.id).toBeDefined();
6. Response Status Codes: Quick Answers
Status codes are like quick replies: thumbs up, thumbs down, or “huh?”
The Five Families
| Range | Meaning | Emoji |
|---|---|---|
| 1xx | “Hold on, working…” | |
| 2xx | “Success! Done!” | |
| 3xx | “Go look over there” | |
| 4xx | “You made a mistake” | |
| 5xx | “Server broke” |
Common Codes to Know
// SUCCESS CODES
200 // OK - Everything worked
201 // Created - New thing made
204 // No Content - Done, nothing to say
// CLIENT ERROR CODES
400 // Bad Request - You sent garbage
401 // Unauthorized - Who are you?
403 // Forbidden - You can't do that
404 // Not Found - Doesn't exist
422 // Unprocessable - Data was wrong
// SERVER ERROR CODES
500 // Internal Error - Server crashed
502 // Bad Gateway - Server's server died
503 // Service Unavailable - Try later
Testing Status Codes
// Expect success
const goodResponse = await apiContext.get('/users');
expect(goodResponse.status()).toBe(200);
// Expect not found
const badResponse = await apiContext.get('/users/99999');
expect(badResponse.status()).toBe(404);
// Expect unauthorized
const secretResponse = await apiContext.get('/admin');
expect(secretResponse.status()).toBe(401);
7. API Authentication: Proving Who You Are
When you enter a building, you might need an ID card. APIs work the same way!
Types of Authentication
graph TD A["Authentication"] --> B["API Key"] A --> C["Bearer Token"] A --> D["Basic Auth"] B --> E["Key in header or URL"] C --> F["Token in Authorization header"] D --> G["Username:Password encoded"]
API Key Authentication
// In the header
const apiContext = await request.newContext({
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
'X-API-Key': 'your-secret-api-key'
}
});
// In the URL (less common)
const response = await apiContext.get(
'/data?api_key=your-secret-api-key'
);
Bearer Token (JWT)
// First, login to get a token
const loginResponse = await apiContext.post('/login', {
data: {
email: 'user@example.com',
password: 'secret123'
}
});
const { token } = await loginResponse.json();
// Use the token for other requests
const protectedContext = await request.newContext({
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
'Authorization': `Bearer ${token}`
}
});
// Now access protected routes!
const profile = await protectedContext.get('/profile');
Basic Authentication
const apiContext = await request.newContext({
baseURL: 'https://api.example.com',
httpCredentials: {
username: 'myuser',
password: 'mypass'
}
});
// Playwright adds the Authorization header for you!
const response = await apiContext.get('/protected');
Putting It All Together
Here’s a complete test that uses everything we learned!
import { test, expect, request } from '@playwright/test';
test('complete API workflow', async () => {
// 1. Create context
const api = await request.newContext({
baseURL: 'https://reqres.in/api'
});
// 2. POST - Create a user
const createRes = await api.post('/users', {
data: { name: 'Neo', job: 'The One' }
});
expect(createRes.status()).toBe(201);
const newUser = await createRes.json();
expect(newUser.name).toBe('Neo');
// 3. GET - Read users
const getRes = await api.get('/users/2');
expect(getRes.ok()).toBeTruthy();
const userData = await getRes.json();
expect(userData.data.email).toContain('@');
// 4. PUT - Update user
const putRes = await api.put('/users/2', {
data: { name: 'Neo', job: 'Savior' }
});
expect(putRes.status()).toBe(200);
// 5. DELETE - Remove user
const delRes = await api.delete('/users/2');
expect(delRes.status()).toBe(204);
// 6. Cleanup
await api.dispose();
});
Quick Summary
| Concept | One-Liner |
|---|---|
| APIRequestContext | Your toolkit for sending API messages |
| HTTP Methods | GET=read, POST=create, PUT=replace, PATCH=update, DELETE=remove |
| Headers | Info on the envelope (auth, content type) |
| Body | The actual data you’re sending |
| Response | The reply you get back |
| Status Codes | Quick answer: 2xx=good, 4xx=your fault, 5xx=server’s fault |
| Authentication | Proving who you are (API key, token, or username/password) |
You did it! Now you know how to be a messenger between your tests and any API. Go test some APIs!
