@duckduckgo/autoconsent 16.28.0 → 16.30.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 (45) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/addon-firefox/compact-rules.json +1 -1
  3. package/dist/addon-firefox/content.bundle.js +17 -5
  4. package/dist/addon-firefox/manifest.json +1 -1
  5. package/dist/addon-firefox/rule-index.json +1 -1
  6. package/dist/addon-firefox/rules.json +1 -1
  7. package/dist/addon-mv3/compact-rules.json +1 -1
  8. package/dist/addon-mv3/content.bundle.js +17 -5
  9. package/dist/addon-mv3/manifest.json +1 -1
  10. package/dist/addon-mv3/rule-index.json +1 -1
  11. package/dist/addon-mv3/rules.json +1 -1
  12. package/dist/autoconsent.cjs.js +17 -5
  13. package/dist/autoconsent.esm.js +17 -5
  14. package/dist/autoconsent.playwright.js +17 -5
  15. package/dist/autoconsent.standalone.js +18 -6
  16. package/dist/types/cmps/base.d.ts +1 -1
  17. package/dist/types/dom-actions.d.ts +2 -1
  18. package/dist/types/rules.d.ts +10 -0
  19. package/dist/types/types.d.ts +1 -1
  20. package/docs/rule-syntax.md +12 -1
  21. package/lib/cmps/base.ts +3 -3
  22. package/lib/dom-actions.ts +26 -2
  23. package/lib/rules.ts +10 -0
  24. package/lib/types.ts +7 -1
  25. package/package.json +1 -1
  26. package/rules/autoconsent/bankmillennium-pl.json +26 -0
  27. package/rules/autoconsent/bibliotheek-nl.json +33 -0
  28. package/rules/autoconsent/ecbeing.json +10 -0
  29. package/rules/autoconsent/r42-cookiebar.json +37 -0
  30. package/rules/autoconsent/stright.json +29 -0
  31. package/rules/compact-rules.json +1 -1
  32. package/rules/rule-index.json +1 -1
  33. package/rules/rules.json +1 -1
  34. package/tests/bankmillennium-pl.spec.ts +5 -0
  35. package/tests/bibliotheek-nl.spec.ts +3 -0
  36. package/tests/ecbeing.spec.ts +3 -0
  37. package/tests/r42-cookiebar.spec.ts +7 -0
  38. package/tests/stright.spec.ts +8 -0
  39. package/tests-wtr/dom-actions/dom-actions.wait-for-then-click.html +14 -0
  40. package/tests-wtr/dom-actions/dom-actions.wait-for-then-click.ts +106 -0
  41. package/tests-wtr/rules/cmp-test-urls.json +0 -2
  42. package/rules/generated/auto_NL_averoachmea.nl_fxj.json +0 -40
  43. package/rules/generated/auto_NL_centraalbeheer.nl_ooy.json +0 -40
  44. package/tests/generated/auto_NL_averoachmea.nl_fxj.spec.ts +0 -6
  45. package/tests/generated/auto_NL_centraalbeheer.nl_ooy.spec.ts +0 -6
@@ -29,7 +29,7 @@ export default class AutoConsentCMPBase implements AutoCMP, DomActionsProvider {
29
29
  elementVisible(selector: ElementSelector, check?: VisibilityCheck): boolean;
30
30
  waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
31
31
  waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
32
- waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
32
+ waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean, retries?: number, retryInterval?: number): Promise<boolean>;
33
33
  wait(ms: number): Promise<true>;
34
34
  hide(selector: string, method?: HideMethod): boolean;
35
35
  stylesheet(cssRule: string, stylesheetId?: string): boolean;
@@ -1,6 +1,7 @@
1
1
  import { ElementSelector, HideMethod, VisibilityCheck } from './rules';
2
2
  import { DomActionsProvider } from './types';
3
3
  import AutoConsent from './web';
4
+ export declare const DEFAULT_CLICK_RETRY_INTERVAL = 300;
4
5
  export declare class DomActions implements DomActionsProvider {
5
6
  autoconsentInstance: AutoConsent;
6
7
  constructor(autoconsentInstance: AutoConsent);
@@ -10,7 +11,7 @@ export declare class DomActions implements DomActionsProvider {
10
11
  elementVisible(selector: ElementSelector, check?: VisibilityCheck): boolean;
11
12
  waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
12
13
  waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
13
- waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
14
+ waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean, retries?: number, retryInterval?: number): Promise<boolean>;
14
15
  wait(ms: number): Promise<true>;
15
16
  cookieContains(substring: string): boolean;
16
17
  hide(selector: string, method?: HideMethod): boolean;
@@ -68,6 +68,16 @@ export type WaitForThenClickRule = {
68
68
  waitForThenClick: ElementSelector;
69
69
  timeout?: number;
70
70
  all?: boolean;
71
+ /**
72
+ * Number of additional click attempts if the element is still visible after the first click.
73
+ * Defaults to 0 (no retries). Only use this on elements that are expected to disappear once the
74
+ * click is handled, otherwise the extra clicks are wasted (or harmful).
75
+ */
76
+ retry?: number;
77
+ /**
78
+ * How long to wait (in ms) for the element to disappear before clicking it again.
79
+ */
80
+ retryInterval?: number;
71
81
  };
72
82
  export type WaitRule = {
73
83
  wait: number;
@@ -26,7 +26,7 @@ export interface DomActionsProvider {
26
26
  elementVisible(selector: ElementSelector, check: VisibilityCheck): boolean;
27
27
  waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
28
28
  waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
29
- waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
29
+ waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean, retries?: number, retryInterval?: number): Promise<boolean>;
30
30
  wait(ms: number): Promise<true>;
31
31
  hide(selector: string, method: HideMethod): boolean;
32
32
  removeClass(selector: ElementSelector, className: string): boolean;
@@ -103,11 +103,22 @@ Click on an element returned by `selector`. If `all` is `true`, all matching ele
103
103
  {
104
104
  "waitForThenClick": ElementSelector,
105
105
  "timeout": 1000,
106
- "all": true | false
106
+ "all": true | false,
107
+ "retry": 0,
108
+ "retryInterval": 300
107
109
  }
108
110
  ```
109
111
  Combines `waitFor` and `click`.
110
112
 
113
+ `retry` is the number of extra click attempts if the element is still visible after the click, `0` by
114
+ default. It works around CMPs that insert a button before its click handler is attached.
115
+ Between attempts the step waits up to `retryInterval` ms (300 by default) for the element to
116
+ disappear, and stops retrying as soon as it does. Prefer this over an unconditional `wait` before the
117
+ click: nothing is added to the step's duration when the first click works.
118
+
119
+ **Only use `retry` on elements that are expected to go away** once the click is handled — a button that
120
+ stays visible (e.g. a toggle) would be clicked repeatedly.
121
+
111
122
  ## Unconditional wait
112
123
  ```javascript
113
124
  {
package/lib/cmps/base.ts CHANGED
@@ -180,12 +180,12 @@ export default class AutoConsentCMPBase implements AutoCMP, DomActionsProvider {
180
180
  return this.autoconsent.domActions.waitForVisible(selector, timeout, check);
181
181
  }
182
182
 
183
- async waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean) {
183
+ async waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean, retries?: number, retryInterval?: number) {
184
184
  if (this.autoconsent.config.visualTest) {
185
185
  await this.highlightElements(this.elementSelector(selector), all);
186
186
  }
187
187
  this.autoconsent.updateState({ clicks: this.autoconsent.state.clicks + 1 });
188
- return this.autoconsent.domActions.waitForThenClick(selector, timeout, all);
188
+ return this.autoconsent.domActions.waitForThenClick(selector, timeout, all, retries, retryInterval);
189
189
  }
190
190
 
191
191
  wait(ms: number) {
@@ -336,7 +336,7 @@ export class AutoConsentCMP extends AutoConsentCMPBase {
336
336
  results.push(this.click(rule.click, rule.all));
337
337
  }
338
338
  if (rule.waitForThenClick) {
339
- results.push(this.waitForThenClick(rule.waitForThenClick, rule.timeout, rule.all));
339
+ results.push(this.waitForThenClick(rule.waitForThenClick, rule.timeout, rule.all, rule.retry, rule.retryInterval));
340
340
  }
341
341
  if (rule.wait) {
342
342
  results.push(this.wait(rule.wait));
@@ -3,6 +3,11 @@ import { DomActionsProvider } from './types';
3
3
  import { getStyleElement, hideElements, appendStylesheetRule, isElementVisible, waitFor } from './utils';
4
4
  import AutoConsent from './web';
5
5
 
6
+ // Default for the `retryInterval` option of `waitForThenClick`.
7
+ export const DEFAULT_CLICK_RETRY_INTERVAL = 300;
8
+ // How often we check if the element is gone while waiting out a `retryInterval`.
9
+ const CLICK_RETRY_POLL_INTERVAL = 50;
10
+
6
11
  export class DomActions implements DomActionsProvider {
7
12
  constructor(public autoconsentInstance: AutoConsent) {}
8
13
 
@@ -66,9 +71,28 @@ export class DomActions implements DomActionsProvider {
66
71
  return waitFor(() => this.elementVisible(selector, check), times, interval);
67
72
  }
68
73
 
69
- async waitForThenClick(selector: ElementSelector, timeout = 10000, all = false): Promise<boolean> {
74
+ async waitForThenClick(
75
+ selector: ElementSelector,
76
+ timeout = 10000,
77
+ all = false,
78
+ retries = 0,
79
+ retryInterval = DEFAULT_CLICK_RETRY_INTERVAL,
80
+ ): Promise<boolean> {
70
81
  await this.waitForElement(selector, timeout);
71
- return await this.click(selector, all);
82
+ let clicked = await this.click(selector, all);
83
+ // Some CMPs insert the button before its click handler is attached, so the first click is
84
+ // silently dropped. There is usually no DOM signal for this, but a click that was handled
85
+ // makes the element go away: if it is still visible, we assume the click was lost and repeat it.
86
+ for (let attempt = 0; clicked && attempt < retries; attempt++) {
87
+ const pollTimes = Math.ceil(retryInterval / CLICK_RETRY_POLL_INTERVAL);
88
+ const isGone = await waitFor(() => !this.elementVisible(selector, 'any'), pollTimes, CLICK_RETRY_POLL_INTERVAL);
89
+ if (isGone) {
90
+ break;
91
+ }
92
+ this.autoconsentInstance.config.logs.rulesteps && console.log('[waitForThenClick] retrying click', selector);
93
+ clicked = await this.click(selector, all);
94
+ }
95
+ return clicked;
72
96
  }
73
97
 
74
98
  wait(ms: number): Promise<true> {
package/lib/rules.ts CHANGED
@@ -99,6 +99,16 @@ export type WaitForThenClickRule = {
99
99
  waitForThenClick: ElementSelector;
100
100
  timeout?: number;
101
101
  all?: boolean;
102
+ /**
103
+ * Number of additional click attempts if the element is still visible after the first click.
104
+ * Defaults to 0 (no retries). Only use this on elements that are expected to disappear once the
105
+ * click is handled, otherwise the extra clicks are wasted (or harmful).
106
+ */
107
+ retry?: number;
108
+ /**
109
+ * How long to wait (in ms) for the element to disappear before clicking it again.
110
+ */
111
+ retryInterval?: number;
102
112
  };
103
113
 
104
114
  export type WaitRule = {
package/lib/types.ts CHANGED
@@ -29,7 +29,13 @@ export interface DomActionsProvider {
29
29
  elementVisible(selector: ElementSelector, check: VisibilityCheck): boolean;
30
30
  waitForElement(selector: ElementSelector, timeout?: number): Promise<boolean>;
31
31
  waitForVisible(selector: ElementSelector, timeout?: number, check?: VisibilityCheck): Promise<boolean>;
32
- waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
32
+ waitForThenClick(
33
+ selector: ElementSelector,
34
+ timeout?: number,
35
+ all?: boolean,
36
+ retries?: number,
37
+ retryInterval?: number,
38
+ ): Promise<boolean>;
33
39
  wait(ms: number): Promise<true>;
34
40
  hide(selector: string, method: HideMethod): boolean;
35
41
  removeClass(selector: ElementSelector, className: string): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "16.28.0",
3
+ "version": "16.30.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "bankmillennium.pl",
3
+ "runContext": {
4
+ "urlPattern": "^https?://(www\\.)?bankmillennium\\.pl/"
5
+ },
6
+ "prehideSelectors": ["#cookie_modal_placeholder dialog.bm-modal--cookie"],
7
+ "detectCmp": [{ "exists": "#cookie_modal_placeholder dialog.bm-modal--cookie" }],
8
+ "detectPopup": [{ "visible": "#cookie_modal_placeholder dialog.bm-modal--cookie" }],
9
+ "optIn": [
10
+ {
11
+ "waitForThenClick": [
12
+ "#cookie_modal_placeholder dialog.bm-modal--cookie",
13
+ "xpath///button[contains(normalize-space(.), 'Potwierd')]"
14
+ ]
15
+ }
16
+ ],
17
+ "optOut": [
18
+ {
19
+ "waitForThenClick": [
20
+ "#cookie_modal_placeholder dialog.bm-modal--cookie",
21
+ "xpath///button[contains(normalize-space(.), 'Odrzu')]"
22
+ ]
23
+ }
24
+ ],
25
+ "test": [{ "cookieContains": "Analytics_consent=2" }, { "cookieContains": "Marketing_consent=2" }]
26
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "bibliotheek-nl",
3
+ "_metadata": {
4
+ "vendorUrl": "https://www.bibliotheek.nl/"
5
+ },
6
+ "cosmetic": false,
7
+ "runContext": {
8
+ "main": true,
9
+ "frame": false,
10
+ "urlPattern": "^https?://(?:www\\.)?(?:onlinebibliotheek|bibliotheek)\\.nl/"
11
+ },
12
+ "prehideSelectors": [".modalwindow-container.bar .modalwindow"],
13
+ "detectCmp": [
14
+ {
15
+ "exists": "#bibliotheek-nl-page"
16
+ }
17
+ ],
18
+ "detectPopup": [
19
+ {
20
+ "visible": ".modalwindow-container.bar .modalwindow"
21
+ }
22
+ ],
23
+ "optIn": [
24
+ {
25
+ "waitForThenClick": ".modalwindow-container.bar .modalwindow button.button.primary.ok"
26
+ }
27
+ ],
28
+ "optOut": [
29
+ {
30
+ "waitForThenClick": ".modalwindow-container.bar .modalwindow button.button.primary.notok"
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "ecbeing",
3
+ "vendorUrl": "https://www.ecbeing.net/",
4
+ "cosmetic": true,
5
+ "prehideSelectors": ["#cookieBox .block-cookie-consent"],
6
+ "detectCmp": [{ "exists": "#cookieBox #consentButton.block-cookie-consent--btn" }],
7
+ "detectPopup": [{ "visible": "#cookieBox" }],
8
+ "optIn": [{ "waitForThenClick": "#cookieBox #consentButton" }],
9
+ "optOut": [{ "hide": "#cookieBox" }]
10
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "r42-cookiebar",
3
+ "cosmetic": false,
4
+ "runContext": {
5
+ "main": true,
6
+ "frame": false
7
+ },
8
+ "prehideSelectors": ["#r42CookieBar"],
9
+ "detectCmp": [
10
+ {
11
+ "exists": "#r42CookieBar .cw-deny"
12
+ }
13
+ ],
14
+ "detectPopup": [
15
+ {
16
+ "visible": "#r42CookieBar[open] .cw-deny"
17
+ }
18
+ ],
19
+ "optIn": [
20
+ {
21
+ "waitForThenClick": "#r42CookieBar .cw-accept"
22
+ }
23
+ ],
24
+ "optOut": [
25
+ {
26
+ "waitForThenClick": "#r42CookieBar .cw-deny",
27
+ "comment": "Weigeren"
28
+ }
29
+ ],
30
+ "test": [
31
+ {
32
+ "waitForVisible": "#r42CookieBar .cw-deny",
33
+ "timeout": 1000,
34
+ "check": "none"
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "stright",
3
+ "prehideSelectors": ["#controlled_banner-cookie_consent-consent_banner", "#controlled_banner-cookie_consent-privacy_setting_banner"],
4
+ "detectCmp": [{ "exists": "#controlled_banner-cookie_consent-consent_banner" }],
5
+ "detectPopup": [{ "visible": "#controlled_banner-cookie_consent-consent_banner" }],
6
+ "optIn": [{ "waitForThenClick": "#controlled_submit-cookie_consent-consent_banner-accept_all" }],
7
+ "optOut": [
8
+ {
9
+ "if": {
10
+ "visible": "#controlled_banner-cookie_consent-consent_banner [data-search-key-button=reject_all_text]"
11
+ },
12
+ "then": [
13
+ {
14
+ "waitForThenClick": "#controlled_banner-cookie_consent-consent_banner [data-search-key-button=reject_all_text]"
15
+ }
16
+ ],
17
+ "else": [
18
+ {
19
+ "waitForThenClick": "#controlled_submit-cookie_consent-consent_banner-privacy_setting_banner_link"
20
+ },
21
+ {
22
+ "waitForThenClick": "#controlled_banner-cookie_consent-privacy_setting_banner [data-search-key-button=reject_all_text]"
23
+ }
24
+ ]
25
+ },
26
+ { "waitForVisible": "#controlled_banner-cookie_consent-consent_banner", "check": "none" }
27
+ ],
28
+ "test": [{ "visible": "#controlled_banner-cookie_consent-consent_banner", "check": "none" }]
29
+ }