Using Fake JSON Data in Cypress and Playwright E2E Tests
June 11, 2026
End-to-end tests that rely on a real database are brittle — they break when data changes, run slowly, and can't be parallelised safely. The better approach: intercept API calls and return controlled fake JSON data. This guide shows exactly how to do that in both Cypress and Playwright.
Why Intercept Instead of Using a Real Database?
- Speed — no database queries, no network round trips. Tests run 3–10x faster.
- Reliability — your test data never changes unexpectedly between runs.
- Isolation — parallel test runs can't step on each other's data.
- Edge cases — you control exactly what the API returns, including error states, empty states, and unusual data combinations.
Cypress: cy.intercept() with Fake JSON
Basic Intercept
Generate your test data with Dummy JSON Generator and save it as a fixture file in cypress/fixtures/.
// cypress/fixtures/users.json ← your generated file
[
{ "id": 1, "fullName": "Ayesha Rahman", "email": "ayesha@example.com", "status": "active" },
{ "id": 2, "fullName": "James O'Brien", "email": "james@example.com", "status": "inactive" }
]// cypress/e2e/users.cy.ts
describe('Users page', () => {
beforeEach(() => {
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
});
it('displays user list', () => {
cy.visit('/users');
cy.wait('@getUsers');
cy.get('[data-testid="user-row"]').should('have.length', 2);
});
it('shows user emails', () => {
cy.visit('/users');
cy.wait('@getUsers');
cy.contains('ayesha@example.com').should('be.visible');
});
});Testing Empty States
it('shows empty state when no users exist', () => {
cy.intercept('GET', '/api/users', { body: [] }).as('getEmptyUsers');
cy.visit('/users');
cy.wait('@getEmptyUsers');
cy.get('[data-testid="empty-state"]').should('be.visible');
cy.contains('No users found').should('be.visible');
});Testing Loading and Error States
it('shows error state on API failure', () => {
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { error: 'Internal server error' }
}).as('failedUsers');
cy.visit('/users');
cy.wait('@failedUsers');
cy.get('[data-testid="error-message"]').should('be.visible');
});
it('shows loading spinner while fetching', () => {
cy.intercept('GET', '/api/users', (req) => {
req.reply((res) => {
res.setDelay(1000); // 1 second delay
res.send({ fixture: 'users.json' });
});
});
cy.visit('/users');
cy.get('[data-testid="loading-spinner"]').should('be.visible');
});Intercepting Dynamic Route Params
cy.intercept('GET', '/api/users/*', { fixture: 'user-single.json' }).as('getUser');
cy.intercept('GET', '/api/users/*/orders', { fixture: 'orders.json' }).as('getUserOrders');Playwright: route.fulfill() with Fake JSON
Basic Route Interception
// tests/fixtures/users.json ← your generated file
// tests/users.spec.ts
import { test, expect } from '@playwright/test';
import users from './fixtures/users.json';
test.describe('Users page', () => {
test.beforeEach(async ({ page }) => {
await page.route('**/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(users),
});
});
});
test('displays user list', async ({ page }) => {
await page.goto('/users');
const rows = page.locator('[data-testid="user-row"]');
await expect(rows).toHaveCount(users.length);
});
test('shows user email', async ({ page }) => {
await page.goto('/users');
await expect(page.getByText('ayesha@example.com')).toBeVisible();
});
});Testing Error States in Playwright
test('shows error message on 500', async ({ page }) => {
await page.route('**/api/users', route => route.fulfill({
status: 500,
body: JSON.stringify({ message: 'Server error' }),
}));
await page.goto('/users');
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
});
test('shows empty state', async ({ page }) => {
await page.route('**/api/users', route => route.fulfill({
status: 200,
body: JSON.stringify([]),
}));
await page.goto('/users');
await expect(page.getByText('No users found')).toBeVisible();
});Reusable Fixtures in Playwright
// tests/fixtures/index.ts
import { test as base } from '@playwright/test';
import users from './users.json';
import products from './products.json';
type Fixtures = {
mockUsers: typeof users;
mockProducts: typeof products;
};
export const test = base.extend<Fixtures>({
mockUsers: async ({ page }, use) => {
await page.route('**/api/users', route =>
route.fulfill({ body: JSON.stringify(users) })
);
await use(users);
},
mockProducts: async ({ page }, use) => {
await page.route('**/api/products', route =>
route.fulfill({ body: JSON.stringify(products) })
);
await use(products);
},
});
// Usage in tests:
// import { test } from './fixtures';
// test('shows users', async ({ page, mockUsers }) => { ... });Recommended Fixture Folder Structure
cypress/fixtures/ (Cypress)
├── users.json ← 50 users
├── user-single.json ← 1 user
├── users-empty.json ← []
├── products.json ← 20 products
└── orders.json ← 30 orders
tests/fixtures/ (Playwright)
├── users.json
├── products.json
└── orders.jsonGenerate each fixture file with Dummy JSON Generator — configure your schema once, then download at different record counts for different test scenarios.
Cypress vs Playwright: Which to Use?
| Feature | Cypress | Playwright |
|---|---|---|
| API mocking syntax | cy.intercept() | page.route() |
| Fixture files | Built-in cy.fixture() | Manual JSON import |
| Multi-browser | Chrome, Firefox, Edge | Chrome, Firefox, Safari, Edge |
| Parallel tests | Paid tier | Free, built-in |
Both work excellently for fake-data-driven E2E tests. Choose based on your team's existing tooling — the interception patterns are similar enough that switching later isn't painful.