@duckduckgo/autoconsent 16.43.1 → 16.44.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 (42) hide show
  1. package/.agents/lib/regional-testing/harness.mjs +536 -0
  2. package/.agents/skills/oxylabs-testing/SKILL.md +99 -0
  3. package/.agents/skills/oxylabs-testing/scripts/oxylabs.mjs +300 -0
  4. package/.agents/skills/oxylabs-testing/scripts/oxylabs.test.mjs +37 -0
  5. package/.agents/skills/proxy-testing/SKILL.md +5 -2
  6. package/.agents/skills/proxy-testing/scripts/regional-proxy.mjs +22 -471
  7. package/.claude/skills/oxylabs-testing/SKILL.md +6 -0
  8. package/.claude/skills/proxy-testing/SKILL.md +1 -1
  9. package/.claude/user-settings.json.TEMPLATE +2 -0
  10. package/AGENTS.md +10 -2
  11. package/CHANGELOG.md +47 -0
  12. package/data/coverage.json +2005 -1512
  13. package/dist/addon-firefox/background.bundle.js +9 -3
  14. package/dist/addon-firefox/compact-rules.json +1 -1
  15. package/dist/addon-firefox/manifest.json +1 -1
  16. package/dist/addon-firefox/popup.bundle.js +9 -3
  17. package/dist/addon-firefox/rule-index.json +1 -1
  18. package/dist/addon-firefox/rules.json +1 -1
  19. package/dist/addon-mv3/background.bundle.js +9 -3
  20. package/dist/addon-mv3/compact-rules.json +1 -1
  21. package/dist/addon-mv3/manifest.json +1 -1
  22. package/dist/addon-mv3/popup.bundle.js +9 -3
  23. package/dist/addon-mv3/rule-index.json +1 -1
  24. package/dist/addon-mv3/rules.json +1 -1
  25. package/dist/autoconsent.standalone.js +1 -1
  26. package/docs/rule-syntax.md +4 -4
  27. package/package.json +2 -2
  28. package/readme.md +1 -1
  29. package/rules/autoconsent/bandcamp.json +42 -3
  30. package/rules/autoconsent/chatgpt.json +2 -2
  31. package/rules/autoconsent/elsevier.json +69 -0
  32. package/rules/autoconsent/fides.json +10 -2
  33. package/rules/autoconsent/lacoccinelle.json +97 -0
  34. package/rules/autoconsent/woo-commerce-com.json +28 -11
  35. package/rules/compact-rules.json +1 -1
  36. package/rules/rule-index.json +1 -1
  37. package/rules/rules.json +1 -1
  38. package/tests/bandcamp.spec.ts +7 -1
  39. package/tests/elsevier.spec.ts +3 -0
  40. package/tests/fides.spec.ts +7 -1
  41. package/tests/lacoccinelle.spec.ts +3 -0
  42. package/tests/woo-commerce-com.spec.ts +4 -1
@@ -66,7 +66,7 @@ Returns true if the given selector matches one or more elements.
66
66
  "check": "any" | "all" | "none"
67
67
  }
68
68
  ```
69
- Returns true if elements matched by ElementSelector are currently visible on the page. If `check` is `all` (default), every element must be visible. If `check` is `none`, no element should be visible. Visibility check is a CSS-based heuristic.
69
+ Returns true if elements matched by ElementSelector are currently visible on the page. If `check` is `all` (default), every element must be visible. If `check` is `none`, no element should be visible. Visibility check is a CSS-based heuristic. Prehiding does not affect this check.
70
70
 
71
71
  ## Wait for element
72
72
 
@@ -87,7 +87,7 @@ Waits until `selector` exists in the page. After `timeout` ms the step fails.
87
87
  "check": "any" | "all" | "none"
88
88
  }
89
89
  ```
90
- Waits until element is visible in the page. After `timeout` ms the step fails.
90
+ Waits until element is visible in the page. After `timeout` ms the step fails. To wait until an element is not visible, use `check: "none"`, not `negated`.
91
91
 
92
92
  ## Click an element
