@duckduckgo/autoconsent 2.1.2 → 2.2.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 (93) hide show
  1. package/.vscode/.idea/.vscode.iml +9 -0
  2. package/.vscode/.idea/modules.xml +8 -0
  3. package/.vscode/.idea/workspace.xml +28 -0
  4. package/.vscode/settings.json +7 -0
  5. package/dist/addon-firefox/background.bundle.js +1 -1
  6. package/dist/addon-firefox/content.bundle.js +1 -1
  7. package/dist/addon-firefox/manifest.json +1 -1
  8. package/dist/addon-firefox/rules.json +821 -48
  9. package/dist/addon-mv3/background.bundle.js +1 -1
  10. package/dist/addon-mv3/content.bundle.js +1 -1
  11. package/dist/addon-mv3/manifest.json +1 -1
  12. package/dist/addon-mv3/rules.json +821 -48
  13. package/dist/autoconsent.cjs.js +1 -1
  14. package/dist/autoconsent.esm.js +1 -1
  15. package/dist/autoconsent.playwright.js +1 -1
  16. package/lib/cmps/all.ts +7 -0
  17. package/lib/cmps/base.ts +37 -24
  18. package/lib/cmps/consentmanager.ts +27 -4
  19. package/lib/cmps/conversant.ts +60 -0
  20. package/lib/cmps/klaro.ts +66 -0
  21. package/lib/cmps/sourcepoint-frame.ts +9 -8
  22. package/lib/cmps/sourcepoint-top.ts +5 -0
  23. package/lib/cmps/uniconsent.ts +70 -0
  24. package/lib/rules.ts +8 -1
  25. package/lib/web.ts +35 -25
  26. package/package.json +1 -1
  27. package/readme.md +29 -0
  28. package/rules/autoconsent/adroll.json +11 -0
  29. package/rules/autoconsent/aws-amazon.json +6 -1
  30. package/rules/autoconsent/axeptio.json +41 -0
  31. package/rules/autoconsent/clickio.json +18 -0
  32. package/rules/autoconsent/complianz-banner.json +13 -0
  33. package/rules/autoconsent/complianz-categories.json +17 -0
  34. package/rules/autoconsent/complianz-notice.json +14 -0
  35. package/rules/autoconsent/complianz-optin.json +20 -0
  36. package/rules/autoconsent/cookie-notice.json +3 -1
  37. package/rules/autoconsent/cookieinformation.json +14 -0
  38. package/rules/autoconsent/dsgvo.json +16 -0
  39. package/rules/autoconsent/eu-cookie-law.json +20 -0
  40. package/rules/autoconsent/ezoic.json +19 -0
  41. package/rules/autoconsent/iubenda.json +23 -0
  42. package/rules/autoconsent/jquery-cookiebar.json +25 -0
  43. package/rules/autoconsent/mediavine.json +20 -0
  44. package/rules/autoconsent/moove.json +28 -0
  45. package/rules/autoconsent/paypal.json +4 -1
  46. package/rules/autoconsent/primebox.json +19 -0
  47. package/rules/autoconsent/sirdata.json +11 -0
  48. package/rules/autoconsent/tarteaucitron.json +18 -0
  49. package/rules/autoconsent/tealium.json +0 -1
  50. package/rules/autoconsent/termly.json +31 -0
  51. package/rules/autoconsent/testcmp.json +4 -1
  52. package/rules/autoconsent/uk-cookie-consent.json +15 -0
  53. package/rules/autoconsent/{usercentrics-1.json → usercentrics-api.json} +2 -2
  54. package/rules/autoconsent/usercentrics-button.json +14 -0
  55. package/rules/autoconsent/vodafone-de.json +5 -5
  56. package/rules/autoconsent/wp-cookie-notice.json +12 -0
  57. package/rules/rules.json +821 -48
  58. package/tests/adroll.spec.ts +15 -0
  59. package/tests/axeptio.spec.ts +9 -0
  60. package/tests/clickio.spec.ts +10 -0
  61. package/tests/complianz-banner.spec.ts +9 -0
  62. package/tests/complianz-categories.spec.ts +14 -0
  63. package/tests/{cookieconsent.spec.ts → complianz-notice.spec.ts} +1 -2
  64. package/tests/complianz-optin.spec.ts +6 -0
  65. package/tests/consentmanager.spec.ts +2 -1
  66. package/tests/conversant.spec.ts +10 -0
  67. package/tests/{cookienotice.spec.ts → cookie-notice.spec.ts} +0 -0
  68. package/tests/cookieinformation.spec.ts +10 -0
  69. package/tests/dsgvo.spec.ts +6 -0
  70. package/tests/eu-cookie-law.spec.ts +6 -0
  71. package/tests/ezoic.spec.ts +8 -0
  72. package/tests/iubenda.spec.ts +8 -0
  73. package/tests/jquery-cookiebar.spec.ts +6 -0
  74. package/tests/klaro.spec.ts +6 -2
  75. package/tests/mediavine.spec.ts +8 -0
  76. package/tests/moove.spec.ts +13 -0
  77. package/tests/onetrust.spec.ts +2 -2
  78. package/tests/primebox.spec.ts +7 -0
  79. package/tests/sirdata.spec.ts +8 -0
  80. package/tests/tarteaucitron.spec.ts +9 -0
  81. package/tests/tealium.spec.ts +1 -1
  82. package/tests/termly.spec.ts +12 -0
  83. package/tests/trustarc.spec.ts +1 -9
  84. package/tests/uk-cookie-consent.spec.ts +7 -0
  85. package/tests/uniconsent.spec.ts +12 -0
  86. package/tests/{usercentrics-1.spec.ts → usercentrics-api.spec.ts} +3 -2
  87. package/tests/usercentrics-button.spec.ts +8 -0
  88. package/tests/wp-cookie-notice.spec.ts +8 -0
  89. package/dist/autoconsent.standalone.js +0 -1
  90. package/rules/autoconsent/cookieconsent.json +0 -8
  91. package/rules/autoconsent/destatis-de.json +0 -8
  92. package/rules/autoconsent/klaro.json +0 -10
  93. package/tests/destatis.spec.ts +0 -7
