--- name: compliance-testing description: >- Test for regulatory compliance including GDPR/CMP consent verification, Better Ads Standards, cookie compliance auditing, and privacy policy validation. Covers automated consent flow testing, third-party script blocking before consent, and cookie inventory validation. Use when: "GDPR test," "compliance," "CMP test," "cookie consent," "Better Ads," "privacy," "consent banner." Related: accessibility-testing, security-testing, ci-cd-integration. license: MIT metadata: author: kindlmann version: "1.0" category: process --- Test applications for regulatory compliance, focusing on privacy regulations (GDPR, CCPA, ePrivacy), consent management, cookie governance, and advertising standards. Compliance is binary -- you either comply or you do not -- and the penalties for non-compliance are significant. Automated testing catches configuration drift and regressions that manual audits miss between review cycles. **Before starting:** Check for `.agents/qa-project-context.md` in the project root. It contains applicable regulations, CMP details, ad networks, and geographic requirements that determine which compliance tests to implement. --- ## Discovery Questions ### Applicable Regulations 1. **Which privacy regulations apply?** GDPR (EU users), CCPA/CPRA (California), ePrivacy Directive (EU cookies), LGPD (Brazil), PIPEDA (Canada). Multiple regulations may apply simultaneously if you serve users in multiple regions. 2. **What is the legal basis for data processing?** Consent (opt-in), legitimate interest, contractual necessity? This determines whether explicit consent is required before processing. 3. **Is there a DPO or legal team to consult?** Compliance testing validates technical implementation against legal requirements. The legal team defines those requirements. ### Consent Management 4. **What CMP is in use?** OneTrust, Cookiebot, Didomi, Usercentrics, or custom? The CMP determines consent storage format, API, and integration patterns. 5. **What consent categories exist?** Typically: Strictly Necessary (always allowed), Analytics/Performance, Functional/Preferences, Marketing/Targeting. 6. **How is consent communicated to third-party scripts?** TCF (Transparency and Consent Framework)? Custom data layer? Direct CMP API? ### Advertising and Accessibility 7. **What ad networks and formats are used?** Google Ads, Meta, programmatic? Display, video, interstitial? The Coalition for Better Ads defines acceptable formats. 8. **Are there accessibility compliance requirements?** ADA, EAA, Section 508? See the `accessibility-testing` skill for detailed WCAG testing. --- ## Core Principles ### 1. Compliance Is Binary There is no "mostly compliant." A cookie that fires before consent is a violation. A consent banner that cannot be dismissed without accepting is a violation. Test for exact compliance. ### 2. Automate What You Can, Audit What You Cannot Automated tests catch: cookies set before consent, scripts loading without consent, consent banner functionality, cookie attributes, consent persistence. Manual audits catch: privacy policy accuracy, legal language correctness, cross-border data transfer documentation. Automate the technical checks; schedule the legal audits. ### 3. Test From the User Perspective Compliance regulations are written from the user's perspective. Tests should simulate real user interactions with the consent flow, not just check backend state. ### 4. Regulations Change, Tests Must Be Updatable New regulations emerge regularly. Structure compliance tests with configuration-driven test data so that changing a threshold or adding a cookie category does not require rewriting the suite. ### 5. Defense in Depth Do not rely solely on the CMP. Verify at multiple layers: CMP configuration, CSP headers, script loading behavior, cookie state, and network requests. --- ## GDPR/CMP Testing with Playwright ### Consent Banner and Dark Pattern Checks ```typescript import { test, expect } from '@playwright/test'; test.describe('GDPR Consent Banner', () => { test.use({ storageState: undefined }); // Fresh context — first visit test('consent banner appears on first visit', async ({ page }) => { await page.goto('/'); const banner = page.getByRole('dialog', { name: /cookie|consent|privacy/i }); await expect(banner).toBeVisible({ timeout: 5000 }); }); test('banner provides accept and reject with equal prominence', async ({ page }) => { await page.goto('/'); const banner = page.getByRole('dialog', { name: /cookie|consent|privacy/i }); const acceptBtn = banner.getByRole('button', { name: /accept|agree|allow/i }); const rejectBtn = banner.getByRole('button', { name: /reject|decline|deny/i }); await expect(acceptBtn).toBeVisible(); await expect(rejectBtn).toBeVisible(); // Dark pattern check: reject button must be reasonably sized, not a tiny link const rejectBox = await rejectBtn.boundingBox(); expect(rejectBox).not.toBeNull(); expect(rejectBox!.width).toBeGreaterThan(60); expect(rejectBox!.height).toBeGreaterThan(30); }); test('banner links to privacy policy', async ({ page }) => { await page.goto('/'); const banner = page.getByRole('dialog', { name: /cookie|consent|privacy/i }); const policyLink = banner.getByRole('link', { name: /privacy policy|learn more/i }); await expect(policyLink).toBeVisible(); await expect(policyLink).toHaveAttribute('href', /privacy/); }); }); ``` ### Cookie State Before and After Consent The critical test: no non-essential cookies may be set before the user gives consent. ```typescript // Cookie classification helpers — maintain based on your cookie inventory function isStrictlyNecessary(name: string): boolean { const necessary = ['__Host-session', 'csrf_token', '__cf_bm', 'consent_status']; return necessary.some((n) => name.startsWith(n)); } function isAnalyticsCookie(name: string): boolean { return ['_ga', '_gid', '_gat', '_gtag', 'analytics_'].some((p) => name.startsWith(p)); } test.describe('Cookie Consent Compliance', () => { test.use({ storageState: undefined }); test('no non-essential cookies before consent', async ({ page, context }) => { await page.goto('/'); await expect(page.getByRole('dialog', { name: /cookie|consent/i })).toBeVisible(); const cookies = await context.cookies(); const violations = cookies.filter((c) => !isStrictlyNecessary(c.name)); expect(violations.map((c) => c.name), 'Non-essential cookies set before consent').toHaveLength(0); }); test('analytics cookies appear after accepting consent', async ({ page, context }) => { await page.goto('/'); const banner = page.getByRole('dialog', { name: /cookie|consent/i }); await banner.getByRole('button', { name: /accept|agree/i }).click(); await page.waitForTimeout(1000); // Allow async cookie setting const cookies = await context.cookies(); expect(cookies.filter((c) => isAnalyticsCookie(c.name)).length).toBeGreaterThan(0); }); test('no non-essential cookies after rejecting consent', async ({ page, context }) => { await page.goto('/'); const banner = page.getByRole('dialog', { name: /cookie|consent/i }); await banner.getByRole('button', { name: /reject|decline|deny/i }).click(); await page.waitForTimeout(1000); const cookies = await context.cookies(); const violations = cookies.filter((c) => !isStrictlyNecessary(c.name)); expect(violations.map((c) => c.name), 'Non-essential cookies after rejection').toHaveLength(0); }); }); ``` ### Consent Persistence and Withdrawal ```typescript test.describe('Consent Persistence', () => { test.use({ storageState: undefined }); test('consent persists across navigations', async ({ page }) => { await page.goto('/'); await page.getByRole('dialog', { name: /consent/i }).getByRole('button', { name: /accept/i }).click(); await page.goto('/about'); await expect(page.getByRole('dialog', { name: /consent/i })).toBeHidden({ timeout: 3000 }); }); test('user can withdraw consent via privacy settings', async ({ page, context }) => { await page.goto('/'); await page.getByRole('dialog', { name: /consent/i }).getByRole('button', { name: /accept/i }).click(); await page.goto('/privacy-settings'); const analyticsToggle = page.getByRole('checkbox', { name: /analytics/i }); if (await analyticsToggle.isChecked()) await analyticsToggle.uncheck(); await page.getByRole('button', { name: /save/i }).click(); await page.waitForTimeout(1000); const cookies = await context.cookies(); expect(cookies.filter((c) => isAnalyticsCookie(c.name))).toHaveLength(0); }); }); ``` ### Third-Party Script Blocking Before Consent The most critical compliance check: tracking scripts must not load before consent. ```typescript test.describe('Script Blocking', () => { test.use({ storageState: undefined }); test('no tracking scripts load before consent', async ({ page }) => { const trackingDomains = [ 'google-analytics.com', 'googletagmanager.com', 'facebook.net', 'connect.facebook.net', 'analytics.tiktok.com', 'bat.bing.com', ]; const violations: string[] = []; page.on('request', (req) => { const url = req.url(); if (trackingDomains.some((d) => url.includes(d))) violations.push(url); }); await page.goto('/'); await page.waitForLoadState('networkidle'); expect(violations, `Tracking scripts before consent:\n${violations.join('\n')}`).toHaveLength(0); }); test('tracking scripts load after consent acceptance', async ({ page }) => { const trackerLoaded: string[] = []; page.on('request', (req) => { if (/google-analytics|googletagmanager/.test(req.url())) trackerLoaded.push(req.url()); }); await page.goto('/'); await page.getByRole('dialog', { name: /consent/i }).getByRole('button', { name: /accept/i }).click(); await page.waitForTimeout(3000); expect(trackerLoaded.length, 'Analytics should load after consent').toBeGreaterThan(0); }); }); ``` --- ## Better Ads Standards The Coalition for Better Ads defines ad formats that trigger browser-level ad blocking (Chrome filters ads on non-compliant sites). ### Unacceptable Ad Formats | Format | Desktop | Mobile | Test Approach | |--------|---------|--------|---------------| | Pop-up ads | Yes | Yes | Check for modal/overlay within 5s of load without user action | | Auto-playing video with sound | Yes | Yes | Monitor `