Skip to main content

API Schema Testing with Zod

Add Zod and Playwright to your project

pnpm create playwright
pnpm add zod

Add custom expect handler to Playwright

playwright.config.ts

expect.extend({
async toMatchSchema(json: any, schema: ZodTypeAny) {
const result: any = await schema.safeParseAsync(json);
if (result.success) {
return {
message: () => 'Result matched schema',
pass: true,
};
} else {
return {
message: () =>
'Result does not match schema: ' +
result.error.issues.map((issue: any) => issue.message).join('\n') +
'\n' +
'Details: ' +
JSON.stringify(result.error, null, 2),
pass: false,
};
}
},
});

Add Typescript Typing for Playwright

global.d.ts
import { ZodTypeAny } from 'zod';

declare global {
namespace PlaywrightTest {
interface Matchers<R, T> {
toMatchSchema(schema: ZodTypeAny): Promise<R>;
}
}
}

Example Usage

user.spec.ts
import {z} from 'zod';
import {test, expect} from '@playwright/test';

const userSchema = z.object({
page: z.number().min(1),
per_page: z.literal(6),
total: z.number().min(1),
total_pages: z.number().min(1),
data: z.array(
z.object({
id: z.number(),
email: z.string(),
first_name: z.string(),
last_name: z.string(),
avatar: z.string().url(),
}),
)
});

const mockUsersPass = {
"page": 1,
"per_page": 6,
"total": 12,
"total_pages": 2,
"data": []
}

test('GET Users - Pass schema validation', async ({request}) => {
const responseJson = mockUsersPass;
await expect(responseJson).toMatchSchema(userSchema);
});


const mockUsersFail = {
"page": '1', // Incorrect Type
"per_page": 6,
"total": 12,
"total_pages": 2,
"data": [{"id": "hello"}] // Invalid Fields
}

test('GET Users - Fail schema validation', async ({request}) => {
const responseJson = mockUsersFail;
await expect(responseJson).toMatchSchema(userSchema);
});

Test Execution

playwright test

Test Results

Playwright Test Output

Playwright Test Detail