← All guides

Deterministic test data: reset your mock API between Playwright & Cypress tests

The classic end-to-end testing failure mode: test A creates a record, test B assumes it isn't there, and your suite passes or fails depending on run order. The usual fixes all hurt β€” truncate-and-reseed scripts against a real database (slow, needs DB access from CI), spinning up throwaway containers per run (slow, infra-heavy), or mocking every request in the browser with MSW (fast, but now your fixtures live in handler code and drift from reality).

If your frontend talks to a Mockbird project, there's a simpler shape: the mock API itself supports named snapshots of its entire data state. Save a baseline once; restore it in one HTTP call before each test (or each run). Every record in every resource comes back exactly as saved β€” same ids, same values, deleted resources rebuilt.

1. Create a project and save a baseline

One request creates a seeded multi-resource backend (no signup needed to try it):

curl -X POST https://mockbird.mockbird.workers.dev/api/projects \
  -H 'content-type: application/json' \
  -d '{"name": "e2e fixtures", "preset": "saas"}'

The response has your project id and adminKey. Arrange the data how your tests expect it β€” seed presets, import a db.json, or hand-edit records in the dashboard β€” then freeze it:

curl -X POST https://mockbird.mockbird.workers.dev/api/projects/<id>/snapshots \
  -H 'content-type: application/json' -H 'x-admin-key: KEY' \
  -d '{"name":"baseline"}'
# β†’ {"ok":true,"name":"baseline","resources":4,"records":111,...}

2. Restore it before every test

Restoring is a single POST β€” no body needed:

curl -X POST https://mockbird.mockbird.workers.dev/api/projects/<id>/snapshots/baseline/restore \
  -H 'x-admin-key: KEY'
# β†’ {"ok":true,"restored":"baseline","records":111,...}

