Audits web pages for GDPR/CCPA cookie consent compliance: clears session, captures pre-consent cookies, detects banners, analyzes accept/reject/preference buttons.
From user-testing-agentnpx claudepluginhub ncklrs/claude-chrome-user-testing --plugin user-testing-agentThis skill uses the workspace's default tool permissions.
criteria.jsonDesigns and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Audit web pages for GDPR and CCPA cookie consent compliance.
Start with a clean browser state:
// Via browser_run_code
async (page) => {
// Clear all cookies
await page.context().clearCookies();
// Clear local storage
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
return 'Session cleared';
}
Before any interaction, record what cookies are set:
async (page) => {
const cookies = await page.context().cookies();
return cookies.map(c => ({
name: c.name,
domain: c.domain,
path: c.path,
expires: c.expires,
httpOnly: c.httpOnly,
secure: c.secure,
sameSite: c.sameSite
}));
}
Classify cookies by purpose:
| Pattern | Category | Essential |
|---|---|---|
session, csrf, auth | Necessary | Yes |
_ga, _gid, analytics | Analytics | No |
_fbp, _gcl, ads | Marketing | No |
prefs, theme, lang | Personalization | No |
Look for cookie consent elements:
// Common selectors for cookie banners
const bannerSelectors = [
'[class*="cookie"]',
'[id*="cookie"]',
'[class*="consent"]',
'[id*="consent"]',
'[class*="gdpr"]',
'[class*="privacy-banner"]',
'[aria-label*="cookie"]',
'[role="dialog"][class*="modal"]'
];
Check consent UI for compliance:
Accept Button Detection:
const acceptSelectors = [
'button:has-text("Accept")',
'button:has-text("Allow")',
'button:has-text("Agree")',
'button:has-text("OK")',
'[class*="accept"]'
];
Reject Button Detection:
const rejectSelectors = [
'button:has-text("Reject")',
'button:has-text("Decline")',
'button:has-text("Deny")',
'a:has-text("Reject")',
'[class*="reject"]',
'[class*="decline"]'
];
Preference Detection:
const preferenceSelectors = [
'button:has-text("Settings")',
'button:has-text("Preferences")',
'button:has-text("Manage")',
'button:has-text("Customize")'
];
Load criteria from criteria.json:
| ID | Criterion | Severity | Check Method |
|---|---|---|---|
| GDPR-1 | Banner before cookies | Critical | Pre-consent cookie check |
| GDPR-2 | No pre-checked boxes | Critical | Checkbox state analysis |
| GDPR-3 | Reject option prominent | Major | Button comparison |
| GDPR-4 | Granular consent | Major | Category options check |
| GDPR-5 | Withdrawal mechanism | Major | Settings accessibility |
| GDPR-6 | Cookie policy link | Minor | Link presence check |
| ID | Criterion | Severity | Check Method |
|---|---|---|---|
| CCPA-1 | Do Not Sell link | Critical | Footer link search |
| CCPA-2 | Opt-out mechanism | Critical | Functional test |
| CCPA-3 | Privacy policy | Major | Link presence check |
| CCPA-4 | Rights disclosure | Minor | Content analysis |
async (page) => {
const checkboxes = await page.$$('input[type="checkbox"]');
const preChecked = [];
for (const checkbox of checkboxes) {
const isChecked = await checkbox.isChecked();
const label = await checkbox.evaluate(el => {
const label = el.closest('label') ||
document.querySelector(`label[for="${el.id}"]`);
return label?.textContent || '';
});
if (isChecked && !isNecessary(label)) {
preChecked.push({ label, checked: true });
}
}
return preChecked;
}
async (page) => {
const accept = await page.$('button:has-text("Accept")');
const reject = await page.$('button:has-text("Reject")');
if (!accept || !reject) return { prominent: false, reason: 'Missing button' };
const acceptBox = await accept.boundingBox();
const rejectBox = await reject.boundingBox();
// Compare sizes
const acceptArea = acceptBox.width * acceptBox.height;
const rejectArea = rejectBox.width * rejectBox.height;
// Reject should be at least 50% the size of accept
return {
prominent: rejectArea >= acceptArea * 0.5,
acceptSize: acceptArea,
rejectSize: rejectArea
};
}
async (page) => {
const ccpaPatterns = [
'Do Not Sell',
'Do Not Share',
'Opt-Out',
'Your Privacy Choices',
'Your California Privacy Rights'
];
for (const pattern of ccpaPatterns) {
const link = await page.$(`a:has-text("${pattern}")`);
if (link) {
return { found: true, text: pattern };
}
}
return { found: false };
}
function calculateScore(results) {
const weights = {
critical: 3,
major: 2,
minor: 1
};
let maxScore = 0;
let actualScore = 0;
for (const result of results) {
const weight = weights[result.severity];
maxScore += weight;
if (result.status === 'pass') {
actualScore += weight;
}
}
const percentage = Math.round((actualScore / maxScore) * 100);
const grade =
percentage >= 90 ? 'A' :
percentage >= 80 ? 'B' :
percentage >= 70 ? 'C' :
percentage >= 60 ? 'D' : 'F';
return { percentage, grade };
}
### FAIL: [ID] - [Title]
- **Severity**: [Critical/Major/Minor]
- **Finding**: [What was observed]
- **Location**: [Where on the page]
- **Fix**: [Remediation guidance]
### PASS: [ID] - [Title]
- **Finding**: [What was observed that indicates compliance]
Pattern: Google Analytics (_ga, _gid) cookies set on page load Detection: Check cookies before any user interaction Fix: Defer analytics until after consent
Pattern: "Marketing cookies" checkbox checked by default Detection: Inspect checkbox state in banner Fix: Default all non-essential to unchecked
Pattern: "Reject" is a small link, "Accept" is a large button Detection: Compare element sizes Fix: Make both options equally prominent
Pattern: No "Do Not Sell" link on California-targeted site Detection: Search footer/privacy areas Fix: Add compliant opt-out link