@@ -1,14 +1,15 @@
1
- import { click, elementExists, elementVisible, waitForElement } from "../rule-executors";
1
+ import { click, doEval, elementExists, elementVisible, wait, waitForElement } from "../rule-executors";
2
2
  import AutoConsentCMPBase from "./base";
3
3
 
4
4
  // Note: JS API is also available:
5
5
  // https://help.consentmanager.net/books/cmp/page/javascript-api
6
6
  export default class ConsentManager extends AutoConsentCMPBase {
7
7
 
8
- prehideSelectors = ["#cmpbox,#cmpbox2"]
8
+ prehideSelectors = ["#cmpbox,#cmpbox2"];
9
+ apiAvailable = false;
9
10
 
10
11
  get hasSelfTest(): boolean {
11
- return false;
12
+ return this.apiAvailable;
12
13
  }
13
14
 
14
15
  get isIntermediate(): boolean {
@@ -20,14 +21,27 @@ export default class ConsentManager extends AutoConsentCMPBase {
20
21
  }
21
22
 
22
23
  async detectCmp() {
23
- return elementExists("#cmpbox");
24
+ this.apiAvailable = await doEval('window.__cmp && typeof __cmp("getCMPData") === "object"');
25
+ if (!this.apiAvailable) {
26
+ return elementExists("#cmpbox");
27
+ } else {
28
+ return true;
29
+ }
24
30
  }
25
31
 
26
32
  async detectPopup() {
33
+ if (this.apiAvailable) {
34
+ return await doEval("!__cmp('consentStatus').userChoiceExists");
35
+ }
27
36
  return elementVisible("#cmpbox .cmpmore", 'any');
28
37
  }
29
38
 
30
39
  async optOut() {
40
+ await wait(500);
41
+ if (this.apiAvailable) {
42
+ return await doEval("__cmp('setConsent', 0)");
43
+ }
44
+
31
45
  if (click(".cmpboxbtnno")) {
32
46
  return true;
33
47
  }
@@ -46,6 +60,15 @@ export default class ConsentManager extends AutoConsentCMPBase {
46
60
  }
47
61
 
48
62
  async optIn() {
63
+ if (this.apiAvailable) {
64
+ return await doEval("__cmp('setConsent', 1)");
65
+ }
49
66
  return click(".cmpboxbtnyes");
50
67
  }
68
+
69
+ async test() {
70
+ if (this.apiAvailable) {
71
+ return await doEval("__cmp('consentStatus').userChoiceExists");
72
+ }
73
+ }
51
74
  }
@@ -0,0 +1,60 @@
1
+ import { click, elementExists, elementVisible, waitForElement, waitForThenClick } from "../rule-executors";
2
+ import { waitFor } from "../utils";
3
+ import AutoConsentCMPBase from "./base";
4
+
5
+ export default class Conversant extends AutoConsentCMPBase {
6
+
7
+ prehideSelectors = [".cmp-root"]
8
+
9
+ constructor() {
10
+ super("Conversant");
11
+ }
12
+
13
+ get hasSelfTest(): boolean {
14
+ return true;
15
+ }
16
+
17
+ get isIntermediate(): boolean {
18
+ return false;
19
+ }
20
+
21
+ async detectCmp() {
22
+ return elementExists(".cmp-root .cmp-receptacle");
23
+ }
24
+
25
+ async detectPopup() {
26
+ return elementVisible(".cmp-root .cmp-receptacle", 'any');
27
+ }
28
+
29
+ async optOut() {
30
+ if (!(await waitForThenClick(".cmp-main-button:not(.cmp-main-button--primary)"))) {
31
+ return false;
32
+ }
33
+
34
+ if (!(await waitForElement(".cmp-view-tab-tabs"))) {
35
+ return false;
36
+ }
37
+
38
+ await waitForThenClick(".cmp-view-tab-tabs > :first-child");
39
+ await waitForThenClick(".cmp-view-tab-tabs > .cmp-view-tab--active:first-child");
40
+
41
+ for (const item of Array.from(document.querySelectorAll('.cmp-accordion-item'))) {
42
+ (<HTMLElement>item.querySelector('.cmp-accordion-item-title')).click();
43
+ await waitFor(() => !!item.querySelector('.cmp-accordion-item-content.cmp-active'), 10, 50);
44
+ const content = item.querySelector('.cmp-accordion-item-content.cmp-active');
45
+ content.querySelectorAll('.cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)').forEach((e: HTMLElement) => e.click());
46
+ content.querySelectorAll('.cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)').forEach((e: HTMLElement) => e.click());
47
+ // await waitFor(() => !item.querySelector('.cmp-toggle-deny--active,.cmp-toggle-checkbox--active'), 5, 50); // this may take a long time
48
+ }
49
+ await click(".cmp-main-button:not(.cmp-main-button--primary)");
50
+ return true;
51
+ }
52
+
53
+ async optIn() {
54
+ return waitForThenClick(".cmp-main-button.cmp-main-button--primary");
55
+ }
56
+
57
+ async test() {
58
+ return document.cookie.includes('cmp-data=0');
59
+ }
60
+ }
@@ -0,0 +1,66 @@
1
+ import { click, doEval, elementExists, elementVisible, waitForElement } from "../rule-executors";
2
+ import AutoConsentCMPBase from "./base";
3
+
4
+ export default class Klaro extends AutoConsentCMPBase {
5
+
6
+ prehideSelectors = [".klaro"]
7
+ settingsOpen = false;
8
+
9
+ constructor() {
10
+ super("Klaro");
11
+ }
12
+
13
+ get hasSelfTest(): boolean {
14
+ return true;
15
+ }
16
+
17
+ get isIntermediate(): boolean {
18
+ return false;
19
+ }
20
+
21
+ async detectCmp() {
22
+ if (elementExists('.klaro > .cookie-modal')) {
23
+ this.settingsOpen = true;
24
+ return true;
25
+ }
26
+ return elementExists(".klaro > .cookie-notice");
27
+ }
28
+
29
+ async detectPopup() {
30
+ return elementVisible(".klaro > .cookie-notice,.klaro > .cookie-modal", 'any');
31
+ }
32
+
33
+ async optOut() {
34
+ if (click('.klaro .cn-decline')) {
35
+ return true;
36
+ }
37
+
38
+ if (!this.settingsOpen) {
39
+ click('.klaro .cn-learn-more');
40
+ await waitForElement('.klaro > .cookie-modal', 2000);
41
+ this.settingsOpen = true;
42
+ }
43
+
44
+ if (click('.klaro .cn-decline')) {
45
+ return true;
46
+ }
47
+
48
+ click('.cm-purpose:not(.cm-toggle-all) > input:not(.half-checked)', true);
49
+ return click('.cm-btn-accept');
50
+ }
51
+
52
+ async optIn() {
53
+ if (click('.klaro .cm-btn-accept-all')) {
54
+ return true;
55
+ }
56
+ if (this.settingsOpen) {
57
+ click('.cm-purpose:not(.cm-toggle-all) > input.half-checked', true);
58
+ return click('.cm-btn-accept');
59
+ }
60
+ return click('.klaro .cookie-notice .cm-btn-success');
61
+ }
62
+
63
+ async test() {
64
+ return await doEval('klaro.getManager().config.services.every(c => c.required || !klaro.getManager().consents[c.name])');
65
+ }
66
+ }
@@ -32,7 +32,7 @@ export default class SourcePoint extends AutoConsentCMPBase {
32
32
  return true;
33
33
  }
34
34
  return (url.pathname === '/index.html' || url.pathname === '/privacy-manager/index.html')
35
- && url.searchParams.has('message_id') && url.searchParams.has('requestUUID');
35
+ && (url.searchParams.has('message_id') || url.searchParams.has('requestUUID') || url.searchParams.has('consentUUID'));
36
36
  }
37
37
 
38
38
  async detectPopup() {
@@ -57,16 +57,17 @@ export default class SourcePoint extends AutoConsentCMPBase {
57
57
 
58
58
  async optOut() {
59
59
  if (!this.isManagerOpen()) {
60
- const actionable = await waitForElement('button.sp_choice_type_12,button.sp_choice_type_13');
60
+ const actionable = await waitForElement('.sp_choice_type_12,.sp_choice_type_13');
61
61
  if (!actionable) {
62
62
  return false;
63
63
  }
64
- if (!elementExists("button.sp_choice_type_12")) {
64
+ if (!elementExists(".sp_choice_type_12")) {
65
65
  // do not sell button
66
- return click("button.sp_choice_type_13");
66
+ return click(".sp_choice_type_13");
67
67
  }
68
68
 
69
- click("button.sp_choice_type_12");
69
+ click(".sp_choice_type_12");
70
+ // the page may navigate at this point but that's okay
70
71
  await waitFor(
71
72
  () => location.pathname === "/privacy-manager/index.html",
72
73
  200,
@@ -88,9 +89,8 @@ export default class SourcePoint extends AutoConsentCMPBase {
88
89
  await wait(1000);
89
90
  return click(rejectSelector1);
90
91
  } else if (path === 1) {
91
- return click(rejectSelector2);
92
+ click(rejectSelector2);
92
93
  } else if (path === 2) {
93
- // TODO: check if this is still working
94
94
  await waitForElement('.pm-features', 10000);
95
95
  click('.checked > span', true);
96
96
 
@@ -99,6 +99,7 @@ export default class SourcePoint extends AutoConsentCMPBase {
99
99
  } catch (e) {
100
100
  enableLogs && console.warn(e);
101
101
  }
102
- return click('.sp_choice_type_SAVE_AND_EXIT');
102
+ click('.sp_choice_type_SAVE_AND_EXIT');
103
+ return true;
103
104
  }
104
105
  }
@@ -35,6 +35,11 @@ export default class SourcePoint extends AutoConsentCMPBase {
35
35
  }
36
36
 
37
37
  async optOut() {
38
+ // unblock scrolling
39
+ const container = document.querySelector('.sp-message-open');
40
+ if (container) {
41
+ container.classList.remove('sp-message-open');
42
+ }
38
43
  return true;
39
44
  }
40
45
 
@@ -0,0 +1,70 @@
1
+ import { elementExists, elementVisible, wait, waitForElement, waitForThenClick } from "../rule-executors";
2
+ import AutoConsentCMPBase from "./base";
3
+
4
+ export default class Uniconsent extends AutoConsentCMPBase {
5
+ constructor() {
6
+ super("Uniconsent");
7
+ }
8
+
9
+ get prehideSelectors(): string[] {
10
+ return ['.unic'];
11
+ }
12
+
13
+ get hasSelfTest(): boolean {
14
+ return true;
15
+ }
16
+
17
+ get isIntermediate(): boolean {
18
+ return false;
19
+ }
20
+
21
+ async detectCmp() {
22
+ return elementExists(".unic .unic-box,.unic .unic-bar");
23
+ }
24
+
25
+ async detectPopup() {
26
+ return elementVisible(".unic .unic-box,.unic .unic-bar", 'any');
27
+ }
28
+
29
+ async optOut() {
30
+ await waitForElement(".unic button", 1000);
31
+ document.querySelectorAll(".unic button").forEach((button: HTMLButtonElement) => {
32
+ const text = button.textContent;
33
+ if (text.includes('Manage Options') || text.includes('Optionen verwalten')) {
34
+ button.click();
35
+ }
36
+ });
37
+
38
+ if (await waitForElement('.unic input[type=checkbox]', 1000)) {
39
+ await waitForElement('.unic button', 1000);
40
+
41
+ document.querySelectorAll('.unic input[type=checkbox]').forEach((c: HTMLInputElement) => {
42
+ if (c.checked) {
43
+ c.click();
44
+ }
45
+ });
46
+
47
+ for (const b of <NodeListOf<HTMLButtonElement>>document.querySelectorAll('.unic button')) {
48
+ const text = b.textContent;
49
+ for (const pattern of ['Confirm Choices', 'Save Choices', 'Auswahl speichern']) {
50
+ if (text.includes(pattern)) {
51
+ b.click();
52
+ return true;
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ return false;
59
+ }
60
+
61
+ async optIn() {
62
+ return waitForThenClick(".unic #unic-agree");
63
+ }
64
+
65
+ async test() {
66
+ await wait(1000);
67
+ const res = elementExists(".unic .unic-box,.unic .unic-bar");
68
+ return !res;
69
+ }
70
+ }
package/lib/rules.ts CHANGED
@@ -28,7 +28,8 @@ export type AutoConsentRuleStep = { optional?: boolean } & Partial<
28
28
  Partial<WaitForThenClickRule> &
29
29
  Partial<WaitRule> &
30
30
  Partial<UrlRule> &
31
- Partial<HideRule>;
31
+ Partial<HideRule> &
32
+ Partial<IfRule>;
32
33
 
33
34
  export type ElementExistsRule = {
34
35
  exists: string;
@@ -80,3 +81,9 @@ export type HideRule = {
80
81
  hide: string[];
81
82
  method?: HideMethod;
82
83
  };
84
+
85
+ export type IfRule = {
86
+ if: Partial<ElementExistsRule> & Partial<ElementVisibleRule>;
87
+ then: AutoConsentRuleStep[];
88
+ else?: AutoConsentRuleStep[];
89
+ };
package/lib/web.ts CHANGED
@@ -107,16 +107,36 @@ export default class AutoConsent {
107
107
 
108
108
  async _start() {
109
109
  enableLogs && console.log(`Detecting CMPs on ${window.location.href}`)
110
- const cmp = await this.findCmp(this.config.detectRetries);
111
- if (cmp) {
112
- enableLogs && console.log("detected CMP:", cmp.name, window.location.href);
113
- this.sendContentMessage({
114
- type: 'cmpDetected',
115
- url: location.href,
116
- cmp: cmp.name,
117
- }); // notify the browser
118
- const isOpen = await this.waitForPopup(cmp);
119
- if (!isOpen) {
110
+ const cmps = await this.findCmp(this.config.detectRetries);
111
+ if (cmps.length > 0) {
112
+ const popupLookups: Promise<boolean>[] = [];
113
+ for (const cmp of cmps) {
114
+ enableLogs && console.log("detected CMP:", cmp.name, window.location.href);
115
+ this.sendContentMessage({
116
+ type: 'cmpDetected',
117
+ url: location.href,
118
+ cmp: cmp.name,
119
+ }); // notify the browser
120
+ popupLookups.push(this.waitForPopup(cmp).then((isOpen) => {
121
+ if (isOpen) {
122
+ if (!this.foundCmp) {
123
+ this.foundCmp = cmp;
124
+ }
125
+ this.sendContentMessage({
126
+ type: 'popupFound',
127
+ cmp: cmp.name,
128
+ url: location.href,
129
+ }); // notify the browser
130
+ return true;
131
+ } else {
132
+ return Promise.reject(`${cmp.name} popup not found`);
133
+ }
134
+ }));
135
+ }
136
+
137
+ const somethingOpen = await Promise.any(popupLookups).catch(() => false);
138
+
139
+ if (!somethingOpen) {
120
140
  enableLogs && console.log('no popup found');
121
141
  if (this.config.enablePrehide) {
122
142
  undoPrehide();
@@ -124,13 +144,6 @@ export default class AutoConsent {
124
144
  return false;
125
145
  }
126
146
 
127
- this.foundCmp = cmp;
128
- this.sendContentMessage({
129
- type: 'popupFound',
130
- cmp: cmp.name,
131
- url: location.href,
132
- }); // notify the browser
133
-
134
147
  if (this.config.autoAction === 'optOut') {
135
148
  return await this.doOptOut();
136
149
  } else if (this.config.autoAction === 'optIn') {
@@ -148,8 +161,7 @@ export default class AutoConsent {
148
161
  }
149
162
  }
150
163
 
151
- async findCmp(retries: number): Promise<AutoCMP> {
152
- let foundCmp: AutoCMP = null;
164
+ async findCmp(retries: number): Promise<AutoCMP[]> {
153
165
  const allFoundCmps: AutoCMP[] = [];
154
166
 
155
167
  for (const cmp of this.rules) {
@@ -161,9 +173,6 @@ export default class AutoConsent {
161
173
  if (result) {
162
174
  enableLogs && console.log(`Found CMP: ${cmp.name}`);
163
175
  allFoundCmps.push(cmp);
164
- if (!foundCmp) {
165
- foundCmp = cmp;
166
- }
167
176
  }
168
177
  } catch (e) {
169
178
  enableLogs && console.warn(`error detecting ${cmp.name}`, e);
@@ -182,7 +191,7 @@ export default class AutoConsent {
182
191
  });
183
192
  }
184
193
 
185
- if (!foundCmp && retries > 0) {
194
+ if (allFoundCmps.length === 0 && retries > 0) {
186
195
  return new Promise((resolve) => {
187
196
  setTimeout(async () => {
188
197
  const result = this.findCmp(retries - 1);
@@ -191,7 +200,7 @@ export default class AutoConsent {
191
200
  });
192
201
  }
193
202
 
194
- return foundCmp;
203
+ return allFoundCmps;
195
204
  }
196
205
 
197
206
  async doOptOut(): Promise<boolean> {
@@ -236,6 +245,7 @@ export default class AutoConsent {
236
245
  } else {
237
246
  enableLogs && console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`);
238
247
  optInResult = await this.foundCmp.optIn();
248
+ enableLogs && console.log(`${this.foundCmp.name}: opt in result ${optInResult}`);
239
249
  }
240
250
 
241
251
  if (this.config.enablePrehide) {
@@ -286,7 +296,7 @@ export default class AutoConsent {
286
296
  if (!isOpen && retries > 0) {
287
297
  return new Promise((resolve) => setTimeout(() => resolve(this.waitForPopup(cmp, retries - 1, interval)), interval));
288
298
  }
289
- enableLogs && console.log(`popup is ${isOpen ? 'open' : 'not open'}`);
299
+ enableLogs && console.log(cmp.name, `popup is ${isOpen ? 'open' : 'not open'}`);
290
300
  return isOpen;
291
301
  }
292
302
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "",
5
5
  "main": "dist/autoconsent.cjs.js",
6
6
  "module": "dist/autoconsent.esm.js",
package/readme.md CHANGED
@@ -106,6 +106,17 @@ Returns true if elements returned from `document.querySelectorAll(selector)` are
106
106
  ```
107
107
  Waits until `selector` exists in the page. After `timeout` ms the step fails.
108
108
 
109
+ ### Wait for visibility
110
+
111
+ ```json
112
+ {
113
+ "waitForVisible": "selector",
114
+ "timeout": 1000,
115
+ "check": "any" | "all" | "none"
116
+ }
117
+ ```
118
+ Waits until element is visible in the page. After `timeout` ms the step fails.
119
+
109
120
  ### Click an element
110
121
  ```json
111
122
  {
@@ -152,6 +163,24 @@ Hide the elements matched by the selectors. `method` defines how elements are hi
152
163
  Evaluates `code` in the context of the page. The rule is considered successful if it *evaluates to a truthy value*.
153
164
  Eval rules are not 100% reliable because they can be blocked by a CSP policy on the page. Therefore, they should only be used as a last resort when none of the other rules are sufficient.
154
165
 
166
+ ### Conditionals
167
+
168
+ ```json
169
+ {
170
+ "if": { "exists": "selector" },
171
+ "then": [
172
+ { "click": ".button1" },
173
+ { "click": ".button3" }
174
+ ],
175
+ "else": [
176
+ { "click": ".button2" }
177
+ ]
178
+ }
179
+ ```
180
+
181
+ Allows to do conditional branching in JSON rules. The `if` section can contain either a "visible" or "exists" rule. Depending on the result of that rule, `then` or `else` sequences will be executed. `else` section is optional.
182
+ The "if" rule is considered successful as long as all rules inside the chosen branch are successful. The other branch, as well as the result of the condition itself, do not affect the result of the whole rule.
183
+
155
184
  ### Optional actions
156
185
 
157
186
  Any rule can include the `"optional": true` to ignore failure.
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "Adroll",
3
+ "prehideSelectors": ["#adroll_consent_container"],
4
+ "detectCmp": [{ "exists": "#adroll_consent_container" }],
5
+ "detectPopup": [{ "visible": "#adroll_consent_container" }],
6
+ "optIn": [ { "waitForThenClick": "#adroll_consent_accept" } ],
7
+ "optOut": [ { "waitForThenClick": "#adroll_consent_reject" } ],
8
+ "test": [
9
+ { "eval": "!document.cookie.includes('__adroll_fpc')" }
10
+ ]
11
+ }
@@ -9,7 +9,12 @@
9
9
  "click": "button[data-id=awsccc-cb-btn-customize]"
10
10
  },
11
11
  {
12
- "eval": "Array.from(document.querySelectorAll('input[aria-checked=true')).forEach(e => e.click()) || true"
12
+ "waitFor": "input[aria-checked]"
13
+ },
14
+ {
15
+ "click": "input[aria-checked=true]",
16
+ "all": true,
17
+ "optional": true
13
18
  },
14
19
  {
15
20
  "click": "button[data-id=awsccc-cs-btn-save]"
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "axeptio",
3
+ "prehideSelectors": [".axeptio_widget"],
4
+ "detectCmp":
5
+ [
6
+ {
7
+ "exists": ".axeptio_widget"
8
+ }
9
+ ],
10
+ "detectPopup":
11
+ [
12
+ {
13
+ "visible": ".axeptio_widget"
14
+ }
15
+ ],
16
+ "optIn":
17
+ [
18
+ {
19
+ "waitFor": ".axeptio-widget--open"
20
+ },
21
+ {
22
+ "click": "button#axeptio_btn_acceptAll"
23
+ }
24
+ ],
25
+ "optOut":
26
+ [
27
+
28
+ {
29
+ "waitFor": ".axeptio-widget--open"
30
+ },
31
+ {
32
+ "click": "button#axeptio_btn_dismiss"
33
+ }
34
+ ],
35
+ "test":
36
+ [
37
+ {
38
+ "eval": "document.cookie.includes('axeptio_authorized_vendors=%2C%2C')"
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "click.io",
3
+ "prehideSelectors": ["#cl-consent"],
4
+ "detectCmp": [{ "exists": "#cl-consent" }],
5
+ "detectPopup": [{ "visible": "#cl-consent" }],
6
+ "optIn": [ { "waitForThenClick": "#cl-consent [data-role=\"b_agree\"]" } ],
7
+ "optOut": [
8
+ { "waitFor": "#cl-consent [data-role=\"b_options\"]" },
9
+ { "wait": 500 },
10
+ { "click": "#cl-consent [data-role=\"b_options\"]" },
11
+ { "waitFor": ".cl-consent-popup.cl-consent-visible [data-role=\"alloff\"]" },
12
+ { "click": ".cl-consent-popup.cl-consent-visible [data-role=\"alloff\"]", "all": true },
13
+ { "click": "[data-role=\"b_save\"]" }
14
+ ],
15
+ "test": [
16
+ { "eval": "document.cookie.includes('__lxG__consent__v2_daisybit=')", "comment": "TODO: this only checks if we interacted at all" }
17
+ ]
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "Complianz banner",
3
+ "prehideSelectors": ["#cmplz-cookiebanner-container"],
4
+ "detectCmp": [{ "exists": "#cmplz-cookiebanner-container .cmplz-cookiebanner" }],
5
+ "detectPopup": [{ "visible": "#cmplz-cookiebanner-container .cmplz-cookiebanner", "check": "any" }],
6
+ "optIn": [
7
+ { "waitForThenClick": ".cmplz-cookiebanner .cmplz-accept" }
8
+ ],
9
+ "optOut": [
10
+ { "waitForThenClick": ".cmplz-cookiebanner .cmplz-deny" }
11
+ ],
12
+ "test": [ { "eval": "document.cookie.includes('cmplz_banner-status=dismissed')" } ]
13
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "Complianz categories",
3
+ "prehideSelectors": [".cc-type-categories[aria-describedby=\"cookieconsent:desc\"]"],
4
+ "detectCmp": [{ "exists": ".cc-type-categories[aria-describedby=\"cookieconsent:desc\"]" }],
5
+ "detectPopup": [{ "visible": ".cc-type-categories[aria-describedby=\"cookieconsent:desc\"]" }],
6
+ "optIn": [
7
+ { "click": ".cc-accept-all", "optional": true },
8
+ { "click": ".cc-allow", "optional": true },
9
+ { "click": ".cc-dismiss", "optional": true }
10
+ ],
11
+ "optOut": [
12
+ { "click": ".cc-dismiss" }
13
+ ],
14
+ "test": [
15
+ { "eval": "!!document.cookie.match(/cmplz_[^=]+=deny/)" }
16
+ ]
17
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "Complianz notice",
3
+ "prehideSelectors": [".cc-type-info[aria-describedby=\"cookieconsent:desc\"]"],
4
+ "detectCmp": [{ "exists": ".cc-type-info[aria-describedby=\"cookieconsent:desc\"]" }],
5
+ "detectPopup": [{ "visible": ".cc-type-info[aria-describedby=\"cookieconsent:desc\"]" }],
6
+ "optIn": [
7
+ { "click": ".cc-accept-all", "optional": true },
8
+ { "click": ".cc-allow", "optional": true },
9
+ { "click": ".cc-dismiss", "optional": true }
10
+ ],
11
+ "optOut": [
12
+ { "hide": ["[aria-describedby=\"cookieconsent:desc\"]"] }
13
+ ]
14
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "Complianz optin",
3
+ "prehideSelectors": [".cc-type-opt-in[aria-describedby=\"cookieconsent:desc\"]"],
4
+ "detectCmp": [{ "exists": ".cc-type-opt-in[aria-describedby=\"cookieconsent:desc\"]" }],
5
+ "detectPopup": [{ "visible": ".cc-type-opt-in[aria-describedby=\"cookieconsent:desc\"]" }],
6
+ "optIn": [
7
+ { "click": ".cc-accept-all", "optional": true },
8
+ { "click": ".cc-allow", "optional": true },
9
+ { "click": ".cc-dismiss", "optional": true }
10
+ ],
11
+ "optOut": [
12
+ { "click": ".cc-settings" },
13
+ { "waitForVisible": "[aria-label=\"cookies preferences popup\"]" },
14
+ { "click": "[aria-label=\"cookies preferences popup\"] input[type=checkbox]:not([disabled])", "all": true },
15
+ { "click": "[aria-label=\"cookies preferences popup\"] [aria-label=\"Accept Selected\"]" }
16
+ ],
17
+ "test": [
18
+ { "eval": "!!document.cookie.match(/cookieconsent_preferences_disabled=[^;]+/)" }
19
+ ]
20
+ }