.claude/skills/api-testing/SKILL.md
REST and GraphQL API testing with Playwright. Use when testing APIs, mocking endpoints, validating responses, or integrating API tests with E2E flows.
npx skillsauth add adaptationio/skrillz api-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive API testing for REST and GraphQL endpoints using Playwright's built-in API testing capabilities.
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');
});
# Playwright includes API testing - no extra packages needed
npm install -D @playwright/test
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' },
},
],
});
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(/@/);
});
test('create new user', async ({ request }) => {
const response = await request.post('/api/users', {
data: {
name: 'John Doe',
email: '[email protected]',
},
});
expect(response.status()).toBe(201);
const user = await response.json();
expect(user.id).toBeDefined();
expect(user.name).toBe('John Doe');
});
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');
});
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);
});
test.describe('authenticated requests', () => {
let token: string;
test.beforeAll(async ({ request }) => {
const response = await request.post('/api/auth/login', {
data: {
email: '[email protected]',
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();
});
});
test('login and access dashboard', async ({ request, context }) => {
// Login (sets cookie automatically)
await request.post('/api/auth/login', {
data: { email: '[email protected]', password: 'pass' },
});
// Cookie is automatically included in subsequent requests
const response = await request.get('/api/dashboard');
expect(response.ok()).toBeTruthy();
});
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);
});
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: '[email protected]',
},
},
},
});
const { data, errors } = await response.json();
expect(errors).toBeUndefined();
expect(data.createUser.id).toBeDefined();
});
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');
});
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');
});
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();
});
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();
});
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();
});
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();
});
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');
});
afterEach or afterAll hooksreferences/graphql-testing.md - Advanced GraphQL patternsreferences/schema-validation.md - JSON Schema with Ajvdevelopment
Setup secure web-based terminal access to WSL2 from mobile/tablet via ttyd + ngrok/Cloudflare/Tailscale. One-command install, start, stop, status. Use when you need remote terminal access, web terminal, browser-based shell, or mobile access to WSL2 environment.
development
Complete development workflows where Claude writes the code while Gemini and Codex provide research, planning, reviews, and different perspectives. Claude remains the main developer. Use for complex projects requiring expert planning and multi-perspective reviews.
development
Systematic progress tracking for skill development. Manages task states (pending/in_progress/completed), updates in real-time, reports progress, identifies blockers, and maintains momentum. Use when tracking skill development, coordinating work, or reporting progress.
testing
Comprehensive testing workflow orchestrating functional testing, example validation, integration testing, and usability assessment. Sequential workflow for complete skill testing from examples through scenarios to integration validation. Use when conducting thorough testing, pre-deployment validation, ensuring skill functionality, or comprehensive quality checks.