Network Recording & Patching

How Meticulous records network traffic, stubs it during replay, and keeps sessions useful as your frontend and APIs evolve.


The short version

Meticulous tests your frontend, not your backend.

When a session is recorded, we store the user events and the network request/response pairs from that walkthrough. On each PR we replay the sessions that exercise your changes against your new frontend build with no live backend: each request the browser makes is intercepted and answered from the recording.

That raises an obvious question: what happens when APIs change, or when the "same" GET returns different data depending on earlier mutations? This page explains how that works - and why getting every stub perfectly right is not what makes Meticulous valuable.


What a recording actually contains

A session is not a set of screenshots. It is roughly:

  1. User events - clicks, keystrokes, scrolls, and so on
  2. Browser state - cookies, local/session storage, viewport, and related metadata
  3. Network traffic - every XHR/fetch (and related) request and response body from that session

Screenshots are taken later, at replay time, against whatever frontend build you give us.

So the original recording is a frozen walkthrough: at this point we clicked X; the app then issued these requests and got these responses.


What happens on a PR (no backend required)

When CI sends us your frontend assets (or a container / preview URL):

  1. We spin up your app in our deterministic browser
  2. We evaluate the selected sessions and replay the flows that exercise your PR's code changes - skipping flows that don't reach the diff, or that only repeat coverage another flow already provides
  3. When the app makes a network call, we stub it from that session's recorded traffic
  4. We take screenshots whenever the UI changes, on both the base and head commits
  5. We show you every visual / behavioral difference

Because network timing and response bodies are controlled, before/after screenshots line up and flake rates stay extremely low.


Mutation-then-GET flows

A common pattern in complex apps:

A user creates a record, fills fields, submits (mutations), then hits a GET for "latest copy of this data." The GET URL looks the same every time, but the response depends on what happened earlier in the flow.

Within a single recorded session, this works cleanly.

That session's network recording contains:

  • the create/update/submit calls and their responses, in order
  • the later GET and the specific response that followed those mutations

On replay we do not re-hit a live backend to recreate state. We stub the whole chain. Sequence matters: the Nth matching GET in the recording is returned for the Nth matching GET at replay time. The mutations don't need to "really" mutate anything - their recorded responses are what put the frontend into the right state for the rest of the flow.

So for "someone walked through this setup once while developing": that exact walkthrough, with that exact data shape and UI state, stays available to re-test on PRs that exercise that path for as long as that session stays in the golden set.

You do not need a developer to manually re-run hundreds of permutations later. You need the recorder to have seen each distinct UI path once. Session selection keeps the ones that still contribute unique coverage.


How we tell similar requests apart

There are two different matching problems:

Inside one session (replay stubbing)

Matching is sequence-aware. Two GETs to the same endpoint with the same shape are not collapsed into one response - we consume recorded entries in order. That preserves mutation → GET causality inside a walkthrough.

We also normalize things that change between environments (preview hostname vs recorded hostname, dynamic path IDs like /applications/123/applications/{id}, GraphQL operation name + field selection, and so on) so the same logical call still matches.

Across sessions (keeping old sessions alive when APIs drift)

Separately, we maintain a project-wide pool of recent request fingerprints → responses from newer recordings.

A fingerprint is keyed on things like:

  • HTTP method
  • Normalized path (dynamic segments generalized)
  • For GraphQL: operation name(s), variable names, selected fields (not variable values)
  • For other JSON POSTs: top-level body keys (not values)

Values are intentionally ignored in the fingerprint. The point of cross-session matching is "same endpoint / same schema shape," not "same invoice ID." That is a deliberate tradeoff - see Why stubs don't need to be perfect.

When a PR's frontend starts requesting a slightly different shape (new GraphQL field, renamed key, new endpoint), the old session's recorded responses can become stale. We then:

  1. Detect network mismatch / divergence on the original replay
  2. Look up a newer donor response with the same fingerprint
  3. Prefer schema-level patching: update the response shape while keeping the original session's primitive values where possible
  4. Re-run the affected sessions with the patched recording
  5. Only keep the patched result if it is actually better (fewer console errors / cleaner screenshots)

If a session becomes fully obsolete and no longer adds unique code coverage, session selection replaces it with a newer recording that does.

If there is no good donor yet, we have fallbacks. In practice, for an active engineering org, new developer and user sessions continuously refill the pool - especially right after an API change, when people are testing the new frontend against the new backend.


Coverage for flows nobody has touched in months

Meticulous is not "continuously re-running only the tests someone thought to write this sprint."

The model is:

  1. Someone goes through a flow once with the recorder on - including obscure settings pages and edge states
  2. That session is mapped to the lines of code it executed
  3. If those lines aren't covered better by another session, it stays in the golden set
  4. PRs that touch those lines re-execute it against the new frontend, with its recorded network traffic (patched over time as schemas evolve)

So coverage of "tests we didn't think about" comes from having seen the UI once, not from someone remembering to maintain a Playwright or Cypress case for it.

