@duckduckgo/autoconsent 14.77.1 → 14.78.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.
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: publicwww-search
3
+ description: Search PublicWWW to find websites using specific HTML/JS/CSS snippets. Use this skill to check if a cookie popup is from a widespread third-party CMP, to find additional test URLs for a CMP rule, or to discover how many sites use a particular consent banner pattern.
4
+ ---
5
+
6
+ # PublicWWW Source Code Search
7
+
8
+ Search website source code via the [PublicWWW](https://publicwww.com) API. Requires `PUBLICWWW_KEY` env var.
9
+
10
+ **Caveat:** PublicWWW indexes server-rendered HTML source only. CMPs injected purely via client-side JavaScript (e.g. tag managers, async script loaders) may not appear in results.
11
+
12
+ ## API
13
+
14
+ Download search results as CSV:
15
+
16
+ ```
17
+ https://publicwww.com/websites/SEARCH_QUERY/?export=csvsnippetsu&key=$PUBLICWWW_KEY
18
+ ```
19
+
20
+ CSV format: `url;rank;snippet` (one site per line, sorted by popularity rank).
21
+
22
+ Use `export=csv` instead of `csvsnippetsu` to get `domain;rank` without snippets.
23
+
24
+ ## Query Syntax
25
+
26
+ - Exact phrase: `"onetrust-banner-sdk"`
27
+ - Combine terms: `"cookie-banner" "reject-all"`
28
+ - Exclude: `"cookie-banner" -wordpress`
29
+ - TLD filter: `"sd-cmp" site:de`
30
+ - File type: `"__cmp" filetype:js` (also `filetype:css`)
31
+ - Page depth: `depth:all "math.min.js"` (0–5, default homepage only)
32
+ - Escape inner quotes: `"data-testid=\"cookie-reject\""`
33
+
34
+ Full syntax: https://publicwww.com/syntax.html
35
+
36
+ ## Examples
37
+
38
+ Check if a banner ID is from a shared CMP:
39
+
40
+ ```bash
41
+ curl -s "https://publicwww.com/websites/%22sd-cmp%22/?export=csv&key=$PUBLICWWW_KEY" | head -20
42
+ ```
43
+
44
+ Find German sites using OneTrust:
45
+
46
+ ```bash
47
+ curl -s "https://publicwww.com/websites/%22onetrust-banner-sdk%22+site%3Ade/?export=csv&key=$PUBLICWWW_KEY" | head -10
48
+ ```
49
+
50
+ Search JS files for a CMP API:
51
+
52
+ ```bash
53
+ curl -s "https://publicwww.com/js/%22__cmp%22+filetype%3Ajs/?export=csv&key=$PUBLICWWW_KEY" | head -10
54
+ ```
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: verify-pr
3
+ description: Verifies an autoconsent pull request by running local CI checks, reviewing rule quality, and inspecting Jenkins E2E results. Use when reviewing, verifying, or approving a PR, when checking if a PR is ready to merge, or when the user asks to validate PR changes.
4
+ ---
5
+
6
+ # Verify PR
7
+
8
+ Copy this checklist and track progress:
9
+
10
+ ```
11
+ PR Verification:
12
+ - [ ] Step 1: Local checks pass
13
+ - [ ] Step 2: Rule review complete
14
+ - [ ] Step 3: CI results checked
15
+ - [ ] Step 4: Browser verification done
16
+ ```
17
+
18
+ ## Step 1: Local Checks
19
+
20
+ ```bash
21
+ npm run lint
22
+ npm run rule-syntax-check
23
+ npm run test:lib
24
+ npx playwright test tests/<cmp>.spec.ts --project webkit
25
+ ```
26
+
27
+ If any check fails, fix the issue and re-run before proceeding.
28
+
29
+ ## Step 2: Rule Review
30
+
31
+ ### JSON rule PRs
32
+
33
+ - [ ] Test spec exists in `tests/` with reachable URLs
34
+ - [ ] Selectors are stable (no CSS module hashes, dynamic IDs, or framework-generated IDs)
35
+ - [ ] `optOut` targets a reject/decline button, not a privacy policy link or close button
36
+ - [ ] `prehideSelectors` are narrow (no `body` or full-page selectors)
37
+ - [ ] `minimumRuleStepVersion: 2` if rule uses `removeClass`, `setStyle`, or `addStyle`
38
+ - [ ] Generated rule fixes (`rules/generated/`) are consistent across all region variants
39
+
40
+ ### Code-based rule PRs
41
+
42
+ - [ ] No hardcoded site-specific values
43
+ - [ ] Fallback paths for regional variants
44
+ - [ ] Uses existing DOM helpers (`this.click()`, `this.waitForElement()`, etc.)
45
+
46
+ ## Step 3: CI Results
47
+
48
+ **GitHub Actions** (`.github/workflows/checks.yml`): Runs `lint` and `test:lib`. Must pass.
49
+
50
+ **Jenkins**: Runs Playwright E2E in 9 regions (US, GB, AU, CA, DE, FR, NL, CH, NO) for modified rules only. Posts a PR comment with artifact ZIP and [review tool](https://zok.pw/autoconsent-review-tool/) link for inspecting screenshots.
51
+
52
+ ### Flaky E2E failures
53
+
54
+ Before concluding a test is broken:
55
+
56
+ 1. Check Jenkins screenshots in the review tool
57
+ 2. Verify the site still shows the same popup
58
+ 3. Region-only failures → consider adding `skipRegions`
59
+ 4. CI retries twice — intermittent failures often self-resolve
60
+
61
+ ## Step 4: Browser Verification
62
+
63
+ Build and load extension: `npm run prepublish` → load `dist/addon-mv3/` in Chrome.
64
+
65
+ Confirm on target site(s):
66
+
67
+ - Popup is detected and handled
68
+ - Page scrolls normally after opt-out
69
+ - Interactive elements remain clickable
70
+
71
+ For CMP rules: test on multiple sites using the same CMP (find sites in `data/coverage.json`).
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: publicwww-search
3
+ description: Search PublicWWW to find websites using specific HTML/JS/CSS snippets. Use this skill to check if a cookie popup is from a widespread third-party CMP, to find additional test URLs for a CMP rule, or to discover how many sites use a particular consent banner pattern.
4
+ ---
5
+
6
+ @.agents/skills/publicwww-search/SKILL.md
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: verify-pr
3
+ description: Verifies an autoconsent pull request by running local CI checks, reviewing rule quality, and inspecting Jenkins E2E results. Use when reviewing, verifying, or approving a PR, when checking if a PR is ready to merge, or when the user asks to validate PR changes.
4
+ ---
5
+
6
+ @.agents/skills/verify-pr/SKILL.md
package/AGENTS.md CHANGED
@@ -10,8 +10,9 @@ lib/cmps/ Code-based CMP rule classes (sourcepoint, onetrust, etc.)
10
10
  lib/rules.ts Type definitions for AutoConsentCMPRule and rule steps
11
11
  lib/eval-snippets.ts Eval snippets for main-world JS execution
12
12
  rules/autoconsent/ Hand-maintained JSON rules
13
- rules/generated/ Auto-generated JSON rules (auto_XX_domain_hash.json)
13
+ rules/generated/ Crawler-generated JSON rules (auto_XX_domain_hash.json)
14
14
  rules/build.ts Merges all rules into rules.json, consentomatic.json, compact-rules.json
15
+ data/ Coverage data (coverage.json) and site lists
15
16
  tests/ Playwright E2E test specs (one per CMP)
16
17
  tests-wtr/ Web Test Runner unit tests for DOM actions and rule logic
17
18
  playwright/runner.ts Test harness: generateCMPTests(name, urls, options)
@@ -42,310 +43,103 @@ npm run watch # auto-rebuild on changes to lib/, addon/, rules/
42
43
  | `npm run create-rule` | Scaffold a new JSON rule + test spec |
43
44
 
44
45
  ## Code Style
45
-
46
46
  - **Preserve existing comments.** Do not remove JSDoc comments, TODO comments, or inline explanations unless the related code is also being removed. Rewriting a comment to reflect updated logic is fine.
47
47
 
48
- ## Working with Rules
49
-
50
- ### JSON Rules
51
-
52
- JSON rules live in `rules/autoconsent/` (hand-maintained) and `rules/generated/` (auto-generated). Each file defines one CMP rule following the `AutoConsentCMPRule` type in `lib/rules.ts`.
53
-
54
- ```json
55
- {
56
- "name": "example-cmp",
57
- "prehideSelectors": ["#cookie-banner"],
58
- "detectCmp": [{ "exists": "#cookie-banner" }],
59
- "detectPopup": [{ "visible": "#cookie-banner" }],
60
- "optIn": [{ "waitForThenClick": "#accept-all" }],
61
- "optOut": [{ "waitForThenClick": "#reject-all" }],
62
- "test": [{ "cookieContains": "consent=rejected" }],
63
- "minimumRuleStepVersion": 1
64
- }
65
- ```
48
+ ## Working with autoconsent rules
66
49
 
50
+ ### Rule syntax
67
51
  For the complete rule syntax reference (all step types, element selectors, conditionals, etc.), see [docs/rule-syntax.md](docs/rule-syntax.md).
68
52
 
69
- ### prehideSelectors
70
-
71
- `prehideSelectors` inject CSS early (before the CMP is even detected) to prevent the cookie popup from flickering on screen. They use `opacity: 0` (not `display: none`) so the popup still occupies layout space and detection via `visible` checks still works. If opt-out doesn't start within 2 seconds, the elements are automatically unhidden to avoid permanently hiding page content.
72
-
73
- Keep prehideSelectors **narrow** — they are applied across all matching rules simultaneously, so an overly broad selector (e.g. `body`) could hide the entire page during the 2-second window.
74
-
75
- ### minimumRuleStepVersion
76
-
77
- New step types are added to the autoconsent engine over time. `minimumRuleStepVersion` declares which version of the step format a rule requires. Clients that don't support the required version silently skip the rule, preventing failures on older app versions.
78
-
79
- **Version history:**
80
- - `1` (default) — all original step types (`exists`, `visible`, `waitFor`, `click`, `waitForThenClick`, `wait`, `hide`, `if`/`then`/`else`, `any`, `eval`, `cookieContains`, etc.)
81
- - `2` — added `removeClass`, `setStyle`, `addStyle`
82
-
83
- **When to set it:** Omit the field (or set to `1`) if the rule only uses original step types. Set to `2` if the rule uses `removeClass`, `setStyle`, or `addStyle`. When future versions add new step types, set accordingly.
84
-
85
- ### Code-Based Rules
86
-
87
- For CMPs requiring complex non-linear logic, CMP API interaction, or complex multi-path flows, use a TypeScript class extending `AutoConsentCMPBase` in `lib/cmps/`. Examples: `sourcepoint-frame.ts`, `onetrust.ts`, `cookiebot.ts`, `consentmanager.ts`.
88
-
89
- Code-based rules implement the `AutoCMP` interface: `detectCmp()`, `detectPopup()`, `optOut()`, `optIn()`, and optionally `test()`. They have access to DOM helpers like `this.click()`, `this.waitForElement()`, `this.waitForVisible()`, and `this.elementExists()`.
90
-
91
- ### When to Use Code vs JSON
92
-
93
- - **JSON:** Linear consent flows, DOM-based detection, single-path opt-out. JSON rules are preferable because they can be shipped in DuckDuckGo apps without a full app release.
94
- - **Code:** Multi-path branching, CMP JavaScript API calls, `Promise.race()` for competing UI states, complex state machines (e.g., Sourcepoint serving GDPR/CCPA/US National variants on different URL paths).
95
-
96
- ### Selector Strategy
97
-
98
- Prefer selectors in this order (most stable first):
99
-
100
- 1. **Stable data attributes:** `[data-testid="cookie-reject"]`, `[data-action="sp-cc"]`, `[data-qa="allow-all-cookies"]`
101
- 2. **Stable IDs:** `#sp-cc-accept`, `#cookie-banner` — but avoid dynamic IDs from React Aria (`#react-aria*`), Radix (`#radix-\:*\:`), or CSS Modules (`.sd-cmp-3cRQ2`), which change between builds/sessions.
102
- 3. **Semantic class substrings:** `[class*="cookie-banner"]`, `[class*="reject"]` — avoid full body class lists (`body.home.wp-singular.page-template...`) which break across pages.
103
- 4. **Structural CSS:** `#banner button.secondary` — avoid deep `nth-child` chains from generated rules.
104
- 5. **XPath text matching (fallback):** `xpath///button[contains(., 'Reject')]` — use as a last resort since button text is language-specific and breaks across locales. Same caution applies to `aria-label` attributes, which are often localized.
105
- 6. **Array selectors** for shadow DOM / iframe piercing: `["host-element", "button"]` finds `button` inside the shadow root of `host-element`. Each string in the array narrows the search scope — if an intermediate element has an open `shadowRoot`, the next selector runs inside it; if it's a same-origin iframe, the next selector runs inside its `contentDocument`. Use when a CMP renders inside shadow DOM or a same-origin iframe.
106
-
107
- When writing or reviewing selectors, also watch out for:
108
- - **Hardcoded attribute values** that are site-specific — use generic selectors in code-based rules.
109
- - **Over-qualified selectors** from generated rules — e.g. `div[id][name][role][aria-modal][tabindex][lang]` requiring every attribute to exist, or redundant `:nth-child(2)#some-id` where the ID alone suffices.
110
-
111
- ## Debugging and Fixing Rules
112
-
113
- ### Identifying Broken Rules
114
-
115
- 1. **Use a real browser** to investigate. A real browser in a computer-use subagent is **highly preferred** over Playwright or Puppeteer-based scripts — cookie popups often behave differently in headless/automated browsers.
116
- 2. **Playwright test failures** are a secondary signal. Run the specific test:
117
- ```bash
118
- npx playwright test tests/sirdata.spec.ts --project webkit
119
- ```
120
- 3. **Check test output** for which stage failed: `cmpDetected`, `popupFound`, `autoconsentDone`, `optOutResult`, `selfTestResult`.
121
- 4. **Use the test extension** (`dist/addon-mv3/`) for manual debugging. Load it in Chrome, visit the site, and check the devtools panel for step-by-step logs.
122
-
123
- ### Common Failure Modes
124
-
125
- **Race conditions:** Consent popups load asynchronously. Use `waitFor` / `waitForThenClick` / `waitForVisible` instead of bare `exists` / `click`. Add `{ "wait": 500 }` before critical actions when the CMP has known async initialization. In code-based rules, use `Promise.race()` for multiple possible UI states. **Never** use `{ "wait": N }` in `detectCmp` or `detectPopup` — the engine handles retries internally.
126
-
127
- **Incorrect consent action selectors:** Generated rules sometimes target a privacy policy link instead of the reject button. Ensure `optOut` steps target an actual reject/decline button.
128
-
129
- **Region-dependent behavior:** Many CMPs show different dialogs by region (GDPR in EU, CCPA in US). See [Regional Differences](#regional-differences) below.
130
-
131
- ### Fixing JSON Rules
132
-
133
- 1. Read the existing rule to understand its current selectors and flow.
134
- 2. Identify the broken step from test output or by inspecting the site.
135
- 3. Edit the JSON file — apply the fix to every occurrence of the selector within the file (`detectCmp`, `detectPopup`, `optOut`, and `test` often use similar selectors).
136
- 4. For site-specific rules, double-check if the popup is still site-specific. If not, consider if a generic rule is more appropriate.
137
- 5. **Always update the corresponding test spec** in `tests/`. If no spec exists, create one.
138
- 6. **Cross-check other rules** — search for the same selectors or CMP provider name across `rules/autoconsent/` and `rules/generated/` to find other rules that may need the same change.
139
- 7. For generated rules, the same CMP may appear across multiple region files (`auto_CH_*.json`, `auto_DE_*.json`, etc.). Apply the fix to all affected files.
140
- 8. Run `npm run lint` to validate.
141
-
142
- ### Fixing Code-Based Rules
143
-
144
- 1. Read the CMP class in `lib/cmps/` and trace the failing code path.
145
- 2. Avoid hardcoded attribute values that are site-specific.
146
- 3. Add path/state detection for new CMP variants. Check `location.pathname`, button presence, or URL parameters.
147
- 4. Add fallback paths when variants may not have the expected buttons.
148
-
149
- ### Adding Fallback Paths
150
-
151
- Use `if`/`then`/`else` for region-dependent or variant-dependent flows:
152
-
153
- ```json
154
- {
155
- "if": { "exists": "#reject-button" },
156
- "then": [{ "waitForThenClick": "#reject-button" }],
157
- "else": [{
158
- "if": { "exists": "#manage-cookies" },
159
- "then": [
160
- { "waitForThenClick": "#manage-cookies" },
161
- { "waitForThenClick": "#reject-all" }
162
- ],
163
- "else": [
164
- { "waitForThenClick": "[role='button'][title='Close']" }
165
- ]
166
- }]
167
- }
168
- ```
169
-
170
- ## Adding New Rules
171
-
172
- 1. Run `npm run create-rule` to scaffold the JSON + test spec.
173
- 2. **Check if the popup is from a third-party CMP provider** (e.g. OneTrust, Cookiebot, Sourcepoint). If so, prefer extending or fixing the existing generic rule rather than creating a site-specific one.
174
- 3. Fill in `detectCmp`, `detectPopup`, `optOut`, `optIn` with stable selectors. Do **not** use `{ "wait": N }` steps in `detectCmp` or `detectPopup` — detection must be fast and non-blocking (the engine retries automatically).
175
- 4. Add a `test` array — prefer `cookieContains` when the CMP stores consent in cookies.
176
- - JSON rules can also use `{ "eval": "SNIPPET_NAME" }` steps to execute predefined JavaScript snippets from `lib/eval-snippets.ts`. Useful for calling CMP APIs (e.g., `window.Cookiebot`, `__cmp('getCMPData')`) in detection, opt-out, or test phases. Each snippet is a named function that returns a boolean. New snippets must be added to `lib/eval-snippets.ts` and referenced by name in the rule JSON.
177
- 5. **Always create or update the corresponding test spec** in `tests/`.
178
- 6. **Cross-check other rules** — search for the same selectors or CMP provider name across `rules/autoconsent/` and `rules/generated/` to see if other rules need the same change or already cover this CMP.
179
- 7. Use `data/coverage.json` to find example sites for testing. It contains per-CMP, per-region URLs: `{ "CmpName": { "REGION": { "exampleSites": [...] } } }`.
180
- 8. Run `npm run lint` and `npm run test:lib`.
181
- 9. Test with Playwright: `npx playwright test tests/my-cmp.spec.ts --project webkit`.
182
-
183
- ### When Generated Rules Need Fixes
53
+ ### Regional Differences
184
54
 
185
- Generated rules (`rules/generated/auto_XX_domain_hash.json`) are created by a crawler and often have:
186
- - Deep `nth-child` chains that break on layout changes
187
- - Dynamic IDs from UI frameworks
188
- - Long body class lists
189
- - Over-qualified selectors requiring many attributes simultaneously
55
+ CMPs behave differently by region:
190
56
 
191
- Fixes typically need to be applied across all region variants of the same domain (e.g., `auto_CH_kitbag.com_*.json`, `auto_DE_kitbag.com_*.json`). Search for the domain to find all related files.
57
+ - **EU/EEA (GDPR):** Full consent dialog with explicit reject/accept options.
58
+ - **US (CCPA/state laws):** Often a simpler notice with "Close" or "Do Not Sell". Some CMPs show nothing.
59
+ - **Other regions:** Varies.
192
60
 
193
- ## Cosmetic Rules
61
+ Use `if`/`then`/`else` to handle regional variants within a single rule.
194
62
 
195
- Cosmetic rules hide the cookie popup via CSS rather than clicking a reject button. They are marked with `"cosmetic": true` and use `hide` steps in their `optOut` array. Use cosmetic rules when a popup has no reject/decline button — only an "Accept" or "Close" option.
63
+ **All rule changes MUST be tested across geographic regions** to catch regional popup variations. Test from real geographic locations using available regional-testing tooling (e.g. proxy-based remote browsers).
196
64
 
197
- ### When to Use Cosmetic vs Click-Based Rules
65
+ ### Generic vs Site-Specific Rules
198
66
 
199
- A popup should use a **click-based rule** (the default) if it has a reject/decline button. This includes buttons with text like "Reject all", "Only necessary cookies", "Decline", and equivalents in other languages. If the popup only has "Accept" / "OK" / "Close" / "Got it" and no way to reject, use a **cosmetic rule** to hide it.
67
+ **Always prefer writing a generic CMP rule over a site-specific rule.** One CMP rule can cover multiple sites. See "Identifying a Consent Management Platform" below for common techniques. If the popup is genuinely custom-built, a site-specific rule is the right call.
200
68
 
201
- ### Common Breakage Patterns
69
+ ### JSON Rules vs Code-based rules
202
70
 
203
- Hiding a popup can break the page if the CMP also locks scrolling or adds overlays. Watch for:
204
-
205
- **Scroll lock via CSS class:** `body` or `html` gets a class like `no-scroll`, `modal-open`, `overflow-hidden`. Fix with:
206
- ```json
207
- { "removeClass": "no-scroll", "selector": "body" }
208
- ```
209
-
210
- **Scroll lock via inline style:** `body.style.overflow = "hidden"`. Fix with:
211
- ```json
212
- { "addStyle": "overflow: auto !important", "selector": "body" }
213
- ```
214
-
215
- **Overlay preventing clicks:** A `position: fixed` div with high z-index covers the page. Fix by hiding it:
216
- ```json
217
- { "hide": "#overlay-selector" }
218
- ```
219
-
220
- **Body position lock:** `body.style.position = "fixed"` with `top: -XXpx`. Fix with:
221
- ```json
222
- { "setStyle": "", "selector": "body" }
223
- ```
224
-
225
- ### Cosmetic Rule Structure
71
+ JSON rules live in `rules/autoconsent/` (hand-maintained) and `rules/generated/` (auto-generated). Each file defines one CMP rule following the `AutoConsentCMPRule` type in `lib/rules.ts`.
226
72
 
227
73
  ```json
228
74
  {
229
- "name": "example-cosmetic",
230
- "cosmetic": true,
75
+ "name": "example-cmp",
231
76
  "prehideSelectors": ["#cookie-banner"],
232
77
  "detectCmp": [{ "exists": "#cookie-banner" }],
233
78
  "detectPopup": [{ "visible": "#cookie-banner" }],
234
- "optOut": [
235
- { "hide": "#cookie-banner" },
236
- { "removeClass": "no-scroll", "selector": "body", "optional": true }
237
- ],
238
- "optIn": [{ "waitForThenClick": "#accept-button" }]
79
+ "optIn": [{ "waitForThenClick": "#accept-all" }],
80
+ "optOut": [{ "waitForThenClick": "#reject-all" }],
81
+ "test": [{ "cookieContains": "consent=rejected" }]
239
82
  }
240
83
  ```
241
84
 
242
- Add breakage fix steps AFTER the `hide` step in `optOut`. Mark breakage fixes as `"optional": true` since they may not always apply.
243
-
244
- ## Triaging Broken Sites
245
-
246
- When investigating a site where cookie popup handling is broken or missing:
247
-
248
- ### Step 1: Check Current State
249
-
250
- Load the bundled extension in Chrome (`dist/addon-mv3/` after `npm run prepublish`), visit the site, and check the devtools panel for autoconsent logs. Determine whether:
251
- - An existing rule matched but failed (which stage? `detectCmp`, `detectPopup`, `optOut`?)
252
- - No rule matched at all
253
-
254
- ### Step 2: Diagnose
255
-
256
- If an **existing rule matched but failed**: identify the broken step from the logs, inspect the site to understand what changed (new selectors, different layout, region variant), and fix the rule.
257
-
258
- If **no rule matched**: determine the CMP type. Check if the popup is from a known CMP (OneTrust, Sourcepoint, Cookiebot, etc.) by inspecting the banner's HTML, class names, and script sources. If it's a known CMP, the existing rule may need updated detection selectors. If it's unknown, create a new rule.
259
-
260
- **Always check if the popup is from a third-party CMP provider.** If so, prefer creating or extending a generic rule rather than a site-specific one. Use `data/coverage.json` to find additional example sites for the same CMP to verify the rule works broadly.
261
-
262
- ### Step 3: Determine Rule Type
85
+ Code-based rules live in `lib/cmps/`. Each file defines one CMP rule following the `AutoConsentCMPBase` type in `lib/rules.ts`.
263
86
 
264
- - If the popup has a **reject/decline button** create or fix a click-based rule
265
- - If the popup has **no reject option** (only accept/close) → create a cosmetic rule
266
- - If the CMP requires **complex logic** (API calls, multiple UI states, iframe communication) → use a code-based CMP class
87
+ **JSON rules are always preferred over code-based rules.** Code-based rules are rarely needed, and should only be used for complex cases, when JSON format is not expressive enough.
267
88
 
268
- ### Step 4: Implement and Test
89
+ ### Selectors
269
90
 
270
- 1. Create or edit the rule file
271
- 2. Add or update the test spec in `tests/`
272
- 3. Run `npm run lint` to validate
273
- 4. Test locally: `npx playwright test tests/<cmp>.spec.ts --project webkit`
274
- 5. Test in multiple regions if the CMP is region-dependent (requires both `REGION` and `PROXY_SERVER` — see [Testing Across Regions](#testing-across-regions))
275
- 6. For cosmetic rules, verify no breakage (scrolling works, page is interactable)
91
+ Prefer selectors stable across builds and locales: data attributes (`[data-testid="..."]`) > stable IDs > class substrings (`[class*="..."]`) > structural CSS > XPath (last resort). **Do NOT use CSS module hashes** (4+ random chars like `.css-1a2b3c`) or framework-generated IDs.
276
92
 
277
- ## Regional Differences
93
+ Array selectors pierce shadow DOM and same-origin iframes. Each selector in the array
94
+ scopes into the previous match's `.shadowRoot` or `.contentDocument`:
278
95
 
279
- CMPs behave differently depending on the user's region due to different privacy regulations:
280
-
281
- - **EU/EEA (GDPR):** Full consent dialog with explicit reject/accept options. Most rules target this variant.
282
- - **US (CCPA/state laws):** Often a simpler notice with just a "Close" button, or a "Do Not Sell" link. Some CMPs show nothing at all in the US.
283
- - **Other regions:** May show GDPR-like dialogs, simplified notices, or nothing.
284
-
285
- ### Handling Regional Variants in Rules
286
-
287
- Use `if`/`then`/`else` conditionals to handle different UIs within a single rule. For code-based rules, add path detection (e.g., Sourcepoint's `/privacy-manager/index.html` vs `/us_pm/index.html`).
288
-
289
- ### Testing Across Regions
290
-
291
- Two things are needed to test from a specific region:
292
-
293
- 1. **`REGION` env var** — filters which test URLs to run (from `data/coverage.json`). This only controls test selection, it does **not** change where requests come from.
294
- 2. **`PROXY_SERVER` env var** — routes browser traffic through a geographic proxy so sites see the correct region. Without a proxy, the site sees your real location regardless of `REGION`.
295
-
296
- ```bash
297
- # Local: only filters tests, requests come from your real location
298
- REGION=DE npx playwright test tests/sirdata.spec.ts --project webkit
299
-
300
- # With proxy: tests are filtered AND requests are routed through the proxy
301
- REGION=DE PROXY_SERVER=socks5://proxy.example:1080 npx playwright test tests/sirdata.spec.ts --project webkit
302
- ```
303
-
304
- In CI, Jenkins loads region-specific `.env` files that set both `REGION` and `PROXY_SERVER` together.
305
-
306
- Test specs support `skipRegions` and `onlyRegions` to control when tests run:
307
-
308
- ```typescript
309
- generateCMPTests('Sirdata', ['https://gizmodo.com/'], {
310
- skipRegions: ['US'], // skip test in these regions
311
- onlyRegions: [], // only run in these regions
312
- });
96
+ ```json
97
+ ["#shadow-host", "button.reject"]
98
+ ["#cmp-container iframe", ".opt-out-btn"]
313
99
  ```
314
100
 
315
- ## PR Review Checklist
316
-
317
- ### CI Pipeline
318
-
319
- Two CI systems run on PRs:
320
-
321
- 1. **GitHub Actions** (`.github/workflows/checks.yml`): runs `npm run lint` and `npm run test:lib` on every push/PR. These must pass.
322
- 2. **Jenkins**: runs Playwright E2E tests in 9 regions (US, GB, AU, CA, DE, FR, NL, CH, NO). Only tests for modified rule files and their corresponding test specs are run. Jenkins posts a PR comment with an artifact ZIP link and a link to the [review tool](https://zok.pw/autoconsent-review-tool/) for inspecting screenshots.
323
-
324
- ### Reviewing New Rule PRs
325
-
326
- - [ ] `npm run lint` passes (ESLint + Prettier + schema validation)
327
- - [ ] JSON rule validates against schema (`npm run rule-syntax-check`)
328
- - [ ] A corresponding test spec exists in `tests/`
329
- - [ ] Test URLs are reachable and relevant
330
- - [ ] Selectors are stable (no dynamic IDs, no full body class lists, no CSS module hashes)
331
- - [ ] `optOut` targets an actual reject/decline button, not a privacy policy link
332
- - [ ] For generated rule fixes: all region variants are updated consistently
333
-
334
- ### Reviewing Code-Based Rule PRs
335
-
336
- - [ ] Lint and unit tests pass
337
- - [ ] No hardcoded site-specific attribute values
338
- - [ ] Fallback paths exist for regional variants
339
- - [ ] Uses existing DOM helpers (`this.click()`, `this.waitForElement()`, etc.)
340
-
341
- ### Handling Flaky E2E Tests
342
-
343
- E2E tests hit live sites and are inherently flaky due to site changes, regional differences, and network conditions. Before concluding a test is broken:
344
-
345
- - Check Jenkins screenshots in the review tool
346
- - If a test fails only in certain regions, consider adding `skipRegions`
347
- - Playwright is configured with retries (2 retries in CI)
348
- - Verify the site still has the same cookie consent popup by visiting it manually
101
+ Single-string selectors cannot pierce — use arrays whenever the target is inside a
102
+ shadow root or same-origin iframe.
103
+
104
+ ### General Guidelines and Gotchas
105
+ - **Regional testing is mandatory** for any rule change — CMPs behave differently under GDPR (EU), CCPA (US), and other jurisdictions. Run the rule against different regions using available regional-testing tooling before considering the change done.
106
+ - When verifying a rule, **look at the screenshots** on top of the API results — sometimes a rule reports success, but the popup is not actually handled - a screenshot will detect this.
107
+ - **Paywalls do not need to be handled.** If the website presents the choice to pay or agree to cookies, the correct solution is to disable the feature on that site, so no code changes required in this case.
108
+ - If the pop-up has an explicit "reject"-like button, you should first consider why HEURISTIC rule didn't handle it. A fix to the heuristic rule is always preferred to a new rule, as long as it doesn't cause potential false-positives on other sites.
109
+ - **selfTests are optional.** It is okay to NOT have a self-test, or have it failing as long as the popup is handled correctly. Confirm this with screenshots.
110
+ - If you cover a new CMP or a new flavor of the existing CMP, ALWAYS try to look for more examples of that case, and add to the spec file.
111
+ - `detectCmp` and `detectPopup` must be fast. Do NOT use waiting steps — the engine retries automatically.
112
+ - **`prehideSelectors` do not affect autoconsent visibility checks.** Prehide selectors are injected early to prevent flicker, and are intentionally implemented using opacity, which hides elements from the user, but not from built-in steps such as `waitForVisible` and `visible`. That said, _prehide selectors should be narrow_: overly broad selectors (e.g. `body`) could hide the entire page.
113
+ - Prefer DOM-based steps when possible `eval` steps are a last resort.
114
+ - Set `minimumRuleStepVersion: 2` if using `removeClass`, `setStyle`, or `addStyle`.
115
+ - Prefer `cookieContains` in `test` when the CMP stores consent in cookies.
116
+ - Use `npm run create-rule` to scaffold a new rule and a spec file.
117
+
118
+ ### Fixing breakage in cosmetic rules
119
+ When using `hide`, the CMP may lock scrolling or add overlays. Add fixes AFTER the `hide` step, marked `"optional": true`:
120
+
121
+ | Problem | Fix |
122
+ |---------|-----|
123
+ | Scroll lock via CSS class | `{ "removeClass": "no-scroll", "selector": "body", "optional": true }` |
124
+ | Scroll lock via inline style | `{ "addStyle": "overflow: auto !important", "selector": "body", "optional": true }` |
125
+ | Overlay blocking clicks | `{ "hide": "#overlay", "optional": true }` |
126
+ | Body position lock | `{ "setStyle": "", "selector": "body", "optional": true }` |
127
+
128
+ Using `removeClass`, `setStyle`, or `addStyle` requires `"minimumRuleStepVersion": 2`.
129
+
130
+ ## Identifying a Consent Management Platform
131
+ The following techniques can help identify a generic CMP:
132
+
133
+ 1. **DOM inspection:** Check class names on popup elements for vendor prefixes
134
+ (`onetrust-`, `didomi-`, `sp_choice_type_`, `cmp-`, `fc-`, `klaro-`, `pd-`, etc.).
135
+ 2. **JS source analysis:** Inspect the popup buttons' click handlers or find the cookie
136
+ that stores consent and search for that cookie name in the page's scripts. Look for:
137
+ - Vendor names in variable/function names or `window` globals.
138
+ - Scripts in `node_modules/`, `vendor/`, or `wp-content/plugins/` paths.
139
+ - License comments with vendor URLs at the top of the script.
140
+ 3. **Cross-site prevalence:** Use the `publicwww-search` skill to search for distinctive
141
+ selectors, script URLs, or copy strings. If the same popup markup appears on many
142
+ sites, it's a CMP.
349
143
 
350
144
  ## CMP Discovery with PublicWWW
351
145
 
@@ -355,9 +149,10 @@ Requires `PUBLICWWW_KEY` environment variable.
355
149
 
356
150
  ## Verification
357
151
 
358
- | Step | Command |
359
- |------|---------|
360
- | Schema + formatting | `npm run lint` |
361
- | Unit tests | `npm run test:lib` |
362
- | Single CMP E2E test | `npx playwright test tests/<cmp>.spec.ts --project webkit` |
363
- | Full E2E suite | `npm run test` |
152
+ After creating or modifying a rule:
153
+
154
+ 1. `npm run build-rules` — rebuild rules.json (required for tests)
155
+ 2. `npm run rule-syntax-check` — validate rule JSON against schema
156
+ 3. `npx playwright test tests/<name>.spec.ts` run the E2E test
157
+ 4. `npm run prepublish` — full build including extension bundle
158
+ 5. Check the rule works across geographic regions using available regional-testing tooling.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # v14.78.0 (Fri May 08 2026)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - Improve agent context: restructure skills, slim AGENTS.md [#1319](https://github.com/duckduckgo/autoconsent/pull/1319) ([@cursoragent](https://github.com/cursoragent) [@muodov](https://github.com/muodov))
6
+
7
+ #### Authors: 2
8
+
9
+ - Cursor Agent ([@cursoragent](https://github.com/cursoragent))
10
+ - Maxim Tsoy ([@muodov](https://github.com/muodov))
11
+
12
+ ---
13
+
1
14
  # v14.77.1 (Thu May 07 2026)
2
15
 
3
16
  #### 🐛 Bug Fix
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.5.5",
4
+ "version": "2026.5.7",
5
5
  "background": {
6
6
  "scripts": [
7
7
  "background.bundle.js"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.5.5",
4
+ "version": "2026.5.7",
5
5
  "background": {
6
6
  "service_worker": "background.bundle.js"
7
7
  },
package/eslint.config.mjs CHANGED
@@ -8,7 +8,7 @@ const c = tseslint.config(
8
8
  ...ddgConfig,
9
9
 
10
10
  {
11
- ignores: ['lib/consentomatic/**/*', 'dist/**/*'],
11
+ ignores: ['lib/consentomatic/**/*', 'dist/**/*', '.agents/**/*'],
12
12
  },
13
13
 
14
14
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "14.77.1",
3
+ "version": "14.78.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {