@duckduckgo/autoconsent 16.48.0 → 16.50.0

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.
Files changed (115) hide show
  1. package/.agents/skills/optimize-button-patterns/SKILL.md +145 -0
  2. package/.agents/skills/oxylabs-testing/SKILL.md +1 -1
  3. package/.claude/skills/optimize-button-patterns/SKILL.md +7 -0
  4. package/.github/workflows/release-labels.yml +2 -6
  5. package/AGENTS.md +2 -0
  6. package/CHANGELOG.md +77 -0
  7. package/data/Readme.md +6 -0
  8. package/data/labelled-button-texts.csv +7006 -0
  9. package/dist/addon-firefox/background.bundle.js +6 -1
  10. package/dist/addon-firefox/compact-rules.json +1 -1
  11. package/dist/addon-firefox/content.bundle.js +26 -13
  12. package/dist/addon-firefox/manifest.json +1 -1
  13. package/dist/addon-firefox/rule-index.json +1 -1
  14. package/dist/addon-firefox/rules.json +1 -1
  15. package/dist/addon-mv3/background.bundle.js +6 -1
  16. package/dist/addon-mv3/compact-rules.json +1 -1
  17. package/dist/addon-mv3/content.bundle.js +26 -13
  18. package/dist/addon-mv3/manifest.json +1 -1
  19. package/dist/addon-mv3/rule-index.json +1 -1
  20. package/dist/addon-mv3/rules.json +1 -1
  21. package/dist/autoconsent.cjs.js +26 -13
  22. package/dist/autoconsent.esm.js +26 -13
  23. package/dist/autoconsent.playwright.js +26 -13
  24. package/dist/autoconsent.standalone.js +27 -14
  25. package/dist/types/eval-snippets.d.ts +3 -0
  26. package/lib/eval-snippets.ts +7 -0
  27. package/lib/heuristic-patterns.ts +20 -11
  28. package/package.json +3 -1
  29. package/rules/autoconsent/amazon-hiring.json +16 -0
  30. package/rules/autoconsent/avada-cookie-consent.json +43 -0
  31. package/rules/autoconsent/axeptio.json +17 -4
  32. package/rules/autoconsent/bigcrunch.json +39 -0
  33. package/rules/autoconsent/borlabs.json +5 -0
  34. package/rules/autoconsent/bricklink.json +33 -0
  35. package/rules/autoconsent/ceneo.json +9 -0
  36. package/rules/autoconsent/conjointly.json +43 -0
  37. package/rules/autoconsent/consentmo.json +51 -7
  38. package/rules/autoconsent/cookie-popup-close-cookies.json +37 -0
  39. package/rules/autoconsent/cookieconsent2.json +6 -1
  40. package/rules/autoconsent/costco.json +27 -0
  41. package/rules/autoconsent/datasign.json +85 -0
  42. package/rules/autoconsent/dhbbank.json +13 -0
  43. package/rules/autoconsent/ee-consent-notice.json +40 -0
  44. package/rules/autoconsent/ensembleartsphilly.json +33 -0
  45. package/rules/autoconsent/evium.de.json +33 -0
  46. package/rules/autoconsent/fera-cookies.json +67 -0
  47. package/rules/autoconsent/iubenda.json +13 -7
  48. package/rules/autoconsent/la7-cmp.json +30 -0
  49. package/rules/autoconsent/laravel-cookie-consent.json +45 -0
  50. package/rules/autoconsent/legislation-gov-uk.json +43 -0
  51. package/rules/autoconsent/mackeeper.json +34 -0
  52. package/rules/autoconsent/ndl-go-jp.json +35 -0
  53. package/rules/autoconsent/pandectes.json +48 -15
  54. package/rules/autoconsent/passle.json +32 -0
  55. package/rules/autoconsent/paypal-us.json +3 -3
  56. package/rules/autoconsent/paypal.json +5 -2
  57. package/rules/autoconsent/primeconsent.json +39 -0
  58. package/rules/autoconsent/shinden.pl.json +34 -0
  59. package/rules/autoconsent/texas-instruments.json +17 -0
  60. package/rules/autoconsent/thegrandyork.json +46 -0
  61. package/rules/autoconsent/thenounproject.json +61 -0
  62. package/rules/autoconsent/toyota.json +16 -5
  63. package/rules/autoconsent/tri-table.json +14 -0
  64. package/rules/autoconsent/truevault-polaris.json +63 -0
  65. package/rules/autoconsent/uft-cookie-notice.json +15 -0
  66. package/rules/autoconsent/wbd-legal-terms.json +32 -0
  67. package/rules/autoconsent/webflow.json +1 -1
  68. package/rules/autoconsent/wix.json +90 -8
  69. package/rules/compact-rules.json +1 -1
  70. package/rules/rule-index.json +1 -1
  71. package/rules/rules.json +1 -1
  72. package/scripts/benchmark-button-classification.ts +75 -0
  73. package/scripts/button-text-benchmark.ts +102 -0
  74. package/tests/amazon-hiring.spec.ts +3 -0
  75. package/tests/avada-cookie-consent.spec.ts +9 -0
  76. package/tests/axeptio.spec.ts +8 -4
  77. package/tests/bigcrunch.spec.ts +16 -0
  78. package/tests/borlabs.spec.ts +7 -3
  79. package/tests/bricklink.spec.ts +3 -0
  80. package/tests/ceneo.spec.ts +3 -0
  81. package/tests/conjointly.spec.ts +3 -0
  82. package/tests/consentmo.spec.ts +1 -0
  83. package/tests/cookie-popup-close-cookies.spec.ts +10 -0
  84. package/tests/cookieconsent2.spec.ts +6 -1
  85. package/tests/costco.spec.ts +5 -0
  86. package/tests/datasign.spec.ts +8 -0
  87. package/tests/dhbbank.spec.ts +6 -0
  88. package/tests/ee-consent-notice.spec.ts +9 -0
  89. package/tests/ensembleartsphilly.spec.ts +6 -0
  90. package/tests/evium.de.spec.ts +5 -0
  91. package/tests/fera-cookies.spec.ts +6 -0
  92. package/tests/iubenda.spec.ts +6 -0
  93. package/tests/la7-cmp.spec.ts +6 -0
  94. package/tests/laravel-cookie-consent.spec.ts +10 -0
  95. package/tests/legislation-gov-uk.spec.ts +3 -0
  96. package/tests/mackeeper.spec.ts +3 -0
  97. package/tests/ndl-go-jp.spec.ts +8 -0
  98. package/tests/pandectes.spec.ts +20 -1
  99. package/tests/passle.spec.ts +8 -0
  100. package/tests/paypal-us.spec.ts +2 -3
  101. package/tests/primeconsent.spec.ts +11 -0
  102. package/tests/shinden.pl.spec.ts +5 -0
  103. package/tests/texas-instruments.spec.ts +3 -0
  104. package/tests/thegrandyork.spec.ts +3 -0
  105. package/tests/thenounproject.spec.ts +3 -0
  106. package/tests/tri-table.spec.ts +5 -0
  107. package/tests/truevault-polaris.spec.ts +9 -0
  108. package/tests/uft-cookie-notice.spec.ts +3 -0
  109. package/tests/wbd-legal-terms.spec.ts +18 -0
  110. package/tests/webflow.spec.ts +7 -1
  111. package/tests/wix.spec.ts +8 -1
  112. package/tests-wtr/heuristics/button-classification-accuracy.test.ts +41 -0
  113. package/tests-wtr/heuristics/heuristics-utils.test.ts +64 -0
  114. package/rules/autoconsent/paypal-cookieprefs.json +0 -29
  115. package/tests/paypal-cookieprefs.spec.ts +0 -7
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Benchmark classifyButtonTextRegex against labelled button texts (exact label match, occurrence-weighted).
3
+ * New labelled rows are produced by the tracker-radar-collector post-processing scripts.
4
+ */
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { parseArgs } from 'util';
8
+ import { classifyButtonTextRegex } from '../lib/heuristics';
9
+ import { buildLabelBenchmarks, classifyRows, formatExample, LabelBenchmark, parseButtonTextCsv, pct } from './button-text-benchmark';
10
+
11
+ const DEFAULT_CSV_PATH = path.join(__dirname, '../data/labelled-button-texts.csv');
12
+ const TOP_FAILURES = 25;
13
+ const USAGE = `Usage: npm run benchmark-buttons -- [--input <path>] [--top <n>]
14
+
15
+ Benchmark classifyButtonTextRegex against labelled button texts (exact label match, occurrence-weighted).
16
+
17
+ -i, --input <path> path to labelled button text CSV (default: ${path.relative(process.cwd(), DEFAULT_CSV_PATH)})
18
+ --top <n> number of failure examples to print per label (default: ${TOP_FAILURES})
19
+ -h, --help show this help`;
20
+
21
+ const { values } = parseArgs({
22
+ options: {
23
+ input: { type: 'string', short: 'i', default: DEFAULT_CSV_PATH },
24
+ top: { type: 'string', default: String(TOP_FAILURES) },
25
+ help: { type: 'boolean', short: 'h' },
26
+ },
27
+ });
28
+ if (values.help) {
29
+ console.log(USAGE);
30
+ process.exit(0);
31
+ }
32
+ const opts = { input: values.input, top: parseInt(values.top, 10) };
33
+
34
+ function printLabelBenchmark(b: LabelBenchmark) {
35
+ console.log(`\n ${b.label}`);
36
+ console.log(
37
+ ` correctly labelled: ${b.rowCorrect}/${b.rowSupport} rows (${pct(b.rowCorrect, b.rowSupport)}), ${b.weightedCorrect}/${b.weightedSupport} weighted (${pct(b.weightedCorrect, b.weightedSupport)})`,
38
+ );
39
+ console.log(
40
+ ` false positives (predicted ${b.label}, labelled otherwise): ${b.falsePositiveExamples.length} rows, ${b.weightedFalsePositives} weighted`,
41
+ );
42
+ for (const [title, examples] of [
43
+ ['top false positives', b.falsePositiveExamples],
44
+ ['top missed', b.missedExamples],
45
+ ] as const) {
46
+ console.log(` ${title}:${examples.length === 0 ? ' (none)' : ''}`);
47
+ for (const example of examples.slice(0, opts.top)) {
48
+ console.log(` ${formatExample(example)}`);
49
+ }
50
+ }
51
+ }
52
+
53
+ function main() {
54
+ const inputPath = path.resolve(opts.input);
55
+ const rows = parseButtonTextCsv(fs.readFileSync(inputPath, 'utf8'));
56
+ if (rows.length === 0) {
57
+ console.error('no labelled rows found in', inputPath);
58
+ process.exit(1);
59
+ }
60
+
61
+ const benchmarks = buildLabelBenchmarks(classifyRows(rows, classifyButtonTextRegex));
62
+ const total = (key: 'rowSupport' | 'weightedSupport' | 'rowCorrect' | 'weightedCorrect' | 'weightedFalsePositives') =>
63
+ benchmarks.reduce((sum, b) => sum + b[key], 0);
64
+
65
+ console.log(`Input: ${inputPath} (${rows.length} labelled rows)`);
66
+ console.log('\nclassifyButtonTextRegex benchmark (excluding other)');
67
+ console.log(
68
+ ` correctly labelled: ${total('rowCorrect')}/${total('rowSupport')} rows, ${total('weightedCorrect')}/${total('weightedSupport')} weighted (${pct(total('weightedCorrect'), total('weightedSupport'))})`,
69
+ );
70
+ console.log(` false positives (weighted): ${total('weightedFalsePositives')}`);
71
+ console.log(` missed (weighted): ${total('weightedSupport') - total('weightedCorrect')}`);
72
+ benchmarks.forEach(printLabelBenchmark);
73
+ }
74
+
75
+ main();
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Helpers for scoring the button classifier against data/labelled-button-texts.csv.
3
+ * Shared by the WTR accuracy test and scripts/benchmark-button-classification.ts, so must not use Node or DOM APIs.
4
+ */
5
+ // browser ESM build, so the same import works in WTR and in Node
6
+ import { parse } from 'csv-parse/browser/esm/sync';
7
+ import { ButtonRegexClassification } from '../lib/types';
8
+
9
+ const VALID_LABELS: readonly ButtonRegexClassification[] = ['settings', 'accept', 'reject', 'acknowledge', 'other'];
10
+
11
+ export const BENCHMARK_LABELS = VALID_LABELS.filter((label) => label !== 'other');
12
+
13
+ export type ButtonTextRow = {
14
+ buttonText: string;
15
+ occurences: number;
16
+ label: ButtonRegexClassification;
17
+ };
18
+
19
+ export type ClassifiedButtonTextRow = ButtonTextRow & {
20
+ predicted: ButtonRegexClassification;
21
+ };
22
+
23
+ export type LabelBenchmark = {
24
+ label: ButtonRegexClassification;
25
+ rowSupport: number;
26
+ weightedSupport: number;
27
+ rowCorrect: number;
28
+ weightedCorrect: number;
29
+ weightedFalsePositives: number;
30
+ falsePositiveExamples: ClassifiedButtonTextRow[];
31
+ missedExamples: ClassifiedButtonTextRow[];
32
+ };
33
+
34
+ /**
35
+ * Parse labelled-button-texts.csv (columns: button_text, occurences, label), keeping only rows with a valid label.
36
+ * Options match the reader in tracker-radar-collector, which also writes this file.
37
+ */
38
+ export function parseButtonTextCsv(content: string): ButtonTextRow[] {
39
+ const records: Record<string, string>[] = parse(content, {
40
+ columns: true,
41
+ skip_empty_lines: true,
42
+ relax_column_count: true,
43
+ trim: true,
44
+ });
45
+
46
+ const rows: ButtonTextRow[] = [];
47
+ for (const record of records) {
48
+ const buttonText = record.button_text;
49
+ const occurences = Number.parseInt(record.occurences, 10);
50
+ const label = record.label?.toLowerCase() as ButtonRegexClassification;
51
+ if (!buttonText || Number.isNaN(occurences) || !VALID_LABELS.includes(label)) {
52
+ continue;
53
+ }
54
+ rows.push({ buttonText, occurences, label });
55
+ }
56
+ return rows;
57
+ }
58
+
59
+ export function classifyRows(
60
+ rows: ButtonTextRow[],
61
+ classifier: (buttonText: string) => ButtonRegexClassification,
62
+ ): ClassifiedButtonTextRow[] {
63
+ return rows.map((row) => ({ ...row, predicted: classifier(row.buttonText) }));
64
+ }
65
+
66
+ function sumOccurences(rows: ButtonTextRow[]): number {
67
+ return rows.reduce((sum, row) => sum + row.occurences, 0);
68
+ }
69
+
70
+ function byOccurence(a: ButtonTextRow, b: ButtonTextRow): number {
71
+ return b.occurences - a.occurences || a.buttonText.localeCompare(b.buttonText);
72
+ }
73
+
74
+ /**
75
+ * Per-label stats, in BENCHMARK_LABELS order. Rows labelled 'other' are excluded, matching the original collector benchmark.
76
+ */
77
+ export function buildLabelBenchmarks(results: ClassifiedButtonTextRow[]): LabelBenchmark[] {
78
+ const benchmarkResults = results.filter((r) => r.label !== 'other');
79
+ return BENCHMARK_LABELS.map((label) => {
80
+ const support = benchmarkResults.filter((r) => r.label === label);
81
+ const correct = support.filter((r) => r.predicted === label);
82
+ const falsePositives = benchmarkResults.filter((r) => r.predicted === label && r.label !== label);
83
+ return {
84
+ label,
85
+ rowSupport: support.length,
86
+ weightedSupport: sumOccurences(support),
87
+ rowCorrect: correct.length,
88
+ weightedCorrect: sumOccurences(correct),
89
+ weightedFalsePositives: sumOccurences(falsePositives),
90
+ falsePositiveExamples: falsePositives.sort(byOccurence),
91
+ missedExamples: support.filter((r) => r.predicted !== label).sort(byOccurence),
92
+ };
93
+ });
94
+ }
95
+
96
+ export function pct(numerator: number, denominator: number): string {
97
+ return denominator === 0 ? 'n/a' : `${((numerator / denominator) * 100).toFixed(1)}%`;
98
+ }
99
+
100
+ export function formatExample(row: ClassifiedButtonTextRow): string {
101
+ return `[${row.label} -> ${row.predicted}] ${JSON.stringify(row.buttonText)} (x${row.occurences})`;
102
+ }
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('amazon-hiring', ['https://hiring.amazon.com/', 'https://hiring.amazon.ca/app']);
@@ -0,0 +1,9 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('avada-cookie-consent', [
4
+ 'https://www.nativeunion.com/products/belt-cable-usb-c-to-usb-c',
5
+ 'https://www.marinbikes.com/',
6
+ 'https://www.simplemodern.com/',
7
+ // Deny button opens the preferences popup
8
+ 'https://www.outdoortechnology.com/',
9
+ ]);
@@ -1,9 +1,13 @@
1
1
  import generateCMPTests from '../playwright/runner';
