@slashgear/gdpr-cookie-scanner 3.3.0 → 3.4.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.
@@ -43,4 +43,23 @@ jobs:
43
43
  run: pnpm exec playwright install chromium --with-deps
44
44
 
45
45
  - name: Test
46
- run: pnpm test
46
+ id: test
47
+ run: |
48
+ start=$(date +%s)
49
+ pnpm test
50
+ echo "duration=$(( $(date +%s) - start ))" >> "$GITHUB_OUTPUT"
51
+
52
+ - name: Record test suite duration
53
+ if: always()
54
+ run: |
55
+ duration="${{ steps.test.outputs.duration }}"
56
+ echo "## Test suite duration" >> "$GITHUB_STEP_SUMMARY"
57
+ echo "" >> "$GITHUB_STEP_SUMMARY"
58
+ echo "| Suite | Duration |" >> "$GITHUB_STEP_SUMMARY"
59
+ echo "| ----- | -------- |" >> "$GITHUB_STEP_SUMMARY"
60
+ echo "| unit + e2e | ${duration}s |" >> "$GITHUB_STEP_SUMMARY"
61
+ if [ "${duration:-0}" -gt 300 ]; then
62
+ echo "" >> "$GITHUB_STEP_SUMMARY"
63
+ echo "> [!WARNING]" >> "$GITHUB_STEP_SUMMARY"
64
+ echo "> Suite exceeded 300 s — possible performance regression." >> "$GITHUB_STEP_SUMMARY"
65
+ fi
package/CHANGELOG.md CHANGED
@@ -1,5 +1,102 @@
1
1
  # @slashgear/gdpr-cookie-scanner
2
2
 