Playwright β€” restore in beforeEach (or once per worker in a fixture if your tests don't write):

// tests/helpers.ts
export async function restoreBaseline() {
  const res = await fetch(
    `https://mockbird.mockbird.workers.dev/api/projects/${process.env.MOCK_PROJECT}/snapshots/baseline/restore`,
    { method: "POST", headers: { "x-admin-key": process.env.MOCK_ADMIN_KEY! } }
  );
  if (!res.ok) throw new Error(`restore failed: ${res.status}`);
}

// tests/todos.spec.ts
import { test, expect } from "@playwright/test";
import { restoreBaseline } from "./helpers";

test.beforeEach(async () => { await restoreBaseline(); });

test("deleting a todo removes it from the list", async ({ page }) => {
  await page.goto("/todos");
  // mutate freely β€” the next test starts from baseline anyway
});

Cypress β€” same idea with cy.request:

beforeEach(() => {
  cy.request({
    method: "POST",
    url: `https://mockbird.mockbird.workers.dev/api/projects/${Cypress.env("MOCK_PROJECT")}/snapshots/baseline/restore`,
    headers: { "x-admin-key": Cypress.env("MOCK_ADMIN_KEY") },
  });
});

Keep MOCK_ADMIN_KEY in a CI secret, not in the repo β€” it's the write key to the whole project. (Or sidestep the secret entirely: create an ephemeral project per CI run β€” the key is born inside the run, masked, and deleted with the project in an always() step.)

3. One snapshot per scenario

You get 10 named snapshots per project, so keep one per state your UI cares about:

SnapshotWhat it holdsTests that use it
baselinethe normal seeded datasetmost specs
emptyall resources, zero recordsempty states, onboarding, "create your first…" flows
edge-casesunicode names, 0-price products, very long stringsrendering & validation specs
bug-1234the exact dataset that reproduces a bug reportthe regression test for it
test.describe("empty states", () => {
  test.beforeEach(() => restoreSnapshot("empty"));
  // ...
});

To build an empty snapshot: wipe each resource with a zero-count reseed, save as empty, then restore baseline to get your data back:

# wipe (repeat per resource), snapshot, un-wipe
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/<id>/resources/todos \
  -H 'content-type: application/json' -H 'x-admin-key: KEY' -d '{"seed":0}'
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/<id>/snapshots \
  -H 'content-type: application/json' -H 'x-admin-key: KEY' -d '{"name":"empty"}'
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/<id>/snapshots/baseline/restore -H 'x-admin-key: KEY'

Saving with an existing name overwrites, so updating a fixture is just: arrange data β†’ save again.

4. Parallel workers: pin a snapshot per request, no restore at all

Restore has one weakness: it mutates shared state. If Playwright runs 4 workers against one project, worker A's restore("empty") yanks the data out from under worker B mid-spec. The usual fixes β€” one project per worker, or workers: 1 β€” cost you setup code or speed.

Mockbird has a third option: any GET (or GraphQL query) can be answered directly from a named snapshot, per-request, without touching live data. Send the X-Mockbird-Snapshot header (or ?mock_snapshot= where you can't set headers):

# live data
curl https://mockbird.mockbird.workers.dev/m/<id>/todos

# same URL, served from the "empty" snapshot β€” live data untouched
curl https://mockbird.mockbird.workers.dev/m/<id>/todos -H 'X-Mockbird-Snapshot: empty'

In Playwright, pin a scenario for a whole spec file by injecting the header at the browser level β€” your app code doesn't change:

test.use({ extraHTTPHeaders: { "X-Mockbird-Snapshot": "edge-cases" } });

test("renders unicode names without clipping", async ({ page }) => {
  await page.goto("/products");   // every API call now sees the edge-cases data
  // ...
});

Or per-request with page.route if only some calls should be pinned. Filters, sorting, pagination, _expand/_embed and nested routes all work against the snapshot's records. Snapshot mode is read-only β€” writes return 405 β€” so specs that mutate data still want the restore pattern from Β§2; but read-mostly UI specs can run fully parallel, one scenario per worker, on a single project with zero interference.

The same header trick fixes Vitest suites too β€” Vitest runs test files in parallel by default, so shared mutable fixtures race; pin each file to its own snapshot instead. Worked examples in the Vitest and Jest guides (Jest's parallel worker processes race the same way).

Playwright-first team? The full Playwright treatment β€” request-fixture specs, cross-host page.route rewrites, the route.fallback composition gotcha, and per-worker snapshot pinning β€” is in the Playwright guide (every snippet run verbatim).

Testing Python instead? The same pinning works as a pytest fixture β€” a requests.Session with the header set, parametrized per scenario. Full example (run before publishing) in the Python guide.

Try it in 5 seconds, no setup β€” the public demo ships with two built-in snapshots, empty and edge-cases (100-char product name, unicode + HTML-ish characters, price 0 and 1999999.99, empty description, out-of-stock one-star item, non-sequential id 9999):

curl "https://mockbird.mockbird.workers.dev/m/demo/products?mock_snapshot=empty"        # β†’ []
curl "https://mockbird.mockbird.workers.dev/m/demo/products?mock_snapshot=edge-cases"   # β†’ 7 deliberately nasty records
curl https://mockbird.mockbird.workers.dev/m/demo/products -H 'X-Mockbird-Snapshot: edge-cases'   # header variant

What restore guarantees

Compared to the alternatives

ApproachReset speedWorks in CI/preview deploysFixture lives in
DB truncate + reseed scriptseconds, needs DB accessonly with DB creds wired into CISQL/ORM scripts
Fresh containers per runtens of secondsneeds Docker in CImigration + seed code
MSW / request interceptioninstantyes (in-browser)handler code you maintain
Mockbird snapshotsone HTTP callyes β€” it's a hosted URLthe mock API itself, editable in a UI

Honest scope note: Mockbird mocks your backend, so this is for testing frontends (or API clients) against realistic data β€” it doesn't reset state inside your real backend. Restores share the management API; mock traffic from your tests counts against the project's 10,000 requests/day cap, which is roomy for a CI suite but worth knowing. Everything is free while Mockbird is in beta.

Snapshots pair well with simulated latency and error responses β€” deterministic data and deterministic failure modes, from one URL. The same snapshot-per-episode loop also makes a clean eval sandbox for AI agents. Create a project β†’

⚑ Skip the terminal: this link creates a live, seeded e-commerce backend (products, orders, customers, reviews) in the dashboard β€” real URL, data browser already open, no signup. Or import your own OpenAPI spec, db.json, CSV, Postman collection, or HAR and mock your exact shapes.