UNPKG

clarity-js

Version:

An analytics library that uses web page interactions to generate aggregated insights

1,007 lines (892 loc) 64.8 kB
import { expect, test } from "@playwright/test"; import { readFileSync } from "fs"; import { join } from "path"; /** * Consent API tests - Tests the production consent functionality by loading * the built clarity.min.js in a browser context. This approach tests the actual * artifact that ships to users rather than individual source modules. */ // Type definitions for test results interface ConsentState { source: number; ad_Storage: string; analytics_Storage: string; } /** * Standard result type for all consent tests. * Every test should capture and verify this complete state for consistent analysis. */ interface ConsentTestResult { consent: ConsentState; hasClskCookie: boolean; hasClckCookie: boolean; clskCookieValue: string; clckCookieValue: string; cookies: string; } const Constant = { Granted: "granted", Denied: "denied", CookieKey: "_clck", SessionKey: "_clsk", } as const; const ConsentSource = { Implicit: 0, API: 1, GCM: 2, TCF: 3, APIv1: 4, APIv2: 5, Cookie: 6, Default: 7, } as const; // Maximum time to wait from when consent() is called to when its callback resolves const CONSENT_CALLBACK_TIMEOUT = 2000; // Delay from when cookie mock is set up to when Clarity can reliably read it const COOKIE_SETUP_DELAY = 500; // Use the minified browser build which properly exposes window.clarity const clarityJsPath = join(__dirname, "../build/clarity.min.js"); /** * Sets up a cookie mock for data: URLs which don't support cookies natively. * Handles both cookie setting and deletion (via max-age or empty values). * @param initialCookieValue - Optional initial cookie value (e.g., "marketing_id=abc123") */ function setupCookieMock(initialCookieValue?: string): void { let cookieStore = initialCookieValue || ""; Object.defineProperty(document, "cookie", { get: () => cookieStore, set: (value: string) => { if (value.includes("max-age=-") || value.includes("=;") || value.includes("=^;")) { const cookieName = value.split("=")[0]; const cookies = cookieStore.split("; ").filter(c => !c.startsWith(cookieName + "=")); cookieStore = cookies.join("; "); } else { const cookieName = value.split("=")[0]; const cookies = cookieStore.split("; ").filter(c => !c.startsWith(cookieName + "=")); cookies.push(value.split(";")[0]); cookieStore = cookies.filter(c => c).join("; "); } }, configurable: true }); } test.describe("consentv2 - Production API", () => { test.beforeEach(async ({ page }) => { await page.goto("data:text/html,<!DOCTYPE html><html><head></head><body></body></html>"); // Expose timeout constants to the page context await page.evaluate(({ timeout, delay }) => { (window as any).CONSENT_CALLBACK_TIMEOUT = timeout; (window as any).COOKIE_SETUP_DELAY = delay; }, { timeout: CONSENT_CALLBACK_TIMEOUT, delay: COOKIE_SETUP_DELAY }); const clarityJs = readFileSync(clarityJsPath, "utf-8"); await page.evaluate((code) => { eval(code); }, clarityJs); }); // ======================== // track=false tests // ======================== test("implicit denied: track=false results in denied consent", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state (before Clarity starts) - no cookies should exist const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: "", clckCookieValue: "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); // Start clarity with track=false (cookies disabled, no network calls) const result = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: false, upload: false }); // Register metadata callback AFTER starting clarity // Signature: clarity('metadata', callback, wait, recall, consentInfo) // The callback receives (data, upgrade, consent) // wait=false (don't wait for data), recall=true (resend on changes), consentInfo=true (include consent) (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, true, true); // Timeout fallback setTimeout(() => { const cookies = document.cookie; resolve({ consent: null, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: "", clckCookieValue: "", cookies }); }, (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult; expect(consentResult.consent).not.toBeNull(); expect(consentResult.consent.source).toBe(ConsentSource.Implicit); expect(consentResult.consent.ad_Storage).toBe(Constant.Denied); expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied); // Verify cookies are not set when track=false expect(consentResult.hasClskCookie).toBe(false); expect(consentResult.hasClckCookie).toBe(false); }); test("cookie consent: track=false with consent cookie results in granted", async ({ page }) => { // This test uses a pre-set cookie (simulating a returning user with consent) // so we manually mock the cookie rather than using setupCookieMock const result = await page.evaluate(({ sessionKey, cookieKey }) => { // Mock document.cookie to simulate a consent cookie // Format: _clck cookie with consent flag set to 1 (granted) // Cookie format: userId^version^expiry^consent^dob const userId = "testuser123"; const version = "2"; const expiry = Math.ceil((Date.now() + 31536e6) / 864e5).toString(36); const consentFlag = "1"; // 1 = granted const dob = "0"; const presetCookieValue = `${userId}^${version}^${expiry}^${consentFlag}^${dob}`; Object.defineProperty(document, "cookie", { writable: true, value: `${cookieKey}=${presetCookieValue}` }); // Capture initial state with the pre-set cookie const initialCookies = document.cookie; const initialState = { cookies: initialCookies, hasClskCookie: initialCookies.includes(`${sessionKey}=`), hasClckCookie: initialCookies.includes(`${cookieKey}=`), clckCookieValue: presetCookieValue }; return new Promise((resolve) => { // Start clarity with track=false but with consent cookie present (window as any).clarity("start", { projectId: "test", track: false, upload: false }); // Register metadata callback (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ initialState, consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, true, true); // Timeout fallback setTimeout(() => { const cookies = document.cookie; resolve({ initialState, consent: null, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: "", clckCookieValue: "", cookies }); }, (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult & { initialState: { cookies: string; hasClskCookie: boolean; hasClckCookie: boolean; clckCookieValue: string } }; // Verify initial state had the pre-set consent cookie expect(consentResult.initialState.hasClckCookie).toBe(true); expect(consentResult.initialState.clckCookieValue).toContain("testuser123"); // Verify consent was read from cookie expect(consentResult.consent).not.toBeNull(); expect(consentResult.consent.source).toBe(ConsentSource.Cookie); expect(consentResult.consent.ad_Storage).toBe(Constant.Granted); expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted); // Cookie should still exist expect(consentResult.hasClckCookie).toBe(true); }); test("consentv2 explicit denial: track=false → denied/denied remains without cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); // Start with track=false (implicit denied), then explicitly deny via consentv2 const result = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: false, upload: false }); setTimeout(() => { (window as any).clarity("consentv2", { ad_Storage: "denied", analytics_Storage: "denied" }); (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); }, (window as any).COOKIE_SETUP_DELAY); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult; expect(consentResult.consent.source).toBe(ConsentSource.APIv2); expect(consentResult.consent.ad_Storage).toBe(Constant.Denied); expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied); // Verify cookies are not set when analytics denied expect(consentResult.hasClskCookie).toBe(false); expect(consentResult.hasClckCookie).toBe(false); expect(consentResult.clskCookieValue).toBe(""); expect(consentResult.clckCookieValue).toBe(""); }); test("consentv2 mixed consent: track=false → denied analytics, granted ads no cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); const result = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: false, upload: false }); (window as any).clarity("consentv2", { ad_Storage: "granted", analytics_Storage: "denied" }); (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult; expect(consentResult.consent.source).toBe(ConsentSource.APIv2); expect(consentResult.consent.ad_Storage).toBe(Constant.Granted); expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied); // Verify cookies are deleted when analytics is denied (regardless of ads) expect(consentResult.hasClskCookie).toBe(false); expect(consentResult.hasClckCookie).toBe(false); expect(consentResult.clskCookieValue).toBe(""); expect(consentResult.clckCookieValue).toBe(""); }); test("consentv2 mixed consent: track=false → granted analytics, denied ads sets cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); // Start with track=false, then grant analytics but deny ads const result = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: false, upload: false }); setTimeout(() => { (window as any).clarity("consentv2", { ad_Storage: "denied", analytics_Storage: "granted" }); (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); }, (window as any).COOKIE_SETUP_DELAY); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult; expect(consentResult.consent.source).toBe(ConsentSource.APIv2); expect(consentResult.consent.ad_Storage).toBe(Constant.Denied); expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted); // Verify _clck cookie is set when analytics granted (even if ads denied) expect(consentResult.hasClckCookie).toBe(true); expect(consentResult.clckCookieValue).not.toBe(""); }); test("consentv2 grants consent: track=false → granted/granted sets cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); // Start with track=false (implicit denied) and verify initial consent state const initialConsent = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: false, upload: false }); (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialConsent).not.toBeNull(); const initialResult = initialConsent as ConsentTestResult; expect(initialResult.consent.source).toBe(ConsentSource.Implicit); expect(initialResult.consent.ad_Storage).toBe(Constant.Denied); expect(initialResult.consent.analytics_Storage).toBe(Constant.Denied); // Cookies should not be set with track=false expect(initialResult.hasClskCookie).toBe(false); expect(initialResult.hasClckCookie).toBe(false); // Grant consent via consentv2 API const updatedConsent = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("consentv2", { ad_Storage: "granted", analytics_Storage: "granted" }); (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(updatedConsent).not.toBeNull(); const updatedResult = updatedConsent as ConsentTestResult; expect(updatedResult.consent.source).toBe(ConsentSource.APIv2); expect(updatedResult.consent.ad_Storage).toBe(Constant.Granted); expect(updatedResult.consent.analytics_Storage).toBe(Constant.Granted); // Verify cookies are set when consent is granted expect(updatedResult.hasClskCookie).toBe(true); expect(updatedResult.hasClckCookie).toBe(true); expect(updatedResult.clskCookieValue).not.toBe(""); expect(updatedResult.clckCookieValue).not.toBe(""); }); // ======================== // track=true tests // ======================== test("implicit granted: track=true results in granted consent", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); // Start clarity with track=true (implicit granted consent) const result = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: true, upload: false }); // Register metadata callback // wait=false (don't wait for data), recall=true (resend on changes), consentInfo=true (include consent) (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, true, true); // Timeout fallback setTimeout(() => { const cookies = document.cookie; resolve({ consent: null, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: "", clckCookieValue: "", cookies }); }, (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult; expect(consentResult.consent).not.toBeNull(); expect(consentResult.consent.source).toBe(ConsentSource.Implicit); expect(consentResult.consent.ad_Storage).toBe(Constant.Granted); expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted); // With track=true, _clck cookie should be set expect(consentResult.hasClckCookie).toBe(true); expect(consentResult.clckCookieValue).not.toBe(""); }); test("consentv2 revokes consent: track=true → denied/denied deletes cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); // Step 1: Start with track=true and verify initial granted state with cookies const initialConsent = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: true, upload: false }); // Get initial consent state (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); // Verify initial state: implicit granted with cookies expect(initialConsent).not.toBeNull(); const initialResult = initialConsent as ConsentTestResult; expect(initialResult.consent.source).toBe(ConsentSource.Implicit); expect(initialResult.consent.ad_Storage).toBe(Constant.Granted); expect(initialResult.consent.analytics_Storage).toBe(Constant.Granted); expect(initialResult.hasClckCookie).toBe(true); // Step 2: Call consentv2 API to deny consent and verify cookies are deleted const updatedConsent = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("consentv2", { ad_Storage: "denied", analytics_Storage: "denied" }); // Get updated consent state and cookie status (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); // Verify updated state: explicit denied with cookies deleted expect(updatedConsent).not.toBeNull(); const updatedResult = updatedConsent as ConsentTestResult; expect(updatedResult.consent.source).toBe(ConsentSource.APIv2); expect(updatedResult.consent.ad_Storage).toBe(Constant.Denied); expect(updatedResult.consent.analytics_Storage).toBe(Constant.Denied); // Verify both cookies are deleted after consent is denied expect(updatedResult.hasClskCookie).toBe(false); expect(updatedResult.hasClckCookie).toBe(false); }); test("consentv2 mixed consent: track=true → granted analytics, denied ads keeps cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); const result = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: true, upload: false }); (window as any).clarity("consentv2", { ad_Storage: "denied", analytics_Storage: "granted" }); (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult; expect(consentResult.consent.source).toBe(ConsentSource.APIv2); expect(consentResult.consent.ad_Storage).toBe(Constant.Denied); expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted); // Verify cookies remain when analytics is granted (even if ads is denied) expect(consentResult.hasClskCookie).toBe(true); expect(consentResult.hasClckCookie).toBe(true); expect(consentResult.clskCookieValue).not.toBe(""); expect(consentResult.clckCookieValue).not.toBe(""); }); test("consentv2 mixed consent: track=true → denied analytics, granted ads deletes cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); // Start with track=true, then deny analytics but grant ads const result = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: true, upload: false }); setTimeout(() => { (window as any).clarity("consentv2", { ad_Storage: "granted", analytics_Storage: "denied" }); (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); }, (window as any).COOKIE_SETUP_DELAY); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult; expect(consentResult.consent.source).toBe(ConsentSource.APIv2); expect(consentResult.consent.ad_Storage).toBe(Constant.Granted); expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied); // Verify cookies are deleted when analytics denied (even if ads granted) expect(consentResult.hasClskCookie).toBe(false); expect(consentResult.hasClckCookie).toBe(false); expect(consentResult.clskCookieValue).toBe(""); expect(consentResult.clckCookieValue).toBe(""); }); test("consentv2 maintains consent: track=true → granted/granted keeps cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); return { cookies, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "" }; }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(initialState.cookies).toBe(""); expect(initialState.hasClskCookie).toBe(false); expect(initialState.hasClckCookie).toBe(false); // Start with track=true (implicit granted) const initialConsent = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("start", { projectId: "test", track: true, upload: false }); // Wait a bit for cookies to be set, then check setTimeout(() => { (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); }, (window as any).COOKIE_SETUP_DELAY); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); // Verify initial state: implicit granted with cookies expect(initialConsent).not.toBeNull(); const initialResult = initialConsent as ConsentTestResult; expect(initialResult.consent.source).toBe(ConsentSource.Implicit); expect(initialResult.consent.ad_Storage).toBe(Constant.Granted); expect(initialResult.consent.analytics_Storage).toBe(Constant.Granted); // Note: _clsk cookie is only written during upload operations (metadata.save()), not during initial setup // With upload: false in test config, only _clck is written by track() function during initialization expect(initialResult.hasClckCookie).toBe(true); expect(initialResult.clckCookieValue).not.toBe(""); // Explicitly grant via consentv2 API (should maintain granted state) const result = await page.evaluate(({ sessionKey, cookieKey }) => { return new Promise((resolve) => { (window as any).clarity("consentv2", { ad_Storage: "granted", analytics_Storage: "granted" }); (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => { if (consent) { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`)); const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`)); resolve({ consent, hasClskCookie: cookies.includes(`${sessionKey}=`), hasClckCookie: cookies.includes(`${cookieKey}=`), clskCookieValue: clskMatch ? clskMatch[1] : "", clckCookieValue: clckMatch ? clckMatch[1] : "", clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "", cookies }); } }, false, false, true); setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT); }); }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey }); expect(result).not.toBeNull(); const consentResult = result as ConsentTestResult; expect(consentResult.consent.source).toBe(ConsentSource.APIv2); expect(consentResult.consent.ad_Storage).toBe(Constant.Granted); expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted); // Verify _clck cookie remains set (user ID/consent preference cookie) // Note: _clsk is only written during upload operations, so we only check _clck in tests with upload: false expect(consentResult.hasClckCookie).toBe(true); expect(consentResult.clckCookieValue).not.toBe(""); }); // ======================== // V1 Consent API tests // The V1 API uses clarity("consent", boolean) where true=granted, false=denied // It sets both ad_Storage and analytics_Storage to the same value // ======================== test("consent v1: track=false → consent(true) grants consent and sets cookies", async ({ page }) => { await page.evaluate(setupCookieMock as () => void); // Verify initial state - no cookies before Clarity starts const initialState = await page.evaluate(({ sessionKey, cookieKey }) => { const cookies = document.cookie; const clskMatch = cookies.match(new RegExp(`${sessionKey}