@gemx-dev/clarity-js 0.8.59 → 0.8.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1164 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { readFileSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ /**
6
+ * Consent API tests - Tests the production consent functionality by loading
7
+ * the built clarity.min.js in a browser context. This approach tests the actual
8
+ * artifact that ships to users rather than individual source modules.
9
+ */
10
+
11
+ // Type definitions for test results
12
+ interface ConsentState {
13
+ source: number;
14
+ ad_Storage: string;
15
+ analytics_Storage: string;
16
+ }
17
+
18
+ /**
19
+ * Standard result type for all consent tests.
20
+ * Every test should capture and verify this complete state for consistent analysis.
21
+ */
22
+ interface ConsentTestResult {
23
+ consent: ConsentState;
24
+ hasClskCookie: boolean;
25
+ hasClckCookie: boolean;
26
+ clskCookieValue: string;
27
+ clckCookieValue: string;
28
+ cookies: string;
29
+ }
30
+
31
+ const Constant = {
32
+ Granted: "granted",
33
+ Denied: "denied",
34
+ CookieKey: "_clck",
35
+ SessionKey: "_clsk",
36
+ } as const;
37
+
38
+ const ConsentSource = {
39
+ Implicit: 0,
40
+ API: 1,
41
+ GCM: 2,
42
+ TCF: 3,
43
+ APIv1: 4,
44
+ APIv2: 5,
45
+ Cookie: 6,
46
+ Default: 7,
47
+ } as const;
48
+
49
+ // Maximum time to wait from when consent() is called to when its callback resolves
50
+ const CONSENT_CALLBACK_TIMEOUT = 2000;
51
+ // Delay from when cookie mock is set up to when Clarity can reliably read it
52
+ const COOKIE_SETUP_DELAY = 500;
53
+
54
+ // Use the minified browser build which properly exposes window.clarity
55
+ const clarityJsPath = join(__dirname, "../build/clarity.min.js");
56
+
57
+ /**
58
+ * Sets up a cookie mock for data: URLs which don't support cookies natively.
59
+ * Handles both cookie setting and deletion (via max-age or empty values).
60
+ */
61
+ function setupCookieMock() {
62
+ let cookieStore = "";
63
+ Object.defineProperty(document, "cookie", {
64
+ get: () => cookieStore,
65
+ set: (value: string) => {
66
+ if (value.includes("max-age=-") || value.includes("=;") || value.includes("=^;")) {
67
+ const cookieName = value.split("=")[0];
68
+ const cookies = cookieStore.split("; ").filter(c => !c.startsWith(cookieName + "="));
69
+ cookieStore = cookies.join("; ");
70
+ } else {
71
+ const cookieName = value.split("=")[0];
72
+ const cookies = cookieStore.split("; ").filter(c => !c.startsWith(cookieName + "="));
73
+ cookies.push(value.split(";")[0]);
74
+ cookieStore = cookies.filter(c => c).join("; ");
75
+ }
76
+ },
77
+ configurable: true
78
+ });
79
+ }
80
+
81
+
82
+
83
+ test.describe("consentv2 - Production API", () => {
84
+ test.beforeEach(async ({ page }) => {
85
+ await page.goto("data:text/html,<!DOCTYPE html><html><head></head><body></body></html>");
86
+
87
+ // Expose timeout constants to the page context
88
+ await page.evaluate(({ timeout, delay }) => {
89
+ (window as any).CONSENT_CALLBACK_TIMEOUT = timeout;
90
+ (window as any).COOKIE_SETUP_DELAY = delay;
91
+ }, { timeout: CONSENT_CALLBACK_TIMEOUT, delay: COOKIE_SETUP_DELAY });
92
+
93
+ const clarityJs = readFileSync(clarityJsPath, "utf-8");
94
+ await page.evaluate((code) => {
95
+ eval(code);
96
+ }, clarityJs);
97
+ });
98
+
99
+ // ========================
100
+ // track=false tests
101
+ // ========================
102
+
103
+ test("implicit denied: track=false results in denied consent", async ({ page }) => {
104
+ await page.evaluate(setupCookieMock);
105
+
106
+ // Verify initial state (before Clarity starts) - no cookies should exist
107
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
108
+ const cookies = document.cookie;
109
+ return {
110
+ cookies,
111
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
112
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
113
+ clskCookieValue: "",
114
+ clckCookieValue: ""
115
+ };
116
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
117
+
118
+ expect(initialState.cookies).toBe("");
119
+ expect(initialState.hasClskCookie).toBe(false);
120
+ expect(initialState.hasClckCookie).toBe(false);
121
+
122
+ // Start clarity with track=false (cookies disabled, no network calls)
123
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
124
+ return new Promise((resolve) => {
125
+ (window as any).clarity("start", {
126
+ projectId: "test",
127
+ track: false,
128
+ upload: false
129
+ });
130
+
131
+ // Register metadata callback AFTER starting clarity
132
+ // Signature: clarity('metadata', callback, wait, recall, consentInfo)
133
+ // The callback receives (data, upgrade, consent)
134
+ // wait=false (don't wait for data), recall=true (resend on changes), consentInfo=true (include consent)
135
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
136
+ if (consent) {
137
+ const cookies = document.cookie;
138
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
139
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
140
+ resolve({
141
+ consent,
142
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
143
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
144
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
145
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
146
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
147
+ cookies
148
+ });
149
+ }
150
+ }, false, true, true);
151
+
152
+ // Timeout fallback
153
+ setTimeout(() => {
154
+ const cookies = document.cookie;
155
+ resolve({
156
+ consent: null,
157
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
158
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
159
+ clskCookieValue: "",
160
+ clckCookieValue: "",
161
+ cookies
162
+ });
163
+ }, (window as any).CONSENT_CALLBACK_TIMEOUT);
164
+ });
165
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
166
+
167
+ expect(result).not.toBeNull();
168
+ const consentResult = result as ConsentTestResult;
169
+ expect(consentResult.consent).not.toBeNull();
170
+ expect(consentResult.consent.source).toBe(ConsentSource.Implicit);
171
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Denied);
172
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied);
173
+ // Verify cookies are not set when track=false
174
+ expect(consentResult.hasClskCookie).toBe(false);
175
+ expect(consentResult.hasClckCookie).toBe(false);
176
+ });
177
+
178
+ test("cookie consent: track=false with consent cookie results in granted", async ({ page }) => {
179
+ // This test uses a pre-set cookie (simulating a returning user with consent)
180
+ // so we manually mock the cookie rather than using setupCookieMock
181
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
182
+ // Mock document.cookie to simulate a consent cookie
183
+ // Format: _clck cookie with consent flag set to 1 (granted)
184
+ // Cookie format: userId^version^expiry^consent^dob
185
+ const userId = "testuser123";
186
+ const version = "2";
187
+ const expiry = Math.ceil((Date.now() + 31536e6) / 864e5).toString(36);
188
+ const consentFlag = "1"; // 1 = granted
189
+ const dob = "0";
190
+ const presetCookieValue = `${userId}^${version}^${expiry}^${consentFlag}^${dob}`;
191
+
192
+ Object.defineProperty(document, "cookie", {
193
+ writable: true,
194
+ value: `${cookieKey}=${presetCookieValue}`
195
+ });
196
+
197
+ // Capture initial state with the pre-set cookie
198
+ const initialCookies = document.cookie;
199
+ const initialState = {
200
+ cookies: initialCookies,
201
+ hasClskCookie: initialCookies.includes(`${sessionKey}=`),
202
+ hasClckCookie: initialCookies.includes(`${cookieKey}=`),
203
+ clckCookieValue: presetCookieValue
204
+ };
205
+
206
+ return new Promise((resolve) => {
207
+ // Start clarity with track=false but with consent cookie present
208
+ (window as any).clarity("start", {
209
+ projectId: "test",
210
+ track: false,
211
+ upload: false
212
+ });
213
+
214
+ // Register metadata callback
215
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
216
+ if (consent) {
217
+ const cookies = document.cookie;
218
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
219
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
220
+ resolve({
221
+ initialState,
222
+ consent,
223
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
224
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
225
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
226
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
227
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
228
+ cookies
229
+ });
230
+ }
231
+ }, false, true, true);
232
+
233
+ // Timeout fallback
234
+ setTimeout(() => {
235
+ const cookies = document.cookie;
236
+ resolve({
237
+ initialState,
238
+ consent: null,
239
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
240
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
241
+ clskCookieValue: "",
242
+ clckCookieValue: "",
243
+ cookies
244
+ });
245
+ }, (window as any).CONSENT_CALLBACK_TIMEOUT);
246
+ });
247
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
248
+
249
+ expect(result).not.toBeNull();
250
+ const consentResult = result as ConsentTestResult & { initialState: { cookies: string; hasClskCookie: boolean; hasClckCookie: boolean; clckCookieValue: string } };
251
+
252
+ // Verify initial state had the pre-set consent cookie
253
+ expect(consentResult.initialState.hasClckCookie).toBe(true);
254
+ expect(consentResult.initialState.clckCookieValue).toContain("testuser123");
255
+
256
+ // Verify consent was read from cookie
257
+ expect(consentResult.consent).not.toBeNull();
258
+ expect(consentResult.consent.source).toBe(ConsentSource.Cookie);
259
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Granted);
260
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted);
261
+ // Cookie should still exist
262
+ expect(consentResult.hasClckCookie).toBe(true);
263
+ });
264
+
265
+ test("consentv2 explicit denial: track=false → denied/denied remains without cookies", async ({ page }) => {
266
+ await page.evaluate(setupCookieMock);
267
+
268
+ // Verify initial state - no cookies before Clarity starts
269
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
270
+ const cookies = document.cookie;
271
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
272
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
273
+ return {
274
+ cookies,
275
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
276
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
277
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
278
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
279
+ };
280
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
281
+
282
+ expect(initialState.cookies).toBe("");
283
+ expect(initialState.hasClskCookie).toBe(false);
284
+ expect(initialState.hasClckCookie).toBe(false);
285
+
286
+ // Start with track=false (implicit denied), then explicitly deny via consentv2
287
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
288
+ return new Promise((resolve) => {
289
+ (window as any).clarity("start", {
290
+ projectId: "test",
291
+ track: false,
292
+ upload: false
293
+ });
294
+
295
+ setTimeout(() => {
296
+ (window as any).clarity("consentv2", {
297
+ ad_Storage: "denied",
298
+ analytics_Storage: "denied"
299
+ });
300
+
301
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
302
+ if (consent) {
303
+ const cookies = document.cookie;
304
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
305
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
306
+ resolve({
307
+ consent,
308
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
309
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
310
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
311
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
312
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
313
+ cookies
314
+ });
315
+ }
316
+ }, false, false, true);
317
+ }, (window as any).COOKIE_SETUP_DELAY);
318
+
319
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
320
+ });
321
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
322
+
323
+ expect(result).not.toBeNull();
324
+ const consentResult = result as ConsentTestResult;
325
+ expect(consentResult.consent.source).toBe(ConsentSource.APIv2);
326
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Denied);
327
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied);
328
+ // Verify cookies are not set when analytics denied
329
+ expect(consentResult.hasClskCookie).toBe(false);
330
+ expect(consentResult.hasClckCookie).toBe(false);
331
+ expect(consentResult.clskCookieValue).toBe("");
332
+ expect(consentResult.clckCookieValue).toBe("");
333
+ });
334
+
335
+ test("consentv2 mixed consent: track=false → denied analytics, granted ads no cookies", async ({ page }) => {
336
+ await page.evaluate(setupCookieMock);
337
+
338
+ // Verify initial state - no cookies before Clarity starts
339
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
340
+ const cookies = document.cookie;
341
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
342
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
343
+ return {
344
+ cookies,
345
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
346
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
347
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
348
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
349
+ };
350
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
351
+
352
+ expect(initialState.cookies).toBe("");
353
+ expect(initialState.hasClskCookie).toBe(false);
354
+ expect(initialState.hasClckCookie).toBe(false);
355
+
356
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
357
+ return new Promise((resolve) => {
358
+ (window as any).clarity("start", {
359
+ projectId: "test",
360
+ track: false,
361
+ upload: false
362
+ });
363
+
364
+ (window as any).clarity("consentv2", {
365
+ ad_Storage: "granted",
366
+ analytics_Storage: "denied"
367
+ });
368
+
369
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
370
+ if (consent) {
371
+ const cookies = document.cookie;
372
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
373
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
374
+ resolve({
375
+ consent,
376
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
377
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
378
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
379
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
380
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
381
+ cookies
382
+ });
383
+ }
384
+ }, false, false, true);
385
+
386
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
387
+ });
388
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
389
+
390
+ expect(result).not.toBeNull();
391
+ const consentResult = result as ConsentTestResult;
392
+ expect(consentResult.consent.source).toBe(ConsentSource.APIv2);
393
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Granted);
394
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied);
395
+ // Verify cookies are deleted when analytics is denied (regardless of ads)
396
+ expect(consentResult.hasClskCookie).toBe(false);
397
+ expect(consentResult.hasClckCookie).toBe(false);
398
+ expect(consentResult.clskCookieValue).toBe("");
399
+ expect(consentResult.clckCookieValue).toBe("");
400
+ });
401
+
402
+ test("consentv2 mixed consent: track=false → granted analytics, denied ads sets cookies", async ({ page }) => {
403
+ await page.evaluate(setupCookieMock);
404
+
405
+ // Verify initial state - no cookies before Clarity starts
406
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
407
+ const cookies = document.cookie;
408
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
409
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
410
+ return {
411
+ cookies,
412
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
413
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
414
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
415
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
416
+ };
417
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
418
+
419
+ expect(initialState.cookies).toBe("");
420
+ expect(initialState.hasClskCookie).toBe(false);
421
+ expect(initialState.hasClckCookie).toBe(false);
422
+
423
+ // Start with track=false, then grant analytics but deny ads
424
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
425
+ return new Promise((resolve) => {
426
+ (window as any).clarity("start", {
427
+ projectId: "test",
428
+ track: false,
429
+ upload: false
430
+ });
431
+
432
+ setTimeout(() => {
433
+ (window as any).clarity("consentv2", {
434
+ ad_Storage: "denied",
435
+ analytics_Storage: "granted"
436
+ });
437
+
438
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
439
+ if (consent) {
440
+ const cookies = document.cookie;
441
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
442
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
443
+ resolve({
444
+ consent,
445
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
446
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
447
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
448
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
449
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
450
+ cookies
451
+ });
452
+ }
453
+ }, false, false, true);
454
+ }, (window as any).COOKIE_SETUP_DELAY);
455
+
456
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
457
+ });
458
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
459
+
460
+ expect(result).not.toBeNull();
461
+ const consentResult = result as ConsentTestResult;
462
+ expect(consentResult.consent.source).toBe(ConsentSource.APIv2);
463
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Denied);
464
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted);
465
+ // Verify _clck cookie is set when analytics granted (even if ads denied)
466
+ expect(consentResult.hasClckCookie).toBe(true);
467
+ expect(consentResult.clckCookieValue).not.toBe("");
468
+ });
469
+
470
+ test("consentv2 grants consent: track=false → granted/granted sets cookies", async ({ page }) => {
471
+ await page.evaluate(setupCookieMock);
472
+
473
+ // Verify initial state - no cookies before Clarity starts
474
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
475
+ const cookies = document.cookie;
476
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
477
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
478
+ return {
479
+ cookies,
480
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
481
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
482
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
483
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
484
+ };
485
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
486
+
487
+ expect(initialState.cookies).toBe("");
488
+ expect(initialState.hasClskCookie).toBe(false);
489
+ expect(initialState.hasClckCookie).toBe(false);
490
+
491
+ // Start with track=false (implicit denied) and verify initial consent state
492
+ const initialConsent = await page.evaluate(({ sessionKey, cookieKey }) => {
493
+ return new Promise((resolve) => {
494
+ (window as any).clarity("start", {
495
+ projectId: "test",
496
+ track: false,
497
+ upload: false
498
+ });
499
+
500
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
501
+ if (consent) {
502
+ const cookies = document.cookie;
503
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
504
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
505
+ resolve({
506
+ consent,
507
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
508
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
509
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
510
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
511
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
512
+ cookies
513
+ });
514
+ }
515
+ }, false, false, true);
516
+
517
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
518
+ });
519
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
520
+
521
+ expect(initialConsent).not.toBeNull();
522
+ const initialResult = initialConsent as ConsentTestResult;
523
+ expect(initialResult.consent.source).toBe(ConsentSource.Implicit);
524
+ expect(initialResult.consent.ad_Storage).toBe(Constant.Denied);
525
+ expect(initialResult.consent.analytics_Storage).toBe(Constant.Denied);
526
+ // Cookies should not be set with track=false
527
+ expect(initialResult.hasClskCookie).toBe(false);
528
+ expect(initialResult.hasClckCookie).toBe(false);
529
+
530
+ // Grant consent via consentv2 API
531
+ const updatedConsent = await page.evaluate(({ sessionKey, cookieKey }) => {
532
+ return new Promise((resolve) => {
533
+ (window as any).clarity("consentv2", {
534
+ ad_Storage: "granted",
535
+ analytics_Storage: "granted"
536
+ });
537
+
538
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
539
+ if (consent) {
540
+ const cookies = document.cookie;
541
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
542
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
543
+ resolve({
544
+ consent,
545
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
546
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
547
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
548
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
549
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
550
+ cookies
551
+ });
552
+ }
553
+ }, false, false, true);
554
+
555
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
556
+ });
557
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
558
+
559
+ expect(updatedConsent).not.toBeNull();
560
+ const updatedResult = updatedConsent as ConsentTestResult;
561
+ expect(updatedResult.consent.source).toBe(ConsentSource.APIv2);
562
+ expect(updatedResult.consent.ad_Storage).toBe(Constant.Granted);
563
+ expect(updatedResult.consent.analytics_Storage).toBe(Constant.Granted);
564
+ // Verify cookies are set when consent is granted
565
+ expect(updatedResult.hasClskCookie).toBe(true);
566
+ expect(updatedResult.hasClckCookie).toBe(true);
567
+ expect(updatedResult.clskCookieValue).not.toBe("");
568
+ expect(updatedResult.clckCookieValue).not.toBe("");
569
+ });
570
+
571
+ // ========================
572
+ // track=true tests
573
+ // ========================
574
+
575
+ test("implicit granted: track=true results in granted consent", async ({ page }) => {
576
+ await page.evaluate(setupCookieMock);
577
+
578
+ // Verify initial state - no cookies before Clarity starts
579
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
580
+ const cookies = document.cookie;
581
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
582
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
583
+ return {
584
+ cookies,
585
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
586
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
587
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
588
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
589
+ };
590
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
591
+
592
+ expect(initialState.cookies).toBe("");
593
+ expect(initialState.hasClskCookie).toBe(false);
594
+ expect(initialState.hasClckCookie).toBe(false);
595
+
596
+ // Start clarity with track=true (implicit granted consent)
597
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
598
+ return new Promise((resolve) => {
599
+ (window as any).clarity("start", {
600
+ projectId: "test",
601
+ track: true,
602
+ upload: false
603
+ });
604
+
605
+ // Register metadata callback
606
+ // wait=false (don't wait for data), recall=true (resend on changes), consentInfo=true (include consent)
607
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
608
+ if (consent) {
609
+ const cookies = document.cookie;
610
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
611
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
612
+ resolve({
613
+ consent,
614
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
615
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
616
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
617
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
618
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
619
+ cookies
620
+ });
621
+ }
622
+ }, false, true, true);
623
+
624
+ // Timeout fallback
625
+ setTimeout(() => {
626
+ const cookies = document.cookie;
627
+ resolve({
628
+ consent: null,
629
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
630
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
631
+ clskCookieValue: "",
632
+ clckCookieValue: "",
633
+ cookies
634
+ });
635
+ }, (window as any).CONSENT_CALLBACK_TIMEOUT);
636
+ });
637
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
638
+
639
+ expect(result).not.toBeNull();
640
+ const consentResult = result as ConsentTestResult;
641
+ expect(consentResult.consent).not.toBeNull();
642
+ expect(consentResult.consent.source).toBe(ConsentSource.Implicit);
643
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Granted);
644
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted);
645
+ // With track=true, _clck cookie should be set
646
+ expect(consentResult.hasClckCookie).toBe(true);
647
+ expect(consentResult.clckCookieValue).not.toBe("");
648
+ });
649
+
650
+ test("consentv2 revokes consent: track=true → denied/denied deletes cookies", async ({ page }) => {
651
+ await page.evaluate(setupCookieMock);
652
+
653
+ // Verify initial state - no cookies before Clarity starts
654
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
655
+ const cookies = document.cookie;
656
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
657
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
658
+ return {
659
+ cookies,
660
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
661
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
662
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
663
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
664
+ };
665
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
666
+
667
+ expect(initialState.cookies).toBe("");
668
+ expect(initialState.hasClskCookie).toBe(false);
669
+ expect(initialState.hasClckCookie).toBe(false);
670
+
671
+ // Step 1: Start with track=true and verify initial granted state with cookies
672
+ const initialConsent = await page.evaluate(({ sessionKey, cookieKey }) => {
673
+ return new Promise((resolve) => {
674
+ (window as any).clarity("start", {
675
+ projectId: "test",
676
+ track: true,
677
+ upload: false
678
+ });
679
+
680
+ // Get initial consent state
681
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
682
+ if (consent) {
683
+ const cookies = document.cookie;
684
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
685
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
686
+ resolve({
687
+ consent,
688
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
689
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
690
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
691
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
692
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
693
+ cookies
694
+ });
695
+ }
696
+ }, false, false, true);
697
+
698
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
699
+ });
700
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
701
+
702
+ // Verify initial state: implicit granted with cookies
703
+ expect(initialConsent).not.toBeNull();
704
+ const initialResult = initialConsent as ConsentTestResult;
705
+ expect(initialResult.consent.source).toBe(ConsentSource.Implicit);
706
+ expect(initialResult.consent.ad_Storage).toBe(Constant.Granted);
707
+ expect(initialResult.consent.analytics_Storage).toBe(Constant.Granted);
708
+ expect(initialResult.hasClckCookie).toBe(true);
709
+
710
+ // Step 2: Call consentv2 API to deny consent and verify cookies are deleted
711
+ const updatedConsent = await page.evaluate(({ sessionKey, cookieKey }) => {
712
+ return new Promise((resolve) => {
713
+ (window as any).clarity("consentv2", {
714
+ ad_Storage: "denied",
715
+ analytics_Storage: "denied"
716
+ });
717
+
718
+ // Get updated consent state and cookie status
719
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
720
+ if (consent) {
721
+ const cookies = document.cookie;
722
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
723
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
724
+ resolve({
725
+ consent,
726
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
727
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
728
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
729
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
730
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
731
+ cookies
732
+ });
733
+ }
734
+ }, false, false, true);
735
+
736
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
737
+ });
738
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
739
+
740
+ // Verify updated state: explicit denied with cookies deleted
741
+ expect(updatedConsent).not.toBeNull();
742
+ const updatedResult = updatedConsent as ConsentTestResult;
743
+ expect(updatedResult.consent.source).toBe(ConsentSource.APIv2);
744
+ expect(updatedResult.consent.ad_Storage).toBe(Constant.Denied);
745
+ expect(updatedResult.consent.analytics_Storage).toBe(Constant.Denied);
746
+
747
+ // Verify both cookies are deleted after consent is denied
748
+ expect(updatedResult.hasClskCookie).toBe(false);
749
+ expect(updatedResult.hasClckCookie).toBe(false);
750
+ });
751
+
752
+ test("consentv2 mixed consent: track=true → granted analytics, denied ads keeps cookies", async ({ page }) => {
753
+ await page.evaluate(setupCookieMock);
754
+
755
+ // Verify initial state - no cookies before Clarity starts
756
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
757
+ const cookies = document.cookie;
758
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
759
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
760
+ return {
761
+ cookies,
762
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
763
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
764
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
765
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
766
+ };
767
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
768
+
769
+ expect(initialState.cookies).toBe("");
770
+ expect(initialState.hasClskCookie).toBe(false);
771
+ expect(initialState.hasClckCookie).toBe(false);
772
+
773
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
774
+ return new Promise((resolve) => {
775
+ (window as any).clarity("start", {
776
+ projectId: "test",
777
+ track: true,
778
+ upload: false
779
+ });
780
+
781
+ (window as any).clarity("consentv2", {
782
+ ad_Storage: "denied",
783
+ analytics_Storage: "granted"
784
+ });
785
+
786
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
787
+ if (consent) {
788
+ const cookies = document.cookie;
789
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
790
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
791
+ resolve({
792
+ consent,
793
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
794
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
795
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
796
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
797
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
798
+ cookies
799
+ });
800
+ }
801
+ }, false, false, true);
802
+
803
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
804
+ });
805
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
806
+
807
+ expect(result).not.toBeNull();
808
+ const consentResult = result as ConsentTestResult;
809
+ expect(consentResult.consent.source).toBe(ConsentSource.APIv2);
810
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Denied);
811
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted);
812
+ // Verify cookies remain when analytics is granted (even if ads is denied)
813
+ expect(consentResult.hasClskCookie).toBe(true);
814
+ expect(consentResult.hasClckCookie).toBe(true);
815
+ expect(consentResult.clskCookieValue).not.toBe("");
816
+ expect(consentResult.clckCookieValue).not.toBe("");
817
+ });
818
+
819
+ test("consentv2 mixed consent: track=true → denied analytics, granted ads deletes cookies", async ({ page }) => {
820
+ await page.evaluate(setupCookieMock);
821
+
822
+ // Verify initial state - no cookies before Clarity starts
823
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
824
+ const cookies = document.cookie;
825
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
826
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
827
+ return {
828
+ cookies,
829
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
830
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
831
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
832
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
833
+ };
834
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
835
+
836
+ expect(initialState.cookies).toBe("");
837
+ expect(initialState.hasClskCookie).toBe(false);
838
+ expect(initialState.hasClckCookie).toBe(false);
839
+
840
+ // Start with track=true, then deny analytics but grant ads
841
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
842
+ return new Promise((resolve) => {
843
+ (window as any).clarity("start", {
844
+ projectId: "test",
845
+ track: true,
846
+ upload: false
847
+ });
848
+
849
+ setTimeout(() => {
850
+ (window as any).clarity("consentv2", {
851
+ ad_Storage: "granted",
852
+ analytics_Storage: "denied"
853
+ });
854
+
855
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
856
+ if (consent) {
857
+ const cookies = document.cookie;
858
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
859
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
860
+ resolve({
861
+ consent,
862
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
863
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
864
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
865
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
866
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
867
+ cookies
868
+ });
869
+ }
870
+ }, false, false, true);
871
+ }, (window as any).COOKIE_SETUP_DELAY);
872
+
873
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
874
+ });
875
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
876
+
877
+ expect(result).not.toBeNull();
878
+ const consentResult = result as ConsentTestResult;
879
+ expect(consentResult.consent.source).toBe(ConsentSource.APIv2);
880
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Granted);
881
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied);
882
+ // Verify cookies are deleted when analytics denied (even if ads granted)
883
+ expect(consentResult.hasClskCookie).toBe(false);
884
+ expect(consentResult.hasClckCookie).toBe(false);
885
+ expect(consentResult.clskCookieValue).toBe("");
886
+ expect(consentResult.clckCookieValue).toBe("");
887
+ });
888
+
889
+ test("consentv2 maintains consent: track=true → granted/granted keeps cookies", async ({ page }) => {
890
+ await page.evaluate(setupCookieMock);
891
+
892
+ // Verify initial state - no cookies before Clarity starts
893
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
894
+ const cookies = document.cookie;
895
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
896
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
897
+ return {
898
+ cookies,
899
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
900
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
901
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
902
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
903
+ };
904
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
905
+
906
+ expect(initialState.cookies).toBe("");
907
+ expect(initialState.hasClskCookie).toBe(false);
908
+ expect(initialState.hasClckCookie).toBe(false);
909
+
910
+ // Start with track=true (implicit granted)
911
+ const initialConsent = await page.evaluate(({ sessionKey, cookieKey }) => {
912
+ return new Promise((resolve) => {
913
+ (window as any).clarity("start", {
914
+ projectId: "test",
915
+ track: true,
916
+ upload: false
917
+ });
918
+
919
+ // Wait a bit for cookies to be set, then check
920
+ setTimeout(() => {
921
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
922
+ if (consent) {
923
+ const cookies = document.cookie;
924
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
925
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
926
+ resolve({
927
+ consent,
928
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
929
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
930
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
931
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
932
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
933
+ cookies
934
+ });
935
+ }
936
+ }, false, false, true);
937
+ }, (window as any).COOKIE_SETUP_DELAY);
938
+
939
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
940
+ });
941
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
942
+
943
+ // Verify initial state: implicit granted with cookies
944
+ expect(initialConsent).not.toBeNull();
945
+ const initialResult = initialConsent as ConsentTestResult;
946
+ expect(initialResult.consent.source).toBe(ConsentSource.Implicit);
947
+ expect(initialResult.consent.ad_Storage).toBe(Constant.Granted);
948
+ expect(initialResult.consent.analytics_Storage).toBe(Constant.Granted);
949
+ // Note: _clsk cookie is only written during upload operations (metadata.save()), not during initial setup
950
+ // With upload: false in test config, only _clck is written by track() function during initialization
951
+ expect(initialResult.hasClckCookie).toBe(true);
952
+ expect(initialResult.clckCookieValue).not.toBe("");
953
+
954
+ // Explicitly grant via consentv2 API (should maintain granted state)
955
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
956
+ return new Promise((resolve) => {
957
+ (window as any).clarity("consentv2", {
958
+ ad_Storage: "granted",
959
+ analytics_Storage: "granted"
960
+ });
961
+
962
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
963
+ if (consent) {
964
+ const cookies = document.cookie;
965
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
966
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
967
+ resolve({
968
+ consent,
969
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
970
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
971
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
972
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
973
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
974
+ cookies
975
+ });
976
+ }
977
+ }, false, false, true);
978
+
979
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
980
+ });
981
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
982
+
983
+ expect(result).not.toBeNull();
984
+ const consentResult = result as ConsentTestResult;
985
+ expect(consentResult.consent.source).toBe(ConsentSource.APIv2);
986
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Granted);
987
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted);
988
+ // Verify _clck cookie remains set (user ID/consent preference cookie)
989
+ // Note: _clsk is only written during upload operations, so we only check _clck in tests with upload: false
990
+ expect(consentResult.hasClckCookie).toBe(true);
991
+ expect(consentResult.clckCookieValue).not.toBe("");
992
+ });
993
+
994
+ // ========================
995
+ // V1 Consent API tests
996
+ // The V1 API uses clarity("consent", boolean) where true=granted, false=denied
997
+ // It sets both ad_Storage and analytics_Storage to the same value
998
+ // ========================
999
+
1000
+ test("consent v1: track=false → consent(true) grants consent and sets cookies", async ({ page }) => {
1001
+ await page.evaluate(setupCookieMock);
1002
+
1003
+ // Verify initial state - no cookies before Clarity starts
1004
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
1005
+ const cookies = document.cookie;
1006
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
1007
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
1008
+ return {
1009
+ cookies,
1010
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
1011
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
1012
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
1013
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
1014
+ };
1015
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
1016
+
1017
+ expect(initialState.cookies).toBe("");
1018
+ expect(initialState.hasClskCookie).toBe(false);
1019
+ expect(initialState.hasClckCookie).toBe(false);
1020
+
1021
+ // Start with track=false (implicit denied), then grant consent via V1 API
1022
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
1023
+ return new Promise((resolve) => {
1024
+ (window as any).clarity("start", {
1025
+ projectId: "test",
1026
+ track: false,
1027
+ upload: false
1028
+ });
1029
+
1030
+ setTimeout(() => {
1031
+ // V1 API: consent(true) grants both ad_Storage and analytics_Storage
1032
+ (window as any).clarity("consent", true);
1033
+
1034
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
1035
+ if (consent) {
1036
+ const cookies = document.cookie;
1037
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
1038
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
1039
+ resolve({
1040
+ consent,
1041
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
1042
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
1043
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
1044
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
1045
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
1046
+ cookies
1047
+ });
1048
+ }
1049
+ }, false, false, true);
1050
+ }, (window as any).COOKIE_SETUP_DELAY);
1051
+
1052
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
1053
+ });
1054
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
1055
+
1056
+ expect(result).not.toBeNull();
1057
+ const consentResult = result as ConsentTestResult;
1058
+ expect(consentResult.consent.source).toBe(ConsentSource.APIv1);
1059
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Granted);
1060
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Granted);
1061
+ // Verify cookies are set when consent is granted via V1 API
1062
+ expect(consentResult.hasClckCookie).toBe(true);
1063
+ expect(consentResult.clckCookieValue).not.toBe("");
1064
+ });
1065
+
1066
+ test("consent v1: track=true → consent(false) revokes consent and deletes cookies", async ({ page }) => {
1067
+ await page.evaluate(setupCookieMock);
1068
+
1069
+ // Verify initial state - no cookies before Clarity starts
1070
+ const initialState = await page.evaluate(({ sessionKey, cookieKey }) => {
1071
+ const cookies = document.cookie;
1072
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
1073
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
1074
+ return {
1075
+ cookies,
1076
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
1077
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
1078
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
1079
+ clckCookieValue: clckMatch ? clckMatch[1] : ""
1080
+ };
1081
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
1082
+
1083
+ expect(initialState.cookies).toBe("");
1084
+ expect(initialState.hasClskCookie).toBe(false);
1085
+ expect(initialState.hasClckCookie).toBe(false);
1086
+
1087
+ // Start with track=true (implicit granted) and verify initial state
1088
+ const initialConsent = await page.evaluate(({ sessionKey, cookieKey }) => {
1089
+ return new Promise((resolve) => {
1090
+ (window as any).clarity("start", {
1091
+ projectId: "test",
1092
+ track: true,
1093
+ upload: false
1094
+ });
1095
+
1096
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
1097
+ if (consent) {
1098
+ const cookies = document.cookie;
1099
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
1100
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
1101
+ resolve({
1102
+ consent,
1103
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
1104
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
1105
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
1106
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
1107
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
1108
+ cookies
1109
+ });
1110
+ }
1111
+ }, false, false, true);
1112
+
1113
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
1114
+ });
1115
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
1116
+
1117
+ // Verify initial state: implicit granted with cookies
1118
+ expect(initialConsent).not.toBeNull();
1119
+ const initialResult = initialConsent as ConsentTestResult;
1120
+ expect(initialResult.consent.source).toBe(ConsentSource.Implicit);
1121
+ expect(initialResult.consent.ad_Storage).toBe(Constant.Granted);
1122
+ expect(initialResult.consent.analytics_Storage).toBe(Constant.Granted);
1123
+ expect(initialResult.hasClckCookie).toBe(true);
1124
+
1125
+ // Revoke consent via V1 API
1126
+ const result = await page.evaluate(({ sessionKey, cookieKey }) => {
1127
+ return new Promise((resolve) => {
1128
+ // V1 API: consent(false) denies both ad_Storage and analytics_Storage
1129
+ (window as any).clarity("consent", false);
1130
+
1131
+ (window as any).clarity("metadata", (_data: any, _upgrade: any, consent: any) => {
1132
+ if (consent) {
1133
+ const cookies = document.cookie;
1134
+ const clskMatch = cookies.match(new RegExp(`${sessionKey}=([^;]+)`));
1135
+ const clckMatch = cookies.match(new RegExp(`${cookieKey}=([^;]+)`));
1136
+ resolve({
1137
+ consent,
1138
+ hasClskCookie: cookies.includes(`${sessionKey}=`),
1139
+ hasClckCookie: cookies.includes(`${cookieKey}=`),
1140
+ clskCookieValue: clskMatch ? clskMatch[1] : "",
1141
+ clckCookieValue: clckMatch ? clckMatch[1] : "",
1142
+ clckConsentCrumb: clckMatch ? (clckMatch[1].split("^")[3] || "") : "",
1143
+ cookies
1144
+ });
1145
+ }
1146
+ }, false, false, true);
1147
+
1148
+ setTimeout(() => resolve(null), (window as any).CONSENT_CALLBACK_TIMEOUT);
1149
+ });
1150
+ }, { sessionKey: Constant.SessionKey, cookieKey: Constant.CookieKey });
1151
+
1152
+ expect(result).not.toBeNull();
1153
+ const consentResult = result as ConsentTestResult;
1154
+ expect(consentResult.consent.source).toBe(ConsentSource.APIv1);
1155
+ expect(consentResult.consent.ad_Storage).toBe(Constant.Denied);
1156
+ expect(consentResult.consent.analytics_Storage).toBe(Constant.Denied);
1157
+ // Verify cookies are deleted when consent is revoked via V1 API
1158
+ expect(consentResult.hasClskCookie).toBe(false);
1159
+ expect(consentResult.hasClckCookie).toBe(false);
1160
+ expect(consentResult.clskCookieValue).toBe("");
1161
+ expect(consentResult.clckCookieValue).toBe("");
1162
+ });
1163
+ });
1164
+