Core Route Interception Setup #
Playwright intercepts outbound requests via page.route(). The matcher accepts strings, globs, or regex. Always register routes before navigation to prevent race conditions. Use route.fulfill() for static JSON or route.continue() for unmatched traffic. Proper timing guarantees seamless test lifecycle integration.
await page.route('**/api/v1/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Test User' }])
});
});
Intercepts exact endpoint and returns deterministic JSON payload.
Dynamic Payload Generation & Contract Validation #
Static mocks degrade quickly as backend contracts evolve. Use callback functions to inspect route.request() and generate context-aware responses. Validate outgoing payloads against OpenAPI schemas before returning stubbed data. This prevents false-positive passes and decouples UI assertions from volatile backends.
await page.route(/\/api\/v1\/orders\/.*/, async route => {
const request = route.request();
const orderId = request.url().split('/').pop();
await route.fulfill({
status: 200,
body: JSON.stringify({ id: orderId, status: 'fulfilled' })
});
});
Extracts path parameters to generate context-aware mock responses.
Handling CORS & Pre-flight OPTIONS Requests #
Browsers automatically send preflight requests for cross-origin calls. Playwright’s router must explicitly handle OPTIONS methods or bypass them entirely. Return appropriate Access-Control-Allow-* headers in mock responses to prevent browser-level network failures. Ignoring preflights causes silent route bypasses in Chromium runners.
await page.route('**/api/**', async route => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, body: '{}' });
} else {
await route.continue();
}
});
Mocks read operations while preserving write operations for integration testing.
Troubleshooting Route Registration Timing #
If mocks fail to trigger, verify page.route() executes before page.goto() or page.click(). Use page.waitForResponse() to assert interception synchronously. For flaky matches, replace glob patterns with regex containing explicit URL parameter boundaries. Advanced orchestration requires strict lifecycle management.
Common Pitfalls #
- Registering routes after page navigation causes missed interceptions and test flakiness.
- Using overly broad globs (
**/api/**) masks unrelated network failures and inflates false-positive pass rates. - Ignoring
OPTIONSpreflight requests triggers CORS errors in Chromium-based browsers. - Hardcoding timestamps or UUIDs in mock payloads breaks assertion logic for dynamic fields.
FAQ #
Why do my Playwright API mocks fail intermittently in CI?
Intermittent failures usually stem from race conditions between route registration and network dispatch. Always call page.route() before triggering navigation or user interactions, and use page.waitForResponse() to synchronize assertions.
Can I mock GraphQL queries using page.route()?
Yes. GraphQL uses a single POST endpoint. Intercept the URL and inspect route.request().postData() to match the operationName or query string, then return a structured JSON response.
How do I verify that the mock was actually triggered?
Use Playwright’s built-in page.waitForResponse() with a predicate function that checks response.url() and response.status(). This guarantees the interception occurred before proceeding with DOM assertions.
Reliability Metrics #
| Metric | Impact |
|---|---|
| Flakiness Reduction | 60-85% reduction in network-dependent test failures |
| Execution Time | 40-70% faster suite execution by eliminating external latency |
| Maintenance Overhead | Low to Medium (requires periodic sync with OpenAPI contracts) |
| CI Stability Score | High (deterministic payloads eliminate rate-limit and timeout variables) |