@duckduckgo/autoconsent 16.51.0 → 16.52.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 (39) hide show
  1. package/.github/workflows/ddg-release.yml +2 -2
  2. package/AGENTS.md +1 -0
  3. package/CHANGELOG.md +31 -0
  4. package/dist/addon-firefox/compact-rules.json +1 -1
  5. package/dist/addon-firefox/content.bundle.js +4 -1
  6. package/dist/addon-firefox/manifest.json +1 -1
  7. package/dist/addon-firefox/rule-index.json +1 -1
  8. package/dist/addon-firefox/rules.json +1 -1
  9. package/dist/addon-mv3/compact-rules.json +1 -1
  10. package/dist/addon-mv3/content.bundle.js +4 -1
  11. package/dist/addon-mv3/manifest.json +1 -1
  12. package/dist/addon-mv3/rule-index.json +1 -1
  13. package/dist/addon-mv3/rules.json +1 -1
  14. package/dist/autoconsent.cjs.js +4 -1
  15. package/dist/autoconsent.esm.js +4 -1
  16. package/dist/autoconsent.playwright.js +4 -1
  17. package/dist/autoconsent.standalone.js +5 -2
  18. package/docs/rule-syntax.md +1 -1
  19. package/lib/dom-actions.ts +5 -1
  20. package/package.json +1 -1
  21. package/rules/autoconsent/community.screwfix.com.json +23 -0
  22. package/rules/autoconsent/la-becanerie.json +13 -0
  23. package/rules/autoconsent/lta.json +22 -0
  24. package/rules/autoconsent/mizuno-com.json +13 -0
  25. package/rules/autoconsent/pigu.json +35 -0
  26. package/rules/autoconsent/quantcast.json +85 -34
  27. package/rules/autoconsent/tealium.json +10 -4
  28. package/rules/compact-rules.json +1 -1
  29. package/rules/rule-index.json +1 -1
  30. package/rules/rules.json +1 -1
  31. package/tests/community.screwfix.com.spec.ts +5 -0
  32. package/tests/la-becanerie.spec.ts +3 -0
  33. package/tests/lta.spec.ts +7 -0
  34. package/tests/mizuno-com.spec.ts +7 -0
  35. package/tests/pigu.spec.ts +3 -0
  36. package/tests/quantcast.spec.ts +15 -7
  37. package/tests/tealium.spec.ts +3 -0
  38. package/tests-wtr/dom-actions/dom-actions.click.ts +18 -0
  39. package/Dockerfile +0 -9
@@ -96,7 +96,7 @@ Waits until element is visible in the page. After `timeout` ms the step fails. T
96
96
  "all": true | false,
97
97
  }
98
98
  ```
99
- Click on an element returned by `selector`. If `all` is `true`, all matching elements are clicked. If `all` is `false`, only the first returned value is clicked.
99
+ Click on an element returned by `selector`. If `all` is `true`, all matching elements are clicked one by one, letting the page handle each click before the next one (back-to-back clicks can lose state updates, e.g. on toggle switches). If `all` is `false`, only the first returned value is clicked.
100
100
 
101
101
  ## Wait for then click
102
102
  ```javascript