What we are not claiming: that we magically invent backend states nobody has ever produced. If a UI state has never been reached in a recorded environment, we can't replay it. In practice, large products accumulate a lot of those states quickly (dev, staging, internal dogfood), and selection keeps the rare ones.


Why stubs don't need to be perfect

What you are optimizing for

Meticulous answers: "If I merge this PR, what will change in the UI - including pages and states I didn't think to check?"

It does not answer: "Is the backend returning the correct business data for record #48291?" Backend correctness stays with your API and contract tests.

Holding network data "close enough" is enough to isolate frontend regressions: layout, components, client-side logic, broken conditionals, wrong empty states, permission-denied UI, and so on.

Analogy

Playwright tests with fixtures or MSW mocks also don't use live production data for every case. You still catch UI bugs. Meticulous is the same idea, except the fixtures are harvested automatically from real sessions and refreshed automatically when schemas drift.

Why imperfect stubs rarely hide the bugs that matter

SituationWhat happens
Frontend CSS/component changeScreenshots differ even if the API payload is slightly off
Frontend logic change (error path, disabled button, wrong branch)Behavior/screenshots differ under the recorded responses
Stub is badly wrongOften shows as console errors, blank/error UI, or network divergence indicators - not a silent green
Stub is slightly wrong but unused fieldsNo visual diff - fine; those fields weren't part of the UI under test
Schema drift from a real API changePatching + dual-run merge prefers the result that actually renders cleanly

A bad stub that makes a page explode is visible. A perfect stub of an invoice amount you never render does not help catch a broken "Create" button.

Breadth beats perfect fidelity for this class of bug

The common pain is not the tests teams already think about - it's the cases they don't realize they're affecting.

That problem is solved by replaying many real UI paths automatically, not by guaranteeing that every stubbed GET returns the exact same row as production would today. One slightly imperfect recording of an obscure settings page that nobody wrote an E2E for is more valuable than a perfect mock of a flow you already test manually.

Backend / data-correctness gaps are real - they are just out of scope for a frontend visual/behavioral regression system, the same way a UI E2E against an ephemeral environment doesn't replace unit tests for interest-calculation logic.


How this fits with the rest of your tests

LayerJob
Unit / integration / API / contract testsLogic, services, and backend correctness
MeticulousExhaustive frontend blast-radius on every PR, without spinning backends, without writing or maintaining UI tests

Meticulous covers the UI regression and blast-radius problem that hand-written E2Es are usually meant to solve: catching broken screens, flows, and states across the app on every PR. Because we only need the frontend build, you also avoid the cost of spinning every dependent service for that verification.


Concrete lifecycle example

  1. Month 0 - An engineer walks through "create record → fill → submit → view status" on staging. The recorder captures events and all network pairs.
  2. Month 0 - Session selection puts it in the golden set (it covers unique UI code).
  3. Month 3 - A PR renames a GraphQL field the status page queries. The original recording is stale → network divergence → we patch the response shape from a newer recording of that operation → re-run → the merged result shows the real UI impact of the rename (or a clean bill of health).
  4. Month 8 - A newer session covers the same lines more efficiently; the old one ages out. Coverage continues; stubs are fresher.

No one had to rewrite a test. No one had to re-walk hundreds of permutations. The original walkthrough kept protecting that UI until something better replaced it.


What is and isn't stubbed

Stubbed by Meticulous:

  • XHR (XMLHttpRequest) requests
  • Fetch API requests
  • WebSocket connections
  • Local storage, session storage, and cookies

Not stubbed by Meticulous:

  • Static assets (CSS, JavaScript, images) loaded directly by the browser via HTML tags
  • Assets referenced with absolute URLs in your HTML (for example, <script src="https://example.com/app.js">)

Static assets are loaded live from whatever URL they're referenced at. Prefer relative URLs (for example, /dist/app.js) so assets load correctly across test environments.

If you wish to test backend code with Meticulous, you can choose which subset of requests to stub in the Network Stubbing tab in your project settings. For Next.js App Router apps, the default is to stub all requests apart from server-component and static-asset requests.


Limitations

  • We test frontend rendering and client behavior under recorded (and patched) API traffic - not live backend correctness.
  • Cross-session donors can come from a different user/app state with the same request shape. Heuristics and conservative merge reduce damage; they don't make it impossible.
  • A UI state that has never been recorded cannot be replayed.
  • For hard cases we have additional repair fallbacks; the best way to evaluate quality for your app is to run Meticulous on real PRs.

Summary

ConcernAnswer
How do mutation → GET flows work?Whole chain is recorded and sequence-stubbed inside that session. No live backend needed to recreate state.
How do stubs stay fresh?Project-wide response pool + schema-preserving patches + golden-set replacement of stale sessions.
How do identical-looking requests differ?Inside a session: order. Across sessions: fingerprint is shape (path/op/fields), not values.
Coverage for forgotten flows?Record once → stays selected while it adds unique coverage → replayed when a PR touches that path.
Must stubs be perfect?No. Signal is UI blast radius. Bad stubs tend to surface loudly; perfect backend data is a different testing layer.