3
+ ## 3.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 39794dc: Crop consent modal screenshot to the element; make after-reject/accept screenshots opt-in.
8
+
9
+ Two behaviour changes:
10
+
11
+ **Modal screenshot is now always captured (cropped)**
12
+ The consent modal screenshot (`modal-initial.png`) is taken whenever a modal
13
+ is detected and `outputDir` is set, regardless of the `--screenshots` flag.
14
+ The screenshot is clipped to the bounding box of the modal element instead of
15
+ capturing the full viewport, giving a tighter, more readable image. If the
16
+ bounding box cannot be determined (rare), it falls back to the viewport
17
+ screenshot.
18
+
19
+ **`--no-screenshots` replaced by `--screenshots`**
20
+ Previously all three screenshots were enabled by default and `--no-screenshots`
21
+ opted out. Now only the modal screenshot is taken by default; passing
22
+ `--screenshots` additionally captures `after-reject.png` and `after-accept.png`
23
+ (full viewport, as before). The `screenshots` field in `ScanOptions` / the
24
+ programmatic API retains the same type (`boolean`) with updated semantics:
25
+ `false` (default) = modal only; `true` = modal + after-reject + after-accept.
26
+
27
+ - 6a71a18: Add multi-language consent button detection (de, es, it, nl, pl, pt).
28
+
29
+ Previously, button classification only covered French and English, causing
30
+ false "no reject button" findings on sites served in other EU locales.
31
+
32
+ The fix has two parts:
33
+
34
+ 1. **Locale-aware pattern map** — `ACCEPT_PATTERNS` / `REJECT_PATTERNS` /
35
+ `PREFERENCES_PATTERNS` are replaced by a `PATTERNS_BY_LOCALE` map keyed by
36
+ BCP 47 primary subtag, covering `en`, `fr`, `de`, `es`, `it`, `nl`, `pl`,
37
+ `pt`. Polish patterns use a negative lookbehind instead of `\b` because
38
+ several Polish words end in non-ASCII characters (ć, ę, ó) that fall
39
+ outside JS `\w`.
40
+
41
+ 2. **`<html lang>` detection** — `detectConsentModal` now reads the page's
42
+ declared language from `document.documentElement.lang` and normalises it to
43
+ a primary subtag (e.g. `"de-DE"` → `"de"`). When the language is
44
+ recognised, only that locale's patterns plus English (universal fallback)
45
+ are tested. When the language is missing or unsupported, all available
46
+ patterns are tried — preserving the previous behaviour for unknown pages.
47
+
48
+ The public export `classifyButtonText(text, lang)` is added for testing and
49
+ programmatic use; 56 new unit tests cover every supported locale.
50
+
51
+ ### Patch Changes
52
+
53
+ - ceed240: Add unit tests for `computeContrastRatio`, `parseRgb`, and `relativeLuminance`.
54
+
55
+ These three pure functions in `consent-modal.ts` were previously only exercised
56
+ indirectly through the E2E suite. The new test file
57
+ (`tests/scanner/contrast-ratio.test.ts`) covers the happy path, edge cases
58
+ (identical colours, fully transparent rgba, non-integer ratios), and documents
59
+ the known limitations — named colours (`white`, `black`) and hex values (`#fff`)
60
+ return `null` until the parser is extended.
61
+
62
+ The functions are now exported so they can be imported by the test suite without
63
+ moving them to a separate module.
64
+
65
+ - df24a36: Fix consent modal detection for CMPs that start hidden (e.g. Axeptio).
66
+
67
+ `MODAL_SELECTORS` was a single flat list where every candidate was required to
68
+ pass `isVisible()`. CMPs such as Axeptio inject their overlay as `display:none`
69
+ during initialisation and reveal it via JS animation a few hundred milliseconds
70
+ later. The visibility check caused the scanner to skip `#axeptio_overlay` and
71
+ fall through to the first matching generic heuristic (e.g. `[id*='consent']`),
72
+ which could be a completely unrelated element.
73
+
74
+ The list is now split into two:
75
+
76
+ - **`CMP_SELECTORS`** — precise, platform-specific identifiers. DOM presence
77
+ alone is treated as a reliable signal. Once the element is found the scanner
78
+ waits up to 3 s for it to become visible (so button extraction sees an
79
+ interactive state) but proceeds regardless, preventing a slow CMP from
80
+ silently falling back to a wrong heuristic.
81
+ - **`HEURISTIC_SELECTORS`** — broad patterns that could match unrelated
82
+ elements. Visibility is still required to avoid false positives.
83
+
84
+ - f9efe0b: Normalise button text whitespace before classification.
85
+
86
+ `classifyButtonType` previously received raw `textContent` that had only been
87
+ `.trim()`-ed. CMP HTML templates frequently embed `&nbsp;` (U+00A0), newlines,
88
+ or tabs inside button labels, causing pattern matching to silently fail.
89
+
90
+ A `normalizeText` helper now collapses any whitespace sequence (including
91
+ U+00A0 and all Unicode spaces covered by JS `\s`) into a single ASCII space
92
+ before the regex is tested. The normalisation is applied in two places:
93
+
94
+ - `classifyButtonText` (public export) — defensive normalisation of any caller-
95
+ provided string.
96
+ - `extractButtons` — the raw `el.textContent()` result is normalised before
97
+ being stored in `ConsentButton.text` and before classification, so the
98
+ report also shows the cleaned label.
99
+
3
100
  ## 3.3.0
4
101
 
5
102
  ### Minor Changes
package/NEXT_STEPS.md CHANGED
@@ -14,25 +14,6 @@ Ideas and improvement areas for `gdpr-cookie-scanner`. Not a roadmap — pick wh
14
14
 
15
15
  ---
16
16
 
17
- ## Language support
18
-
19
- Currently only French and English patterns are covered. A lot of EU sites served in other locales will get false "no reject button" results.
20
-
21
- Languages to add at minimum (ordered by EU population / GDPR enforcement activity):
22
-
23
- | Locale | Reject examples | Accept examples |
24
- | ------- | ------------------------ | -------------------------------- |
25
- | `de-DE` | Ablehnen, Alle ablehnen | Alle akzeptieren, Zustimmen |
26
- | `es-ES` | Rechazar, Rechazar todo | Aceptar, Aceptar todo |
27
- | `it-IT` | Rifiuta, Rifiuta tutto | Accetta, Accetta tutto |
28
- | `nl-NL` | Weigeren, Alles weigeren | Accepteren, Alles accepteren |
29
- | `pl-PL` | Odrzuć, Odrzuć wszystkie | Zaakceptuj, Zaakceptuj wszystkie |
30
- | `pt-PT` | Rejeitar, Rejeitar tudo | Aceitar, Aceitar tudo |
31
-
32
- The patterns live in `src/scanner/consent-modal.ts`. A locale-aware pattern map keyed by BCP 47 tag would be cleaner than one giant regex.
33
-
34
- ---
35
-
36
17
  ## Dark pattern detection gaps
