Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
advanced/network-advanced.md
1# Advanced Network Interception23## Table of Contents451. [Request Modification](#request-modification)62. [GraphQL Mocking](#graphql-mocking)73. [HAR Recording & Playback](#har-recording--playback)84. [Conditional Mocking](#conditional-mocking)95. [Network Throttling](#network-throttling)1011## Request Modification1213### Modify Request Headers1415```typescript16test("add auth header to requests", async ({ page }) => {17await page.route("**/api/**", (route) => {18const headers = {19...route.request().headers(),20Authorization: "Bearer test-token",21"X-Test-Header": "test-value",22};23route.continue({ headers });24});2526await page.goto("/dashboard");27});28```2930### Modify Request Body3132```typescript33test("modify POST body", async ({ page }) => {34await page.route("**/api/orders", async (route) => {35if (route.request().method() === "POST") {36const postData = route.request().postDataJSON();3738// Add test metadata39const modifiedData = {40...postData,41testMode: true,42testTimestamp: Date.now(),43};4445await route.continue({46postData: JSON.stringify(modifiedData),47});48} else {49await route.continue();50}51});5253await page.goto("/checkout");54await page.getByRole("button", { name: "Place Order" }).click();55});56```5758### Transform Response5960```typescript61test("modify API response", async ({ page }) => {62await page.route("**/api/products", async (route) => {63// Fetch real response64const response = await route.fetch();65const json = await response.json();6667// Modify response68const modified = json.map((product: any) => ({69...product,70price: product.price * 0.9, // 10% discount71testMode: true,72}));7374await route.fulfill({75response,76json: modified,77});78});7980await page.goto("/products");81});82```8384## GraphQL Mocking8586### Mock by Operation Name8788```typescript89test("mock GraphQL query", async ({ page }) => {90await page.route("**/graphql", async (route) => {91const postData = route.request().postDataJSON();9293if (postData.operationName === "GetUser") {94return route.fulfill({95json: {96data: {97user: {98id: "1",99name: "Test User",100email: "[email protected]",101},102},103},104});105}106107if (postData.operationName === "GetProducts") {108return route.fulfill({109json: {110data: {111products: [112{ id: "1", name: "Product A", price: 29.99 },113{ id: "2", name: "Product B", price: 49.99 },114],115},116},117});118}119120// Pass through unmocked operations121return route.continue();122});123124await page.goto("/dashboard");125});126```127128### GraphQL Mock Fixture129130```typescript131// fixtures/graphql.fixture.ts132type GraphQLMock = {133operation: string;134variables?: Record<string, any>;135response: { data?: any; errors?: any[] };136};137138type GraphQLFixtures = {139mockGraphQL: (mocks: GraphQLMock[]) => Promise<void>;140};141142export const test = base.extend<GraphQLFixtures>({143mockGraphQL: async ({ page }, use) => {144await use(async (mocks) => {145await page.route("**/graphql", async (route) => {146const postData = route.request().postDataJSON();147148const mock = mocks.find((m) => {149if (m.operation !== postData.operationName) return false;150151// Optionally match variables152if (m.variables) {153return (154JSON.stringify(m.variables) === JSON.stringify(postData.variables)155);156}157return true;158});159160if (mock) {161return route.fulfill({ json: mock.response });162}163164return route.continue();165});166});167},168});169170// Usage171test("dashboard with mocked GraphQL", async ({ page, mockGraphQL }) => {172await mockGraphQL([173{174operation: "GetDashboardStats",175response: {176data: { stats: { users: 100, revenue: 50000 } },177},178},179{180operation: "GetUser",181variables: { id: "1" },182response: {183data: { user: { id: "1", name: "John" } },184},185},186]);187188await page.goto("/dashboard");189await expect(page.getByText("100 users")).toBeVisible();190});191```192193### Mock GraphQL Mutations194195```typescript196test("mock GraphQL mutation", async ({ page }) => {197await page.route("**/graphql", async (route) => {198const postData = route.request().postDataJSON();199200if (postData.operationName === "CreateOrder") {201const { input } = postData.variables;202203return route.fulfill({204json: {205data: {206createOrder: {207id: "order-123",208status: "PENDING",209items: input.items,210total: input.items.reduce(211(sum: number, item: any) => sum + item.price * item.quantity,2120,213),214},215},216},217});218}219220return route.continue();221});222223await page.goto("/checkout");224await page.getByRole("button", { name: "Place Order" }).click();225226await expect(page.getByText("Order #order-123")).toBeVisible();227});228```229230## HAR Recording & Playback231232### Record HAR File233234```typescript235// Record network traffic236test("record HAR", async ({ page, context }) => {237// Start recording238await context.routeFromHAR("./recordings/checkout.har", {239update: true, // Create/update HAR file240url: "**/api/**",241});242243await page.goto("/checkout");244await page.getByRole("button", { name: "Place Order" }).click();245246// HAR file is saved automatically247});248```249250### Playback HAR File251252```typescript253// Use recorded HAR for offline testing254test("playback HAR", async ({ page, context }) => {255await context.routeFromHAR("./recordings/checkout.har", {256url: "**/api/**",257update: false, // Don't update, just playback258});259260await page.goto("/checkout");261262// All API calls served from HAR file263await expect(page.getByText("Order confirmed")).toBeVisible();264});265```266267### HAR with Fallback268269```typescript270test("HAR with live fallback", async ({ page, context }) => {271await context.routeFromHAR("./recordings/api.har", {272url: "**/api/**",273update: false,274notFound: "fallback", // Use real network if not in HAR275});276277await page.goto("/dashboard");278});279```280281## Conditional Mocking282283### Mock Based on Request Body284285```typescript286test("conditional mock by body", async ({ page }) => {287await page.route("**/api/search", async (route) => {288const body = route.request().postDataJSON();289290if (body.query === "error") {291return route.fulfill({292status: 500,293json: { error: "Search failed" },294});295}296297if (body.query === "empty") {298return route.fulfill({299json: { results: [] },300});301}302303// Default response304return route.fulfill({305json: {306results: [{ id: 1, title: `Result for: ${body.query}` }],307},308});309});310311await page.goto("/search");312313// Test different scenarios314await page.getByLabel("Search").fill("error");315await page.getByLabel("Search").press("Enter");316await expect(page.getByText("Search failed")).toBeVisible();317});318```319320### Mock Nth Request321322```typescript323test("different response on retry", async ({ page }) => {324let callCount = 0;325326await page.route("**/api/status", (route) => {327callCount++;328329if (callCount < 3) {330return route.fulfill({331status: 503,332json: { error: "Service unavailable" },333});334}335336// Succeed on 3rd attempt337return route.fulfill({338json: { status: "ok" },339});340});341342await page.goto("/dashboard");343344// App should retry and eventually succeed345await expect(page.getByText("Connected")).toBeVisible();346});347```348349### Mock with Delay350351```typescript352test("slow network simulation", async ({ page }) => {353await page.route("**/api/data", async (route) => {354// Simulate 2 second delay355await new Promise((resolve) => setTimeout(resolve, 2000));356357return route.fulfill({358json: { data: "loaded" },359});360});361362await page.goto("/dashboard");363364// Loading state should appear365await expect(page.getByText("Loading...")).toBeVisible();366367// Then data appears368await expect(page.getByText("loaded")).toBeVisible();369});370```371372## Network Throttling373374### Slow 3G Simulation375376```typescript377test("slow network experience", async ({ page, context }) => {378// Create CDP session for network throttling379const client = await context.newCDPSession(page);380381await client.send("Network.emulateNetworkConditions", {382offline: false,383downloadThroughput: (500 * 1024) / 8, // 500 Kbps384uploadThroughput: (500 * 1024) / 8,385latency: 400, // 400ms386});387388await page.goto("/");389390// Test loading states appear391await expect(page.getByTestId("skeleton-loader")).toBeVisible();392});393```394395### Offline Mode396397Use `context.setOffline(true/false)` to simulate network connectivity changes.398399> **For comprehensive offline testing patterns:**400>401> - **Network failure simulation** (error recovery, graceful degradation): See [error-testing.md](error-testing.md#offline-testing)402> - **Offline-first/PWA testing** (service workers, caching, background sync): See [service-workers.md](service-workers.md#offline-testing)403404### Network Throttling Fixture405406```typescript407// fixtures/network.fixture.ts408type NetworkCondition = "slow3g" | "fast3g" | "offline";409410const conditions = {411slow3g: { downloadThroughput: 50000, uploadThroughput: 50000, latency: 2000 },412fast3g: { downloadThroughput: 180000, uploadThroughput: 75000, latency: 150 },413};414415type NetworkFixtures = {416setNetworkCondition: (condition: NetworkCondition) => Promise<void>;417};418419export const test = base.extend<NetworkFixtures>({420setNetworkCondition: async ({ page, context }, use) => {421const client = await context.newCDPSession(page);422423await use(async (condition) => {424if (condition === "offline") {425await context.setOffline(true);426} else {427await client.send("Network.emulateNetworkConditions", {428offline: false,429...conditions[condition],430});431}432});433434// Reset435await context.setOffline(false);436},437});438```439440## Anti-Patterns to Avoid441442| Anti-Pattern | Problem | Solution |443| ------------------------ | ------------------------------ | -------------------------------- |444| Mocking all requests | Tests don't reflect reality | Mock only what's necessary |445| No cleanup of routes | Routes persist across tests | Use fixtures with cleanup |446| Ignoring request method | Mock applies to wrong requests | Check `route.request().method()` |447| Hardcoded mock responses | Brittle, hard to maintain | Use factories for mock data |448449## Related References450451- **Basic Mocking**: See [test-suite-structure.md](../core/test-suite-structure.md) for simple mocking452- **WebSockets**: See [websockets.md](../browser-apis/websockets.md) for real-time mocking453