93
93
  ```javascript
@@ -183,7 +183,7 @@ The optional `stylesheetId` is a marker used to prevent duplicate inserts when t
183
183
  "cookieContains": "substring"
184
184
  }
185
185
  ```
186
- Checks if the substring is present in the document.cookie string.
186
+ Checks if the substring is present in the document.cookie string. `HttpOnly` cookies are not in this string.
187
187
 
188
188
  ## Eval
189
189
 
@@ -234,7 +234,7 @@ Evaluates a list of steps in order. If any return true (success), then the step
234
234
  }
235
235
  ```
236
236
 
237
- If `negated` is true, the result will be inverted. For example, `{ "exists": ".my-class", "negated": true }` will return true if `.my-class` does not exist.
237
+ If `negated` is true, the result will be inverted. For example, `{ "exists": ".my-class", "negated": true }` will return true if `.my-class` does not exist. The result is inverted after the step is complete, so `negated` does not change what a waiting step waits for.
238
238
 
239
239
  ## Optional actions
240
240
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "16.43.1",
3
+ "version": "16.44.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {
@@ -45,7 +45,7 @@
45
45
  "@duckduckgo/eslint-config": "github:duckduckgo/eslint-config#v0.3.0",
46
46
  "@esm-bundle/chai": "^4.3.4-fix.0",
47
47
  "@octokit/rest": "^22.0.1",
48
- "@playwright/test": "^1.17.1",
48
+ "@playwright/test": "^1.63.0",
49
49
  "@types/chai": "^5.0.0",
50
50
  "@types/chrome": "^0.1.0",
51
51
  "@types/mocha": "^10.0.1",
package/readme.md CHANGED
@@ -88,7 +88,7 @@ For more details on rule types, see [Rule Syntax Reference](docs/rule-syntax.md)
88
88
  The agent skills in `.agents/skills/` need a few environment variables.
89
89
 
90
90
  1. Copy the contents from [`.claude/user-settings.json.TEMPLATE`](.claude/user-settings.json.TEMPLATE) to your user settings (e.g. `~/.claude/settings.json`), in addition to your existing config. If your settings already have an `env` block, merge the entries into it: a second `env` key would silently override the first.
91
- 2. Replace the values in the template. Proxy values must be complete URLs with an `http://`, `https://`, or `socks5://` scheme, and without embedded credentials. `REGIONAL_PROXY_USERNAME` and `REGIONAL_PROXY_PASSWORD` are optional: they are only used for `https://` proxies, so remove them if your proxies don't need authentication.
91
+ 2. Replace the values in the template. Proxy values must be complete URLs with an `http://`, `https://`, or `socks5://` scheme, and without embedded credentials. `REGIONAL_PROXY_USERNAME` and `REGIONAL_PROXY_PASSWORD` are optional: they are only used for `https://` proxies, so remove them if your proxies don't need authentication. `OXYLABS_USER` and `OXYLABS_PASSWORD` are optional too: they enable the `oxylabs-testing` skill for sites that show our proxies a bot wall, so remove them if you don't have an Oxylabs account.
92
92
  3. Start a new Claude Code session: settings are only read at startup.
93
93
 
94
94
  For other agents or plain shell use, `export` the same variables in your shell profile instead.
@@ -10,20 +10,59 @@
10
10
  ],
11
11
  "detectPopup": [
12
12
  {
13
- "visible": ".cookie-dialog-initial"
13
+ "any": [
14
+ {
15
+ "visible": ".cookie-dialog-initial"
16
+ },
17
+ {
18
+ "visible": ["menu-bar", ".cookie-dialog-initial"]
19
+ },
20
+ {
21
+ "visible": ["page-footer", ".cookie-dialog-initial"]
22
+ }
23
+ ]
14
24
  }
15
25
  ],