2
2
 
3
3
  generateCMPTests('axeptio', [
4
- 'https://www.welcometothejungle.com/',
5
- 'https://www.dorcel.com/',
6
- 'https://mistral.ai/news/le-chat-dives-deep',
7
- 'https://lahalledesgourmets.com/pages/angelina',
4
+ 'https://docs.mistral.ai/',
5
+ 'https://www.prestashop-project.org/',
6
+ 'https://www.spareka.fr/',
8
7
  'https://www.narbonneaccessoires.fr/fr-fr/',
9
8
  ]);
9
+
10
+ // Step-based widget: "Close" rejects, "Accept all" only advances to the next step
11
+ generateCMPTests('axeptio', ['https://mistral.ai/pricing', 'https://mistral.ai/news/le-chat-dives-deep'], {
12
+ testOptIn: false,
13
+ });
@@ -0,0 +1,16 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests(
4
+ 'bigcrunch',
5
+ [
6
+ 'https://tvtropes.org/',
7
+ 'https://www.mlbtraderumors.com/',
8
+ 'https://www.alternet.org/',
9
+ 'https://attackofthefanboy.com/',
10
+ 'https://www.hoopsrumors.com/',
11
+ 'https://crooksandliars.com/',
12
+ ],
13
+ {
14
+ skipRegions: ['US'],
15
+ },
16
+ );
@@ -1,5 +1,9 @@
1
1
  import generateCMPTests from '../playwright/runner';
