How to Test Cookie Consent on Web (Complete Guide)
Cookie consent mechanisms sit at the intersection of law, user experience, and technical implementation. When a banner fails to block tracking scripts, a site can violate GDPR, CCPA, or ePrivacy rules
Why Cookie Consent Testing Matters
Cookie consent mechanisms sit at the intersection of law, user experience, and technical implementation. When a banner fails to block tracking scripts, a site can violate GDPR, CCPA, or ePrivacy rules, exposing the organization to fines that reach millions of euros. Beyond liability, a broken consent flow erodes trust: users who see persistent pop‑ups or are unable to reject non‑essential cookies often abandon the site, directly affecting conversion rates.
From a testing perspective, consent banners are unusually brittle. They rely on JavaScript that must run before any third‑party tag fires, they often depend on server‑side geo‑IP detection, and they are frequently overridden by A/B test frameworks or CMS plugins. Consequently, bugs appear only under specific combinations of browser, locale, device, and network conditions—situations that scripted regression suites rarely cover. A dedicated test effort that combines manual checks, automated assertions, and exploratory, persona‑driven runs is therefore essential to catch the full spectrum of failures before they reach production.
How Cookie Consent Works on the Web
Understanding the technical flow helps you design effective tests. A typical consent implementation follows these steps:
- Page load – The HTML document begins parsing. A small inline script (often bundled with a consent‑management platform, CMP) runs synchronously.
- Decision lookup – The script checks for a stored consent signal (usually a cookie named
consent,euconsent, or a localStorage key). If none exists, it proceeds to show the banner. - Banner rendering – The CMP injects a modal or banner into the DOM, applies CSS (often via a shadow DOM or iframe), and attaches event listeners to the accept/reject/prefer‑buttons.
- User interaction – Clicking a button triggers a callback that writes the chosen consent string to storage and sets a flag indicating the decision has been persisted.
- Tag gating – All subsequent third‑party scripts (analytics, ads, social widgets) consult the same flag before executing. If the flag indicates rejection, the loader either blocks the script or loads a stripped‑down version.
- Renewal – Many regulations require periodic re‑prompting (e.g., every 12 months). The CMP stores a timestamp and re‑shows the banner when the interval elapses.
Key technical points to verify:
- Storage location – Does the consent value end up in a cookie, localStorage, sessionStorage, or IndexedDB?
- Scope – Is the storage scoped to the exact origin, or does it leak across subdomains?
- Timing – Does the banner appear before any network requests for tracking scripts?
- Persistence – Does a reload or navigation preserve the decision?
- Fallback – Does the site still function (albeit without tracking) when JavaScript is disabled?
These details become the basis for both manual verification and automated assertions.
Test Matrix for Cookie Consent
Below is a comprehensive matrix that groups test ideas by category, lists specific scenarios, and indicates the preferred validation method (manual, automated, or both). Use this matrix to build a test plan that covers happy paths, error conditions, accessibility, security, and production‑only edge cases.
| Category | Test ID | Description | Expected Outcome | Validation Method |
|---|---|---|---|---|
| Happy Path | H1 | User visits site for first time, sees banner, clicks “Accept All”. | Consent stored, all third‑party tags fire, no banner on subsequent page views. | Automated (check storage + network) |
| H2 | User clicks “Reject All”. | Consent stored, tracking scripts blocked, essential site functions remain. | Automated | |
| H3 | User clicks “Customize”, enables only analytics, disables ads. | Only analytics cookies set, ad‑related requests absent. | Automated | |
| H4 | User closes banner via the “X” (if allowed) without making a choice. | Consent remains unset, banner reappears on next navigation or after timeout. | Manual + automated | |
| Error Paths | E1 | Network latency delays the consent script; tracking tags attempt to load before banner appears. | No tracking until consent is given; no console errors about missing consent variable. | Automated (simulate throttling) |
| E2 | Consent script throws an exception (e.g., missing dependency). | Banner fails to render, fallback shows a generic privacy notice or site blocks non‑essential scripts. | Manual (inspect console) | |
| E3 | User clears cookies/localStorage mid‑session. | Banner reappears on next page view, previous decision lost. | Automated | |
| E4 | Consent storage exceeds size limit (e.g., overly long vendor list). | Script truncates or falls back to default state; no crash. | Manual | |
| Accessibility | A1 | Banner is navigable via Tab key; focus order is logical. | All interactive elements reachable, visible focus indicator. | Automated (axe-core) + manual |
| A2 | Screen reader announces banner role, state, and button labels correctly. | ARIA live region or role="dialog" with appropriate labels. | Manual (NVDA/VoiceOver) | |
| A3 | Color contrast between banner text and background meets WCAG AA (4.5:1). | Contrast ratio ≥ 4.5:1 for normal text, ≥ 3:1 for large text. | Automated (axe) | |
| A4 | Banner can be dismissed via Escape key (if spec permits). | Escape closes banner without storing a choice. | Manual | |
| Security / Privacy | S1 | Consent value is not exposed via URL fragments or referrer headers. | No consent data in document.location.hash or Referer. | Manual (network inspection) |
| S2 | Third‑party scripts cannot read or overwrite the consent cookie without proper SameSite attributes. | Cookie flags: SameSite=Lax or Strict, HttpOnly if appropriate. | Manual (cookie inspection) | |
| S3 | Consent modal does not create a click‑jacking vulnerability (no transparent overlays). | Banner resides in top‑level frame, frame-ancestors CSP restricts embedding. | Manual (CSP check) | |
| S4 | Vendor list in consent string is signed or integrity‑checked (if CMP supports). | Tampering detected, fallback to default state. | Manual | |
| Cross‑Browser / Device | C1 | Banner renders correctly in Chrome, Firefox, Safari, Edge (desktop). | Same visual layout, functional buttons. | Automated (BrowserStack) |
| C2 | Banner adapts to viewport ≤ 480px (mobile). | No overflow, touch targets ≥ 44 px. | Manual + automated (responsive testing) | |
| C3 | Behavior consistent under iOS Safari’s strict cookie blocking (third‑party cookies disabled). | First‑party consent cookie still set; third‑party tags blocked as per choice. | Manual | |
| Performance | P1 | Banner adds < 50 ms to First Contentful Paint (FCP) on 3G simulated connection. | Measured via Lighthouse; no significant impact. | Automated (Lighthouse CI) |
| P2 | Consent script does not block the main thread for > 200 ms. | Long Task API shows no long tasks attributed to consent code. | Manual (Chrome DevTools) | |
| Legal / Compliance | L1 | Consent string includes required IAB TC fields (if using IAB framework). | Presence of gdpr_applies, tc_string with correct version. | Manual (decode TC string) |
| L2 | Banner links to privacy policy and cookie policy; links are reachable and open in new tab. | target="_blank" or same‑tab navigation works. | Manual | |
| L3 | After 12 months (or configured period), banner reappears for returning user. | Timestamp check triggers re‑prompt. | Manual (adjust system clock or mock date) | |
| Edge Cases – Production‑Only | X1 | GeoIP‑based regulation switching: user from EU sees GDPR banner; user from US sees CCPA‑style banner. | Correct banner variant served based on IP. | Manual (VPN/proxy) or automated with geo‑mocking |
| X2 | A/B test framework serves two different banner designs to 50 % traffic each. | Both variants functional; no script conflicts. | Manual (toggle feature flag) | |
| X3 | Third‑party tag manager loads consent script asynchronously after a delay. | Banner still appears before any tracking; race‑condition handled. | Automated (introduce artificial delay) | |
| X4 | Service worker intercepts network requests and attempts to set its own cookies regardless of consent. | Consent‑gating logic blocks SW‑initiated tracking requests; SW logs show consent check. | Manual (DevTools > Application > Service Workers) | |
| X5 | Consent banner rendered inside an iframe (e.g., embedded checkout). | Banner respects parent’s consent; iframe does not load tracking until consent granted. | Manual (nested frames) | |
| X6 | User has disabled JavaScript; site relies on noscript fallback to show a static privacy notice. | Notice visible, no tracking scripts loaded (they are inside ‑blocked tags). | Manual (disable JS) | |
| X7 | Consent cookie is marked Secure but site is served over HTTP in a staging environment. | Browser rejects cookie; banner reappears on every load (expected degradation). | Manual (protocol switch) | |
| X8 | Consent string contains non‑ASCII characters (e.g., localized vendor names). | Storage and retrieval preserve UTF‑8; no corruption. | Manual (character check) |
*How to use the matrix*: For each test ID, write a test case in your test management tool, assign an owner, and decide whether it will be executed manually, via a unit/integration test, or as part of an end‑to‑end suite. Prioritize H‑ and A‑tests for every release; run X‑tests in a staging environment that mirrors production variability.
Manual Testing Approach – Step‑by‑Step
A disciplined manual session catches issues that automated scripts may overlook, especially those related to visual layout, focus management, and contextual behavior. Follow this procedure on a clean browser profile (no existing cookies, cache cleared) for each target browser/device combination.
- Prepare the environment
- Open an incognito/private window.
- Disable extensions that alter cookies or block trackers (e.g., uBlock Origin) unless you are specifically testing those extensions.
- Set network throttling to “Slow 3G” (Chrome DevTools → Network → Throttling) to expose timing bugs.
- First‑visit baseline
- Navigate to the landing URL.
- Verify that the consent banner appears within 2 seconds of DOMContentLoaded (use the Performance tab to mark the timestamp).
- Inspect the banner’s DOM node: check for
role="dialog"oraria-modal="true"and ensure it is inserted directly under(not inside a shadow DOM that hides it from assistive tech unless intentional).
- Keyboard navigation
- Press
Tabrepeatedly. Confirm focus moves from the browser address bar to the first interactive element in the banner (usually “Accept All”). - Ensure a visible focus outline (minimum 2 px solid) appears on each element as it receives focus.
- Press
Shift+Tabto verify reverse order. - Attempt to close the banner with
Esc(if allowed) and confirm the banner disappears without setting consent.
- Screen‑reader validation
- Enable NVDA (Windows) or VoiceOver (macOS/iOS).
- Navigate to the banner and listen for the announcement: it should state the purpose (“Cookie preferences”), list the button labels, and indicate the current state (e.g., “Not accepted”).
- Activate each button and verify the screen reader announces the outcome (“Accepted all cookies”, “Saved preferences”).
- Interaction scenarios
- Accept All: Click the button, then reload the page. Confirm the banner does not reappear. Open Application → Cookies/storage and locate the consent token; validate its format (e.g., a base64‑encoded TC string).
- Reject All: Repeat the above; ensure tracking-related cookies (e.g.,
_ga,_fbp) are absent. - Custom Select: Open the preferences pane, toggle individual categories, save, and verify that only the selected categories result in corresponding cookies or localStorage entries.
- No choice (X): Click the close icon if present; navigate away and back; the banner should reappear.
- Network verification
- With the Network tab open, filter for requests to known third‑party domains (e.g.,
google-analytics.com,doubleclick.net). - After each interaction (accept, reject, custom), confirm that requests matching the allowed categories fire, while disallowed ones are blocked (status
(canceled)or not sent).
- Persistence across sessions
- Close the incognito window, open a new one, and revisit the site. The banner should remain hidden if a valid consent was stored.
- Explicitly clear site storage (Application → Clear storage) and verify the banner returns.
- Accessibility contrast check
- Use the Chrome DevTools Contrast checker (or the axe extension) on the banner’s text and background. Record the ratio; if below 4.5:1, note a defect.
- Legal link validation
- Right‑click each policy link, choose “Open link in new tab”, and confirm the URL points to the correct privacy or cookie policy page. Ensure the link opens without JavaScript errors.
- Document findings
- For each observed deviation, capture a screenshot, console error, network log, and the exact steps to reproduce. Tag the issue with the corresponding test ID from the matrix (e.g., A2, X3).
Repeating this routine for each browser/device matrix (Chrome desktop, Firefox mobile, Safari iOS, Edge Android) provides broad coverage while keeping the effort tractable.
Automated Testing Strategies
Automated checks give you fast feedback on regressions and allow you to scale coverage across many configurations. Below are practical patterns for unit, integration, and end‑to‑end (E2E) testing, with concrete code snippets using popular frameworks.
Unit‑Level Checks (JavaScript/TypeScript)
If your consent logic lives in a dedicated module (e.g., consentManager.js), you can test the pure functions in isolation.
// consentManager.test.js
import { giveConsent, rejectConsent, isCategoryAllowed } from './consentManager';
describe('Consent manager core logic', () => {
beforeEach(() => {
// reset storage before each test
localStorage.clear();
document.cookie.split(';').forEach(c => {
document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
});
});
test('accept all sets consent flag and allows analytics', () => {
giveConsent({ analytics: true, ads: true });
expect(localStorage.getItem('consent')).toBeTruthy();
expect(isCategoryAllowed('analytics')).toBe(true);
expect(isCategoryAllowed('ads')).toBe(true);
});
test('reject all blocks all non‑essential categories', () => {
rejectConsent();
const consent = localStorage.getItem('consent');
expect(consent).toBeTruthy(); // still stored as a decision
expect(isCategoryAllowed('analytics')).toBe(false);
expect(isCategoryAllowed('ads')).toBe(false);
});
test('custom selection respects toggles', () => {
giveConsent({ analytics: true, ads: false });
expect(isCategoryAllowed('analytics')).toBe(true);
expect(isCategoryAllowed('ads')).toBe(false);
});
});
Run these with Jest or Vitest on every commit. They guard against regressions in the consent decision‑making algorithm.
Integration Checks (DOM + Storage)
Integration tests render the banner component and verify UI interactions. Using React Testing Library or Vue Test Utils:
// consentBanner.integration.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import ConsentBanner from '../components/ConsentBanner';
test('banner shows on first load', () => {
render(<ConsentBanner />);
const banner = screen.getByRole('dialog', { name: /cookie preferences/i });
expect(banner).toBeInTheDocument();
});
test('accept all hides banner and stores consent', () => {
render(<ConsentBanner />);
fireEvent.click(screen.getByRole('button', { name: /accept all/i }));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
expect(localStorage.getItem('consent')).toBeTruthy();
});
test('reject all blocks tracking scripts (mock)', () => {
const mockGtag = jest.fn();
window.gtag = mockGtag;
render(<ConsentBanner />);
fireEvent.click(screen.getByRole('button', { name: /reject all/i }));
// simulate a tracking call that should be gated
window.gtag('event', 'page_view');
expect(mockGtag).not.toHaveBeenCalled();
});
These tests run in jsdom or a real browser via Playwright’s component testing mode, ensuring that the banner’s event listeners correctly update storage and that your site’s analytics wrapper respects the flag.
End‑to‑End Tests (Playwright Example)
E2E tests validate the full flow: network requests, third‑party script blocking, and persistence across page navigations. Playwright offers built‑in context isolation, making it ideal for consent testing.
// consent.spec.js
const { test, expect } = require('@playwright/test');
test.describe('Cookie consent flow', () => {
test.beforeEach(async ({ page }) => {
// start with a clean context
await page.context().clearCookies();
await page.goto('https://example-shop.com');
});
test('accept all enables analytics and ads', async ({ page }) => {
const banner = page.getByRole('dialog', { name: /cookie preferences/i });
await expect(banner).toBeVisible();
await page.getByRole('button', { name: /accept all/i }).click();
await expect(banner).toBeHidden();
// verify storage
const consentValue = await page.evaluate(() => localStorage.getItem('consent'));
expect(consentValue).toBeTruthy();
// allow a short window for scripts to fire
await page.waitForTimeout(1500);
// check that analytics request fired
await expect(page).toHaveURL(/.*/); // dummy to ensure navigation settled
const analyticsRequest = page.request().filter(request =>
request.url().includes('google-analytics.com/collect')
);
await expect(analyticsRequest).toHaveCount(1);
});
test('reject all blocks non‑essential requests', async ({ page }) => {
await page.getByRole('button', { name: /reject all/i }).click();
await page.waitForTimeout(1500);
const fbRequest = page.request().filter(r =>
r.url().includes('facebook.com/tr/')
);
await expect(fbRequest).toHaveCount(0);
});
test('persists choice across navigation', async ({ page }) => {
await page.getByRole('button', { name: /accept all/i }).click();
await page.goto('/products/123');
const banner = page.getByRole('dialog', { name: /cookie preferences/i });
await expect(banner).toBeHidden();
});
test('banner respects DNT header', async ({ page }) => {
// simulate Do Not Track
await page.setExtraHTTPHeaders({ 'dnt': '1' });
await page.reload();
const banner = page.getByRole('dialog', { name: /cookie preferences/i });
// depending on policy, banner may still show; assert according to your spec
await expect(banner).toBeVisible(); // example: we still show banner to let user override
});
});
Key points in the snippet
page.context().clearCookies()guarantees a pristine state per test.getByRolelocators rely on accessible names, making the test resilient to CSS changes.- After clicking a button, we wait briefly (
waitForTimeout) to let any queued network requests settle; a more robust approach is to usepage.waitForResponsewith a predicate. - The final test demonstrates how to inject custom headers (e.g., DNT,
Sec‑GPC) to verify that your consent logic honors upstream privacy signals.
Running the Suite in CI
Add the following to your package.json:
{
"scripts": {
"test:unit": "vitest run",
"test:e2e": "playwright test"
}
}
In your CI pipeline (GitHub Actions, GitLab CI, etc.):
name: Web Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npm run test:unit
- run: npm run test:e2e
This yields rapid feedback on unit logic and slower but thorough validation of the full consent flow.
Tooling and Ecosystem Helpers
Beyond writing your own tests, several open‑source and commercial utilities can accelerate validation.
| Tool | Purpose | How to integrate |
|---|---|---|
| axe‑core (browser extension or npm package) | Automated accessibility audits, includes checks for dialog role, focus order, and contrast. | Run axe.run() in Playwright or Cypress, or add the extension for manual spot checks. |
| Cookie‑Scanner (by Cookiebot) | Crawls a site and reports all cookies, their expiration, and whether they are set before consent. | Use the online scanner for a quick audit; CI integration via their API. |
| Consent‑Checker (open‑source) | Detects common CMP scripts (OneTrust, Cookiebot, TrustArc) and verifies that they appear before any third‑party tag. | Install via npm i consent-checker and run as a Node script against a built bundle. |
| Lighthouse | Performance audit; can flag long‑running scripts caused by consent managers. | Include Lighthouse CI in PR checks; look for “Total Blocking Time” > 150 ms. |
| Privacy‑Sandbox Debugger (Chrome DevTools) | Shows whether the browser blocks third‑party cookies due to user settings or enterprise policies. | Open DevTools → Application → Storage → Cookies → toggle “Show blocked cookies”. |
GeoIP‑Mock (e.g., geoip-lite or mock-geoip) | Allows you to simulate different origins for testing region‑specific banners. | In Playwright: await page.route('https://ipinfo.io/json', route => route.fulfill({ json: { country: 'DE' } })); |
| User-Agent Switcher | Facilitates testing across mobile/desktop UA strings without needing physical devices. | Set via page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ...') in Playwright. |
When selecting tools, prioritize those that can be run headlessly in CI. Manual checks remain valuable for subjective aspects like visual design and screen‑reader experience, but automating the repetitive checks (storage, network, accessibility) reduces human error and speeds up release cycles.
Edge Cases That Surface Only in Production
Even with exhaustive lab testing, certain failure modes manifest only when the site encounters real‑world traffic patterns, third‑party dynamics, or infrastructural quirks. Below are the most common production‑only gotchas and how to detect them.
1. GeoIP‑Driven Regulation Switching
Many sites serve a GDPR banner to EU visitors and a CCPA‑style notice to users from California. If the geo‑lookup service is misconfigured or returns stale data, a user may see the wrong legal text, leading to non‑compliance.
Detection
- Use a VPN or proxy to change your apparent IP address to a known EU country (e.g., DE) and a US state (e.g., CA).
- Verify that the banner’s header text, link URLs, and vendor list correspond to the appropriate regulation.
- In automated tests, mock the geo‑endpoint (
https://ipinfo.io/json) to return differentcountryvalues and assert the banner’s content changes accordingly.
2. A/B Test Framework Interference
Feature‑flag services (LaunchDarkly, Optimizely) sometimes load after the consent script, causing the banner to be duplicated, hidden, or styled incorrectly. Moreover, a variant may deliberately omit the consent manager to measure “baseline” performance, unintentionally creating a compliance hole.
Detection
- Enable/disable the flag via the service’s dashboard or by setting a cookie (
_flags={"consentBannerVariant":"control"}) before page load. - Run the full consent matrix for each variant; assert that the banner’s DOM structure and JavaScript entry point remain unchanged.
- Look for console errors like
Cannot read property 'addEventListener' of nullindicating the banner tried to attach to a missing node.
3. Third‑Party Tag Manager Loading Consent Asynchronously
If the consent script itself is loaded via a tag manager (GTM, Tealium) with a delay (e.g., “fire on DOM Ready + 500 ms”), there is a window where analytics tags may fire before consent is known. Some CMPs mitigate this by inserting a blocking inline script that sets a consent flag early, but misconfiguration can break the guard.
Detection
- In the Network tab, sort by timeline; locate the request for the consent script and compare its timestamp to the first request for a known third‑party endpoint.
- If any third‑party request precedes the consent script, log a defect.
- Automated test: use Playwright to intercept requests and assert that no
collectortrcall occurs before a response from the consent endpoint URL contains the stringconsent.
4. Service Worker Bypassing Consent
A service worker that caches API responses may also attempt to set its own cookies during a fetch handler, ignoring the page‑level consent flag. This is especially problematic for offline‑first PWAs.
Detection
- Open DevTools → Application → Service Workers, check “Update on reload” and “Bypass for network requests”.
- Trigger a network request that the SW intercepts (e.g., an API call to
/api/user). - Inspect the
Set-Cookieheader in the response; if it appears despite a prior “reject all”, the SW is not checking consent. - Fix: have the SW consult
localStorage.getItem('consent')(or a cookie) before setting any tracking‑related cookies.
5. Iframe‑Embedded Consent (Checkout Flows)
E‑commerce sites often embed a payment iframe from a third‑party provider. If the parent page’s consent decision is not communicated to the iframe, the iframe may load its own trackers, creating a leakage path.
Detection
- Load the checkout page, open the iframe’s devtools (right‑click → “Inspect frame”).
- Verify that the iframe does not set any advertising or analytics cookies unless the parent has granted consent.
- If the iframe provides a
postMessageAPI for consent passing, ensure your parent page sends the appropriate message after a user choice.
6. Consent Corruption via Overly Long Vendor Lists
Some CMPs serialize the full IAB vendor list (over 1 000 entries) into the consent string. Older browsers or restrictive cookie size limits (4 KB) may cause the string to be truncated, resulting in a malformed token that the CMP treats as “no consent”.
Detection
- After accepting all, inspect the consent cookie’s length (
document.cookie.match(/consent=([^;]+)/)[1].length). - If it exceeds 3 800 characters (leaving room for cookie attributes), you are at risk.
- Mitigate by enabling the CMP’s “compact mode” or server‑side storage (e.g., sending the token to your backend and issuing a short identifier).
7. CORS‑Blocked Consent Endpoints
When the consent script fetches a vendor list from a subdomain (consent.example.com) and the response lacks proper Access‑Control‑Allow‑Origin, the request fails silently, leaving the banner in a perpetual “loading” state.
Detection
- Look for network errors with status
(failed)or(blocked:cors)in the DevTools console. - Confirm that the response headers include
Access-Control-Allow-Origin: *or the specific origin. - If you control the endpoint, add the header; otherwise, consider proxying the request through your own domain to avoid CORS.
8. Consent Ignored When JavaScript Is Disabled
A small but notable fraction of users browse with JS disabled (via extensions or corporate policy). If your site relies solely on JS to set the consent cookie, those users will never have a recorded preference, causing the banner to appear on every page view or, worse, allowing trackers to load via ‑embedded pixels.
Detection
- Disable JavaScript in browser settings (or use the DevTools “Disable JavaScript” toggle).
- Reload the page; verify that a static notice appears (if you provide one) and that no third‑party requests fire.
- If you observe tracking pixels despite JS off, move essential consent logic to a server‑side check (e.g., read a cookie set during a prior JS‑enabled visit and honor it via a middleware that blocks outgoing requests).
By systematically reproducing these scenarios in a staging environment that mirrors production (feature flags, geo‑IP services, tag managers, service workers), you can catch the bugs before they affect real users.
Consolidated Checklist
Use this short list as a final gate before releasing any change that touches the consent mechanism, a third‑party tag, or the CMP configuration.
- [ ] First‑visit banner appears within 2 s of
DOMContentLoadedon all target browsers. - [ ] Keyboard navigation: tab order logical, visible focus,
Escbehaves per spec. - [ ] Screen‑reader: role, labels, and state announcements are correct.
- [ ] Contrast: text/background ≥ 4.5:1 (AA) for normal text, ≥ 3:1 for large.
- [ ] Accept All: banner hides, consent stored, all permitted third‑party requests fire.
- [ ] Reject All: banner hides, consent stored, no non‑essential requests fire.
- [ ] Custom selection: only chosen categories result in cookies or storage writes.
- [ ] Persistence: decision survives page reloads, navigation, and new tabs (same origin).
- [ ] Clear storage: removing cookies/localStorage makes banner reappear.
- [ ] Network timing: no third‑party request precedes consent script execution.
- [ ] Service worker: does not set tracking
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free