16
26
  "optIn": [
17
27
  {
18
- "waitForThenClick": ".cookie-dialog-initial button.g-button[type=\"submit\"]"
28
+ "any": [
29
+ {
30
+ "waitForThenClick": ".cookie-dialog-initial button.g-button[type=\"submit\"]",
31
+ "timeout": 500
32
+ },
33
+ {
34
+ "waitForThenClick": ["menu-bar", ".cookie-dialog-initial button.g-button[type=\"submit\"]"],
35
+ "timeout": 500
36
+ },
37
+ {
38
+ "waitForThenClick": ["page-footer", ".cookie-dialog-initial button.g-button[type=\"submit\"]"],
39
+ "timeout": 500
40
+ }
41
+ ]
19
42
  }
20
43
  ],
21
44
  "optOut": [
22
45
  {
23
- "waitForThenClick": ".cookie-dialog-initial button.g-button.outline"
46
+ "any": [
47
+ {
48
+ "waitForThenClick": ".cookie-dialog-initial button.g-button.outline",
49
+ "timeout": 500
50
+ },
51
+ {
52
+ "waitForThenClick": ["menu-bar", ".cookie-dialog-initial button.g-button.outline"],
53
+ "timeout": 500
54
+ },
55
+ {
56
+ "waitForThenClick": ["page-footer", ".cookie-dialog-initial button.g-button.outline"],
57
+ "timeout": 500
58
+ }
59
+ ]
24
60
  }
25
61
  ],
26
62
  "test": [
63
+ {
64
+ "wait": 500
65
+ },
27
66
  {
28
67
  "cookieContains": "cookie_preferences=%7B%22allow%22%3A%5B%5D"
29
68
  }
@@ -9,8 +9,8 @@
9
9
  "frame": false,
10
10
  "urlPattern": "^https?://(\\w+\\.)?chatgpt\\.com/"
11
11
  },
12
- "comment": "Covers the server-rendered consent dialog. Once the app hydrates it swaps in a banner whose buttons are only distinguishable by text, which the heuristic rule handles better.",
13
- "prehideSelectors": ["dialog[data-cookie-consent]:has(form[data-privacy-consent-form])"],
12
+ "comment": "Covers the server-rendered consent dialog. Once the app hydrates it swaps in a banner whose buttons are only distinguishable by text, which the heuristic rule handles better. Below 48rem the site reopens the dialog with showModal() and marks it aria-modal, so prehiding it would leave the page inert with nothing visible to dismiss.",
13
+ "prehideSelectors": ["dialog[data-cookie-consent]:not([aria-modal]):has(form[data-privacy-consent-form])"],
14
14
  "detectCmp": [
15
15
  {
16
16
  "exists": "dialog[data-cookie-consent] form[data-privacy-consent-form] input[name=\"action\"][value=\"reject\"]"
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "elsevier",
3
+ "vendorUrl": "https://www.elsevier.com/",
4
+ "runContext": {
5
+ "urlPattern": "^https?://(\\w+\\.)?elsevier\\.(com|support)/"
6
+ },
7
+ "prehideSelectors": ["[data-testid=\"cookie-banner\"]"],
8
+ "detectCmp": [
9
+ {
10
+ "exists": "[data-testid=\"cookie-banner\"] [data-testid=\"cookie-settings-button\"]"
11
+ }
12
+ ],
13
+ "detectPopup": [
14
+ {
15
+ "visible": "[data-testid=\"cookie-banner\"] [data-testid=\"cookie-settings-button\"]"
16
+ }
17
+ ],
18
+ "optIn": [
19
+ {
20
+ "waitForThenClick": "[data-testid=\"accept-all-cookies-button\"]"
21
+ }
22
+ ],
23
+ "optOut": [
24
+ {
25
+ "if": {
26
+ "exists": "[data-testid=\"reject-all-cookies-button\"]"
27
+ },
28
+ "then": [
29
+ {
30
+ "comment": "the banner is rendered before React attaches its click handler, so a lost first click needs a retry",
31
+ "waitForThenClick": "[data-testid=\"reject-all-cookies-button\"]",
32
+ "retry": 3,
33
+ "retryInterval": 1000
34
+ }
35
+ ],
36
+ "else": [
37
+ {
38
+ "comment": "no reject button on the banner: open the OneTrust preference center it controls and reject there",
39
+ "waitForThenClick": "[data-testid=\"cookie-settings-button\"]",
40
+ "retry": 3,
41
+ "retryInterval": 1000
42
+ },
43
+ {
44
+ "waitForVisible": "#onetrust-pc-sdk",
45
+ "timeout": 10000
46
+ },
47
+ {
48
+ "waitForThenClick": "#onetrust-pc-sdk input.category-switch-handler:checked",
49
+ "all": true,
50
+ "optional": true
51
+ },
52
+ {
53
+ "waitForThenClick": "#onetrust-pc-sdk .save-preference-btn-handler",
54
+ "timeout": 5000
55
+ }
56
+ ]
57
+ },
58
+ {
59
+ "waitForVisible": "[data-testid=\"cookie-banner\"]",
60
+ "check": "none",
61
+ "timeout": 10000
62
+ }
63
+ ],
64
+ "test": [
65
+ {
66
+ "cookieContains": "OptanonAlertBoxClosed"
67
+ }
68
+ ]
69
+ }
@@ -11,6 +11,10 @@
11
11
  {
12
12
  "visible": "#fides-overlay-wrapper #fides-banner"
13
13
  },
14
+ {
15
+ "visible": "#fides-overlay-wrapper .fides-banner-hidden",
16
+ "check": "none"
17
+ },
14
18
  {
15
19
  "eval": "EVAL_FIDES_DETECT_POPUP"
16
20
  }
@@ -30,7 +34,9 @@
30
34
  },
