How to Test GDPR Export, Delete, and Consent Flows
Short answer
GDPR and privacy flows fail on incomplete exports, partial delete cascades, stale consent after policy updates, and missing audit trails—not on whether a settings page has a delete button. Seed users with data across every entity type, probe export archives and post-delete absence everywhere PII can hide, and map scenarios to compliance matrices with requirement traceability.
Part of Testing Guides by UI patterns.
Who this is for
Teams shipping EU-facing SaaS, healthcare adjacency, HR platforms, or any product with data subject requests (DSAR), marketing consent banners, cookie preferences, and account deletion—especially multi-service architectures with warehouses and third-party processors.
Why testing GDPR privacy matters
Privacy bugs are regulatory incidents, not UX nitpicks:
- Incomplete export (Art. 15) — ZIP missing analytics warehouse or support tickets; supervisory authority complaint.
- Incomplete erasure (Art. 17) — Auth user deleted but S3 objects, search index, and backup snapshots retain PII.
- Consent not provable (Art. 7) — Marketing emails without logged consent version and timestamp.
- Withdrawal ignored — User opts out; CDP still syncs segments.
- Retention violations — Scheduled delete job skips cold storage; GDPR retention schedules fail audit.
E2E must probe every storage class your ROPA documents—UI "account deleted" toast is insufficient.
Complexity map
| Scenario | Edge case | Why tests break | Approach |
|---|---|---|---|
| Export completeness | Missing microservice | Partial ZIP | Entity checklist probe |
| Delete cascade | FK restrict | Orphan PII | Probe all tables + blob |
| Async delete job | 202 accepted | Premature assert | poll job probe |
| Consent version bump | Old users grandfathered | Wrong email | Probe consent_version |
| Marketing opt-out | HubSpot sync lag | Still emailed | Probe integration queue |
| Cookie categories | Analytics loads before consent | Illegal track | Network assert blocked |
| Child accounts | Family plan | Parent vs child | Probe scope per uid |
| Legal hold | Delete blocked | Silent fail | Probe hold flag + UI |
| Re-signup same email | Ghost data | Residual PII | Probe clean slate |
| Processor subprocessors | Third-party API | Remote copy | Mock processor probe |
| Export format | JSON vs PDF | Missing machine-readable | Parse archive manifest |
| Audit trail | No DSAR log | Compliance fail | Probe audit_event |
Seed user with cross-service footprint
await request.post('/api/test/seed-privacy-user', {
data: {
runId,
entities: ['profile', 'orders', 'uploads', 'support_tickets', 'analytics_events'],
},
});
Maintain entity manifest in repo matching Records of Processing Activities—tests drift when new service stores PII without manifest update.
Export completeness Assert
await page.goto('/settings/privacy');
await page.getByRole('button', { name: 'Download my data' }).click();
await expect(page.getByText(/preparing|email when ready/i)).toBeVisible();
await expect.poll(async () => {
const job = await request.get(`/api/test/dsar-export?runId=${runId}`).then(r => r.json());
return job.status;
}, { timeout: 120_000 }).toBe('completed');
expect(job.includedEntities).toEqual(expect.arrayContaining([
'profile', 'orders', 'uploads', 'support_tickets', 'analytics_events',
]));
expect(job.missingEntities).toHaveLength(0);
// Optional: fetch signed URL in test env and parse ZIP manifest
const manifest = await parseExportManifest(job.downloadUrl);
expect(manifest.files.map(f => f.entity)).toEqual(expect.arrayContaining(job.includedEntities));
Export completeness is the primary GDPR E2E bar—document every entity in manifest tests.
Delete cascade Assert
await page.getByRole('button', { name: 'Delete account' }).click();
await page.getByLabel('Type DELETE to confirm').fill('DELETE');
await page.getByRole('button', { name: 'Permanently delete' }).click();
await expect.poll(async () =>
(await request.get(`/api/test/privacy-erasure?runId=${runId}`)).json()
).toMatchObject({ status: 'completed', piiRemaining: [] });
// Spot probes
await expect(request.get(`/api/test/user-profile?runId=${runId}`)).resolves.toMatchObject({ status: 404 });
await expect(request.get(`/api/test/blob-exists?runId=${runId}`)).resolves.toMatchObject({ exists: false });
await expect(request.get(`/api/test/search-index?runId=${runId}`)).resolves.toMatchObject({ hits: 0 });
Consent version flow
await request.post('/api/test/set-consent-policy', { data: { version: '2024-06-01' } });
await page.goto('/'); // banner
await page.getByRole('button', { name: 'Accept all' }).click();
const consent = await request.get(`/api/test/consent?runId=${runId}`).then(r => r.json());
expect(consent.version).toBe('2024-06-01');
expect(consent.marketing).toBe(true);
// Policy update requires re-consent
await request.post('/api/test/set-consent-policy', { data: { version: '2024-07-01' } });
await page.reload();
await expect(page.getByRole('dialog')).toContainText(/updated policy/i);
Link scenarios to compliance controls via requirement traceability and // @Scenario: in SmartTests for SOC2/GDPR matrices.
Requirement slices to cover
consent_version— policy id accepteddata_request_type— export, delete, restrict, object
When TrueCoverage shows prod delete requests rising but tests only cover export, run /testchimp evolve.
CI checklist
- Entity manifest reviewed quarterly with eng + legal
- Export job polled to completion—never assert button click only
- Delete probes all known PII stores (DB, blob, search, cache)
- Consent version bump spec after policy change
- No real user PII in fixtures—synthetic runId users only
- Audit log probe for DSAR events (audit guide)
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
| Delete Auth user only | Firestore/S3 orphan | Cascade checklist |
| Export UI click only | Empty ZIP untested | Poll job + manifest |
| Skip marketing integrations | Still synced | Queue probe |
| Hard-delete in prod CI | Illegal | Isolated test project |
| Assert email sent only | Attachment incomplete | Parse archive |
| No audit entry | Audit fail | Probe dsar_requested |
Example scenario
Situation: EU user requests full data export then account deletion after download.
Expected outcome: Export ZIP contains all manifest entities; post-delete zero PII probes; DSAR audit complete.
Why UI-only automation breaks: Export email sent but ZIP missing support tickets; delete removes profile but S3 avatars remain.
- Arrange: Seed privacy-user across five entity types for runId.
- Act: Request export, wait for job, download; then delete account with confirmation.
- Assert: Export manifest complete; erasure probe piiRemaining=[]; audit has export+delete events.
TestChimp workflow: Track data_request_type in prod; evolve delete cascade when new storage service ships.
Same Arrange/Act/Assert pattern as expired-coupon checkout.
Connect scenarios to your QA workflow
Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).
Related scenarios
- Audit compliance logs — DSAR audit trails
- Healthcare portals — HIPAA overlap (stricter PHI rules)
- Push notifications — consent for messaging
- Firebase auth delete — auth vs datastore orphan pattern
External references
- GDPR Art. 15 Right of access
- GDPR Art. 17 Right to erasure
- ICO guidance on right to erasure
- EDPB consent guidelines
Frequently asked questions
Are all data_request_types covered by scenarios?
Map export, delete, restrict, and object to SmartTests with // @Scenario: ids in markdown compliance matrices. Use TrueCoverage data_request_type prod vs test to find gaps—run /testchimp evolve when delete rises in prod.
How do I prove export completeness?
Maintain entity manifest matching ROPA; poll DSAR job; assert includedEntities and parse ZIP manifest—never trust email sent alone.
What does delete cascade must include?
Every store with PII: primary DB, blobs, search indexes, caches, analytics identities, third-party processor test doubles—probe each via test routes.
How do consent version updates work in E2E?
Bump policy version in Arrange, assert banner reappears, accept, probe stored consent_version and marketing flags match policy.
Can I run GDPR tests against production?
Never destructive deletes in prod. Use isolated test tenant with synthetic users and processor sandboxes only.
How do audit logs relate to DSAR?
Probe audit_event_type dsar_export_requested and account_deleted with actor self-service—required for enterprise compliance vertical.
HIPAA vs GDPR in tests?
GDPR focuses export/delete completeness; HIPAA adds minimum necessary and PHI bans in logs—see healthcare guide for clinical data fixtures.
Apply these patterns in your repo
Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.