@@ -26,7 +26,11 @@ export class DomActions implements DomActionsProvider {
26
26
 
27
27
  if (elements.length > 0) {
28
28
  if (all) {
29
- elements.forEach((e) => e.click());
29
+ for (const e of elements) {
30
+ e.click();
31
+ // Let the page handle each click first: back-to-back clicks can overwrite each other's state updates.
32
+ await new Promise((resolve) => setTimeout(resolve, 0));
33
+ }
30
34
  } else {
31
35
  elements[0].click();
32
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "16.51.0",
3
+ "version": "16.52.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "community.screwfix.com",
3
+ "vendorUrl": "https://trustarc.com/",
4
+ "comment": "TrustArc banner whose custom CSS adds a full-page #consent-banner::before backdrop that is not removed when the banner closes, leaving the page dimmed and unclickable.",
5
+ "minimumRuleStepVersion": 3,
6
+ "runContext": {
7
+ "main": true,
8
+ "frame": false,
9
+ "urlPattern": "^https?://community\\.screwfix\\.com/"
10
+ },
11
+ "prehideSelectors": ["#consent-banner"],
12
+ "detectCmp": [{ "exists": "#consent-banner #truste-consent-track" }],
13
+ "detectPopup": [{ "visible": "#consent-banner #truste-consent-track" }],
14
+ "optIn": [
15
+ { "waitForThenClick": "#truste-consent-button" },
16
+ { "stylesheet": "#consent-banner::before { display: none !important; }", "stylesheetId": "screwfix-backdrop" }
17
+ ],
18
+ "optOut": [
19
+ { "waitForThenClick": "#truste-consent-required" },
20
+ { "stylesheet": "#consent-banner::before { display: none !important; }", "stylesheetId": "screwfix-backdrop" }
21
+ ],
22
+ "test": [{ "cookieContains": "cmapi_cookie_privacy=permit_1_required" }]
23
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "la-becanerie",
3
+ "vendorUrl": "https://www.la-becanerie.com/",
4
+ "runContext": {
5
+ "urlPattern": "^https://(www\\.)?la-becanerie\\.com/"
6
+ },
7
+ "prehideSelectors": [".cookieconsent .cc_banner-wrapper"],
8
+ "detectCmp": [{ "exists": ".cookieconsent .cc_banner-wrapper [onclick*='refuseAllCookiesPreferences']" }],
9
+ "detectPopup": [{ "visible": ".cookieconsent .cc_banner" }],
10
+ "optIn": [{ "waitForThenClick": ".cc_banner [onclick*='acceptAllCookiesPreferences']" }],
11
+ "optOut": [{ "waitForThenClick": ".cc_banner [onclick*='refuseAllCookiesPreferences']" }],
12
+ "test": [{ "cookieContains": "_cprefs=%7B%22stats%22%3A1%2C%22fct%22%3A1%2C%22mkt%22%3A1%7D" }]
13
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "lta",
3
+ "_metadata": {
4
+ "vendorUrl": "https://www.lta.org.uk/"
5
+ },
6
+ "cosmetic": false,
7
+ "runContext": {
8
+ "main": true,
9
+ "frame": false
10
+ },
11
+ "prehideSelectors": ["#lta-cookies-banner"],
12
+ "detectCmp": [{ "exists": "#lta-cookies-banner" }],
13
+ "detectPopup": [{ "visible": "#lta-cookies-banner" }],
14
+ "optIn": [{ "waitForThenClick": "#lta-cookies-button" }],
15
+ "optOut": [
16
+ { "waitForThenClick": "#js-lta-cookie-modal-trigger" },
17
+ { "waitForVisible": "#lta-cookies-modal" },
18
+ { "click": "#lta-cookies-form input[type=radio][id^=decline-]", "all": true, "optional": true },
19
+ { "click": "#js-lta-cookie-modal-set" }
20
+ ],
21
+ "test": [{ "cookieContains": "%22perf%22:false" }]
22
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "mizuno.com",
3
+ "vendorUrl": "https://emea.mizuno.com/",
4
+ "runContext": {
5
+ "urlPattern": "^https?://(\\w+\\.)?mizuno\\.com/"
6
+ },
7
+ "prehideSelectors": ["#consent-tracking"],
8
+ "detectCmp": [{ "exists": "#consent-tracking .btn-necessary" }],
9
+ "detectPopup": [{ "visible": "#consent-tracking" }],
10
+ "optIn": [{ "waitForThenClick": "#consent-tracking .btn-all" }],
11
+ "optOut": [{ "waitForThenClick": "#consent-tracking .btn-necessary" }],
12
+ "test": [{ "cookieContains": "\"marketingCookie\":false" }]
13
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "pigu",
3
+ "vendorUrl": "https://pigugroup.eu/",
4
+ "detectCmp": [
5
+ {
6
+ "exists": "#consent_block[widget=\"CookieTracker\"]"
7
+ }
8
+ ],
9
+ "detectPopup": [
10
+ {
11
+ "visible": "#consent_block [widget-attachpoint=\"basicAgree\"]"
12
+ }
13
+ ],
14
+ "optIn": [
15
+ {
16
+ "waitForThenClick": "#consent_block [widget-attachpoint=\"agree\"]",
17
+ "retry": 10,
18
+ "retryInterval": 500
19
+ }
20
+ ],
21
+ "optOut": [
22
+ {
23
+ "waitForThenClick": "#consent_block [widget-attachpoint=\"basicAgree\"]",
24
+ "retry": 10,
25
+ "retryInterval": 500
26
+ }
27
+ ],
28
+ "test": [
29
+ {
30
+ "waitForVisible": "#consent_block [widget-attachpoint=\"basicAgree\"]",
31
+ "check": "none",
32
+ "timeout": 2000
33
+ }
34
+ ]
35
+ }
@@ -8,71 +8,122 @@
8
8
  ],
9
9
  "detectPopup": [
10
10
  {
11
- "visible": "#qc-cmp2-ui"
11
+ "comment": "#qc-cmp2-ui is the GDPR dialog, #qc-cmp2-usp the US 'Do Not Process My Personal Information' (USP/MSPA) dialog",
12
+ "visible": "#qc-cmp2-ui,#qc-cmp2-usp"
12
13
  }
13
14
  ],
14
15
  "optOut": [
15
16
  {
17
+ "comment": "US dialog: opt out with the toggles, then confirm",
16
18
  "if": {
17
- "exists": "#disagree-btn"
19
+ "exists": "#qc-cmp2-usp"
18
20
  },
19
21
  "then": [
20
22
  {
21
- "click": "#disagree-btn"
22
- }
23
- ],
24
- "else": [
23
+ "waitFor": "#qc-cmp2-usp .qc-cmp2-toggle-switch",
24
+ "timeout": 2000,
25
+ "optional": true
26
+ },
27
+ {
28
+ "comment": "each toggle reads 'I want to opt-out of ...', so switching them on is the opt-out",
29
+ "click": "#qc-cmp2-usp .qc-cmp2-toggle-switch > button[aria-checked=\"false\"]",
30
+ "all": true,
31
+ "optional": true
32
+ },
25
33
  {
26
- "waitFor": ".qc-cmp2-summary-buttons > button[mode=\"secondary\"]",
34
+ "waitForThenClick": "#qc-cmp2-usp .qc-usp-ui-form-content button[mode=\"primary\"]",
27
35
  "timeout": 2000
28
36
  },
29
37
  {
38
+ "comment": "confirming opens a success modal that stays until it's closed",
39
+ "waitForThenClick": "#qc-cmp2-mspa-success-modal button",
40
+ "timeout": 2000,
41
+ "optional": true
42
+ }
43
+ ],
44
+ "else": [
45
+ {
46
+ "comment": "match the reject button even while it's disabled: falling through to the branch below would click it too early",
30
47
  "if": {
31
- "exists": ".qc-cmp2-summary-buttons > button[mode=\"secondary\"]:nth-of-type(2)"
48
+ "exists": "#disagree-btn"
32
49
  },
33
50
  "then": [
34
51
  {
35
- "click": ".qc-cmp2-summary-buttons > button[mode=\"secondary\"]:nth-of-type(2)"
52
+ "comment": "the reject button renders disabled with a loader, and clicks on it are dropped until it's enabled",
53
+ "waitForThenClick": "#disagree-btn:not([disabled])",
54
+ "timeout": 5000
36
55
  }
37
56
  ],
38
57
  "else": [
39
58
  {
40
- "click": ".qc-cmp2-summary-buttons > button[mode=\"secondary\"]:nth-of-type(1)"
41
- },
42
- {
43
- "waitForVisible": "#qc-cmp2-ui .qc-cmp2-consent-info"
44
- },
45
- {
46
- "click": ".qc-cmp2-toggle-switch > button[aria-checked=\"true\"]",
47
- "all": true,
48
- "optional": true
49
- },
50
- {
51
- "click": [
52
- ".qc-cmp2-main",
53
- "xpath///button[contains(., 'REJECT ALL') or contains(., 'ALLE VERWERPEN') or contains(., 'ΑΠΟΡΡΙΠΤΩ ΤΑ ΠΑΝΤΑ') or contains(., 'RESPINGERE TOTALĂ') or contains(., 'ALLE ABLEHNEN') or contains(., 'ODRZUCENIE') or contains(., 'BLOQUEAR TODO') or contains(., 'REJEITAR TODOS') or contains(., 'RIFIUTA TUTTO') or contains(., 'TOUT REFUSER') or contains(., 'ОТКЛОНИТЬ ВСЕХ')]"
54
- ],
55
- "optional": true
56
- },
57
- {
58
- "wait": 500
59
+ "waitFor": ".qc-cmp2-summary-buttons button[mode=\"secondary\"]",
60
+ "timeout": 2000
59
61
  },
60
62
  {
63
+ "comment": "without #disagree-btn, a secondary button in second place rejects; otherwise reject from the 'more options' screen",
61
64
  "if": {
62
- "exists": ".qc-cmp2-buttons-desktop > button[mode=primary]"
65
+ "exists": ".qc-cmp2-summary-buttons button[mode=\"secondary\"]:nth-of-type(2)"
63
66
  },
64
67
  "then": [
65
68
  {
66
- "click": ".qc-cmp2-buttons-desktop > button[mode=primary]"
69
+ "click": ".qc-cmp2-summary-buttons button[mode=\"secondary\"]:nth-of-type(2)"
67
70
  }
68
71
  ],
69
72
  "else": [
70
73
  {
71
- "waitForThenClick": [
74
+ "click": ".qc-cmp2-summary-buttons button[mode=\"secondary\"]:nth-of-type(1)"
75
+ },
76
+ {
77
+ "waitForVisible": "#qc-cmp2-ui .qc-cmp2-consent-info"
78
+ },
79
+ {
80
+ "comment": "turn off the purposes that are switched on by default",
81
+ "click": ".qc-cmp2-toggle-switch > button[aria-checked=\"true\"]",
82
+ "all": true,
83
+ "optional": true
84
+ },
85
+ {
86
+ "comment": "'Reject all' has a localized label",
87
+ "click": [
72
88
  ".qc-cmp2-main",
73
- "xpath///button[contains(.,'SAVE & EXIT') or contains(.,'SALVA ED ESCI') or contains(.,'GUARDAR Y SALIR') or contains(.,'SPEICHERN & VERLASSEN')"
89
+ "xpath///button[contains(., 'REJECT ALL') or contains(., 'ALLE VERWERPEN') or contains(., 'ΑΠΟΡΡΙΠΤΩ ΤΑ ΠΑΝΤΑ') or contains(., 'RESPINGERE TOTALĂ') or contains(., 'ALLE ABLEHNEN') or contains(., 'ODRZUCENIE') or contains(., 'BLOQUEAR TODO') or contains(., 'REJEITAR TODOS') or contains(., 'RIFIUTA TUTTO') or contains(., 'TOUT REFUSER') or contains(., 'ОТКЛОНИТЬ ВСЕХ')]"
74
90
  ],
75
- "timeout": 5000
91
+ "optional": true
92
+ },
93
+ {
94
+ "comment": "'Reject all' can save and close the dialog on its own; give it up to 1s to close before the visibility check",
95
+ "waitForVisible": "#qc-cmp2-ui",
96
+ "check": "none",
97
+ "timeout": 1000,
98
+ "optional": true
99
+ },
100
+ {
101
+ "comment": "only save if 'Reject all' left the dialog open",
102
+ "if": {
103
+ "visible": "#qc-cmp2-ui"
104
+ },
105
+ "then": [
106
+ {
107
+ "comment": "'Save & Exit' is the primary button in the desktop layout; otherwise match its localized label",
108
+ "if": {
109
+ "exists": ".qc-cmp2-buttons-desktop > button[mode=primary]"
110
+ },
111
+ "then": [
112
+ {
113
+ "click": ".qc-cmp2-buttons-desktop > button[mode=primary]"
114
+ }
115
+ ],
116
+ "else": [
117
+ {
118
+ "waitForThenClick": [
119
+ ".qc-cmp2-main",
120
+ "xpath///button[contains(.,'SAVE & EXIT') or contains(.,'SALVA ED ESCI') or contains(.,'GUARDAR Y SALIR') or contains(.,'SPEICHERN & VERLASSEN')"
121
+ ],
122
+ "timeout": 5000
123
+ }
124
+ ]
125
+ }
126
+ ]
76
127
  }
77
128
  ]
78
129
  }
@@ -83,7 +134,7 @@
83
134
  ],
84
135
  "optIn": [
85
136
  {
86
- "click": ".qc-cmp2-summary-buttons > button[mode=\"primary\"]"
137
+ "click": ".qc-cmp2-summary-buttons button[mode=\"primary\"]"
87
138
  }
88
139
  ]
89
140
  }
@@ -6,11 +6,17 @@
6
6
  "optOut": [
7
7
  { "eval": "EVAL_TEALIUM_1" },
8
8
  { "eval": "EVAL_TEALIUM_DONOTSELL" },
9
- { "hide": "#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal" },
10
9
  {
11
- "waitForThenClick": "#cm-acceptNone,.js-accept-essential-cookies,#continueWithoutAccepting,#no_consent,#consent_prompt_decline,#teal-decline-consent",
12
- "timeout": 1000,
13
- "optional": true
10
+ "if": { "exists": "#__tealiumGDPRecModal #rejectAll" },
11
+ "then": [{ "waitForThenClick": "#__tealiumGDPRecModal #rejectAll" }],
12
+ "else": [
13
+ { "hide": "#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal" },
14
+ {
15
+ "waitForThenClick": "#cm-acceptNone,.js-accept-essential-cookies,#continueWithoutAccepting,#no_consent,#consent_prompt_decline,#teal-decline-consent",
16
+ "timeout": 1000,
17
+ "optional": true
18
+ }
19
+ ]
14
20
  }
15
21
  ],
16
22
  "optIn": [{ "hide": "#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs" }, { "eval": "EVAL_TEALIUM_2" }],