37
18
 
38
19
  Patterns that are explicitly listed in CNIL/EDPB guidelines but not yet detected:
@@ -81,12 +62,6 @@ Patterns that are explicitly listed in CNIL/EDPB guidelines but not yet detected
81
62
 
82
63
  - **Report output tests** — no tests currently validate the content of generated Markdown, HTML, or JSON files. Add snapshot tests for at least the JSON output and spot-checks for key sections in HTML.
83
64
 
84
- - **Contrast ratio unit tests** — `computeContrastRatio` is pure logic but only exercised through E2E tests. Deserves a dedicated unit test file covering edge cases (identical colours, transparent backgrounds, named/hex inputs once supported).
85
-
86
- - **Locale-specific button pattern tests** — once multi-language patterns land, add a test case per locale to prevent regressions.
87
-
88
- - **Performance baseline** — the E2E suite takes ~2 minutes. As features grow, tracking suite duration in CI would help catch regressions early.
89
-
90
65
  ---
91
66
 
92
67
  ## Infrastructure
package/README.md CHANGED
@@ -63,18 +63,18 @@ gdpr-scan scan <url> [options]
63
63
 
64
64
  ### Options
65
65
 
66
- | Option | Default | Description |
67
- | ------------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------ |
68
- | `-o, --output <dir>` | `./gdpr-reports` | Output directory for the report |
69
- | `-t, --timeout <ms>` | `30000` | Navigation timeout |
70
- | `-f, --format <formats>` | `html` | Output formats: `md`, `html`, `json`, `pdf` (comma-separated) |
71
- | `--viewport <preset>` | `desktop` | Viewport preset: `desktop` (1280×900), `tablet` (768×1024), `mobile` (390×844) |
72
- | `--fail-on <threshold>` | `F` | Exit with code 1 if grade is below this letter (`A`/`B`/`C`/`D`/`F`) or score is below this number (`0–100`) |
73
- | `--json-summary` | — | Emit a machine-readable JSON line to stdout after the scan (parseable by `jq`) |
74
- | `--strict` | — | Treat unrecognised cookies and unknown third-party requests as requiring consent |
75
- | `--no-screenshots` | — | Disable screenshot capture |
76
- | `-l, --locale <locale>` | `fr-FR` | Browser locale |
77
- | `-v, --verbose` | — | Show full stack trace on error |
66
+ | Option | Default | Description |
67
+ | ------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- |
68
+ | `-o, --output <dir>` | `./gdpr-reports` | Output directory for the report |
69
+ | `-t, --timeout <ms>` | `30000` | Navigation timeout |
70
+ | `-f, --format <formats>` | `html` | Output formats: `md`, `html`, `json`, `pdf` (comma-separated) |
71
+ | `--viewport <preset>` | `desktop` | Viewport preset: `desktop` (1280×900), `tablet` (768×1024), `mobile` (390×844) |
72
+ | `--fail-on <threshold>` | `F` | Exit with code 1 if grade is below this letter (`A`/`B`/`C`/`D`/`F`) or score is below this number (`0–100`) |
73
+ | `--json-summary` | — | Emit a machine-readable JSON line to stdout after the scan (parseable by `jq`) |
74
+ | `--strict` | — | Treat unrecognised cookies and unknown third-party requests as requiring consent |
75
+ | `--screenshots` | — | Also capture full-page screenshots after reject and accept interactions (the consent modal is always screenshotted when detected) |
76
+ | `-l, --locale <locale>` | `fr-FR` | Browser locale |
77
+ | `-v, --verbose` | — | Show full stack trace on error |
78
78
 
