From playwright-api
Assertion patterns for Playwright API tests. Covers status code checks, JSON body validation, response time thresholds, header verification, pagination shape assertions, and JSON schema validation using inline schemas or ajv.
How this skill is triggered — by the user, by Claude, or both
Slash command
/playwright-api:api-assertionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Always assert the HTTP status code first before accessing the response body. Use exact numeric codes rather than range checks:
Always assert the HTTP status code first before accessing the response body. Use exact numeric codes rather than range checks:
// Single exact status
expect(response.status()).toBe(200);
expect(response.status()).toBe(201);
expect(response.status()).toBe(204);
expect(response.status()).toBe(400);
expect(response.status()).toBe(401);
expect(response.status()).toBe(403);
expect(response.status()).toBe(404);
expect(response.status()).toBe(422);
For responses that may return multiple acceptable codes, use a conditional check:
const status = response.status();
expect([200, 204]).toContain(status);
Use Playwright's built-in ok() helper when only a 2xx check is needed:
expect(response.ok()).toBe(true);
toMatchObjectUse toMatchObject when you want to assert a subset of the response body without failing on extra keys:
const body = await response.json();
expect(body).toMatchObject({
id: expect.any(Number),
name: 'Alice',
email: '[email protected]',
});
expect(body).toHaveProperty('id');
expect(body).toHaveProperty('createdAt');
expect(body.data).toBeDefined();
expect(typeof body.id).toBe('number');
expect(typeof body.name).toBe('string');
expect(Array.isArray(body.items)).toBe(true);
expect(body).toMatchObject({
user: {
id: expect.any(Number),
address: {
country: 'US',
},
},
});
// Non-empty array
expect(body.data.length).toBeGreaterThan(0);
// Array of objects with required shape
for (const item of body.data) {
expect(item).toHaveProperty('id');
expect(item).toHaveProperty('name');
}
// Array contains a specific item
expect(body.data).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: 1, name: 'Alice' }),
])
);
Measure response time for performance-sensitive endpoints by capturing the start and end timestamps around the request:
test('should respond within acceptable time', async ({ apiClient }) => {
const start = Date.now();
const response = await apiClient.get('/products');
const duration = Date.now() - start;
expect(response.status()).toBe(200);
expect(duration).toBeLessThan(2000); // 2 s SLA
});
Apply tighter thresholds for read-only cached endpoints and looser thresholds for write operations:
| Endpoint type | Recommended threshold |
|---|---|
| Cached list / read | < 500 ms |
| Uncached list / read | < 1000 ms |
| Create / update | < 2000 ms |
| Heavy aggregate / report | < 5000 ms |
const contentType = response.headers()['content-type'];
expect(contentType).toContain('application/json');
// Cache-control on public endpoints
const cacheControl = response.headers()['cache-control'];
expect(cacheControl).toContain('max-age=');
// CORS headers
expect(response.headers()['access-control-allow-origin']).toBeDefined();
// Pagination cursor in response headers
expect(response.headers()['x-next-cursor']).toBeDefined();
Assert the standard pagination envelope rather than hardcoding counts:
const body = await response.json();
// Offset-based pagination
expect(body).toMatchObject({
data: expect.any(Array),
total: expect.any(Number),
page: expect.any(Number),
perPage: expect.any(Number),
});
expect(body.data.length).toBeLessThanOrEqual(body.perPage);
// Cursor-based pagination
expect(body).toMatchObject({
data: expect.any(Array),
nextCursor: expect.anything(),
});
For simple shapes, validate structure directly using toMatchObject with expect.any:
const body = await response.json();
expect(body).toMatchObject({
id: expect.any(Number),
name: expect.any(String),
email: expect.any(String),
createdAt: expect.any(String),
});
When a formal JSON Schema is available in tests/api/schemas/, import and compile it with ajv:
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import userSchema from '../../schemas/user.schema.json';
const ajv = new Ajv();
addFormats(ajv);
const validate = ajv.compile(userSchema);
test('should return a valid user schema', async ({ apiClient }) => {
const response = await apiClient.get('/users/1');
expect(response.status()).toBe(200);
const body = await response.json();
const valid = validate(body);
if (!valid) {
throw new Error(`Schema validation failed: ${JSON.stringify(validate.errors, null, 2)}`);
}
});
import Ajv from 'ajv';
const ajv = new Ajv();
const userSchema = {
type: 'object',
required: ['id', 'name', 'email'],
properties: {
id: { type: 'integer' },
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' },
createdAt: { type: 'string', format: 'date-time' },
},
additionalProperties: false,
};
const validate = ajv.compile(userSchema);
test('should match user schema', async ({ apiClient }) => {
const response = await apiClient.get('/users/1');
expect(response.status()).toBe(200);
const body = await response.json();
expect(validate(body)).toBe(true);
});
Assert the shape of error responses consistently:
test('should return 422 with validation errors', async ({ apiClient }) => {
const response = await apiClient.post('/users', {
data: { name: '' }, // missing required email
});
expect(response.status()).toBe(422);
const body = await response.json();
expect(body).toMatchObject({
message: expect.any(String),
errors: expect.arrayContaining([
expect.objectContaining({ field: 'email' }),
]),
});
});
Guides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
npx claudepluginhub gagandeepp/software-agent-teams --plugin playwright-api