Realistic test data without real customers, addresses, or payment details
Copying production data for tests moves real names, addresses, and order histories into less protected environments and frequently violates core GDPR principles. This article shows how to build realistic test data for Cypress and Playwright using anonymization, pseudonymization, and Faker-based generators, so your E2E tests stay reliable without putting real people at risk.
Table of Contents
- 1. Why production data copies in test environments are a GDPR risk
- 2. The legal framework: Articles 5, 25, 32 GDPR and breach notification
- 3. Anonymization vs. pseudonymization of existing production dumps
- 4. Generating synthetic test data with Faker libraries
- 5. Building reusable test data factories for Cypress and Playwright
- 6. What must never end up in test data
- 7. Database seeding strategies for Magento-like test environments
- 8. Automatically anonymizing staging databases via CI
- 9. Production data copy vs. GDPR-compliant test data compared
- 10. Summary
- 11. FAQ
1. Why production data copies in test environments are a GDPR risk
In many teams it's standard practice: a mysqldump of the production database lands on the staging server overnight so the new checkout feature can be tested with "real" data. That moves real names, addresses, email addresses, complete order histories, and sometimes even masked payment references into an environment that rarely has the same protection level as the production system. Staging servers often run with weaker access control, no two-factor authentication, shared credentials for freelancers and agencies, or even sit on developer laptops outside the corporate infrastructure entirely.
From a GDPR perspective, a test environment is not a legal free-for-all. Personal data remains personal data regardless of whether it sits in a production or a test database. Every additional copy operation increases the attack surface and the number of people with access, without any additional, clearly defined processing purpose to justify it. That missing purpose alone makes the practice legally exposed, long before any actual data breach occurs.
2. The legal framework: Articles 5, 25, 32 GDPR and breach notification
Art. 5(1)(c) GDPR requires data minimization: only personal data actually necessary for the specific purpose may be processed. For functional and visual E2E tests, a real customer name is almost never necessary, a plausible synthetic name fully serves the same purpose. Art. 25 GDPR (privacy by design and by default) additionally requires technical and organizational measures to be considered as part of the system architecture itself, including where test data actually comes from.
Art. 32 GDPR demands a level of protection appropriate to the risk, which staging systems in practice regularly fail to meet. If a data leak occurs there, the 72-hour breach notification duty under Art. 33 GDPR applies just as it would in production. On top of that, the data processing agreement (DPA) with hosting or cloud providers often only covers the contractually agreed production systems, not spontaneously spun-up test or development environments, creating a liability gap exactly when it matters most.
3. Anonymization vs. pseudonymization of existing production dumps
The two terms are frequently confused but are legally fundamentally different. Pseudonymization (Art. 4(5) GDPR) replaces identifying attributes with placeholders whose mapping remains reversible via a separately stored key. Pseudonymized data is still personal data and remains fully subject to the GDPR. Anonymization, by contrast, irreversibly removes the personal reference so re-identification is no longer possible, even with additional knowledge. Only genuine anonymization fully removes a dataset from the scope of the GDPR.
In practice, simply hashing email addresses is not sufficient, since known domains and name patterns allow original values to be reversed via a dictionary attack. A more robust approach is format-preserving substitution: names get replaced with random but grammatically plausible names, addresses with real but mismatched streets from the same postal code region, and quasi-identifiers like birth date or postal code get aggregated or generalized to prevent re-identification through combining multiple attributes (k-anonymity). Tools like myanon or a custom anonymization script built on a MySQL dump can perform this substitution automatically and reproducibly.
4. Generating synthetic test data with Faker libraries
Rather than anonymizing existing production data after the fact, the more robust approach is to generate test data as fully synthetic from the start. Libraries like Faker.js in the JavaScript ecosystem or fakerphp/faker in PHP generate realistic-looking but entirely fabricated names, addresses, phone numbers, and product descriptions, with no connection to any real person ever existing. A fixed seed value is essential for reproducible E2E tests: the same seed produces exactly the same records on every test run, which massively simplifies debugging and snapshot comparisons.
For the German market, the de_DE locale provides addresses, postal codes, IBANs, and phone numbers in the correct national format, which makes test coverage for form validation more realistic than generic US placeholder data. A good Faker factory doesn't just generate isolated fields, it builds consistent objects: a generated order references real SKUs present in the test catalog, and a customer's shipping address stays stable across multiple test cases instead of being re-rolled on every call.
// test/factories/customer-factory.js
// Faker-based factory for fully synthetic customer and order data.
// Never touches real PII, safe to commit to the repository.
import { faker } from '@faker-js/faker/locale/de';
/**
* Builds a synthetic customer object with a fixed seed for reproducibility.
* @param {number} seed - Deterministic seed so repeated test runs match.
* @param {object} overrides - Optional field overrides for edge cases.
*/
export function buildCustomer(seed, overrides = {}) {
faker.seed(seed);
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
return {
firstName,
lastName,
// Synthetic domain, never a real mailbox, avoids accidental delivery
email: faker.internet.email({ firstName, lastName, provider: 'example-test.invalid' }),
phone: faker.phone.number('+49 ### #######'),
address: {
street: faker.location.streetAddress(),
postalCode: faker.location.zipCode('#####'),
city: faker.location.city(),
country: 'DE',
},
...overrides,
};
}
export function buildOrder(customer, skus, seed) {
faker.seed(seed);
return {
customerEmail: customer.email,
items: skus.map((sku) => ({ sku, qty: faker.number.int({ min: 1, max: 3 }) })),
total: faker.commerce.price({ min: 20, max: 400, dec: 2 }),
};
}
5. Building reusable test data factories for Cypress and Playwright
To keep synthetic test data from being generated slightly differently in every test case, it's worth building a central test data factory as a standalone module imported by both Cypress and Playwright suites. Factory functions like buildCustomer() or buildOrder() accept override parameters for edge cases, such as a customer without a phone number or an order with an item that's out of stock, without duplicating the rest of the test data logic.
Ideally, generated data isn't created through the UI but written directly into the test database via an API, GraphQL, or CLI interface. That dramatically speeds up test setup and decouples data seeding from the actual UI flow under test. Every factory instance should also carry a unique test-run ID inside the generated record, so a teardown script can reliably remove exactly the data that particular run created, without interfering with tests running in parallel.
{
"testRunId": "run-8f2c1a90",
"customer": {
"firstName": "Lennart",
"lastName": "Brandes",
"email": "lennart.brandes.8f2c@example-test.invalid",
"phone": "+49 151 5566778",
"address": {
"street": "Uhlandstrasse 42",
"postalCode": "10719",
"city": "Berlin",
"country": "DE"
}
},
"order": {
"customerEmail": "lennart.brandes.8f2c@example-test.invalid",
"items": [
{ "sku": "MS-TEST-1001", "qty": 2 },
{ "sku": "MS-TEST-1042", "qty": 1 }
],
"total": "128.50"
}
}
6. What must never end up in test data
A clear denylist protects teams more reliably than good intentions alone. No fixture file or test script should ever contain real email addresses, not even those of colleagues or interns, since a test run could accidentally trigger a real delivery. Equally off-limits are real payment or card data, even when a payment provider offers a "test mode," because card number patterns and IBANs often sit unredacted in logs and fixtures for years. Real phone numbers don't belong in test data either, and real customer names copied from support tickets or bug reports must never make their way directly into fixtures.
Scraped competitor data also has no place in test fixtures: beyond the GDPR risk if it contains personal references, it also carries copyright and unfair-competition exposure. An automated guard in the CI pipeline that scans fixture files for suspicious patterns before every merge reliably catches human error before it reaches the main branch.
#!/usr/bin/env bash
# scripts/ci-scan-fixtures.sh
# Fails the build if fixture files contain PII-like patterns.
set -euo pipefail
FIXTURE_DIR="tests/fixtures"
VIOLATIONS=0
# Reject real-looking email domains (allow only the synthetic test domain)
if grep -RInE '[A-Za-z0-9._%+-]+@(?!example-test\.invalid)[A-Za-z0-9.-]+\.[A-Za-z]{2,}' "$FIXTURE_DIR"; then
echo "ERROR: possible real email address found in fixtures"
VIOLATIONS=1
fi
# Reject anything resembling a card number (13-19 digits, optional separators)
if grep -RInE '[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{1,7}' "$FIXTURE_DIR"; then
echo "ERROR: possible card number found in fixtures"
VIOLATIONS=1
fi
# Reject anything resembling a real IBAN pattern
if grep -RInE '\bDE[0-9]{20}\b' "$FIXTURE_DIR"; then
echo "ERROR: possible real IBAN found in fixtures"
VIOLATIONS=1
fi
exit $VIOLATIONS
7. Database seeding strategies for Magento-like test environments
A two-tier seeding model works well for e-commerce test environments. The first tier is a static baseline, meaning categories, products, prices, and tax rules, which rarely changes and is built once from synthetic but consistent data. The second tier is dynamic test data such as customers, carts, and orders, freshly generated per test run through the Faker factory and torn down again afterward. This separation keeps the test environment fast to restore and prevents data clutter from accumulating uncontrolled over months.
A declarative seed manifest, similar in spirit to Magento's own declarative schema approach, makes it transparent which entities get created in which order and with which Faker seed, instead of scattering seeding logic across loose scripts.
<!-- tests/seed/catalog-seed-manifest.xml -->
<!-- Declarative manifest describing synthetic baseline data for staging -->
<seedManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<entity type="category" seed="1001" count="12">
<source>faker:commerce.department</source>
</entity>
<entity type="product" seed="1002" count="200">
<source>faker:commerce.productName</source>
<field name="sku" pattern="MS-TEST-####"/>
<field name="price" min="9.90" max="499.00"/>
</entity>
<entity type="customer" seed="dynamic" count="0">
<!-- generated per test run via customer-factory.js, never static -->
<note>Dynamic entity, created and torn down per test run</note>
</entity>
</seedManifest>
8. Automatically anonymizing staging databases via CI
Sometimes a test scenario genuinely depends on realistic data volume and distribution, for example load testing or migrating large catalogs, where purely synthetic data doesn't sufficiently reflect production reality. In those cases a copy remains useful, but it must then be automatically anonymized before it ever reaches a staging environment. The critical point is automation: a manual anonymization step run "whenever there's time" will inevitably get skipped once a deadline is tight.
Anonymization therefore belongs in the deployment pipeline as a fixed, non-skippable step, sitting directly between the database restore and opening the staging environment for access. A downstream automated check that spot-checks for remaining real-looking patterns serves as an additional safety net.
# .github/workflows/staging-refresh.yml
# Restores a production snapshot to staging, then anonymizes it
# before the environment becomes reachable. Anonymization is not optional.
name: staging-db-refresh
on:
schedule:
- cron: "0 3 * * 1"
jobs:
refresh-and-anonymize:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Restore latest production snapshot to staging
run: bin/mysql < backups/latest-production-snapshot.sql
- name: Run anonymization script (mandatory gate)
run: bash scripts/anonymize-staging-db.sh
- name: Verify no real PII patterns remain
run: bash scripts/ci-scan-fixtures.sh --target=staging-db
- name: Open staging environment for access
if: success()
run: bash scripts/open-staging-access.sh
9. Production data copy vs. GDPR-compliant test data compared
The table below compares a classic, unprocessed production data copy against the two GDPR-compliant alternatives: automatically anonymized dumps and fully synthetic Faker data.
| Criterion | Production data copy (unprocessed) | Automatically anonymized / synthetic |
|---|---|---|
| Personal data | Fully personal | No personal reference |
| Legal basis required | Yes, usually missing | No |
| Breach notification on leak | Art. 33/34 GDPR applies | Not applicable |
| DPA coverage for staging | Often not covered | Not required |
| Reproducibility | Changes with every refresh | Deterministic via seed |
| Maintenance effort | Low, but risky | Higher upfront, then low |
The comparison makes it clear: the perceived time savings from an unprocessed production data copy are more than offset by legal risk, missing reproducibility, and potential breach notification duties. Synthetic and automatically anonymized data cost some upfront setup effort, but pay off afterward with more stable, faster, and legally safe E2E tests.
Mironsoft
GDPR-compliant test data and E2E test automation for Magento and Hyvä stores
Ready to build test data without GDPR risk?
We build Faker-based test data factories, automated staging anonymization, and robust Cypress and Playwright fixtures for your store, speeding up your E2E tests without ever putting real customer data at risk.
Test data audit
Assessment of where real production data ends up in your test and staging systems
Faker factories
Reusable test data factories for Cypress, Playwright, and Magento seeds
CI anonymization
Automated staging anonymization as a fixed pipeline step
10. Summary
GDPR-compliant test data solves a problem many teams underestimate: every unprocessed production data copy in a test or staging environment is a full-fledged GDPR processing activity with all the obligations under Art. 5, 25, and 32 GDPR, including the 72-hour breach notification duty if it leaks. Anonymizing and pseudonymizing existing dumps are possible stopgap solutions, but they only partially solve the problem and must be technically automated to be reliable.
The more robust path runs through fully synthetic test data: Faker-based factories for Cypress and Playwright, a declarative seeding model for the baseline catalog, and a clear denylist of what must never go into a fixture file. Where a realistic data copy remains unavoidable, anonymization belongs in the CI/CD pipeline as a non-skippable, automatically verified step.
GDPR-Compliant Test Data - The Essentials at a Glance
Legal framework
Art. 5, 25, and 32 GDPR apply in staging just as in production, including breach notification duties.
Anonymization vs. pseudonymization
Only genuine, irreversible anonymization removes data from the scope of the GDPR.
Faker factories
Deterministically seeded, reusable factories for Cypress and Playwright instead of copy-pasted data.
Automated safety net
CI guards against real PII in fixtures, staging anonymization as a non-skippable pipeline step.