Learn-skills.dev api-testing
REST and GraphQL API testing with Playwright. Use when testing APIs, mocking endpoints, validating responses, or integrating API tests with E2E flows.
install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/adaptationio/skrillz/api-testing" ~/.claude/skills/neversight-learn-skills-dev-api-testing-4da125 && rm -rf "$T"
manifest:
data/skills-md/adaptationio/skrillz/api-testing/SKILL.mdsource content
API Testing with Playwright
Comprehensive API testing for REST and GraphQL endpoints using Playwright's built-in API testing capabilities.
Quick Start
import { test, expect } from '@playwright/test'; test('GET /api/users returns users', async ({ request }) => { const response = await request.get('/api/users'); expect(response.ok()).toBeTruthy(); expect(response.status()).toBe(200); const users = await response.json(); expect(users).toHaveLength(10); expect(users[0]).toHaveProperty('email'); });
Installation
# Playwright includes API testing - no extra packages needed npm install -D @playwright/test
Configuration
playwright.config.ts:
import { defineConfig } from '@playwright/test'; export default defineConfig({ testDir: './tests', use: { baseURL: 'http://localhost:3000', extraHTTPHeaders: { 'Accept': 'application/json', }, }, projects: [ { name: 'api', testMatch: /.*\.api\.spec\.ts/, }, { name: 'e2e', testMatch: /.*\.e2e\.spec\.ts/, use: { browserName: 'chromium' }, }, ], });
REST API Testing
GET Requests
test('fetch user by ID', async ({ request }) => { const response = await request.get('/api/users/123'); expect(response.ok()).toBeTruthy(); const user = await response.json(); expect(user.id).toBe(123); expect(user.email).toMatch(/@/); });
POST Requests
test('create new user', async ({ request }) => { const response = await request.post('/api/users', { data: { name: 'John Doe', email: 'john@example.com', }, }); expect(response.status()).toBe(201); const user = await response.json(); expect(user.id).toBeDefined(); expect(user.name).toBe('John Doe'); });
PUT/PATCH Requests
test('update user', async ({ request }) => { const response = await request.put('/api/users/123', { data: { name: 'Jane Doe', }, }); expect(response.ok()).toBeTruthy(); const user = await response.json(); expect(user.name).toBe('Jane Doe'); });
DELETE Requests
test('delete user', async ({ request }) => { const response = await request.delete('/api/users/123'); expect(response.status()).toBe(204); // Verify deletion const getResponse = await request.get('/api/users/123'); expect(getResponse.status()).toBe(404); });
Authentication
Bearer Token
test.describe('authenticated requests', () => { let token: string; test.beforeAll(async ({ request }) => { const response = await request.post('/api/auth/login', { data: { email: 'test@example.com', password: 'password123', }, }); const data = await response.json(); token = data.token; }); test('access protected endpoint', async ({ request }) => { const response = await request.get('/api/protected', { headers: { 'Authorization': `Bearer ${token}`, }, }); expect(response.ok()).toBeTruthy(); }); });
Cookie-Based Auth
test('login and access dashboard', async ({ request, context }) => { // Login (sets cookie automatically) await request.post('/api/auth/login', { data: { email: 'test@example.com', password: 'pass' }, }); // Cookie is automatically included in subsequent requests const response = await request.get('/api/dashboard'); expect(response.ok()).toBeTruthy(); });
GraphQL Testing
Query
test('GraphQL query users', async ({ request }) => { const response = await request.post('/graphql', { data: { query: ` query GetUsers { users { id name email } } `, }, }); const { data, errors } = await response.json(); expect(errors).toBeUndefined(); expect(data.users).toHaveLength(10); });
Mutation
test('GraphQL create user', async ({ request }) => { const response = await request.post('/graphql', { data: { query: ` mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name email } } `, variables: { input: { name: 'John Doe', email: 'john@example.com', }, }, }, }); const { data, errors } = await response.json(); expect(errors).toBeUndefined(); expect(data.createUser.id).toBeDefined(); });
With Authentication
test('GraphQL with auth', async ({ request }) => { const response = await request.post('/graphql', { headers: { 'Authorization': `Bearer ${token}`, }, data: { query: ` query Me { me { id email role } } `, }, }); const { data } = await response.json(); expect(data.me.role).toBe('admin'); });
API Mocking (for E2E tests)
Mock API Responses
test('display mocked products', async ({ page }) => { // Mock the API await page.route('**/api/products', route => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, name: 'Mock Product', price: 99.99 }, ]), }); }); await page.goto('/products'); await expect(page.locator('.product')).toHaveCount(1); await expect(page.locator('.product-name')).toHaveText('Mock Product'); });
Simulate Errors
test('handle API error gracefully', async ({ page }) => { await page.route('**/api/products', route => { route.fulfill({ status: 500, body: JSON.stringify({ error: 'Server error' }), }); }); await page.goto('/products'); await expect(page.locator('.error-message')).toBeVisible(); });
Delay Responses
test('show loading state', async ({ page }) => { await page.route('**/api/products', async route => { await new Promise(r => setTimeout(r, 2000)); route.fulfill({ status: 200, body: JSON.stringify([]), }); }); await page.goto('/products'); await expect(page.locator('.loading-spinner')).toBeVisible(); });
Response Validation
JSON Schema Validation
import Ajv from 'ajv'; const ajv = new Ajv(); const userSchema = { type: 'object', properties: { id: { type: 'number' }, name: { type: 'string' }, email: { type: 'string', format: 'email' }, }, required: ['id', 'name', 'email'], }; test('validate response schema', async ({ request }) => { const response = await request.get('/api/users/1'); const user = await response.json(); const validate = ajv.compile(userSchema); expect(validate(user)).toBeTruthy(); });
Response Headers
test('check response headers', async ({ request }) => { const response = await request.get('/api/users'); expect(response.headers()['content-type']).toContain('application/json'); expect(response.headers()['x-rate-limit-remaining']).toBeDefined(); });
File Upload
import path from 'path'; test('upload file', async ({ request }) => { const response = await request.post('/api/upload', { multipart: { file: { name: 'test.pdf', mimeType: 'application/pdf', buffer: Buffer.from('PDF content'), }, description: 'Test document', }, }); expect(response.ok()).toBeTruthy(); const result = await response.json(); expect(result.filename).toBe('test.pdf'); });
Best Practices
- Separate API and E2E tests - Use different test files/projects
- Use fixtures for common data - Avoid repetition
- Test error cases - 400, 401, 403, 404, 500 responses
- Validate response schemas - Catch breaking changes early
- Mock external APIs - Don't depend on third-party availability
- Clean up test data - Use
orafterEach
hooksafterAll
References
- Advanced GraphQL patternsreferences/graphql-testing.md
- JSON Schema with Ajvreferences/schema-validation.md