← Back to list

Stop Hardcoding API Mocks: How to Modify Playwright Responses on the Fly

If you write automated UI tests, you are likely familiar with this frustrating scenario: You need to test a specific edge case in your…

Tejas Singh · 2026-02-22 07:59 · 2 claps · 4.4 min read
#playwrights #playwright-automation #rest-api #500-internal-server-error
Open on Medium ↗
Wiki topics: 💻 · Programming

Stop Hardcoding API Mocks: How to Modify Playwright Responses on the Fly

If you write automated UI tests, you are likely familiar with this frustrating scenario: You need to test a specific edge case in your application — like an “Out of Stock” state, a locked user account, or a rare server error.

To trigger that exact state in the UI, you need specific data from the backend. But how do you get it?

You usually face two bad options: mutate your actual database (which is slow and causes conflicts if others are testing), or hardcode a massive fake API response (which becomes a maintenance nightmare).

Thankfully, Playwright offers a surprisingly elegant third option: intercepting and modifying API responses on the fly.

Here is a deep dive into why traditional mocking is holding your tests back, and how Playwright’s route.fetch() can give you test realism and control at the same time.

The Core Problem: Why Traditional Mocking Fails

Let’s say you work at an e-commerce company. You need to write a test to ensure that when a product’s inventory reaches zero, the “Add to Cart” button correctly disables and says “Out of Stock.”

When the page loads, your browser makes a call to GET /api/products/123.

Traditionally, QA engineers and developers bypass the real database entirely by writing a full mock. They intercept the network request and inject a hardcoded JSON object:

// The Problematic Way: Hardcoding the entire response
await page.route('**/api/products/123', async (route) => {
  await route.fulfill({
    json: {
      id: 123,
      name: 'Wireless Headphones',
      price: 99.99,
      stock: 0,           // <-- The only thing we actually care about
      description: 'High quality noise-canceling headphones.'
    }
  });
});

What is the problem here? Test Drift.

Fast forward six months. The backend team updates the API. They add a new required field called "currency": "USD" and change the image data structure.

Your test still intercepts the call and injects the old, hardcoded response from six months ago. Because the frontend expects the new "currency" field but your test doesn't provide it, your UI crashes. You waste hours debugging what you think is a broken application, only to realize your test data is just outdated.

You mocked the whole world just to change one variable (stock: 0).

The Solution: Modifying Responses “On the Fly”

Instead of faking the entire response, what if you could let the real request go to the server, grab the real response on its way back, tweak only the stock value, and then hand it to the browser?

Playwright allows you to do exactly this. You act as a middleman.

// The Elegant Way: Intercept, Fetch, Modify, Fulfill
await page.route('**/api/products/123', async (route) => {

  // 1. Fetch the REAL response from the actual server
  const response = await route.fetch();
  let json = await response.json();

  // 2. Tweak ONLY the exact data we need for this test
  json.stock = 0; 

  // 3. Fulfill the route using the real response + our tiny edit
  await route.fulfill({
    response,   
    json        
  });
});

How This Works (Line-by-Line)

Let’s break down exactly what Playwright is doing behind the scenes:

  • **await page.route('**/api/products/123', ...)**
  • Playwright acts as a traffic controller. It tells the browser, “Whenever you try to call this API endpoint, pause. Do not send it directly to the server; let me handle it first.”
  • **const response = await route.fetch();**
  • Playwright takes that paused request and forwards it to the real backend server. The server processes it normally and sends back fresh, 100% accurate, up-to-date data.
  • **let json = await response.json();**
  • Playwright catches the server’s reply and parses the body into a readable JavaScript object (json).
  • **json.stock = 0;**
  • This is where the magic happens. We mutate only the specific variable we want to test. The product name, the price, and any newly added fields (like our "currency" example) remain exactly as the real server sent them.
  • **await route.fulfill({ response, json });**
  • Playwright hands the data back to the waiting web browser. By passing response, Playwright ensures the browser gets all the real HTTP headers and status codes (like 200 OK). By passing json, it swaps in our modified payload.

The web page receives the data, completely unaware it was tampered with. It sees stock: 0 and renders the "Out of Stock" UI perfectly.

Why You Should Adopt This Pattern

By adopting this “on the fly” modification strategy, you achieve three major benefits:

  • Unbreakable Tests: If the backend team completely restructures the API tomorrow, your test adapts automatically. It always fetches the real, current response first before applying your specific override.
  • High Realism: You are testing against real backend data and real headers, not an artificial environment you dreamed up in a test file.
  • Total Control: You can safely simulate dangerous edge cases (negative account balances, missing user roles) without ever risking the integrity of your real test database.

Stop mocking the entire API just to test a single edge case. Fetch the real data, tweak what you need, and keep your tests fast and deterministic.

Bonus: Simulating Server Errors and Outages

We just looked at modifying a successful response to test a specific UI state. But what happens when your backend completely fails?

If your API goes down and returns a 500 Internal Server Error, does your application crash, show a blank white screen, or gracefully display a "Something went wrong" message to the user?

You can use this exact same page.route mechanism to simulate partial outages without actually taking your staging server offline. In this case, we don't even need to fetch the real data first—we just instantly intercept and reject it.

// Simulating a complete API failure
await page.route('**/api/products/123', async (route) => {
  await route.fulfill({
    status: 500,
    contentType: 'application/json',
    body: JSON.stringify({ message: 'Database connection failed' })
  });
});

By adding this to your test suite, you can confidently verify your application’s error boundaries. You are ensuring that even on your server’s worst day, your users still get a polished, controlled experience.

Final Thoughts: The Best of Both Worlds

Testing is all about confidence.

When you hardcode massive API mocks, you lose confidence because your tests slowly drift away from reality. When you rely solely on manipulating a real staging database, you lose speed and introduce test flakiness.

By using Playwright’s route.fetch() combined with route.fulfill(), you unlock a hybrid approach. You get the realism of a live backend combined with the surgical control of a mocked environment.

The next time you find yourself copying and pasting a 500-line JSON payload into your test files just to change a single boolean value, stop. Let the real server do the heavy lifting, intercept the response, and modify it on the fly.

Your future self (and your backend team) will thank you.

Have you tried this dynamic mocking approach in your Playwright tests, or do you still prefer keeping all your mocks in static JSON files? Let me know in the comments below! (If you found this helpful, feel free to drop a 👏 and follow for more tips on automation, Playwright, and modern testing strategies.)


메타데이터
post_id
aa7b41f832f0
slug
stop-hardcoding-api-mocks-how-to-modify-playwright-responses-on-the-fly-aa7b41f832f0
url
https://medium.com/@imtejassingh/stop-hardcoding-api-mocks-how-to-modify-playwright-responses-on-the-fly-aa7b41f832f0
canonical_url
https://medium.com/@imtejassingh/stop-hardcoding-api-mocks-how-to-modify-playwright-responses-on-the-fly-aa7b41f832f0
author_url
https://medium.com/@imtejassingh
status
ok
fetched_at
2026-07-14 04:38:37