E2E Test Data Management on Staging with Playwright
A recurring question on r/QualityAssurance asks: "How do you handle test data for E2E tests running against dev/staging?" The answers reveal that most teams are still improvising. Hardcoded test users, shared staging databases, manual seed scripts that break every sprint. There is a better way.
“Generates real Playwright code, not proprietary YAML. Open-source and free vs $7.5K/mo competitors. Self-hosted, no cloud dependency. Tests are yours to keep, zero vendor lock-in.”
Assrt project philosophy
1. Why Shared Staging Data Breaks E2E Tests
The most common test data approach is also the most fragile: hardcode a test user in your staging environment and have all tests share it. "test@example.com" with password "Test123!" appears in test suites at every company that runs E2E tests against a shared staging environment. It works until it does not.
The failure modes are predictable. Test A creates an order for the shared user. Test B, running in parallel, checks the user's order count and finds an unexpected order from Test A. Test B fails. Neither test has a bug. The test data is the problem. This category of failure accounts for roughly 30% to 40% of flaky tests in organizations that run E2E suites against shared environments, based on surveys from the Google Testing Blog and Sauce Labs.
A related problem is staging environment drift. Developers use staging for manual testing, product managers demo features there, sales teams run customer demos against it. All of these activities modify the database state that your automated tests depend on. A test that expects a specific product catalog fails because someone deleted a product during a demo yesterday.
The worst variant is time-dependent data. Tests that rely on subscriptions, trial periods, or date-based logic break when the staging data ages past the expected window. A test for "trial expires in 7 days" works on Monday but fails on Tuesday because the seed data was created last week and the trial already expired.
2. API-Based Setup vs. UI-Based Setup
There are two schools of thought on how E2E tests should create their test data. The UI-based approach runs through the application's interface to create everything the test needs: register a new user through the signup form, create a project through the dashboard, add items through the UI. The API-based approach bypasses the UI and creates data directly through API calls or database operations.
UI-based setup is tempting because it feels "realistic." If the test creates data the same way a user would, the setup itself serves as an implicit test of those flows. The problem is speed and fragility. A Playwright test that creates a user through the signup form, verifies the email, completes onboarding, and then starts the actual test scenario takes 15 to 30 seconds just for setup. If you have 100 tests that each need a fresh user, you are spending 25 to 50 minutes on setup alone.
API-based setup is faster by an order of magnitude. A direct API call to create a user with a pre-verified email takes 50 to 200 milliseconds. Playwright supports this natively through its request context. You can call your API in a beforeEach hook, create exactly the data your test needs, and have the test start immediately with the correct state.
The recommended pattern is to use API-based setup for preconditions and UI-based interactions only for the specific flow you are testing. If you are testing the checkout flow, do not test signup and product creation in the same test. Create the user and product via API, then exercise checkout through the UI. This keeps tests focused, fast, and independent.
Skip the test data headaches
Assrt generates Playwright tests that handle their own setup and teardown. Each test creates what it needs and cleans up after itself.
Get Started →3. Seeding Strategies That Scale
For data that every test needs (reference data, configuration, feature flags), a seed script that runs before the test suite is the standard approach. The key is making the seed idempotent: running it twice produces the same state as running it once. Use upserts instead of inserts, check for existence before creating, and version your seed data alongside your test code.
A practical pattern is the "test factory." Instead of hardcoding test data, create factory functions that generate unique data for each test run. A createTestUser() function generates a user with a UUID-based email (test-abc123@yourapp.com), unique username, and randomized but valid profile data. Each test gets its own isolated data that cannot conflict with other tests.
For complex data hierarchies (an organization with teams, members, projects, and permissions), build composable factories. createTestOrg() calls createTestUser() for the admin, createTestTeam() for each team, and wires the relationships together. This pattern mirrors how Prisma and other ORMs handle test data, and it works well with Playwright's fixture system.
Playwright fixtures are particularly powerful for test data management. You can define a custom fixture that creates a fresh user, logs them in, and provides an authenticated page context to every test. The fixture handles setup in beforeEach and cleanup in afterEach, keeping the test body focused entirely on the scenario being tested. Tools like Assrt generate tests that follow this fixture pattern by default, which makes the generated tests immediately compatible with existing Playwright infrastructure.
4. Isolation Patterns for Parallel Test Execution
Running tests in parallel is essential for keeping suite execution time manageable, but parallel execution requires data isolation. Two tests running simultaneously cannot share the same user account, the same order, or the same inventory item without creating race conditions.
The simplest isolation pattern is "create everything fresh." Each test creates its own user, its own data, and operates in complete isolation. This works well for most scenarios and is the approach Playwright's documentation recommends. The tradeoff is that test setup takes longer, but the reliability improvement usually outweighs the speed cost.
For applications with expensive setup (multi-tenant SaaS where creating an organization takes seconds, not milliseconds), a pool-based approach works better. Before the test suite runs, create a pool of 20 test organizations. Each test worker claims an organization from the pool, uses it, and returns it when done. A cleanup step between uses resets the organization to a known state. This amortizes the expensive setup across the entire suite.
Playwright's worker-scoped fixtures support this pattern natively. A worker fixture creates one test organization per worker process, and all tests in that worker share it sequentially. Since Playwright runs tests within a single worker file sequentially by default, there are no race conditions. Across workers, each has its own isolated organization.
5. Cleanup and Data Lifecycle Management
Test data accumulates. Without cleanup, your staging database grows with thousands of test users, test orders, and test transactions. This causes performance degradation (queries slow down as tables grow), storage costs, and confusion when anyone tries to query staging data for debugging.
The most reliable cleanup strategy is convention-based identification. All test data uses a recognizable prefix or tag: emails start with "test-", organization names start with "[TEST]", records have a is_test_data boolean. A nightly cron job deletes everything matching these patterns. This is coarse but effective, and it does not require tests to clean up after themselves (which they often fail to do when tests abort mid-execution).
For more granular control, track test data in a separate manifest. Each test run writes the IDs of all created resources to a JSON file or database table. After the suite completes, a cleanup script reads the manifest and deletes everything. If a test crashes before cleanup, the manifest still has the IDs for the nightly sweep to catch.
Database snapshots offer the nuclear option. Take a snapshot of your staging database in a known-good state. After each test suite run (or nightly), restore from the snapshot. This guarantees a clean state but requires downtime during the restore and does not work well if other people use staging concurrently.
The best approach combines all three: convention-based tagging for easy identification, manifest tracking for immediate cleanup, and periodic snapshot restores as a safety net. Your staging environment stays clean, your tests stay isolated, and your database performance stays consistent. The investment in test data management pays for itself in reduced flakiness and faster debugging when tests do fail.
Let your tests handle their own data
Assrt generates Playwright tests with built-in data setup and teardown. Each test is self-contained and ready for parallel execution.