Using Generated Identities in E2E Tests (Playwright & Cypress)

End-to-end tests that walk through signup, onboarding, or checkout create real records in a real backend, and that backend enforces the same rules production does: emails must be unique, duplicate orders get rejected, and yesterday’s passing test fails today with an "account already exists" error. The reliable fix is a fresh, disposable identity for every run — a generated name, email, address, and phone number the system under test has never seen.

This guide collects patterns that work in real Playwright and Cypress suites: keeping emails unique across parallel workers, choosing between deterministic and random data, loading identities from exported fixtures versus generating them inline, and cleaning up the accounts your tests leave behind.

Why signup and checkout flows need fresh identities

A signup test that reuses the same email is only green once. On the second run the unique-email constraint rejects the registration and the test fails for a reason that has nothing to do with the feature under test. Checkout flows hide the same problem in subtler form: many backends apply idempotency logic that quietly deduplicates repeat orders from the same customer, so a rerun can end up asserting against an order that was never actually created.

Parallel execution makes it worse. Playwright shards tests across workers by default and Cypress runs specs concurrently in CI, so two workers reaching for the same hardcoded identity collide mid-run: one wins the registration, the other fails with a duplicate error, and the failure moves around between runs — the definition of flakiness. Fresh identities per test, per worker, per run eliminate the entire class of problem.

Three ways to keep emails unique

The lightest technique is plus-addressing: most mail systems deliver qa+anything@yourteam.dev to the qa@yourteam.dev inbox, so a suite can append a run identifier after the plus sign and mint unlimited unique addresses that all arrive in one mailbox you control — ideal when a test must actually receive a verification email. Be aware that some validators, occasionally including the one you are testing, reject the plus sign; that is itself a bug worth catching.

When delivery does not matter, a timestamp suffix is the simplest guarantee: build the local part from a base name plus the epoch milliseconds at test start. For parallel runs, add a per-worker prefix — Playwright exposes a worker index and Cypress can derive one from the spec name or CI job id — so two workers can never produce the same address even in the same millisecond. Combining base name, worker prefix, and timestamp yields addresses that are unique, traceable to a specific run, and easy to purge later by pattern.

Deterministic or random? Seed it and log it

Random identities widen coverage: an apostrophe in O’Brien, a hyphenated surname, or a thirty-character street name will eventually surface bugs a fixed value never would. The danger is an assertion that silently depends on the random draw — a test that generates a random name and then asserts its alphabetical position in a user list passes or fails depending on which name came out. That is a flaky test in disguise.

The remedy is seeding. Every serious data library accepts a seed, and a seeded run reproduces identical identities every time, turning "cannot reproduce" into ordinary debugging. A good default: fixed seed in CI for reproducibility, random seed in a scheduled exploratory job for coverage, and the seed always printed at the start of the run so any failure can be replayed. Keep the roles separate — fields that must vary, like emails, get uniqueness bolted on independently of the deterministic fields your assertions actually check.

Exported fixtures vs generating inline

Exporting identities from a generator as CSV or JSON and dropping the file into your fixtures folder has real advantages: the data is reviewable in a pull request, identical on every machine, and dependency-free — Cypress loads JSON fixtures natively and Playwright reads them with a single import. The weakness is uniqueness: a static email fails on the second run, so the usual pattern is to load stable profile fields from the fixture and stamp a fresh unique email onto each record at runtime.

Generating inline with a library gives fresh data on every run and per-test control, at the cost of another dependency. Many teams land on a hybrid: exported fixtures for scenario-shaped data reviewers should see, inline generation for throwaway bulk. One hard rule either way: generated card numbers are Luhn-valid and perfect for testing input masks and client-side validation, but they must never be sent to a payment gateway — payment integration tests belong in your provider’s sandbox using the official test card numbers it publishes.

Run the same suite with JP, DE, and FR identities

Once identities come from a generator, switching locale is a single parameter — and a remarkably cheap bug-finder. Japanese identities catch encoding problems end to end: kanji that survive the form but land in the database as mojibake, or name-order assumptions that render family and given names backwards. German identities stress layout, because long compound street names overflow fixed-width columns and truncate in confirmation emails. French identities push accented characters through name and email fields, which naive regex validators frequently reject.

Practically, parameterize the suite over a small locale matrix and run it as a separate CI job. You are not translating the tests — the assertions stay the same. You are only feeding different identities through the same flows, which is exactly why the bugs this finds are cheap to fix and embarrassing to ship.

Clean up accounts — and keep test users out of git

Every green run leaves accounts behind, and a staging database with thousands of stale test users eventually causes failures of its own: slow queries, broken pagination assertions, analytics polluted by fake signups. Prefer teardown through an API call or direct database cleanup in an afterEach or global-teardown hook over clicking through account deletion in the UI, which is slow and can itself fail. Better still, point the suite at a dedicated test tenant that a nightly job wipes entirely; the per-worker email prefixes described earlier make pattern-based purging trivial.

Finally, keep test identities out of source control whenever they carry credentials. A fixture of names and addresses is harmless; a committed password or realistic-looking API key is not, and secret scanners will rightly flag it. Inject test-account passwords through environment variables or your CI secret store, gitignore locally exported identity dumps, and never borrow a real customer record as a fixture — a generated identity exists precisely so you do not have to.

Frequently Asked Questions

Can I use the generated card numbers in payment tests? +

Only for UI-level checks: input masks, Luhn validation, and brand detection from the leading digits. The numbers are not linked to any account and every real gateway will decline them. Integration tests that reach a payment provider must run against its sandbox with the official test cards it documents.

Should I default to random or fixed data? +

Fix everything an assertion depends on and randomize the rest. Run CI with a fixed seed so failures reproduce, add a scheduled job with a random seed to hunt edge cases, and always print the seed in the logs so any discovery can be replayed.

How do parallel workers avoid creating the same user? +

Give each worker its own namespace. Playwright exposes a worker index you can prepend to every generated email, and Cypress runs can use the spec name or a CI job id. Combined with a timestamp suffix, collisions become practically impossible even across jobs started simultaneously.

Do I need to delete test accounts after every run? +

Not necessarily after every run, but you need a deliberate strategy. A dedicated test tenant with a scheduled full wipe is the least fragile option; per-test API teardown is the tidiest. What fails in practice is having no plan and letting staging accumulate test users until unrelated tests start timing out.

Last updated: 2026-07-26

All guides