Test data management
Manage test data, fixtures, and environments for reliable Maestro flows across local development and CI.
Most "flaky test" stories are actually "shared test data" stories. Two flows mutate the same row, two CI jobs spawn the same user, a developer's local seed drifts from staging — the flow looks broken but the test data is. This guide is the playbook for keeping test data sane.
TL;DR
Generate unique data per run, never reuse. Reset state at the suite boundary, not between flows. Keep credentials in environment variables, not in YAML. Use env in flow config to pass values, and a .env.test file at the repo root for defaults.
Where test data comes from
In a Maestro flow, "test data" is one of:
- Hard-coded constants —
inputText: "qa@example.com". Simplest, brittlest. envvariables — read from${EMAIL}at runtime. Better.- Generated at run time — a UUID, a timestamp. Best for write paths.
- Fetched from a fixture API — a per-job pre-seeded user. Most robust.
You will use all four. The trick is matching the strategy to the path.
Generating unique data
For any flow that creates a record, generate a unique value:
RUN_ID is set by your CI job (${{ github.run_id }}, ${CI_PIPELINE_ID}, etc). Locally, fall back to a timestamp:
Now two parallel runs can never collide on email.
Reading users from the environment
For flows that need an existing user (a logged-in customer with prior orders, say), don't hard-code credentials:
Provide values at run time:
Store QA_PASSWORD in CI secrets. Never commit credentials.
Resetting state
For flows that depend on a pristine starting state, reset at the boundary, not in every flow:
Mid-suite resets are tempting and almost always wrong: they make flow ordering significant, break parallelism, and hide the real problem (flows that aren't independent).
Fixture APIs
The most robust setup, used by every team running Maestro at scale:
- Build a
_testnamespace into your backend (gated by env / feature flag). - Expose endpoints like
POST /_test/usersthat return a fresh user. - Each flow's first step calls the endpoint via
runScript:
This eliminates almost every shared-state flake.
Per-environment defaults
A .env.test file at the repo root holds non-secret defaults:
Load it in your runner script:
Override per-CI-job for prod-like environments.
App build under test
Mismatched test data is often a mismatched app build problem. Pin the build to the test data version:
- The app build under test should hit
API_URL(set at build time, not via runtime config that the user can change). - The fixture data should be seeded at the same version the app expects.
- Schema migrations on the backend break the app build and the flows. Coordinate via your release pipeline.
Cleaning up
Every test run should be cleanable. The pattern:
Even better: tag fixture records with the run ID and run a nightly job that purges anything older than 7 days. Manual cleanup never happens.
Sensitive data
- Don't put real PII in test fixtures. Use Faker.
- Don't
inputTextreal card numbers. Use the network's test PAN. - Treat any test user with admin scope as a production user — it has access to real data, even if it's "test" data.
Common pitfalls
- One shared QA account. Every flow logs in as
qa@example.com. The first flow's session corrupts the second. Generate per-flow. - Local DB drift. Developer's local DB has 3-month-old seeds; staging was reset last week. Flows pass locally, fail in CI. Fix: nightly local seed reset as part of
make dev-up. - Coupling flow order. "Run create-user before login-user." Don't. Make every flow self-contained.