# Intercept Network Call Utility ## Principle Intercept network requests with a single declarative call that returns a Promise. Automatically parse JSON responses, support both spy (observe) and stub (mock) patterns, and use powerful glob pattern matching for URL filtering. ## Rationale Vanilla Playwright's network interception requires multiple steps: - `page.route()` to setup, `page.waitForResponse()` to capture - Manual JSON parsing - Verbose syntax for conditional handling - Complex filter predicates The `interceptNetworkCall` utility provides: - **Single declarative call**: Setup and wait in one statement - **Automatic JSON parsing**: Response pre-parsed, strongly typed - **Flexible URL patterns**: Glob matching with picomatch - **Spy or stub modes**: Observe real traffic or mock responses - **Concise API**: Reduces boilerplate by 60-70% ## Pattern Examples ### Example 1: Spy on Network (Observe Real Traffic) **Context**: Capture and inspect real API responses for validation. **Implementation**: ```typescript import { test } from '@seontechnologies/playwright-utils/intercept-network-call/fixtures'; test('should spy on users API', async ({ page, interceptNetworkCall }) => { // Setup interception BEFORE navigation const usersCall = interceptNetworkCall({ url: '**/api/users', // Glob pattern }); await page.goto('/dashboard'); // Wait for response and access parsed data const { responseJson, status } = await usersCall; expect(status).toBe(200); expect(responseJson).toHaveLength(10); expect(responseJson[0]).toHaveProperty('name'); }); ``` **Key Points**: - Intercept before navigation (critical for race-free tests) - Returns Promise with `{ responseJson, status, requestBody }` - Glob patterns (`**` matches any path segment) - JSON automatically parsed ### Example 2: Stub Network (Mock Response) **Context**: Mock API responses for testing UI behavior without backend. **Implementation**: ```typescript test('should stub users API', async ({ page, interceptNetworkCall }) => { const mockUsers = [ { id: 1, name: 'Test User 1' }, { id: 2, name: 'Test User 2' }, ]; const usersCall = interceptNetworkCall({ url: '**/api/users', fulfillResponse: { status: 200, body: mockUsers, }, }); await page.goto('/dashboard'); await usersCall; // UI shows mocked data await expect(page.getByText('Test User 1')).toBeVisible(); await expect(page.getByText('Test User 2')).toBeVisible(); }); ``` **Key Points**: - `fulfillResponse` mocks the API - No backend needed - Test UI logic in isolation - Status code and body fully controllable ### Example 3: Conditional Response Handling **Context**: Different responses based on request method or parameters. **Implementation**: ```typescript test('conditional mocking', async ({ page, interceptNetworkCall }) => { await interceptNetworkCall({ url: '**/api/data', handler: async (route, request) => { if (request.method() === 'POST') { // Mock POST success await route.fulfill({ status: 201, body: JSON.stringify({ id: 'new-id', success: true }), }); } else if (request.method() === 'GET') { // Mock GET with data await route.fulfill({ status: 200, body: JSON.stringify([{ id: 1, name: 'Item' }]), }); } else { // Let other methods through await route.continue(); } }, }); await page.goto('/data-page'); }); ``` **Key Points**: - `handler` function for complex logic - Access full `route` and `request` objects - Can mock, continue, or abort - Flexible for advanced scenarios ### Example 4: Error Simulation **Context**: Testing error handling in UI when API fails. **Implementation**: ```typescript test('should handle API errors gracefully', async ({ page, interceptNetworkCall }) => { // Simulate 500 error const errorCall = interceptNetworkCall({ url: '**/api/users', fulfillResponse: { status: 500, body: { error: 'Internal Server Error' }, }, }); await page.goto('/dashboard'); await errorCall; // Verify UI shows error state await expect(page.getByText('Failed to load users')).toBeVisible(); await expect(page.getByTestId('retry-button')).toBeVisible(); }); // Simulate network timeout test('should handle timeout', async ({ page, interceptNetworkCall }) => { await interceptNetworkCall({ url: '**/api/slow', handler: async (route) => { // Never respond - simulates timeout await new Promise(() => {}); }, }); await page.goto('/slow-page'); // UI should show timeout error await expect(page.getByText('Request timed out')).toBeVisible({ timeout: 10000 }); }); ``` **Key Points**: - Mock error statuses (4xx, 5xx) - Test timeout scenarios - Validate error UI states - No real failures needed ### Example 5: Multiple Intercepts (Order Matters!) **Context**: Intercepting different endpoints in same test - setup order is critical. **Implementation**: ```typescript test('multiple intercepts', async ({ page, interceptNetworkCall }) => { // ✅ CORRECT: Setup all intercepts BEFORE navigation const usersCall = interceptNetworkCall({ url: '**/api/users' }); const productsCall = interceptNetworkCall({ url: '**/api/products' }); const ordersCall = interceptNetworkCall({ url: '**/api/orders' }); // THEN navigate await page.goto('/dashboard'); // Wait for all (or specific ones) const [users, products] = await Promise.all([usersCall, productsCall]); expect(users.responseJson).toHaveLength(10); expect(products.responseJson).toHaveLength(50); }); ``` **Key Points**: - Setup all intercepts before triggering actions - Use `Promise.all()` to wait for multiple calls - Order: intercept → navigate → await - Prevents race conditions ## URL Pattern Matching **Supported glob patterns:** ```typescript '**/api/users'; // Any path ending with /api/users '/api/users'; // Exact match '**/users/*'; // Any users sub-path '**/api/{users,products}'; // Either users or products '**/api/users?id=*'; // With query params ``` **Uses picomatch library** - same pattern syntax as Playwright's `page.route()` but cleaner API. ## Comparison with Vanilla Playwright | Vanilla Playwright | intercept-network-call | | ----------------------------------------------------------- | ------------------------------------------------------------ | | `await page.route('/api/users', route => route.continue())` | `const call = interceptNetworkCall({ url: '**/api/users' })` | | `const resp = await page.waitForResponse('/api/users')` | (Combined in single statement) | | `const json = await resp.json()` | `const { responseJson } = await call` | | `const status = resp.status()` | `const { status } = await call` | | Complex filter predicates | Simple glob patterns | **Reduction:** ~5-7 lines → ~2-3 lines per interception ## Related Fragments - `network-first.md` - Core pattern: intercept before navigate - `network-recorder.md` - HAR-based offline testing - `overview.md` - Fixture composition basics ## Anti-Patterns **❌ Intercepting after navigation:** ```typescript await page.goto('/dashboard'); // Navigation starts const usersCall = interceptNetworkCall({ url: '**/api/users' }); // Too late! ``` **✅ Intercept before navigate:** ```typescript const usersCall = interceptNetworkCall({ url: '**/api/users' }); // First await page.goto('/dashboard'); // Then navigate const { responseJson } = await usersCall; // Then await ``` **❌ Ignoring the returned Promise:** ```typescript interceptNetworkCall({ url: '**/api/users' }); // Not awaited! await page.goto('/dashboard'); // No deterministic wait - race condition ``` **✅ Always await the intercept:** ```typescript const usersCall = interceptNetworkCall({ url: '**/api/users' }); await page.goto('/dashboard'); await usersCall; // Deterministic wait ```