79
79
  ### Examples
80
80
 
@@ -85,8 +85,8 @@ gdpr-scan scan https://example.com
85
85
  # With custom output directory
86
86
  gdpr-scan scan https://example.com -o ./reports
87
87
 
88
- # Scan in English, without screenshots
89
- gdpr-scan scan https://example.com --locale en-US --no-screenshots
88
+ # Scan in English with full interaction screenshots (reject + accept)
89
+ gdpr-scan scan https://example.com --locale en-US --screenshots
90
90
 
91
91
  # Generate a Markdown report instead
92
92
  gdpr-scan scan https://example.com -f md
@@ -243,7 +243,7 @@ All fields of `ScanResult` — cookies, network requests, modal analysis, compli
243
243
  const result = await scan("https://example.com", {
244
244
  locale: "fr-FR", // browser locale, also controls report language
245
245
  timeout: 60_000, // navigation timeout in ms (default: 30 000)
246
- screenshots: true, // capture screenshots (requires outputDir)
246
+ screenshots: true, // also capture after-reject and after-accept screenshots (modal is always screenshotted)
247
247
  outputDir: "./reports", // where to save screenshots
248
248
  verbose: false, // log scanner phases to stdout
249
249
  viewport: "mobile", // 'desktop' (default) | 'tablet' | 'mobile'
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ program
16
16
  .argument("<url>", "URL of the website to scan")
17
17
  .option("-o, --output <dir>", "Output directory for the report", "./gdpr-reports")
18
18
  .option("-t, --timeout <ms>", "Navigation timeout in milliseconds", "30000")
19
- .option("--no-screenshots", "Disable screenshot capture")
19
+ .option("--screenshots", "Capture full-page screenshots after reject and accept interactions (the consent modal is always screenshotted when detected)")
20
20
  .option("-l, --locale <locale>", "Browser locale for language detection", "fr-FR")
21
21
  .option("-v, --verbose", "Show detailed output", false)
22
22
  .option("-f, --format <formats>", "Output formats: md, html, json, pdf (comma-separated)", "html")
@@ -54,7 +54,7 @@ program
54
54
  url: normalizedUrl,
55
55
  outputDir,
56
56
  timeout: parseInt(opts.timeout, 10),
57
- screenshots: opts.screenshots !== false,
57
+ screenshots: Boolean(opts.screenshots),
58
58
  locale: opts.locale,
59
59
  verbose: opts.verbose,
60
60
  formats,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -29,7 +29,9 @@ import type { ScanResult, ViewportPreset } from "./types.js";
29
29
  export interface ScanApiOptions {
30
30
  /** Browser navigation timeout in ms. Default: 30 000. */
31
31
  timeout?: number;
32
- /** Whether to capture screenshots. Requires `outputDir`. Default: false. */
32
+ /** Capture full-page screenshots after reject and accept interactions.
33
+ * The consent modal is always screenshotted (cropped to the element) when detected.
34
+ * Requires `outputDir`. Default: false. */
33
35
  screenshots?: boolean;
34
36
  /** Directory where screenshots (and optionally reports) are saved. */
35
37
  outputDir?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,YAAY,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,aAAa,EACb,eAAe,EACf,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,wGAAwG;IACxG,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBzF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,YAAY,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,aAAa,EACb,eAAe,EACf,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;gDAE4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,wGAAwG;IACxG,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBzF"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAqBxD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAwB7C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAW,EAAE,UAA0B,EAAE;IAClE,MAAM,aAAa,GACjB,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC;IAEnF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;QAC1B,GAAG,EAAE,aAAa;QAClB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,MAAM;QAClC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,KAAK;QACzC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO;QACjC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,KAAK;QACjC,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS;QACvC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;KAChC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAqBxD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AA0B7C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAW,EAAE,UAA0B,EAAE;IAClE,MAAM,aAAa,GACjB,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC;IAEnF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;QAC1B,GAAG,EAAE,aAAa;QAClB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,MAAM;QAClC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,KAAK;QACzC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO;QACjC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,KAAK;QACjC,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS;QACvC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;KAChC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC;AACvB,CAAC"}
@@ -1,10 +1,23 @@
1
1
  import type { Page } from "playwright";
2
- import type { ConsentModal } from "../types.js";
2
+ import type { ConsentModal, ConsentButtonType } from "../types.js";
3
3
  import type { ScanOptions } from "../types.js";
4
4
  /**
5
5
  * Find a privacy policy link within a given scope (modal selector) or the full page.
6
6
  * Returns the absolute URL of the first matching link, or null.
7
7
  */
8
8
  export declare function findPrivacyPolicyUrl(page: Page, scopeSelector?: string): Promise<string | null>;
9
+ /**
10
+ * Classify a consent button label for a given page language.
11
+ * `lang` should be the BCP 47 primary subtag (e.g. "de", "fr") read from
12
+ * <html lang>, or null when the language is undetermined.
13
+ */
14
+ export declare function classifyButtonText(text: string, lang: string | null): ConsentButtonType;
9
15
  export declare function detectConsentModal(page: Page, options: ScanOptions): Promise<ConsentModal>;
16
+ /**
17
+ * Basic contrast ratio computation from RGB strings.
18
+ * Returns null if colors cannot be parsed.
19
+ */
20
+ export declare function computeContrastRatio(fg: string, bg: string): number | null;
21
+ export declare function parseRgb(color: string): [number, number, number] | null;
22
+ export declare function relativeLuminance([r, g, b]: [number, number, number]): number;
10
23
  //# sourceMappingURL=consent-modal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consent-modal.d.ts","sourceRoot":"","sources":["../../src/scanner/consent-modal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAqD,MAAM,aAAa,CAAC;AAEnG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAqE/C;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkCxB;AAcD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAmFhG"}
1
+ {"version":3,"file":"consent-modal.d.ts","sourceRoot":"","sources":["../../src/scanner/consent-modal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAkC,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEnG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA8E/C;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkCxB;AAkGD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,CAGvF;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA2GhG;AAwHD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU1E;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAIvE;AAED,wBAAgB,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAM7E"}
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Ordered list of CSS selectors to try for detecting a consent modal/banner.
3
- * Covers major CMP platforms (Axeptio, Cookiebot, OneTrust, Didomi, Tarteaucitron, etc.)
2
+ * Selectors for well-known CMP platforms.
3
+ * These are precise enough that DOM presence alone is a reliable signal —
4
+ * no visibility check required. Some CMPs (e.g. Axeptio) inject their
5
+ * container as display:none and reveal it after JS initialisation, so
6
+ * isVisible() would incorrectly skip them.
4
7
  */
5
- const MODAL_SELECTORS = [
6
- // Well-known CMPs
8
+ const CMP_SELECTORS = [
7
9
  "#axeptio_overlay",
8
10
  "#axeptio-root",
9
11
  "#CybotCookiebotDialog",
@@ -24,7 +26,13 @@ const MODAL_SELECTORS = [
24
26
  "#cookie-banner",
25
27
  "#cookie-notice",
26
28
  "#cookie-law-info-bar",
27
- // Generic heuristics
29
+ ];
30
+ /**
31
+ * Generic heuristic selectors.
32
+ * These are broad enough to match unrelated elements, so a visibility
33
+ * check is required to avoid false positives.
34
+ */
35
+ const HEURISTIC_SELECTORS = [
28
36
  "[class*='cookie'][class*='banner']",
29
37
  "[class*='cookie'][class*='modal']",
30
38
  "[class*='cookie'][class*='popup']",
@@ -97,33 +105,137 @@ export async function findPrivacyPolicyUrl(page, scopeSelector) {
97
105
  })
98
106
  .catch(() => null);
99
107
  }
100
- const ACCEPT_PATTERNS = [
101
- /\b(accept|accepter|acceptez|tout accepter|accept all|j'accepte|i accept|agree|ok\b|d'accord|continuer|continue|valider|confirmer)\b/i,
102
- ];
103
- const REJECT_PATTERNS = [
104
- /\b(refus|refuse|refuser|tout refuser|rejet|rejeter|tout rejeter|reject|reject all|deny|decline|non merci|no thanks|continuer sans accepter|skip)\b/i,
105
- ];
106
- const PREFERENCES_PATTERNS = [
107
- /\b(param[eè]tres|pr[eé]f[eé]rences|personnaliser|customise|customize|manage|g[eé]rer|options|choose|choisir|configure)\b/i,
108
- ];
108
+ /**
109
+ * Button label patterns keyed by BCP 47 primary-language subtag.
110
+ * English is always included as a fallback when the page language is known.
111
+ * When the language is unknown all locales are tested.
112
+ */
113
+ const PATTERNS_BY_LOCALE = {
114
+ en: {
115
+ accept: /\b(accept|accept all|agree|ok|i accept|i agree|continue)\b/i,
116
+ reject: /\b(refuse|reject|reject all|deny|decline|no thanks|skip|opt[- ]out|continue without)\b/i,
117
+ preferences: /\b(manage|customize|customise|settings|options|choose|configure)\b/i,
118
+ },
119
+ fr: {
120
+ accept: /\b(accepter|acceptez|tout accepter|j'accepte|d'accord|continuer|valider|confirmer)\b/i,
121
+ reject: /\b(refus|refuser|tout refuser|rejeter|tout rejeter|non merci|continuer sans accepter)\b/i,
122
+ preferences: /\b(param[eè]tres|pr[eé]f[eé]rences|personnaliser|g[eé]rer|choisir)\b/i,
123
+ },
124
+ de: {
125
+ accept: /\b(akzeptieren|alle akzeptieren|zustimmen|einverstanden|annehmen|alle annehmen|ich stimme zu)\b/i,
126
+ reject: /\b(ablehnen|alle ablehnen|abweisen|nicht zustimmen|nein danke)\b/i,
127
+ preferences: /\b(einstellungen|anpassen|verwalten|konfigurieren|ausw[äa]hlen|mehr optionen)\b/i,
128
+ },
129
+ es: {
130
+ accept: /\b(aceptar|aceptar todo|acepto|estoy de acuerdo)\b/i,
131
+ reject: /\b(rechazar|rechazar todo|denegar|no gracias|continuar sin aceptar)\b/i,
132
+ preferences: /\b(ajustes|configurar|gestionar|opciones|personalizar|preferencias)\b/i,
133
+ },
134
+ it: {
135
+ accept: /\b(accetta|accetta tutto|accetto|acconsento|acconsento a tutto)\b/i,
136
+ reject: /\b(rifiuta|rifiuta tutto|nega|no grazie)\b/i,
137
+ preferences: /\b(impostazioni|personalizza|gestisci|opzioni|configura|preferenze)\b/i,
138
+ },
139
+ nl: {
140
+ accept: /\b(accepteren|alles accepteren|akkoord|ik ga akkoord)\b/i,
141
+ reject: /\b(weigeren|alles weigeren|afwijzen|nee bedankt)\b/i,
142
+ preferences: /\b(instellingen|aanpassen|beheren|instellen|voorkeuren)\b/i,
143
+ },
144
+ pl: {
145
+ // No \b anchors: Polish words often end in non-ASCII characters (ć, ę, ó)
146
+ // which are outside JS \w, so \b would not match at the word end.
147
+ // Negative lookbehind prevents "zgadzam się" from matching inside "nie zgadzam się".
148
+ accept: /zaakceptuj( wszystkie)?|(?<!nie\s)zgadzam si[eę]|akceptuj[eę]/i,
149
+ reject: /odrzuć( wszystkie)?|nie zgadzam si[eę]|odm[oó]w/i,
150
+ preferences: /ustawienia|dostosuj|zarz[aą]dzaj|opcje|skonfiguruj|preferencje/i,
151
+ },
152
+ pt: {
153
+ accept: /\b(aceitar|aceitar tudo|aceito|concordo)\b/i,
154
+ reject: /\b(rejeitar|rejeitar tudo|recusar|n[aã]o obrigado)\b/i,
155
+ preferences: /\b(configura[çc][oõ]es|personalizar|gerir|op[çc][oõ]es|defini[çc][oõ]es|prefer[eê]ncias)\b/i,
156
+ },
157
+ };
158
+ /**
159
+ * Build the applicable accept/reject/preferences pattern lists for a given
160
+ * primary-language tag detected from the page's <html lang> attribute.
161
+ *
162
+ * - Known language → locale patterns + English fallback
163
+ * - Unknown / missing → all available patterns (most robust)
164
+ */
165
+ function resolveButtonPatterns(lang) {
166
+ if (!lang || !(lang in PATTERNS_BY_LOCALE)) {
167
+ const all = Object.values(PATTERNS_BY_LOCALE);
168
+ return {
169
+ accept: all.map((p) => p.accept),
170
+ reject: all.map((p) => p.reject),
171
+ preferences: all.map((p) => p.preferences),
172
+ };
173
+ }
174
+ const locale = PATTERNS_BY_LOCALE[lang];
175
+ const en = PATTERNS_BY_LOCALE.en;
176
+ const sets = lang === "en" ? [locale] : [locale, en];
177
+ return {
178
+ accept: sets.map((p) => p.accept),
179
+ reject: sets.map((p) => p.reject),
180
+ preferences: sets.map((p) => p.preferences),
181
+ };
182
+ }
183
+ /**
184
+ * Collapse any whitespace sequence (including &nbsp; / \u00A0, \n, \t, …)
185
+ * into a single ASCII space and strip leading/trailing whitespace.
186
+ * In JS, \s already covers \u00A0 and other Unicode spaces.
187
+ */
188
+ function normalizeText(raw) {
189
+ return raw.replace(/\s+/g, " ").trim();
190
+ }
191
+ /**
192
+ * Classify a consent button label for a given page language.
193
+ * `lang` should be the BCP 47 primary subtag (e.g. "de", "fr") read from
194
+ * <html lang>, or null when the language is undetermined.
195
+ */
196
+ export function classifyButtonText(text, lang) {
197
+ const { accept, reject, preferences } = resolveButtonPatterns(lang);
198
+ return classifyButtonType(normalizeText(text), accept, reject, preferences);
199
+ }
109
200
  export async function detectConsentModal(page, options) {
110
- // Try each selector until we find a visible modal
111
201
  let foundSelector = null;
112
- for (const selector of MODAL_SELECTORS) {
202
+ // Step 1 specific CMP selectors: presence in DOM is sufficient.
203
+ // Some CMPs (e.g. Axeptio) insert their container as display:none and
204
+ // animate it in after JS initialisation. Requiring isVisible() would
205
+ // skip them and fall through to a wrong generic selector.
206
+ // Once the element is found we wait briefly (up to 3 s) for it to
207
+ // become visible so button extraction sees an interactive state.
208
+ for (const selector of CMP_SELECTORS) {
113
209
  try {
114
210
  const element = await page.$(selector);
115
211
  if (!element)
116
212
  continue;
117
- const isVisible = await element.isVisible();
118
- if (isVisible) {
119
- foundSelector = selector;
120
- break;
121
- }
213
+ await page.waitForSelector(selector, { state: "visible", timeout: 3000 }).catch(() => { });
214
+ foundSelector = selector;
215
+ break;
122
216
  }
123
217
  catch {
124
218
  continue;
125
219
  }
126
220
  }
221
+ // Step 2 — generic heuristics: require visibility to avoid false positives.
222
+ if (!foundSelector) {
223
+ for (const selector of HEURISTIC_SELECTORS) {
224
+ try {
225
+ const element = await page.$(selector);
226
+ if (!element)
227
+ continue;
228
+ const isVisible = await element.isVisible();
229
+ if (isVisible) {
230
+ foundSelector = selector;
231
+ break;
232
+ }
233
+ }
234
+ catch {
235
+ continue;
236
+ }
237
+ }
238
+ }
127
239
  // Fallback: look for any large fixed/sticky element with cookie-related text
128
240
  if (!foundSelector) {
129
241
  foundSelector = await page.evaluate(() => {
@@ -160,10 +272,13 @@ export async function detectConsentModal(page, options) {
160
272
  privacyPolicyUrl: null,
161
273
  };
162
274
  }
275
+ // Detect the page's declared language for locale-aware button classification
276
+ const rawLang = await page.$eval("html", (el) => el.lang ?? "").catch(() => "");
277
+ const pageLang = rawLang.split("-")[0].toLowerCase() || null;
163
278
  // Extract modal text
164
279
  const modalText = await page.$eval(foundSelector, (el) => el.textContent ?? "").catch(() => "");
165
280
  // Find all buttons and interactive elements within the modal
166
- const buttons = await extractButtons(page, foundSelector);
281
+ const buttons = await extractButtons(page, foundSelector, pageLang);
167
282
  // Find checkboxes / toggles
168
283
  const checkboxes = await extractCheckboxes(page, foundSelector);
169
284
  // Detect if there are nested layers (e.g., "more options" behind a click)
@@ -182,12 +297,13 @@ export async function detectConsentModal(page, options) {
182
297
  privacyPolicyUrl,
183
298
  };
184
299
  }
185
- async function extractButtons(page, modalSelector) {
300
+ async function extractButtons(page, modalSelector, lang) {
301
+ const { accept, reject, preferences } = resolveButtonPatterns(lang);
186
302
  const buttonEls = await page.$$(`${modalSelector} button, ${modalSelector} [role="button"], ${modalSelector} a[href="#"]`);
187
303
  const buttons = [];
188
304
  for (const el of buttonEls) {
189
305
  try {
190
- const text = ((await el.textContent()) ?? "").trim();
306
+ const text = normalizeText((await el.textContent()) ?? "");
191
307
  if (!text)
192
308
  continue;
193
309
  const isVisible = await el.isVisible();
@@ -200,7 +316,7 @@ async function extractButtons(page, modalSelector) {
200
316
  color: style.color,
201
317
  };
202
318
  });
203
- const type = classifyButtonType(text);
319
+ const type = classifyButtonType(text, accept, reject, preferences);
204
320
  // Build a unique selector for this button
205
321
  const selector = await el.evaluate((node) => {
206
322
  const el = node;
@@ -264,12 +380,12 @@ async function extractCheckboxes(page, modalSelector) {
264
380
  }, modalSelector)
265
381
  .catch(() => []);
266
382
  }
267
- function classifyButtonType(text) {
268
- if (ACCEPT_PATTERNS.some((p) => p.test(text)))
383
+ function classifyButtonType(text, accept, reject, preferences) {
384
+ if (accept.some((p) => p.test(text)))
269
385
  return "accept";
270
- if (REJECT_PATTERNS.some((p) => p.test(text)))
386
+ if (reject.some((p) => p.test(text)))
271
387
  return "reject";
272
- if (PREFERENCES_PATTERNS.some((p) => p.test(text)))
388
+ if (preferences.some((p) => p.test(text)))
273
389
  return "preferences";
274
390
  if (/\b(ferm|close|×|✕)\b/i.test(text))
275
391
  return "close";
@@ -279,7 +395,7 @@ function classifyButtonType(text) {
279
395
  * Basic contrast ratio computation from RGB strings.
280
396
  * Returns null if colors cannot be parsed.
281
397
  */
282
- function computeContrastRatio(fg, bg) {
398
+ export function computeContrastRatio(fg, bg) {
283
399
  const fgRgb = parseRgb(fg);
284
400
  const bgRgb = parseRgb(bg);
285
401
  if (!fgRgb || !bgRgb)
@@ -290,13 +406,13 @@ function computeContrastRatio(fg, bg) {
290
406
  const darker = Math.min(fgL, bgL);
291
407
  return parseFloat(((lighter + 0.05) / (darker + 0.05)).toFixed(2));
292
408
  }
293
- function parseRgb(color) {
409
+ export function parseRgb(color) {
294
410
  const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
295
411
  if (!match)
296
412
  return null;
297
413
  return [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)];
298
414
  }
299
- function relativeLuminance([r, g, b]) {
415
+ export function relativeLuminance([r, g, b]) {
300
416
  const toLinear = (c) => {
301
417
  const s = c / 255;
302
418
  return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);