API Testing

Back

Loading concept...

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!

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.