API Automation with Page Object Model (POM)
What is API Automation in Playwright?
Playwright is not only used for UI automation; it also supports powerful API testing using built-in HTTP request methods.
API automation helps to:
- Validate backend services
- Test REST APIs
- Verify response data
- Perform authentication testing
- Integrate API + UI workflows
Why Use Page Object Model (POM) for API Testing?
Page Object Model is a design pattern used to:
- Separate API logic from test logic
- Improve code reusability
- Make framework scalable
- Reduce code duplication
- Improve maintenance
Recommended Project Structure
playwright-api-framework/
│
├── tests/
│ ├── user.spec.ts
│ └── booking.spec.ts
│
├── api/
│ ├── UserAPI.ts
│ └── BookingAPI.ts
│
├── utils/
│ ├── apiClient.ts
│ └── testData.ts
│
├── fixtures/
│ └── auth.json
│
├── playwright.config.ts
├── package.json
└── tsconfig.json
Step 1: Install Playwright
Initialize Project
npm init -y
Install Playwright
npm init playwright@latest
Choose:
- TypeScript
- VS Code
- GitHub Actions (optional)
Step 2: Configure Playwright
playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: 'https://reqres.in/api',
extraHTTPHeaders: {
'Content-Type': 'application/json'
}
}
});
Step 3: Create Base API Client
utils/apiClient.ts
This class handles common API operations.
import { APIRequestContext, request } from '@playwright/test';
export class APIClient {
private apiContext!: APIRequestContext;
async createContext() {
this.apiContext = await request.newContext({
baseURL: 'https://reqres.in/api',
extraHTTPHeaders: {
'Content-Type': 'application/json'
}
});
}
getContext() {
return this.apiContext;
}
}
Step 4: Create API Page Object
api/UserAPI.ts
import { APIRequestContext, expect } from '@playwright/test';
export class UserAPI {
readonly request: APIRequestContext;
constructor(request: APIRequestContext) {
this.request = request;
}
async getUsers(pageNumber: number) {
const response = await this.request.get(`/users?page=${pageNumber}`);
expect(response.status()).toBe(200);
return await response.json();
}
async createUser(name: string, job: string) {
const response = await this.request.post('/users', {
data: {
name,
job
}
});
expect(response.status()).toBe(201);
return await response.json();
}
async updateUser(id: number, name: string, job: string) {
const response = await this.request.put(`/users/${id}`, {
data: {
name,
job
}
});
expect(response.status()).toBe(200);
return await response.json();
}
async deleteUser(id: number) {
const response = await this.request.delete(`/users/${id}`);
expect(response.status()).toBe(204);
}
}
Step 5: Write API Test Cases
tests/user.spec.ts
import { test, request, expect } from '@playwright/test';
import { UserAPI } from '../api/UserAPI';
test.describe('User API Tests', () => {
let userAPI: UserAPI;
test.beforeEach(async () => {
const apiContext = await request.newContext({
baseURL: 'https://reqres.in/api'
});
userAPI = new UserAPI(apiContext);
});
test('Get Users List', async () => {
const response = await userAPI.getUsers(2);
expect(response.page).toBe(2);
expect(response.data.length).toBeGreaterThan(0);
});
test('Create New User', async () => {
const response = await userAPI.createUser(
'Deepesh',
'QA Engineer'
);
expect(response.name).toBe('Deepesh');
expect(response.job).toBe('QA Engineer');
});
test('Update Existing User', async () => {
const response = await userAPI.updateUser(
2,
'Deepesh Updated',
'Senior QA'
);
expect(response.name).toBe('Deepesh Updated');
});
test('Delete User', async () => {
await userAPI.deleteUser(2);
});
});
Step 6: Execute Tests
Run All Tests
npx playwright test
Run Specific File
npx playwright test tests/user.spec.ts
Run in Headed Mode
npx playwright test --headed
Generate HTML Report
npx playwright show-report
API Assertions Examples
Validate Status Code
expect(response.status()).toBe(200);
Validate Response Body
expect(responseBody.name).toBe('Deepesh');
Validate Headers
expect(response.headers()['content-type'])
.toContain('application/json');
Authentication Example
Login API
async login(email: string, password: string) {
const response = await this.request.post('/login', {
data: {
email,
password
}
});
return await response.json();
}
Token Handling Example
const token = loginResponse.token;
const apiContext = await request.newContext({
extraHTTPHeaders: {
Authorization: `Bearer ${token}`
}
});
Reusable Test Data
utils/testData.ts
export const users = {
adminUser: {
name: 'Deepesh',
job: 'Automation Engineer'
}
};
Advanced Framework Enhancements
You can extend this framework with:
| Feature | Purpose |
|---|---|
| Environment Config | DEV/UAT/PROD support |
| Logger | API request-response logs |
| Allure Reports | Advanced reporting |
| Retry Mechanism | Handle flaky APIs |
| CI/CD | Jenkins/GitHub Actions |
| Database Validation | Backend verification |
| Schema Validation | Validate JSON structure |
| Faker Library | Dynamic test data |
| Parallel Execution | Faster execution |
Real-World Enterprise Structure
framework/
│
├── api/
├── tests/
├── fixtures/
├── utils/
├── helpers/
├── data/
├── reports/
├── logs/
├── schemas/
├── config/
└── .github/workflows/
Best Practices
1. Keep API Logic Separate
Do not write API calls directly inside test files.
✅ Correct:
userAPI.createUser()
❌ Wrong:
request.post(...)
inside every test.
2. Use Environment Variables
BASE_URL=https://uat-api.company.com
3. Validate Response Time
expect(response.ok()).toBeTruthy();
4. Use Soft Assertions Carefully
expect.soft(response.status()).toBe(200);
5. Create Reusable Helpers
Example:
- Token manager
- Payload builder
- Common assertions
Advantages of Playwright API Automation
| Advantage | Description |
|---|---|
| Fast Execution | No browser required |
| Built-in Assertions | Easy validations |
| API + UI Combo | End-to-end workflow |
| Parallel Support | Faster pipelines |
| TypeScript Support | Better maintainability |
| Auto Waiting | Stable execution |
API + UI Hybrid Testing Example
test('Create user via API and validate in UI', async ({ page }) => {
// Create user via API
await userAPI.createUser('Deepesh', 'QA');
// Validate in UI
await page.goto('/users');
await expect(
page.locator('text=Deepesh')
).toBeVisible();
});