10 steps. That's the sequence experienced Playwright teams use to build a test data factory that survives parallel CI and hundreds of tests. Start by defining clear data contracts, add a PageFactory and a TestDataFactory, wire a WaitStrategyFactory for consistent waits, prefer API seeding, and encode the whole pattern in playwright.config.ts and CI so changes don't ripple through your suite. Begin by adding those foundation components to your codebase and require PR review for any change to factories or seeding logic so the pattern scales without maintenance debt.
Failing to centralise test data and object creation forces teams into a maintenance spiral, because a small constructor change can mean edits across hundreds of tests.
1. Decide scope and data contracts before you write factories
Data contracts are the guardrails for a durable factory. The New Stack frames data-driven testing as a deliberate separation of test inputs from test scripts so the same test logic can run many scenarios without code changes. Start by naming the kinds of test data your suite will use: created entities such as users and organisations, immutable reference sets like country lists and product SKUs, and transient per-test fixtures including tokens or one-off records.
Record the format for each type, whether JSON or a domain-specific builder API, and commit those contracts alongside your tests. QASkills.sh recommends choosing a narrow, reviewable entry point to prove the pattern before expanding it. Concretely, pick one endpoint, one entity and one happy-path scenario and instrument the review checklist so reviewers can validate any new dataset or generated content.
2. Centralise object creation with a PageFactory
PageFactory is the design that saves time. Eduard Dubilyer, CTO at Skipper Soft, describes a PageFactory that wraps Playwright Page and exposes getters for each Page Object so tests instantiate the factory once instead of wiring multiple new calls. That single point of construction isolates changes to constructors and any cross-cutting concerns such as logging or injected test doubles.
Extend the pattern to interaction behaviour with a WaitStrategyFactory. Skipper Soft shows an enumerated strategy model with DEFAULT, FAST and SLOW strategies. In their example, SLOW uses a 10000 ms locator wait, FAST uses 1000 ms, and DEFAULT falls back to Playwright defaults. Hiding wait timeouts behind a strategy prevents ad hoc timeouts scattered through tests and keeps retry semantics consistent across the suite.
3. Treat TestDataFactory as a first-class artifact
TestDataFactory should produce immutable, validated payloads and expose a matching cleanup API. Dubilyer and Skipper Soft recommend builders that return predictable, schema-validated objects rather than ad hoc JSON files.
When UI creation is too slow or flaky, allow the factory to call service-layer endpoints to persist state.
Pair these factories with an explicit QA review step. QASkills.sh specifically flags the risk when AI agents or code generators produce tests and data. A review step ensures generated data follows domain rules and doesn't leave long-lived noise in shared environments.
4. Prefer API seeding and selective UI setup
API seeding is faster and more reliable for CI than creating everything through UI flows. QASkills.sh and other Playwright guidance recommend creating a small set of minimal, well-tested seed endpoints or admin fixtures that TestDataFactory can call. Keep those endpoints stable, versioned and monitored so seeding stays predictable.
Where API seeding isn't possible, run Playwright setup tests as a single-ticket setup job that prepares authenticated storageState files or baseline data. Gustavo Meilus gives examples of a dedicated setup project and reuse of prepared storageState files for authentication, so you avoid repeating long login flows inside every test.
Parallel-safe design is a must when you enable Playwright fullyParallel execution or run projects concurrently. The New Stack and QASkills.sh emphasise that shared-data race conditions show up quickly under parallel runs. TestDataFactory should either create unique tenants and users per worker or allocate from a pool of pre-provisioned test accounts with strict per-test cleanup.
When you use prepared authentication state, treat storageState snapshots as immutable artifacts tied to specific workers and regenerate them during CI setup so tests don't contaminate one another.
Deterministic cleanup prevents the long tail of flakiness. Put in place a two-tier cleanup strategy: immediate teardown in the test where doable, and an asynchronous sweep job that runs after the suite to delete leaked artifacts. When tests must reuse a tenant across cases, design idempotent setup steps that reset state rather than create conflicting duplicates.
QASkills.sh warns that failing to plan cleanup systematically is the primary source of QA debt. Make teardown part of the contract for every factory method that creates persistent state.
Configuration matters. Gustavo Meilus recommends separating environment configuration from code with dotenv, setting baseURL and use options for shared settings, and defining projects that compose setup and browser jobs. Typical choices to support a test data factory are a setup project that prepares storageState and seeded data, a set of browser projects that depend on that setup, fullyParallel enabled where appropriate, and multi-format reporting such as html and json for pipelines to consume.
Use storageState to reuse authenticated sessions when it reduces flakiness, but pair that with deterministic regeneration in CI so snapshots don't become stale. Make the setup project a required dependency in CI so browser jobs run only after seed data and storageState are prepared.
Data-driven testing keeps logic separate from inputs. The New Stack describes the approach where inputs live outside test code so the same test runs multiple scenarios. Keep canonical datasets in version control, validate them with lightweight schema tests, and link each dataset to the TestDataFactory that produces runtime payloads.
Avoid embedding live secrets or production-like personal data in datasets. If you must exercise sensitive formats, synthesise realistic data that follows schema rules but can't map to real users.
Tracing and tooling speed failure diagnosis. QASkills.sh points to Playwright’s CLI, codegen, UI Mode and Trace Viewer for capturing deterministic traces of failing runs. Record traces and attach them to CI artifacts so developers can see exactly what led to a failure and confirm the TestDataFactory behaved as expected.
Keep change discipline tight: require PR reviewers to validate any change to factories, wait strategies or seeding logic because these changes affect many tests.
Scale by proof. Both QASkills.sh and Skipper Soft recommend starting with a narrow, instrumented workflow and expanding only after it proves stable. Instrument a small set of end-to-end tests with TestDataFactory-backed seeding, ensure they pass reliably in CI, and then migrate other tests to the same pattern.
Maintain a short reviewer checklist that covers schema validation, cleanup hooks, parallel-safety and dependency impact on CI duration. That checklist yields a repeatable gate for future changes.
Here is a short scenario that saves time when you start. First, add a PageFactory that accepts a Playwright Page and exposes getters for LoginPage and DashboardPage. Second, add a WaitStrategyFactory with FAST at 1000 ms, SLOW at 10000 ms and DEFAULT delegating to Playwright. Third, put in place a TestDataFactory with a buildUser method that returns a schema-validated object and a seedUser API call that posts to a versioned admin endpoint.
In CI create a setup project that runs once: it calls TestDataFactory.seedUser to create a bank of worker-specific users, writes storageState snapshots for those users and commits them as CI artifacts. Browser projects then run fullyParallel against those snapshots. Tests teardown created records immediately and a sweep job runs after the pipeline to remove leaked data.
First, define explicit data contracts and store them with tests. Second, centralise object creation with a PageFactory and normalise waits with a WaitStrategyFactory. Third, prefer API seeding, make TestDataFactory produce immutable validated payloads and require PR review for any factory or seeding change.
Related Articles
- CSS vs Lottie: how to choose the right web animation
- 4 steps to validate LLM systems in production
- MCP 402 calls: 7-step payment flow for tools
Begin by adding a PageFactory and a TestDataFactory to your codebase, wire a WaitStrategyFactory for consistent locator behaviour, and encode a setup project plus storageState reuse in playwright.config.ts so CI can seed and reuse authenticated state. Use API seeding where available, validate datasets in source control, and require PR review for any change to factories or seeding logic so the pattern scales without creating maintenance debt.
This article was created with AI assistance.