2
2
 
3
- generateCMPTests('borlabs', ['https://www.kesselheld.de/', 'https://cocoon-hotels.com/de/cocoon-salzburg/'], {
4
- skipRegions: ['US'],
5
- });
3
+ generateCMPTests(
4
+ 'borlabs',
5
+ ['https://www.kesselheld.de/', 'https://cocoon-hotels.com/de/cocoon-salzburg/', 'https://www.headphonecheck.com/'],
6
+ {
7
+ skipRegions: ['US'],
8
+ },
9
+ );
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('bricklink', ['https://www.bricklink.com/v2/main.page', 'https://store.bricklink.com/']);
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('ceneo', ['https://www.ceneo.pl/', 'https://www.ceneolokalnie.pl/']);
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('conjointly', ['https://conjointly.com/']);
@@ -8,6 +8,7 @@ generateCMPTests(
8
8
  'https://mgemi.com/products/the-felize-saddle-suede',
9
9
  'https://eu.blackdiamondequipment.com/',
10
10
  'https://www.directdoors.com/',
11
+ 'https://goorin.com/',
11
12
  ],
12
13
  {
13
14
  skipRegions: ['US'],
@@ -0,0 +1,10 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('cookie-popup-close-cookies', [
4
+ 'https://catflooringaccessories.com/product/fpb10x3/',
5
+ 'https://www.coba.com/',
6
+ 'https://www.copely.com/',
7
+ 'https://www.movingedge.com/',
8
+ 'https://www.arvitec.com/',
9
+ 'https://www.cobaflooring.com/',
10
+ ]);
@@ -1,3 +1,8 @@
1
1
  import generateCMPTests from '../playwright/runner';
2
2
 
3
- generateCMPTests('cookieconsent2', ['https://alpha-heidelberg.de/', 'https://antitec.fi/', 'https://servero.nl/onze-producten/']);
3
+ generateCMPTests('cookieconsent2', [
4
+ 'https://alpha-heidelberg.de/',
5
+ 'https://antitec.fi/',
6
+ 'https://servero.nl/onze-producten/',
7
+ 'https://www.goaexperience.co.uk/guide/festivals-and-events',
8
+ ]);
@@ -0,0 +1,5 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('costco', ['https://www.costco.ca/', 'https://www.costcobusinesscentre.ca/'], {
4
+ testOptIn: false,
5
+ });
@@ -0,0 +1,8 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('datasign', [
4
+ 'https://www.jfe-holdings.co.jp/en/',
5
+ 'https://www.otafuku.co.jp/',
6
+ 'https://www.billboard-japan.com/',
7
+ 'https://www.dydo.co.jp/',
8
+ ]);
@@ -0,0 +1,6 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ // rejecting reloads the page, which interrupts the self-test
4
+ generateCMPTests('dhbbank', ['https://www.dhbbank.de/', 'https://www.dhbbank.nl/', 'https://www.dhbbank.com/', 'https://www.dhbbank.be/'], {
5
+ testSelfTest: false,
6
+ });
@@ -0,0 +1,9 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('ee-consent-notice', [
4
+ 'https://bryantpark.org/activities/category/winter-village',
5
+ 'https://assuredguaranty.com/',
6
+ 'https://www.janorth.org/',
7
+ 'https://bellegrove.org/',
8
+ 'https://artsmn.org/',
9
+ ]);
@@ -0,0 +1,6 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('ensembleartsphilly', [
4
+ 'https://www.ensembleartsphilly.org/tickets-and-events/ensemble-arts-philly-presents/gary-gulman',
5
+ 'https://philorch.ensembleartsphilly.org/',
6
+ ]);
@@ -0,0 +1,5 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('evium.de', ['https://www.evium.de/'], {
4
+ testSelfTest: false,
5
+ });
@@ -0,0 +1,6 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('fera-cookies', [
4
+ 'https://planthealthportal.defra.gov.uk/trade/imports/imports-from-the-eu/bcpscps/faqs-for-animal-and-plant-health-agencies-apha-operational-hours/',
5
+ 'https://www.cropmonitor.co.uk/',
6
+ ]);
@@ -3,3 +3,9 @@ import generateCMPTests from '../playwright/runner';
3
3
  generateCMPTests('iubenda', ['https://www.rossignol.com/us/', 'https://www.lofficielusa.com/', 'https://www.3bmeteo.com/'], {
4
4
  skipRegions: ['AU'],
5
5
  });
6
+
7
+ // accept-only banner without reject or customize buttons
8
+ generateCMPTests('iubenda', ['https://www.yogurtland.com/', 'https://www.greenplanet.net/'], {
9
+ skipRegions: ['AU'],
10
+ testSelfTest: false,
11
+ });
@@ -0,0 +1,6 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ // the site reloads the page after opt-out, so the self-test cannot run in the original context
4
+ generateCMPTests('la7-cmp', ['https://www.la7.it/', 'https://tg.la7.it/', 'https://www.sedanoallegro.it/', 'https://tv.torinofc.it/'], {
5
+ testSelfTest: false,
6
+ });
@@ -0,0 +1,10 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('laravel-cookie-consent', [
4
+ 'https://podstatus.com/',
5
+ 'https://www.arzneimittel-datenbank.de/',
6
+ 'https://www.opendag.no/',
7
+ 'https://teatenerife.es/',
8
+ 'https://radacini.ro/',
9
+ 'https://30cc.be/',
10
+ ]);
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('legislation-gov-uk', ['https://www.legislation.gov.uk/']);
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('mackeeper', ['https://mackeeper.com/blog/all-you-need-to-know-about-mac-power-management/', 'https://mackeeper.com/']);
@@ -0,0 +1,8 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('ndl-go-jp', [
4
+ 'https://ndlsearch.ndl.go.jp/imagebank',
5
+ 'https://www.ndl.go.jp/',
6
+ 'https://www.ndl.go.jp/en/',
7
+ 'https://dl.ndl.go.jp/',
8
+ ]);
@@ -3,6 +3,25 @@ import generateCMPTests from '../playwright/runner';
3
3
  generateCMPTests('pandectes', [
4
4
  'https://hollandscountryclothing.co.uk/',
5
5
  'https://cluse.com/',
6
- 'https://www.parent.com/',
7
6
  'https://swedishstockings.com/', // full-page overlay ("blocking") banner variant
8
7
  ]);
8
+
9
+ // shadow DOM <pandectes-cmp> banner variant
10
+ generateCMPTests(
11
+ 'pandectes',
12
+ [
13
+ 'https://enron.com/',
14
+ 'https://www.parent.com/',
15
+ 'https://wetnwildbeauty.com/',
16
+ 'https://www.yakima.com/',
17
+ 'https://us.select-sport.com/collections/soccer-balls-training-series',
18
+ ],
19
+ {
20
+ testOptIn: false,
21
+ },
22
+ );
23
+
24
+ // shadow DOM variant without a decline button: opt out through preferences
25
+ generateCMPTests('pandectes', ['https://asmc.de/', 'https://crossrope.com/'], {
26
+ testOptIn: false,
27
+ });
@@ -0,0 +1,8 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('passle', [
4
+ 'https://technologylaw.fkks.com/post/102jge9/california-requires-browsers-and-mobile-devices-to-support-opt-out-preference-sig',
5
+ 'https://www.insurtechworld.org/',
6
+ 'https://news.fintech.io/',
7
+ 'https://www.bbeb.com/',
8
+ ]);
@@ -2,6 +2,5 @@ import generateCMPTests from '../playwright/runner';
2
2
 
3
3
  generateCMPTests('paypal-us', ['https://www.paypal.com/us/home'], {});
4
4
 
5
- // This banner variant has no "Decline" button, so opting out goes through the cookie
6
- // preferences page, which navigates away and back, running the rule more than once.
7
- generateCMPTests('paypal-us', ['https://www.paypal.com/ca/home'], { expectedRuns: 3 });
5
+ // This banner variant has no "Decline" button, so the rule hides it and no choice is stored.
6
+ generateCMPTests('paypal-us', ['https://www.paypal.com/ca/home'], { testSelfTest: false });
@@ -0,0 +1,11 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('primeconsent', [
4
+ 'https://www.jclewislincoln.com/2025-lincoln-aviator-dimensions',
5
+ 'https://jclewisford.net/',
6
+ 'https://www.experiencepoint.com/',
7
+ 'https://www.valenciaautocenter.com/',
8
+ 'https://www.upl-ltd.com/',
9
+ 'https://www.cornhuskernissan.com/',
10
+ 'https://privacypillar.com/',
11
+ ]);
@@ -0,0 +1,5 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('shinden.pl', ['https://shinden.pl/episode/61872-tensei-shitara-slime-datta-ken-3rd-season/view/231781'], {
4
+ testSelfTest: false,
5
+ });
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('texas-instruments', ['https://education.ti.com/', 'https://www.t3europe.eu/', 'https://www.t3deutschland.de/']);
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('thegrandyork', ['https://www.thegrandyork.co.uk/']);
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('thenounproject', ['https://thenounproject.com/']);
@@ -0,0 +1,5 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('tri-table', ['https://ckm.pl/', 'https://tri-table.com/'], {
4
+ skipRegions: ['US'],
5
+ });
@@ -0,0 +1,9 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('truevault-polaris', [
4
+ 'https://www.mcgeeandco.com/products/calvert-brass-tissue-box-cover',
5
+ 'https://www.studio-mcgee.com/',
6
+ 'https://www.thorne.com/',
7
+ 'https://www.crossfit.com/',
8
+ 'https://www.philzcoffee.com/',
9
+ ]);
@@ -0,0 +1,3 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests('uft-cookie-notice', ['https://fribourg.ch/en/', 'https://www.la-gruyere.ch/', 'https://www.schwarzsee.ch/']);
@@ -0,0 +1,18 @@
1
+ import generateCMPTests from '../playwright/runner';
2
+
3
+ generateCMPTests(
4
+ 'wbd-legal-terms',
5
+ [
6
+ 'https://www.foodnetwork.com/how-to/packages/shopping/product-reviews/best-food-processors',
7
+ 'https://www.hgtv.com/',
8
+ 'https://www.golfdigest.com/',
9
+ 'https://www.tlc.com/',
10
+ 'https://www.discovery.com/',
11
+ ],
12
+ {
13
+ // shown only in some US states
14
+ onlyRegions: ['US'],
15
+ testOptIn: false,
16
+ testSelfTest: false,
17
+ },
18
+ );
@@ -1,3 +1,9 @@
1
1
  import generateCMPTests from '../playwright/runner';
2
2
 
3
- generateCMPTests('webflow', ['https://olark.com/', 'https://www.nexaas.com/', 'https://www.rupahealth.com/', 'https://www.hk.co/']);
3
+ generateCMPTests('webflow', [
4
+ 'https://www.ctgt.ai/research/distillation-censorship-transfer',
5
+ 'https://www.dock.io/',
6
+ 'https://www.talkabroad.com/',
7
+ 'https://www.pocoyo.com/',
8
+ 'https://www.storypark.com/',
9
+ ]);
package/tests/wix.spec.ts CHANGED
@@ -1,3 +1,10 @@
1
1
  import generateCMPTests from '../playwright/runner';
2
2
 
3
- generateCMPTests('wix', ['https://www.wix.com/']);
3
+ generateCMPTests('wix', [
4
+ 'https://www.wix.com/',
5
+ 'https://www.poconopoolandspallc.com/',
6
+ 'https://www.karlswarehousing.com/',
7
+ 'https://www.stormkingpw.com/',
8
+ 'https://www.iconicoffices.com/',
9
+ 'https://www.steigertools.nl/',
10
+ ]);
@@ -0,0 +1,41 @@
1
+ import { expect } from '@esm-bundle/chai';
2
+ import { classifyButtonTextRegex } from '../../lib/heuristics';
3
+ import {
4
+ BENCHMARK_LABELS,
5
+ buildLabelBenchmarks,
6
+ classifyRows,
7
+ formatExample,
8
+ LabelBenchmark,
9
+ parseButtonTextCsv,
10
+ pct,
11
+ } from '../../scripts/button-text-benchmark';
12
+
13
+ // Minimum occurrence-weighted accuracy required per label.
14
+ const MIN_WEIGHTED_ACCURACY = 0.9;
15
+
16
+ describe('classifyButtonTextRegex against labelled button texts', () => {
17
+ let benchmarks: LabelBenchmark[];
18
+
19
+ before(async () => {
20
+ const response = await fetch('/data/labelled-button-texts.csv');
21
+ const rows = parseButtonTextCsv(await response.text());
22
+ expect(rows.length).to.be.greaterThan(0, 'expected labelled rows in the CSV');
23
+ benchmarks = buildLabelBenchmarks(classifyRows(rows, classifyButtonTextRegex));
24
+ });
25
+
26
+ BENCHMARK_LABELS.forEach((label, i) => {
27
+ it(`never predicts "${label}" for a button labelled differently`, () => {
28
+ const examples = benchmarks[i].falsePositiveExamples.slice(0, 10).map(formatExample).join('\n ');
29
+ expect(benchmarks[i].falsePositiveExamples.length).to.equal(0, `false positives for "${label}":\n ${examples}\n`);
30
+ });
31
+
32
+ it(`classifies >${MIN_WEIGHTED_ACCURACY * 100}% of weighted "${label}" buttons correctly`, () => {
33
+ const { weightedCorrect, weightedSupport } = benchmarks[i];
34
+ expect(weightedSupport).to.be.greaterThan(0, `expected labelled rows for "${label}"`);
35
+ expect(weightedCorrect / weightedSupport).to.be.greaterThan(
36
+ MIN_WEIGHTED_ACCURACY,
37
+ `weighted accuracy for "${label}" was ${pct(weightedCorrect, weightedSupport)} (${weightedCorrect}/${weightedSupport})`,
38
+ );
39
+ });
40
+ });
41
+ });