31
35
  "then": [
32
36
  {
33
- "click": "#fides-banner .fides-reject-all-button"
37
+ "waitForThenClick": ".fides-banner:not(.fides-banner-hidden) .fides-reject-all-button",
38
+ "retry": 5,
39
+ "retryInterval": 500
34
40
  }
35
41
  ],
36
42
  "else": [
@@ -38,7 +44,9 @@
38
44
  "click": "#fides-banner .fides-manage-preferences-button"
39
45
  },
40
46
  {
41
- "waitForThenClick": ".fides-reject-all-button"
47
+ "waitForThenClick": ".fides-modal-content .fides-reject-all-button",
48
+ "retry": 5,
49
+ "retryInterval": 500
42
50
  }
43
51
  ]
44
52
  }
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "lacoccinelle",
3
+ "vendorUrl": "https://www.lacoccinelle.net/",
4
+ "runContext": {
5
+ "urlPattern": "^https?://(\\w+\\.)?lacoccinelle\\.net/"
6
+ },
7
+ "prehideSelectors": ["#qc-cmp2-container"],
8
+ "detectCmp": [
9
+ {
10
+ "exists": "#qc-cmp2-container"
11
+ }
12
+ ],
13
+ "detectPopup": [
14
+ {
15
+ "visible": "#qc-cmp2-ui, #qc-cmp2-usp",
16
+ "check": "any"
17
+ }
18
+ ],
19
+ "optIn": [
20
+ {
21
+ "if": {
22
+ "exists": "#qc-cmp2-usp"
23
+ },
24
+ "then": [
25
+ {
26
+ "waitForThenClick": "#qc-cmp2-usp .qc-usp-close-icon"
27
+ }
28
+ ],
29
+ "else": [
30
+ {
31
+ "waitForThenClick": "#accept-btn"
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "optOut": [
37
+ {
38
+ "if": {
39
+ "exists": "#qc-cmp2-usp"
40
+ },
41
+ "then": [
42
+ {
43
+ "waitForThenClick": "#qc-cmp2-usp .qc-cmp2-toggle-switch > button[aria-checked=\"false\"]",
44
+ "all": true,
45
+ "retry": 3
46
+ },
47
+ {
48
+ "waitForThenClick": "#qc-cmp2-usp .qc-usp-ui-content button[mode=\"primary\"]"
49
+ },
50
+ {
51
+ "waitForThenClick": "#qc-cmp2-mspa-success-modal-button-footer-container button",
52
+ "timeout": 5000,
53
+ "optional": true
54
+ }
55
+ ],
56
+ "else": [
57
+ {
58
+ "comment": "the \"more options\" button is repurposed as a paid offer, the partner list is the only free reject path",
59
+ "waitForThenClick": "#qc-cmp2-container button.qc-cmp2-link-inline"
60
+ },
61
+ {
62
+ "waitForThenClick": "#reject-all-btn"
63
+ },
64
+ {
65
+ "comment": "outside the EEA the choice is not saved automatically",
66
+ "waitForVisible": "#qc-cmp2-ui",
67
+ "check": "none",
68
+ "timeout": 1500,
69
+ "optional": true
70
+ },
71
+ {
72
+ "if": {
73
+ "visible": "#save-and-exit",
74
+ "check": "any"
75
+ },
76
+ "then": [
77
+ {
78
+ "click": "#save-and-exit",
79
+ "all": true
80
+ },
81
+ {
82
+ "waitForVisible": "#qc-cmp2-ui",
83
+ "check": "none",
84
+ "timeout": 5000
85
+ }
86
+ ]
87
+ }
88
+ ]
89
+ }
90
+ ],
91
+ "test": [
92
+ {
93
+ "visible": "#qc-cmp2-ui, #qc-cmp2-usp",
94
+ "check": "none"
95
+ }
96
+ ]
97
+ }
@@ -1,19 +1,36 @@
1
1
  {
2
2
  "name": "woo-commerce-com",
3
+ "comment": "The banner renders its buttons disabled until it is ready, and clicks before that are ignored, so every click waits for :not([disabled]).",
3
4
  "prehideSelectors": [".wccom-comp-privacy-banner .wccom-privacy-banner"],
4
5
  "detectCmp": [{ "exists": ".wccom-comp-privacy-banner .wccom-privacy-banner" }],
5
- "detectPopup": [{ "exists": ".wccom-comp-privacy-banner .wccom-privacy-banner" }],
6
- "optIn": [{ "click": ".wccom-privacy-banner__content-buttons button.is-primary" }],
6
+ "detectPopup": [{ "visible": ".wccom-comp-privacy-banner .wccom-privacy-banner" }],
7
+ "optIn": [{ "waitForThenClick": ".wccom-privacy-banner__content-buttons button.is-style-primary:not([disabled])" }],
7
8
  "optOut": [
8
9
  {
9
- "click": ".wccom-privacy-banner__content-buttons button.is-secondary"
10
- },
11
- {
12
- "waitForThenClick": "input[type=checkbox][checked]:not([disabled])",
13
- "all": true
14
- },
15
- {
16
- "click": "div.wccom-modal__footer > button"
10
+ "comment": "'Reject all' is only offered in some regions: it is the secondary button followed by another secondary ('Customize')",
11
+ "if": { "exists": ".wccom-privacy-banner__content-buttons button.is-style-secondary:has(+ button.is-style-secondary)" },
12
+ "then": [
13
+ {
14
+ "waitForThenClick": ".wccom-privacy-banner__content-buttons button.is-style-secondary:not([disabled]):has(+ button.is-style-secondary)"
15
+ }
16
+ ],
17
+ "else": [
18
+ {
19
+ "waitForThenClick": ".wccom-privacy-banner__content-buttons button.is-style-secondary:not([disabled]):has(+ button.is-style-primary)"
20
+ },
21
+ {
22
+ "waitForVisible": ".wccom-modal__footer button.is-style-primary"
23
+ },
24
+ {
25
+ "click": ".wccom-customize-privacy-checkbox input[type=checkbox]:checked:not([disabled])",
26
+ "all": true,
27
+ "optional": true
28
+ },
29
+ {
30
+ "waitForThenClick": ".wccom-modal__footer button.is-style-primary:not([disabled])"
31
+ }
32
+ ]
17
33
  }
18
- ]
34
+ ],
35
+ "test": [{ "cookieContains": "wccom_cookie_consent_analytics=denied" }]
19
36
  }