[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6AFK2ACJDAP46NQ5JZMERBM":3,"skill-same:01M6AFK2ACJDAP46NQ5JZMERBM":108},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":31,"publishedAt":46,"linkOnly":47,"markdownContent":48,"markdownBody":49,"frontMatter":50,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":58,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":61,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M6AFK2ACJDAP46NQ5JZMERBM","cookie-consent-testing",null,"Automated cookie consent validation using Selenium and Playwright. Covers banner interaction testing, consent state verification, tag firing audit after consent choices, regression testing for cookie compliance, and CI\u002FCD pipeline integration.","mukul975\u002FPrivacy-Data-Protection-Skills","mukul975","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F42860185?v=4","01M6ADEQR8T5M4EEG6C67BQCWF","Mahipal","01M6ADNKWX1HF4Q5DKEXT5M774",336,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fcookie-consent-testing\u002FSKILL.md",[18,23,26],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署",{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[32,33,34,35,36,37,38,39,40,41,42,43,44,45],"testing","cookie consent testing","playwright","validation","state","verification","selenium","audit","integration","ci","compliance","privacy","pipeline","regression-testing","2026-11-10 10:12:30",false,"---\nname: cookie-consent-testing\ndescription: >-\n  Automated cookie consent validation using Selenium and Playwright. Covers banner\n  interaction testing, consent state verification, tag firing audit after consent\n  choices, regression testing for cookie compliance, and CI\u002FCD pipeline integration.\nlicense: Apache-2.0\nmetadata:\n  author: mukul975\n  version: \"1.0\"\n  domain: privacy\n  subdomain: cookie-consent-compliance\n  tags: \"cookie-testing, selenium, playwright, consent-validation, regression-testing\"\n---\n\n# Automated Cookie Consent Validation\n\n## Overview\n\nManual cookie consent testing is insufficient for modern web applications where deployments occur multiple times daily and third-party scripts update independently. Automated testing using browser automation frameworks — Selenium and Playwright — enables continuous verification that the cookie consent banner functions correctly, that non-essential cookies are blocked before consent, that consent choices are respected, and that tag firing aligns with the user's consent state. Integrating these tests into the CI\u002FCD pipeline ensures that every deployment is verified for cookie compliance before reaching production.\n\n## Test Architecture\n\n### Test Categories\n\n| Category | What It Tests | When to Run |\n|----------|--------------|-------------|\n| Banner display | Banner appears on first visit; correct layout and text | Every deployment |\n| Pre-consent blocking | No non-essential cookies\u002Ftags before user interaction | Every deployment |\n| Consent acceptance | Accept All sets correct cookies and fires correct tags | Every deployment |\n| Consent rejection | Reject All blocks all non-essential cookies and tags | Every deployment |\n| Granular consent | Per-category toggles work correctly | Every deployment |\n| Consent persistence | Consent state survives page navigation and browser restart | Daily |\n| Consent withdrawal | Changing consent removes cookies and stops tags | Weekly |\n| Consent expiry | Banner re-appears after consent expires | Monthly |\n| GPC signal | GPC header triggers automatic opt-out | Every deployment |\n| Regression | New cookies not introduced without documentation | Every deployment |\n\n### Test Environment Setup\n\n**Playwright Configuration for Pinnacle E-Commerce Ltd:**\n\n```javascript\n\u002F\u002F playwright.config.js\nconst { defineConfig, devices } = require('@playwright\u002Ftest');\n\nmodule.exports = defineConfig({\n  testDir: '.\u002Ftests\u002Fcookie-consent',\n  timeout: 30000,\n  retries: 1,\n  use: {\n    baseURL: 'https:\u002F\u002Fstaging.pinnacle-ecommerce.com',\n    trace: 'on-first-retry',\n    screenshot: 'only-on-failure',\n  },\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] },\n    },\n    {\n      name: 'firefox',\n      use: { ...devices['Desktop Firefox'] },\n    },\n    {\n      name: 'webkit',\n      use: { ...devices['Desktop Safari'] },\n    },\n    {\n      name: 'mobile-chrome',\n      use: { ...devices['Pixel 5'] },\n    },\n    {\n      name: 'mobile-safari',\n      use: { ...devices['iPhone 13'] },\n    },\n  ],\n});\n```\n\n## Test Implementations\n\n### Test 1: Pre-Consent Cookie Blocking\n\nVerify that no non-essential cookies are set before the user interacts with the consent banner.\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fpre-consent-blocking.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\nconst ESSENTIAL_COOKIES = [\n  'session_id',\n  'csrf_token',\n  'consent_state',\n  'load_balancer',\n  'pinnacle_consent_eu',\n  'pinnacle_consent_uk',\n  'pinnacle_consent_ccpa',\n];\n\nconst NON_ESSENTIAL_COOKIES = [\n  '_ga',\n  '_ga_',\n  '_gid',\n  '_fbp',\n  '_fbc',\n  '_gcl_au',\n  '_hjSession',\n  '_hjSessionUser',\n  'IDE',\n  'fr',\n  'NID',\n];\n\ntest.describe('Pre-Consent Cookie Blocking', () => {\n  test('no non-essential cookies are set on page load before consent', async ({ page }) => {\n    \u002F\u002F Navigate to homepage without any prior consent\n    await page.goto('\u002F');\n\n    \u002F\u002F Wait for page to fully load including all third-party scripts\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Get all cookies\n    const cookies = await page.context().cookies();\n    const cookieNames = cookies.map(c => c.name);\n\n    \u002F\u002F Verify no non-essential cookies exist\n    for (const nonEssential of NON_ESSENTIAL_COOKIES) {\n      const found = cookieNames.filter(name => name.startsWith(nonEssential));\n      expect(found, `Non-essential cookie ${nonEssential} found before consent`).toHaveLength(0);\n    }\n  });\n\n  test('no analytics network requests before consent', async ({ page }) => {\n    const analyticsRequests = [];\n\n    \u002F\u002F Monitor network requests\n    page.on('request', request => {\n      const url = request.url();\n      if (\n        url.includes('google-analytics.com') ||\n        url.includes('analytics.google.com') ||\n        url.includes('facebook.com\u002Ftr') ||\n        url.includes('connect.facebook.net') ||\n        url.includes('hotjar.com')\n      ) {\n        analyticsRequests.push(url);\n      }\n    });\n\n    await page.goto('\u002F');\n    await page.waitForLoadState('networkidle');\n\n    expect(analyticsRequests, 'Analytics requests fired before consent').toHaveLength(0);\n  });\n\n  test('no localStorage tracking entries before consent', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.waitForLoadState('networkidle');\n\n    const trackingKeys = await page.evaluate(() => {\n      const suspicious = [];\n      for (let i = 0; i \u003C localStorage.length; i++) {\n        const key = localStorage.key(i);\n        if (key.startsWith('_hj') || key.startsWith('_ga') || key.includes('fb_')) {\n          suspicious.push(key);\n        }\n      }\n      return suspicious;\n    });\n\n    expect(trackingKeys, 'Tracking localStorage entries found before consent').toHaveLength(0);\n  });\n});\n```\n\n### Test 2: Banner Display and Interaction\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fbanner-display.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\ntest.describe('Cookie Banner Display', () => {\n  test('banner appears on first visit', async ({ page }) => {\n    await page.goto('\u002F');\n\n    \u002F\u002F Banner should be visible\n    const banner = page.locator('[data-testid=\"cookie-banner\"]');\n    await expect(banner).toBeVisible();\n  });\n\n  test('banner has Accept All button', async ({ page }) => {\n    await page.goto('\u002F');\n    const acceptButton = page.locator('[data-testid=\"cookie-accept-all\"]');\n    await expect(acceptButton).toBeVisible();\n    await expect(acceptButton).toHaveText(\u002FAccept All\u002Fi);\n  });\n\n  test('banner has Reject All button with equal prominence', async ({ page }) => {\n    await page.goto('\u002F');\n    const acceptButton = page.locator('[data-testid=\"cookie-accept-all\"]');\n    const rejectButton = page.locator('[data-testid=\"cookie-reject-all\"]');\n\n    await expect(rejectButton).toBeVisible();\n    await expect(rejectButton).toHaveText(\u002FReject All\u002Fi);\n\n    \u002F\u002F Verify equal visual prominence (same size)\n    const acceptBox = await acceptButton.boundingBox();\n    const rejectBox = await rejectButton.boundingBox();\n\n    expect(Math.abs(acceptBox.width - rejectBox.width)).toBeLessThan(10);\n    expect(Math.abs(acceptBox.height - rejectBox.height)).toBeLessThan(5);\n  });\n\n  test('banner has Customise\u002FManage Preferences option', async ({ page }) => {\n    await page.goto('\u002F');\n    const customiseButton = page.locator('[data-testid=\"cookie-customise\"]');\n    await expect(customiseButton).toBeVisible();\n  });\n\n  test('banner does not reappear after making a choice', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-accept-all\"]').click();\n\n    \u002F\u002F Navigate to another page\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n\n    const banner = page.locator('[data-testid=\"cookie-banner\"]');\n    await expect(banner).not.toBeVisible();\n  });\n\n  test('banner is keyboard accessible', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.keyboard.press('Tab');\n\n    \u002F\u002F Focus should be within the banner\n    const focusedElement = await page.evaluate(() => {\n      const el = document.activeElement;\n      return el ? el.closest('[data-testid=\"cookie-banner\"]') !== null : false;\n    });\n    expect(focusedElement).toBe(true);\n  });\n});\n```\n\n### Test 3: Consent State Verification\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fconsent-state.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\ntest.describe('Consent State Verification', () => {\n  test('Accept All sets all consent categories to granted', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-accept-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    const cookies = await page.context().cookies();\n    const consentCookie = cookies.find(c => c.name === 'pinnacle_consent_eu');\n\n    expect(consentCookie).toBeTruthy();\n    const consentState = JSON.parse(decodeURIComponent(consentCookie.value));\n    expect(consentState.analytics).toBe(true);\n    expect(consentState.advertising).toBe(true);\n    expect(consentState.functionality).toBe(true);\n  });\n\n  test('Reject All sets all consent categories to denied', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-reject-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    const cookies = await page.context().cookies();\n    const consentCookie = cookies.find(c => c.name === 'pinnacle_consent_eu');\n\n    expect(consentCookie).toBeTruthy();\n    const consentState = JSON.parse(decodeURIComponent(consentCookie.value));\n    expect(consentState.analytics).toBe(false);\n    expect(consentState.advertising).toBe(false);\n    expect(consentState.functionality).toBe(false);\n  });\n\n  test('Reject All blocks GA4 cookies', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-reject-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Navigate to multiple pages to give GA4 time to attempt cookie setting\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n    await page.goto('\u002Fabout');\n    await page.waitForLoadState('networkidle');\n\n    const cookies = await page.context().cookies();\n    const gaCookies = cookies.filter(c => c.name.startsWith('_ga'));\n    expect(gaCookies).toHaveLength(0);\n  });\n\n  test('Granular consent: analytics only sets only analytics cookies', async ({ page }) => {\n    await page.goto('\u002F');\n\n    \u002F\u002F Open customisation layer\n    await page.locator('[data-testid=\"cookie-customise\"]').click();\n\n    \u002F\u002F Enable only analytics\n    const analyticsToggle = page.locator('[data-testid=\"consent-toggle-analytics\"]');\n    await analyticsToggle.click();\n\n    \u002F\u002F Ensure advertising is off\n    const advertisingToggle = page.locator('[data-testid=\"consent-toggle-advertising\"]');\n    const isAdvertisingChecked = await advertisingToggle.isChecked();\n    expect(isAdvertisingChecked).toBe(false);\n\n    \u002F\u002F Confirm choices\n    await page.locator('[data-testid=\"cookie-confirm-choices\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    const cookies = await page.context().cookies();\n    const cookieNames = cookies.map(c => c.name);\n\n    \u002F\u002F GA cookies should be present\n    expect(cookieNames.some(n => n.startsWith('_ga'))).toBe(true);\n\n    \u002F\u002F Advertising cookies should NOT be present\n    expect(cookieNames.some(n => n === '_fbp')).toBe(false);\n    expect(cookieNames.some(n => n === '_gcl_au')).toBe(false);\n  });\n});\n```\n\n### Test 4: Tag Firing Audit\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Ftag-firing.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\ntest.describe('Tag Firing Audit', () => {\n  test('GA4 tag fires only after analytics consent', async ({ page }) => {\n    let ga4Fired = false;\n\n    page.on('request', request => {\n      if (request.url().includes('google-analytics.com\u002Fg\u002Fcollect') ||\n          request.url().includes('analytics.google.com\u002Fg\u002Fcollect')) {\n        ga4Fired = true;\n      }\n    });\n\n    \u002F\u002F Load page — GA4 should not fire\n    await page.goto('\u002F');\n    await page.waitForLoadState('networkidle');\n    expect(ga4Fired, 'GA4 fired before consent').toBe(false);\n\n    \u002F\u002F Accept analytics\n    await page.locator('[data-testid=\"cookie-customise\"]').click();\n    await page.locator('[data-testid=\"consent-toggle-analytics\"]').click();\n    await page.locator('[data-testid=\"cookie-confirm-choices\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Navigate to trigger a pageview\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n    expect(ga4Fired, 'GA4 did not fire after analytics consent').toBe(true);\n  });\n\n  test('Meta Pixel does not fire when advertising is rejected', async ({ page }) => {\n    let metaPixelFired = false;\n\n    page.on('request', request => {\n      if (request.url().includes('facebook.com\u002Ftr') ||\n          request.url().includes('connect.facebook.net')) {\n        metaPixelFired = true;\n      }\n    });\n\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-reject-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n    await page.goto('\u002Fproducts\u002Fwidget-pro');\n    await page.waitForLoadState('networkidle');\n\n    expect(metaPixelFired, 'Meta Pixel fired after reject all').toBe(false);\n  });\n\n  test('Google Consent Mode sends correct consent state', async ({ page }) => {\n    const consentPings = [];\n\n    page.on('request', request => {\n      const url = request.url();\n      if (url.includes('google-analytics.com') && url.includes('gcs=')) {\n        const gcsMatch = url.match(\u002Fgcs=([^&]+)\u002F);\n        if (gcsMatch) consentPings.push(gcsMatch[1]);\n      }\n    });\n\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-reject-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Navigate to trigger consent mode ping\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F If consent mode pings are sent, verify denied state\n    if (consentPings.length > 0) {\n      for (const gcs of consentPings) {\n        \u002F\u002F G100 = all denied, G111 = all granted\n        expect(gcs).toContain('100');\n      }\n    }\n  });\n});\n```\n\n### Test 5: Cookie Regression Detection\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fregression.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\nconst fs = require('fs');\n\n\u002F\u002F Baseline of known cookies — update when new cookies are intentionally added\nconst KNOWN_COOKIES = new Set([\n  'session_id',\n  'csrf_token',\n  'consent_state',\n  'pinnacle_consent_eu',\n  'load_balancer',\n  '_ga',\n  '_ga_PINNACLE',\n  '_gid',\n  '_fbp',\n  '_fbc',\n  '_gcl_au',\n  '_hjSessionUser',\n  '_hjSession',\n  'locale',\n  'currency',\n  'recently_viewed',\n  'cart_session',\n  'auth_token',\n]);\n\ntest.describe('Cookie Regression Detection', () => {\n  test('no unknown cookies after Accept All', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-accept-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Visit several pages to trigger all tag scenarios\n    const pages = ['\u002F', '\u002Fproducts', '\u002Fproducts\u002Fwidget-pro', '\u002Fcart', '\u002Fabout', '\u002Fblog'];\n    for (const path of pages) {\n      await page.goto(path);\n      await page.waitForLoadState('networkidle');\n    }\n\n    const cookies = await page.context().cookies();\n    const unknownCookies = cookies.filter(c => {\n      \u002F\u002F Check if cookie name matches any known prefix\n      return !Array.from(KNOWN_COOKIES).some(known =>\n        c.name === known || c.name.startsWith(known)\n      );\n    });\n\n    if (unknownCookies.length > 0) {\n      const unknownNames = unknownCookies.map(c => `${c.name} (domain: ${c.domain}, expires: ${c.expires})`);\n      console.error('Unknown cookies detected:', unknownNames);\n\n      \u002F\u002F Write to report file for review\n      fs.writeFileSync(\n        'test-results\u002Funknown-cookies.json',\n        JSON.stringify(unknownCookies, null, 2)\n      );\n    }\n\n    expect(unknownCookies, `Unknown cookies found: ${unknownCookies.map(c => c.name).join(', ')}`).toHaveLength(0);\n  });\n});\n```\n\n### Test 6: GPC Signal Handling\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fgpc-signal.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\ntest.describe('Global Privacy Control Signal', () => {\n  test('GPC signal triggers automatic opt-out for California users', async ({ browser }) => {\n    \u002F\u002F Create context with GPC header\n    const context = await browser.newContext({\n      extraHTTPHeaders: {\n        'Sec-GPC': '1',\n      },\n      locale: 'en-US',\n      geolocation: { latitude: 34.0522, longitude: -118.2437 }, \u002F\u002F Los Angeles\n      permissions: ['geolocation'],\n    });\n\n    const page = await context.newPage();\n    await page.goto('\u002F');\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Verify advertising cookies are not set\n    const cookies = await context.cookies();\n    const adCookies = cookies.filter(c =>\n      c.name === '_fbp' || c.name === '_gcl_au' || c.name.startsWith('IDE')\n    );\n\n    expect(adCookies, 'Advertising cookies set despite GPC signal').toHaveLength(0);\n\n    await context.close();\n  });\n});\n```\n\n## CI\u002FCD Pipeline Integration\n\n### GitHub Actions Workflow\n\n```yaml\nname: Cookie Consent Compliance Tests\non:\n  push:\n    branches: [main, staging]\n  pull_request:\n    branches: [main]\n  schedule:\n    - cron: '0 6 * * 1'  # Weekly Monday 6 AM UTC\n\njobs:\n  cookie-consent-tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: '20'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Install Playwright browsers\n        run: npx playwright install --with-deps\n\n      - name: Run cookie consent tests\n        run: npx playwright test tests\u002Fcookie-consent\u002F\n        env:\n          BASE_URL: ${{ vars.STAGING_URL }}\n\n      - name: Upload test results\n        uses: actions\u002Fupload-artifact@v4\n        if: always()\n        with:\n          name: cookie-consent-test-results\n          path: |\n            test-results\u002F\n            playwright-report\u002F\n\n      - name: Post results to Slack\n        if: failure()\n        uses: slackapi\u002Fslack-github-action@v1\n        with:\n          payload: |\n            {\n              \"text\": \"Cookie consent tests FAILED on ${{ github.ref }}. Review: ${{ github.server_url }}\u002F${{ github.repository }}\u002Factions\u002Fruns\u002F${{ github.run_id }}\"\n            }\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_PRIVACY_WEBHOOK }}\n```\n\n## Key Legal and Technical References\n\n- **ePrivacy Directive 2002\u002F58\u002FEC, Article 5(3)** — Legal requirement that automated tests verify\n- **CJEU Case C-673\u002F17 (Planet49)** — Active consent requirements validated by banner interaction tests\n- **CNIL Deliberation No. 2020-091** — Equal prominence requirement tested by button size comparison\n- **CCPA\u002FCPRA Cal. Civ. Code §1798.135(e)** — GPC signal handling tested in GPC test suite\n- **Playwright Documentation** — Browser automation framework for cross-browser testing\n- **WCAG 2.1 Level AA** — Accessibility requirements tested by keyboard navigation tests\n- **Google Consent Mode Documentation** — Consent state parameters validated in tag firing tests\n","# Automated Cookie Consent Validation\n\n## Overview\n\nManual cookie consent testing is insufficient for modern web applications where deployments occur multiple times daily and third-party scripts update independently. Automated testing using browser automation frameworks — Selenium and Playwright — enables continuous verification that the cookie consent banner functions correctly, that non-essential cookies are blocked before consent, that consent choices are respected, and that tag firing aligns with the user's consent state. Integrating these tests into the CI\u002FCD pipeline ensures that every deployment is verified for cookie compliance before reaching production.\n\n## Test Architecture\n\n### Test Categories\n\n| Category | What It Tests | When to Run |\n|----------|--------------|-------------|\n| Banner display | Banner appears on first visit; correct layout and text | Every deployment |\n| Pre-consent blocking | No non-essential cookies\u002Ftags before user interaction | Every deployment |\n| Consent acceptance | Accept All sets correct cookies and fires correct tags | Every deployment |\n| Consent rejection | Reject All blocks all non-essential cookies and tags | Every deployment |\n| Granular consent | Per-category toggles work correctly | Every deployment |\n| Consent persistence | Consent state survives page navigation and browser restart | Daily |\n| Consent withdrawal | Changing consent removes cookies and stops tags | Weekly |\n| Consent expiry | Banner re-appears after consent expires | Monthly |\n| GPC signal | GPC header triggers automatic opt-out | Every deployment |\n| Regression | New cookies not introduced without documentation | Every deployment |\n\n### Test Environment Setup\n\n**Playwright Configuration for Pinnacle E-Commerce Ltd:**\n\n```javascript\n\u002F\u002F playwright.config.js\nconst { defineConfig, devices } = require('@playwright\u002Ftest');\n\nmodule.exports = defineConfig({\n  testDir: '.\u002Ftests\u002Fcookie-consent',\n  timeout: 30000,\n  retries: 1,\n  use: {\n    baseURL: 'https:\u002F\u002Fstaging.pinnacle-ecommerce.com',\n    trace: 'on-first-retry',\n    screenshot: 'only-on-failure',\n  },\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] },\n    },\n    {\n      name: 'firefox',\n      use: { ...devices['Desktop Firefox'] },\n    },\n    {\n      name: 'webkit',\n      use: { ...devices['Desktop Safari'] },\n    },\n    {\n      name: 'mobile-chrome',\n      use: { ...devices['Pixel 5'] },\n    },\n    {\n      name: 'mobile-safari',\n      use: { ...devices['iPhone 13'] },\n    },\n  ],\n});\n```\n\n## Test Implementations\n\n### Test 1: Pre-Consent Cookie Blocking\n\nVerify that no non-essential cookies are set before the user interacts with the consent banner.\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fpre-consent-blocking.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\nconst ESSENTIAL_COOKIES = [\n  'session_id',\n  'csrf_token',\n  'consent_state',\n  'load_balancer',\n  'pinnacle_consent_eu',\n  'pinnacle_consent_uk',\n  'pinnacle_consent_ccpa',\n];\n\nconst NON_ESSENTIAL_COOKIES = [\n  '_ga',\n  '_ga_',\n  '_gid',\n  '_fbp',\n  '_fbc',\n  '_gcl_au',\n  '_hjSession',\n  '_hjSessionUser',\n  'IDE',\n  'fr',\n  'NID',\n];\n\ntest.describe('Pre-Consent Cookie Blocking', () => {\n  test('no non-essential cookies are set on page load before consent', async ({ page }) => {\n    \u002F\u002F Navigate to homepage without any prior consent\n    await page.goto('\u002F');\n\n    \u002F\u002F Wait for page to fully load including all third-party scripts\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Get all cookies\n    const cookies = await page.context().cookies();\n    const cookieNames = cookies.map(c => c.name);\n\n    \u002F\u002F Verify no non-essential cookies exist\n    for (const nonEssential of NON_ESSENTIAL_COOKIES) {\n      const found = cookieNames.filter(name => name.startsWith(nonEssential));\n      expect(found, `Non-essential cookie ${nonEssential} found before consent`).toHaveLength(0);\n    }\n  });\n\n  test('no analytics network requests before consent', async ({ page }) => {\n    const analyticsRequests = [];\n\n    \u002F\u002F Monitor network requests\n    page.on('request', request => {\n      const url = request.url();\n      if (\n        url.includes('google-analytics.com') ||\n        url.includes('analytics.google.com') ||\n        url.includes('facebook.com\u002Ftr') ||\n        url.includes('connect.facebook.net') ||\n        url.includes('hotjar.com')\n      ) {\n        analyticsRequests.push(url);\n      }\n    });\n\n    await page.goto('\u002F');\n    await page.waitForLoadState('networkidle');\n\n    expect(analyticsRequests, 'Analytics requests fired before consent').toHaveLength(0);\n  });\n\n  test('no localStorage tracking entries before consent', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.waitForLoadState('networkidle');\n\n    const trackingKeys = await page.evaluate(() => {\n      const suspicious = [];\n      for (let i = 0; i \u003C localStorage.length; i++) {\n        const key = localStorage.key(i);\n        if (key.startsWith('_hj') || key.startsWith('_ga') || key.includes('fb_')) {\n          suspicious.push(key);\n        }\n      }\n      return suspicious;\n    });\n\n    expect(trackingKeys, 'Tracking localStorage entries found before consent').toHaveLength(0);\n  });\n});\n```\n\n### Test 2: Banner Display and Interaction\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fbanner-display.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\ntest.describe('Cookie Banner Display', () => {\n  test('banner appears on first visit', async ({ page }) => {\n    await page.goto('\u002F');\n\n    \u002F\u002F Banner should be visible\n    const banner = page.locator('[data-testid=\"cookie-banner\"]');\n    await expect(banner).toBeVisible();\n  });\n\n  test('banner has Accept All button', async ({ page }) => {\n    await page.goto('\u002F');\n    const acceptButton = page.locator('[data-testid=\"cookie-accept-all\"]');\n    await expect(acceptButton).toBeVisible();\n    await expect(acceptButton).toHaveText(\u002FAccept All\u002Fi);\n  });\n\n  test('banner has Reject All button with equal prominence', async ({ page }) => {\n    await page.goto('\u002F');\n    const acceptButton = page.locator('[data-testid=\"cookie-accept-all\"]');\n    const rejectButton = page.locator('[data-testid=\"cookie-reject-all\"]');\n\n    await expect(rejectButton).toBeVisible();\n    await expect(rejectButton).toHaveText(\u002FReject All\u002Fi);\n\n    \u002F\u002F Verify equal visual prominence (same size)\n    const acceptBox = await acceptButton.boundingBox();\n    const rejectBox = await rejectButton.boundingBox();\n\n    expect(Math.abs(acceptBox.width - rejectBox.width)).toBeLessThan(10);\n    expect(Math.abs(acceptBox.height - rejectBox.height)).toBeLessThan(5);\n  });\n\n  test('banner has Customise\u002FManage Preferences option', async ({ page }) => {\n    await page.goto('\u002F');\n    const customiseButton = page.locator('[data-testid=\"cookie-customise\"]');\n    await expect(customiseButton).toBeVisible();\n  });\n\n  test('banner does not reappear after making a choice', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-accept-all\"]').click();\n\n    \u002F\u002F Navigate to another page\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n\n    const banner = page.locator('[data-testid=\"cookie-banner\"]');\n    await expect(banner).not.toBeVisible();\n  });\n\n  test('banner is keyboard accessible', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.keyboard.press('Tab');\n\n    \u002F\u002F Focus should be within the banner\n    const focusedElement = await page.evaluate(() => {\n      const el = document.activeElement;\n      return el ? el.closest('[data-testid=\"cookie-banner\"]') !== null : false;\n    });\n    expect(focusedElement).toBe(true);\n  });\n});\n```\n\n### Test 3: Consent State Verification\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fconsent-state.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\ntest.describe('Consent State Verification', () => {\n  test('Accept All sets all consent categories to granted', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-accept-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    const cookies = await page.context().cookies();\n    const consentCookie = cookies.find(c => c.name === 'pinnacle_consent_eu');\n\n    expect(consentCookie).toBeTruthy();\n    const consentState = JSON.parse(decodeURIComponent(consentCookie.value));\n    expect(consentState.analytics).toBe(true);\n    expect(consentState.advertising).toBe(true);\n    expect(consentState.functionality).toBe(true);\n  });\n\n  test('Reject All sets all consent categories to denied', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-reject-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    const cookies = await page.context().cookies();\n    const consentCookie = cookies.find(c => c.name === 'pinnacle_consent_eu');\n\n    expect(consentCookie).toBeTruthy();\n    const consentState = JSON.parse(decodeURIComponent(consentCookie.value));\n    expect(consentState.analytics).toBe(false);\n    expect(consentState.advertising).toBe(false);\n    expect(consentState.functionality).toBe(false);\n  });\n\n  test('Reject All blocks GA4 cookies', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-reject-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Navigate to multiple pages to give GA4 time to attempt cookie setting\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n    await page.goto('\u002Fabout');\n    await page.waitForLoadState('networkidle');\n\n    const cookies = await page.context().cookies();\n    const gaCookies = cookies.filter(c => c.name.startsWith('_ga'));\n    expect(gaCookies).toHaveLength(0);\n  });\n\n  test('Granular consent: analytics only sets only analytics cookies', async ({ page }) => {\n    await page.goto('\u002F');\n\n    \u002F\u002F Open customisation layer\n    await page.locator('[data-testid=\"cookie-customise\"]').click();\n\n    \u002F\u002F Enable only analytics\n    const analyticsToggle = page.locator('[data-testid=\"consent-toggle-analytics\"]');\n    await analyticsToggle.click();\n\n    \u002F\u002F Ensure advertising is off\n    const advertisingToggle = page.locator('[data-testid=\"consent-toggle-advertising\"]');\n    const isAdvertisingChecked = await advertisingToggle.isChecked();\n    expect(isAdvertisingChecked).toBe(false);\n\n    \u002F\u002F Confirm choices\n    await page.locator('[data-testid=\"cookie-confirm-choices\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    const cookies = await page.context().cookies();\n    const cookieNames = cookies.map(c => c.name);\n\n    \u002F\u002F GA cookies should be present\n    expect(cookieNames.some(n => n.startsWith('_ga'))).toBe(true);\n\n    \u002F\u002F Advertising cookies should NOT be present\n    expect(cookieNames.some(n => n === '_fbp')).toBe(false);\n    expect(cookieNames.some(n => n === '_gcl_au')).toBe(false);\n  });\n});\n```\n\n### Test 4: Tag Firing Audit\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Ftag-firing.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\ntest.describe('Tag Firing Audit', () => {\n  test('GA4 tag fires only after analytics consent', async ({ page }) => {\n    let ga4Fired = false;\n\n    page.on('request', request => {\n      if (request.url().includes('google-analytics.com\u002Fg\u002Fcollect') ||\n          request.url().includes('analytics.google.com\u002Fg\u002Fcollect')) {\n        ga4Fired = true;\n      }\n    });\n\n    \u002F\u002F Load page — GA4 should not fire\n    await page.goto('\u002F');\n    await page.waitForLoadState('networkidle');\n    expect(ga4Fired, 'GA4 fired before consent').toBe(false);\n\n    \u002F\u002F Accept analytics\n    await page.locator('[data-testid=\"cookie-customise\"]').click();\n    await page.locator('[data-testid=\"consent-toggle-analytics\"]').click();\n    await page.locator('[data-testid=\"cookie-confirm-choices\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Navigate to trigger a pageview\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n    expect(ga4Fired, 'GA4 did not fire after analytics consent').toBe(true);\n  });\n\n  test('Meta Pixel does not fire when advertising is rejected', async ({ page }) => {\n    let metaPixelFired = false;\n\n    page.on('request', request => {\n      if (request.url().includes('facebook.com\u002Ftr') ||\n          request.url().includes('connect.facebook.net')) {\n        metaPixelFired = true;\n      }\n    });\n\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-reject-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n    await page.goto('\u002Fproducts\u002Fwidget-pro');\n    await page.waitForLoadState('networkidle');\n\n    expect(metaPixelFired, 'Meta Pixel fired after reject all').toBe(false);\n  });\n\n  test('Google Consent Mode sends correct consent state', async ({ page }) => {\n    const consentPings = [];\n\n    page.on('request', request => {\n      const url = request.url();\n      if (url.includes('google-analytics.com') && url.includes('gcs=')) {\n        const gcsMatch = url.match(\u002Fgcs=([^&]+)\u002F);\n        if (gcsMatch) consentPings.push(gcsMatch[1]);\n      }\n    });\n\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-reject-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Navigate to trigger consent mode ping\n    await page.goto('\u002Fproducts');\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F If consent mode pings are sent, verify denied state\n    if (consentPings.length > 0) {\n      for (const gcs of consentPings) {\n        \u002F\u002F G100 = all denied, G111 = all granted\n        expect(gcs).toContain('100');\n      }\n    }\n  });\n});\n```\n\n### Test 5: Cookie Regression Detection\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fregression.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\nconst fs = require('fs');\n\n\u002F\u002F Baseline of known cookies — update when new cookies are intentionally added\nconst KNOWN_COOKIES = new Set([\n  'session_id',\n  'csrf_token',\n  'consent_state',\n  'pinnacle_consent_eu',\n  'load_balancer',\n  '_ga',\n  '_ga_PINNACLE',\n  '_gid',\n  '_fbp',\n  '_fbc',\n  '_gcl_au',\n  '_hjSessionUser',\n  '_hjSession',\n  'locale',\n  'currency',\n  'recently_viewed',\n  'cart_session',\n  'auth_token',\n]);\n\ntest.describe('Cookie Regression Detection', () => {\n  test('no unknown cookies after Accept All', async ({ page }) => {\n    await page.goto('\u002F');\n    await page.locator('[data-testid=\"cookie-accept-all\"]').click();\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Visit several pages to trigger all tag scenarios\n    const pages = ['\u002F', '\u002Fproducts', '\u002Fproducts\u002Fwidget-pro', '\u002Fcart', '\u002Fabout', '\u002Fblog'];\n    for (const path of pages) {\n      await page.goto(path);\n      await page.waitForLoadState('networkidle');\n    }\n\n    const cookies = await page.context().cookies();\n    const unknownCookies = cookies.filter(c => {\n      \u002F\u002F Check if cookie name matches any known prefix\n      return !Array.from(KNOWN_COOKIES).some(known =>\n        c.name === known || c.name.startsWith(known)\n      );\n    });\n\n    if (unknownCookies.length > 0) {\n      const unknownNames = unknownCookies.map(c => `${c.name} (domain: ${c.domain}, expires: ${c.expires})`);\n      console.error('Unknown cookies detected:', unknownNames);\n\n      \u002F\u002F Write to report file for review\n      fs.writeFileSync(\n        'test-results\u002Funknown-cookies.json',\n        JSON.stringify(unknownCookies, null, 2)\n      );\n    }\n\n    expect(unknownCookies, `Unknown cookies found: ${unknownCookies.map(c => c.name).join(', ')}`).toHaveLength(0);\n  });\n});\n```\n\n### Test 6: GPC Signal Handling\n\n```javascript\n\u002F\u002F tests\u002Fcookie-consent\u002Fgpc-signal.spec.js\nconst { test, expect } = require('@playwright\u002Ftest');\n\ntest.describe('Global Privacy Control Signal', () => {\n  test('GPC signal triggers automatic opt-out for California users', async ({ browser }) => {\n    \u002F\u002F Create context with GPC header\n    const context = await browser.newContext({\n      extraHTTPHeaders: {\n        'Sec-GPC': '1',\n      },\n      locale: 'en-US',\n      geolocation: { latitude: 34.0522, longitude: -118.2437 }, \u002F\u002F Los Angeles\n      permissions: ['geolocation'],\n    });\n\n    const page = await context.newPage();\n    await page.goto('\u002F');\n    await page.waitForLoadState('networkidle');\n\n    \u002F\u002F Verify advertising cookies are not set\n    const cookies = await context.cookies();\n    const adCookies = cookies.filter(c =>\n      c.name === '_fbp' || c.name === '_gcl_au' || c.name.startsWith('IDE')\n    );\n\n    expect(adCookies, 'Advertising cookies set despite GPC signal').toHaveLength(0);\n\n    await context.close();\n  });\n});\n```\n\n## CI\u002FCD Pipeline Integration\n\n### GitHub Actions Workflow\n\n```yaml\nname: Cookie Consent Compliance Tests\non:\n  push:\n    branches: [main, staging]\n  pull_request:\n    branches: [main]\n  schedule:\n    - cron: '0 6 * * 1'  # Weekly Monday 6 AM UTC\n\njobs:\n  cookie-consent-tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: '20'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Install Playwright browsers\n        run: npx playwright install --with-deps\n\n      - name: Run cookie consent tests\n        run: npx playwright test tests\u002Fcookie-consent\u002F\n        env:\n          BASE_URL: ${{ vars.STAGING_URL }}\n\n      - name: Upload test results\n        uses: actions\u002Fupload-artifact@v4\n        if: always()\n        with:\n          name: cookie-consent-test-results\n          path: |\n            test-results\u002F\n            playwright-report\u002F\n\n      - name: Post results to Slack\n        if: failure()\n        uses: slackapi\u002Fslack-github-action@v1\n        with:\n          payload: |\n            {\n              \"text\": \"Cookie consent tests FAILED on ${{ github.ref }}. Review: ${{ github.server_url }}\u002F${{ github.repository }}\u002Factions\u002Fruns\u002F${{ github.run_id }}\"\n            }\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_PRIVACY_WEBHOOK }}\n```\n\n## Key Legal and Technical References\n\n- **ePrivacy Directive 2002\u002F58\u002FEC, Article 5(3)** — Legal requirement that automated tests verify\n- **CJEU Case C-673\u002F17 (Planet49)** — Active consent requirements validated by banner interaction tests\n- **CNIL Deliberation No. 2020-091** — Equal prominence requirement tested by button size comparison\n- **CCPA\u002FCPRA Cal. Civ. Code §1798.135(e)** — GPC signal handling tested in GPC test suite\n- **Playwright Documentation** — Browser automation framework for cross-browser testing\n- **WCAG 2.1 Level AA** — Accessibility requirements tested by keyboard navigation tests\n- **Google Consent Mode Documentation** — Consent state parameters validated in tag firing tests\n",{"name":5,"license":15,"metadata":51,"description":7},{"tags":52,"author":9,"domain":43,"version":53,"subdomain":54},"cookie-testing, selenium, playwright, consent-validation, regression-testing","1.0","cookie-consent-compliance","main","9b2ef9eae161c00a17241d42a388571321b33e9f","skills\u002Fprivacy\u002Fcookie-consent-testing",67,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills","https:\u002F\u002Fgithub.com\u002Fmukul975","Mahipal.engineer","🔐 Cybersecurity | 💻 Dev | 📷 Street Photographer\r\n🎓 MSc | 📚 Research & AI Security","2026-03-17 04:06:52",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add mukul975\u002FPrivacy-Data-Protection-Skills --skill cookie-consent-testing",{"tool":70,"command":71},"bunx","bunx skills add mukul975\u002FPrivacy-Data-Protection-Skills --skill cookie-consent-testing",{"tool":73,"command":74},"pnpm","pnpm dlx skills add mukul975\u002FPrivacy-Data-Protection-Skills --skill cookie-consent-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fcookie-consent-testing\u002FSKILL.md",true,[78,84,90,94,98,102],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"assets\u002Ftemplate.md","ASSET","资产文件",1389,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fcookie-consent-testing\u002Fassets\u002Ftemplate.md",{"path":85,"resourceType":86,"resourceTypeDesc":87,"sizeBytes":88,"sourceHtmlUrl":89},"references\u002Ffull-details.md","REFERENCE","参考文档",2546,"https:\u002F\u002Fgithub.com\u002Fasermax\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fkatachi\u002Fskills\u002Fdelta-details\u002Freferences\u002Ffull-details.md",{"path":91,"resourceType":86,"resourceTypeDesc":87,"sizeBytes":92,"sourceHtmlUrl":93},"references\u002Fquestion-mode.md",1753,"https:\u002F\u002Fgithub.com\u002Fasermax\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fkatachi\u002Fskills\u002Fdelta-details\u002Freferences\u002Fquestion-mode.md",{"path":95,"resourceType":86,"resourceTypeDesc":87,"sizeBytes":96,"sourceHtmlUrl":97},"references\u002Fstandards.md",1588,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fcookie-consent-testing\u002Freferences\u002Fstandards.md",{"path":99,"resourceType":86,"resourceTypeDesc":87,"sizeBytes":100,"sourceHtmlUrl":101},"references\u002Fworkflows.md",2196,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fcookie-consent-testing\u002Freferences\u002Fworkflows.md",{"path":103,"resourceType":104,"resourceTypeDesc":105,"sizeBytes":106,"sourceHtmlUrl":107},"scripts\u002Fprocess.py","SCRIPT","脚本",9632,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fcookie-consent-testing\u002Fscripts\u002Fprocess.py",[109,128,145,157,174,187],{"skillNo":110,"name":111,"nameZh":6,"description":112,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":120,"publishedAt":46},"01M6AG6K8R3XW10ENWH2FH18RH","workplace-email-privacy","Implements email and internet monitoring compliance in the workplace per Barbulescu v Romania (ECHR Grand Chamber), EDPB guidance, and national labour law. Covers acceptable use policies, legitimate expectation of privacy, proportionality testing, and content vs metadata monitoring. Keywords: email monitoring, Barbulescu, workplace privacy, internet monitoring, acceptable use policy, ECHR, proportionality.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fworkplace-email-privacy\u002FSKILL.md",[115,118,119],{"categoryNo":116,"categoryName":117,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000005","邮件与沟通",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},[121,122,43,42,123,124,125,32,126,127],"email","workplace email privacy","content","data","policy","monitoring","metadata",{"skillNo":129,"name":130,"nameZh":6,"description":131,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":138,"publishedAt":46},"01M6AG6FRCBJY7D061BJZMNV49","whistleblower-data","Implements data protection compliance for whistleblowing systems under EU Directive 2019\u002F1937 and GDPR. Covers anonymous reporting channels, identity protection for whistleblowers and accused persons, retention limits, access restrictions, and retaliation prevention. Addresses national transpositions and DPA guidance. Keywords: whistleblower, Directive 2019\u002F1937, anonymous reporting, identity protection, retaliation, retention, reporting channel.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fwhistleblower-data\u002FSKILL.md",[134,137],{"categoryNo":135,"categoryName":136,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000033","数据分析师",{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},[124,139,140,141,42,142,143,43,144],"whistleblower data","identity","reporting","data protection","gdpr","prevention",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":149,"categories":150,"keywords":152,"publishedAt":46},"01M6AG6CJP0PW3X65HC7VP9SR9","vendor-termination-data","Vendor termination data return and deletion procedures per GDPR Article 28(3)(g). Covers data extraction formats, deletion certification requirements, transition planning, residual data handling, and post-termination verification.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fvendor-termination-data\u002FSKILL.md",[151],{"categoryNo":135,"categoryName":136,"taxonomyType":29,"taxonomyTypeDesc":30},[124,153,154,37,155,143,43,156],"vendor termination data","planning","extraction","certification",{"skillNo":158,"name":159,"nameZh":6,"description":160,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":161,"categories":162,"keywords":164,"publishedAt":46},"01M6AG69E277DXZB2NF6KV17ZA","vendor-risk-scoring","Vendor privacy risk tiering methodology for processor management. Covers scoring factors including data volume, sensitivity, transfer locations, certifications, breach history, and control maturity with weighted risk calculation and tier assignment.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fvendor-risk-scoring\u002FSKILL.md",[163],{"categoryNo":135,"categoryName":136,"taxonomyType":29,"taxonomyTypeDesc":30},[165,166,167,168,169,170,171,172,43,124,173],"vendor risk scoring","risk","risk scoring","methodology","risk-scoring","assessment","transfer","certifications","processor",{"skillNo":175,"name":176,"nameZh":6,"description":177,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":178,"categories":179,"keywords":181,"publishedAt":46},"01M6AG6655ABFKT2BBWHCP4PAT","vendor-privacy-due-diligence","Pre-contract vendor privacy due diligence per GDPR Article 28(1). Covers risk questionnaires, technical controls assessment, certification review, data flow analysis, and documented sufficiency decisions for processor engagement.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fvendor-privacy-due-diligence\u002FSKILL.md",[180],{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},[43,182,143,183,170,184,185,124,173,166,156,186],"vendor privacy due diligence","review","onboarding","flow","analysis",{"skillNo":188,"name":189,"nameZh":6,"description":190,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":191,"categories":192,"keywords":200,"publishedAt":46},"01M6AG62C3VD8SYNGH4N7RHZTE","vendor-privacy-audit","On-site and remote vendor audit procedures per GDPR Article 28(3)(h). Covers audit planning, evidence collection methodologies, finding classification, remediation tracking, and audit report generation for processor compliance verification.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FPrivacy-Data-Protection-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fprivacy\u002Fvendor-privacy-audit\u002FSKILL.md",[193,196,199],{"categoryNo":194,"categoryName":195,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":197,"categoryName":198,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000036","内容创作者",{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},[201,39,43,202,37,203,42,143,204,173,205,206,207,154],"vendor privacy audit","report","audit report","remediation","collection","remediation-tracking","classification"]