@duckduckgo/autoconsent 8.1.0 → 9.0.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 (40) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/build.sh +1 -0
  3. package/dist/addon-firefox/background.bundle.js +60 -43
  4. package/dist/addon-firefox/content.bundle.js +484 -382
  5. package/dist/addon-firefox/manifest.json +1 -1
  6. package/dist/addon-mv3/background.bundle.js +60 -43
  7. package/dist/addon-mv3/content.bundle.js +484 -382
  8. package/dist/addon-mv3/manifest.json +1 -1
  9. package/dist/addon-mv3/popup.bundle.js +71 -33
  10. package/dist/addon-mv3/popup.html +28 -0
  11. package/dist/autoconsent.cjs.js +484 -382
  12. package/dist/autoconsent.esm.js +484 -382
  13. package/dist/autoconsent.playwright.js +1 -1
  14. package/dist/autoconsent.unit.js +10370 -0
  15. package/lib/cmps/airbnb.ts +5 -6
  16. package/lib/cmps/base.ts +97 -41
  17. package/lib/cmps/consentmanager.ts +13 -14
  18. package/lib/cmps/conversant.ts +8 -9
  19. package/lib/cmps/cookiebot.ts +8 -9
  20. package/lib/cmps/evidon.ts +7 -8
  21. package/lib/cmps/klaro.ts +13 -14
  22. package/lib/cmps/onetrust.ts +15 -16
  23. package/lib/cmps/sourcepoint-frame.ts +25 -26
  24. package/lib/cmps/tiktok.ts +7 -7
  25. package/lib/cmps/trustarc-frame.ts +27 -28
  26. package/lib/cmps/trustarc-top.ts +5 -6
  27. package/lib/cmps/uniconsent.ts +9 -10
  28. package/lib/dom-actions.ts +145 -0
  29. package/lib/eval-snippets.ts +17 -2
  30. package/lib/types.ts +24 -1
  31. package/lib/utils.ts +32 -1
  32. package/lib/web.ts +46 -34
  33. package/package.json +4 -4
  34. package/playwright/runner.ts +11 -3
  35. package/playwright/unit.ts +15 -0
  36. package/readme.md +1 -1
  37. package/tests/{rule-executors.spec.ts → dom-actions.spec.ts} +20 -21
  38. package/tests/klaro.spec.ts +1 -0
  39. package/lib/config.ts +0 -2
  40. package/lib/rule-executors.ts +0 -147
package/lib/web.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { MessageSender, AutoCMP, RuleBundle, Config, ConsentState } from './types';
2
2
  import { ConsentOMaticCMP, ConsentOMaticConfig } from './cmps/consentomatic';
3
3
  import { AutoConsentCMPRule } from './rules';
4
- import { enableLogs } from './config';
5
4
  import { BackgroundMessage, InitMessage } from './messages';
6
- import { prehide, undoPrehide, wait } from './rule-executors';
7
5
  import { evalState, resolveEval } from './eval-handler';
8
6
  import { getRandomID } from './random';
9
7
  import { dynamicCMPs } from './cmps/all';
10
8
  import { AutoConsentCMP } from './cmps/base';
9
+ import { DomActions } from './dom-actions';
10
+ import { normalizeConfig } from './utils';
11
11
 
12
12
  function filterCMPs(rules: AutoCMP[], config: Config) {
13
13
  return rules.filter((cmp) => {
@@ -32,14 +32,14 @@ export default class AutoConsent {
32
32
  detectedPopups: [],
33
33
  selfTest: null,
34
34
  };
35
+ domActions: DomActions;
35
36
  protected sendContentMessage: MessageSender;
36
37
 
37
- constructor(sendContentMessage: MessageSender, config: Config = null, declarativeRules: RuleBundle = null) {
38
+ constructor(sendContentMessage: MessageSender, config: Partial<Config> = null, declarativeRules: RuleBundle = null) {
38
39
  evalState.sendContentMessage = sendContentMessage;
39
40
  this.sendContentMessage = sendContentMessage;
40
41
  this.rules = [];
41
42
 
42
- enableLogs && console.log('autoconsent init', window.location.href);
43
43
  this.updateState({ lifecycle: 'loading' });
44
44
 
45
45
  this.addDynamicRules();
@@ -56,12 +56,15 @@ export default class AutoConsent {
56
56
  sendContentMessage(initMsg);
57
57
  this.updateState({ lifecycle: 'waitingForInitResponse' });
58
58
  }
59
+ this.domActions = new DomActions(this);
59
60
  }
60
61
 
61
- initialize(config: Config, declarativeRules: RuleBundle) {
62
- this.config = config;
63
- if (!config.enabled) {
64
- enableLogs && console.log("autoconsent is disabled");
62
+ initialize(config: Partial<Config>, declarativeRules: RuleBundle) {
63
+ const normalizedConfig = normalizeConfig(config);
64
+ normalizedConfig.logs.lifecycle && console.log('autoconsent init', window.location.href);
65
+ this.config = normalizedConfig;
66
+ if (!normalizedConfig.enabled) {
67
+ normalizedConfig.logs.lifecycle && console.log("autoconsent is disabled");
65
68
  return;
66
69
  }
67
70
 
@@ -69,7 +72,7 @@ export default class AutoConsent {
69
72
  this.parseDeclarativeRules(declarativeRules);
70
73
  }
71
74
 
72
- this.rules = filterCMPs(this.rules, config);
75
+ this.rules = filterCMPs(this.rules, normalizedConfig);
73
76
 
74
77
  if (config.enablePrehide) {
75
78
  if (document.documentElement) {
@@ -130,12 +133,13 @@ export default class AutoConsent {
130
133
  }
131
134
 
132
135
  async _start() {
133
- enableLogs && console.log(`Detecting CMPs on ${window.location.href}`);
136
+ const logsConfig = this.config.logs;
137
+ logsConfig.lifecycle && console.log(`Detecting CMPs on ${window.location.href}`);
134
138
  this.updateState({ lifecycle: 'started' });
135
139
  const foundCmps = await this.findCmp(this.config.detectRetries);
136
140
  this.updateState({ detectedCmps: foundCmps.map(c => c.name) });
137
141
  if (foundCmps.length === 0) {
138
- enableLogs && console.log("no CMP found", location.href);
142
+ logsConfig.lifecycle && console.log("no CMP found", location.href);
139
143
  if (this.config.enablePrehide) {
140
144
  this.undoPrehide();
141
145
  }
@@ -151,7 +155,7 @@ export default class AutoConsent {
151
155
  }
152
156
 
153
157
  if (foundPopups.length === 0) {
154
- enableLogs && console.log('no popup found');
158
+ logsConfig.lifecycle && console.log('no popup found');
155
159
  if (this.config.enablePrehide) {
156
160
  this.undoPrehide();
157
161
  }
@@ -168,7 +172,7 @@ export default class AutoConsent {
168
172
  msg: `Found multiple CMPs, check the detection rules.`,
169
173
  cmps: foundPopups.map((cmp) => cmp.name),
170
174
  };
171
- enableLogs && console.warn(errorDetails.msg, errorDetails.cmps);
175
+ logsConfig.errors && console.warn(errorDetails.msg, errorDetails.cmps);
172
176
  this.sendContentMessage({
173
177
  type: 'autoconsentError',
174
178
  details: errorDetails,
@@ -182,12 +186,13 @@ export default class AutoConsent {
182
186
  } else if (this.config.autoAction === 'optIn') {
183
187
  return await this.doOptIn();
184
188
  } else {
185
- enableLogs && console.log("waiting for opt-out signal...", location.href);
189
+ logsConfig.lifecycle && console.log("waiting for opt-out signal...", location.href);
186
190
  return true;
187
191
  }
188
192
  }
189
193
 
190
194
  async findCmp(retries: number): Promise<AutoCMP[]> {
195
+ const logsConfig = this.config.logs;
191
196
  this.updateState({ findCmpAttempts: this.state.findCmpAttempts + 1 })
192
197
  const foundCMPs: AutoCMP[] = [];
193
198
 
@@ -198,7 +203,7 @@ export default class AutoConsent {
198
203
  }
199
204
  const result = await cmp.detectCmp();
200
205
  if (result) {
201
- enableLogs && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
206
+ logsConfig.lifecycle && console.log(`Found CMP: ${cmp.name} ${window.location.href}`);
202
207
  this.sendContentMessage({
203
208
  type: 'cmpDetected',
204
209
  url: location.href,
@@ -207,12 +212,12 @@ export default class AutoConsent {
207
212
  foundCMPs.push(cmp);
208
213
  }
209
214
  } catch (e) {
210
- enableLogs && console.warn(`error detecting ${cmp.name}`, e);
215
+ logsConfig.errors && console.warn(`error detecting ${cmp.name}`, e);
211
216
  }
212
217
  }
213
218
 
214
219
  if (foundCMPs.length === 0 && retries > 0) {
215
- await wait(500);
220
+ await this.domActions.wait(500);
216
221
  return this.findCmp(retries - 1);
217
222
  }
218
223
 
@@ -220,6 +225,7 @@ export default class AutoConsent {
220
225
  }
221
226
 
222
227
  async detectPopups(cmps: AutoCMP[]): Promise<AutoCMP[]> {
228
+ const logsConfig = this.config.logs;
223
229
  const result: AutoCMP[] = [];
224
230
  const popupLookups = cmps.map((cmp) => this.waitForPopup(cmp).then((isOpen) => {
225
231
  if (isOpen) {
@@ -232,7 +238,7 @@ export default class AutoConsent {
232
238
  result.push(cmp);
233
239
  }
234
240
  }).catch((e) => {
235
- enableLogs && console.warn(`error waiting for a popup for ${cmp.name}`, e);
241
+ logsConfig.errors && console.warn(`error waiting for a popup for ${cmp.name}`, e);
236
242
  return null
237
243
  }));
238
244
  await Promise.all(popupLookups);
@@ -240,15 +246,16 @@ export default class AutoConsent {
240
246
  }
241
247
 
242
248
  async doOptOut(): Promise<boolean> {
249
+ const logsConfig = this.config.logs;
243
250
  this.updateState({ lifecycle: 'runningOptOut' })
244
251
  let optOutResult;
245
252
  if (!this.foundCmp) {
246
- enableLogs && console.log('no CMP to opt out');
253
+ logsConfig.errors && console.log('no CMP to opt out');
247
254
  optOutResult = false;
248
255
  } else {
249
- enableLogs && console.log(`CMP ${this.foundCmp.name}: opt out on ${window.location.href}`);
256
+ logsConfig.lifecycle && console.log(`CMP ${this.foundCmp.name}: opt out on ${window.location.href}`);
250
257
  optOutResult = await this.foundCmp.optOut();
251
- enableLogs && console.log(`${this.foundCmp.name}: opt out result ${optOutResult}`);
258
+ logsConfig.lifecycle && console.log(`${this.foundCmp.name}: opt out result ${optOutResult}`);
252
259
  }
253
260
 
254
261
  if (this.config.enablePrehide) {
@@ -279,15 +286,16 @@ export default class AutoConsent {
279
286
  }
280
287
 
281
288
  async doOptIn(): Promise<boolean> {
289
+ const logsConfig = this.config.logs;
282
290
  this.updateState({ lifecycle: 'runningOptIn' })
283
291
  let optInResult;
284
292
  if (!this.foundCmp) {
285
- enableLogs && console.log('no CMP to opt in');
293
+ logsConfig.errors && console.log('no CMP to opt in');
286
294
  optInResult = false;
287
295
  } else {
288
- enableLogs && console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`);
296
+ logsConfig.lifecycle && console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`);
289
297
  optInResult = await this.foundCmp.optIn();
290
- enableLogs && console.log(`${this.foundCmp.name}: opt in result ${optInResult}`);
298
+ logsConfig.lifecycle && console.log(`${this.foundCmp.name}: opt in result ${optInResult}`);
291
299
  }
292
300
 
293
301
  if (this.config.enablePrehide) {
@@ -318,12 +326,13 @@ export default class AutoConsent {
318
326
  }
319
327
 
320
328
  async doSelfTest(): Promise<boolean> {
329
+ const logsConfig = this.config.logs;
321
330
  let selfTestResult;
322
331
  if (!this.foundCmp) {
323
- enableLogs && console.log('no CMP to self test');
332
+ logsConfig.errors && console.log('no CMP to self test');
324
333
  selfTestResult = false;
325
334
  } else {
326
- enableLogs && console.log(`CMP ${this.foundCmp.name}: self-test on ${window.location.href}`);
335
+ logsConfig.lifecycle && console.log(`CMP ${this.foundCmp.name}: self-test on ${window.location.href}`);
327
336
  selfTestResult = await this.foundCmp.test();
328
337
  }
329
338
 
@@ -338,20 +347,22 @@ export default class AutoConsent {
338
347
  }
339
348
 
340
349
  async waitForPopup(cmp: AutoCMP, retries = 5, interval = 500): Promise<boolean> {
341
- enableLogs && console.log('checking if popup is open...', cmp.name);
350
+ const logsConfig = this.config.logs;
351
+ logsConfig.lifecycle && console.log('checking if popup is open...', cmp.name);
342
352
  const isOpen = await cmp.detectPopup().catch((e) => {
343
- enableLogs && console.warn(`error detecting popup for ${cmp.name}`, e);
353
+ logsConfig.errors && console.warn(`error detecting popup for ${cmp.name}`, e);
344
354
  return false;
345
355
  }); // ignore possible errors in one-time popup detection
346
356
  if (!isOpen && retries > 0) {
347
- await wait(interval);
357
+ await this.domActions.wait(interval);
348
358
  return this.waitForPopup(cmp, retries - 1, interval);
349
359
  }
350
- enableLogs && console.log(cmp.name, `popup is ${isOpen ? 'open' : 'not open'}`);
360
+ logsConfig.lifecycle && console.log(cmp.name, `popup is ${isOpen ? 'open' : 'not open'}`);
351
361
  return isOpen;
352
362
  }
353
363
 
354
364
  prehideElements(): boolean {
365
+ const logsConfig = this.config.logs;
355
366
  // hide rules not specific to a single CMP rule
356
367
  const globalHidden = [
357
368
  "#didomi-popup,.didomi-popup-container,.didomi-popup-notice,.didomi-consent-popup-preferences,#didomi-notice,.didomi-popup-backdrop,.didomi-screen-medium",
@@ -372,16 +383,16 @@ export default class AutoConsent {
372
383
  this.state.prehideOn &&
373
384
  !['runningOptOut', 'runningOptIn'].includes(this.state.lifecycle)
374
385
  ) {
375
- enableLogs && console.log('Process is taking too long, unhiding elements');
386
+ logsConfig.lifecycle && console.log('Process is taking too long, unhiding elements');
376
387
  this.undoPrehide();
377
388
  }
378
389
  }, this.config.prehideTimeout || 2000);
379
- return prehide(selectors.join(','));
390
+ return this.domActions.prehide(selectors.join(','));
380
391
  }
381
392
 
382
393
  undoPrehide(): boolean {
383
394
  this.updateState({ prehideOn: false })
384
- return undoPrehide();
395
+ return this.domActions.undoPrehide();
385
396
  }
386
397
 
387
398
  updateState(change: Partial<ConsentState>) {
@@ -396,7 +407,8 @@ export default class AutoConsent {
396
407
  }
397
408
 
398
409
  async receiveMessageCallback(message: BackgroundMessage) {
399
- if (enableLogs && !['evalResp', 'report'].includes(message.type) /* evals are noisy */) {
410
+ const logsConfig = this.config?.logs;
411
+ if (logsConfig?.messages) {
400
412
  console.log('received from background', message, window.location.href);
401
413
  }
402
414
  switch (message.type) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "8.1.0",
3
+ "version": "9.0.0",
4
4
  "description": "",
5
5
  "main": "dist/autoconsent.cjs.js",
6
6
  "module": "dist/autoconsent.esm.js",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "clean": "rm -r dist",
12
- "lint": "eslint lib/ playwright/ tests/ rules/autoconsent/*.json",
12
+ "lint": "eslint addon/ lib/ playwright/ tests/ rules/autoconsent/*.json",
13
13
  "bundle": "./build.sh",
14
14
  "watch": "npm run prepublish && chokidar \"lib\" \"addon\" \"rules/autoconsent\" -c \"npm run prepublish\"",
15
15
  "create-rule": "node rules/create-rule.mjs",
@@ -17,7 +17,7 @@
17
17
  "test:webkit": "playwright test --project webkit",
18
18
  "test:firefox": "playwright test --project firefox",
19
19
  "test:chrome": "playwright test --project chrome",
20
- "test:lib": "playwright test tests/rule-executors.spec.ts",
20
+ "test:lib": "playwright test tests/dom-actions.spec.ts",
21
21
  "test:sample": "playwright test tests/_sample-test.spec.ts --project webkit",
22
22
  "fetch-fanboy-list": "wget https://www.fanboy.co.nz/fanboy-cookiemonster.txt",
23
23
  "fetch-site-list": "curl https://s3.amazonaws.com/cdn.cliqz.com/re-consent/supported_sites.txt > sites.txt",
@@ -34,7 +34,7 @@
34
34
  "@playwright/test": "^1.17.1",
35
35
  "@puppeteer/replay": "^2.11.0",
36
36
  "@types/chai": "^4.3.1",
37
- "@types/chrome": "^0.0.254",
37
+ "@types/chrome": "^0.0.256",
38
38
  "@types/mocha": "^10.0.1",
39
39
  "@typescript-eslint/eslint-plugin": "^6.12.0",
40
40
  "@typescript-eslint/parser": "^6.12.0",
@@ -3,9 +3,17 @@ import path from 'path';
3
3
  import { test, expect, Page, Frame } from "@playwright/test";
4
4
  import { waitFor } from "../lib/utils";
5
5
  import { ContentScriptMessage } from "../lib/messages";
6
- import { enableLogs } from "../lib/config";
7
6
  import { AutoAction } from "../lib/types";
8
7
 
8
+ const LOG_MESSAGES: ContentScriptMessage['type'][] = [
9
+ 'optInResult',
10
+ 'optOutResult',
11
+ 'autoconsentDone',
12
+ 'autoconsentError',
13
+ 'selfTestResult',
14
+ ];
15
+ const LOG_PAGE_LOGS = false;
16
+
9
17
  const testRegion = (process.env.REGION || "NA").trim();
10
18
  test.describe.configure({ mode: 'parallel' });
11
19
 
@@ -59,7 +67,7 @@ export function generateTest(
59
67
  test.skip();
60
68
  }
61
69
 
62
- enableLogs && page.on('console', async msg => {
70
+ LOG_PAGE_LOGS && page.on('console', async msg => {
63
71
  console.log(` page log:`, msg.text());
64
72
  });
65
73
  await page.exposeBinding("autoconsentSendMessage", messageCallback);
@@ -79,7 +87,7 @@ export function generateTest(
79
87
 
80
88
  let selfTestFrame: Frame = null;
81
89
  async function messageCallback({ frame }: { frame: Frame }, msg: ContentScriptMessage) {
82
- enableLogs && msg.type !== 'eval' && console.log(msg);
90
+ LOG_MESSAGES.includes(msg.type) && console.log(msg);
83
91
  received.push(msg);
84
92
  switch (msg.type) {
85
93
  case 'init': {
@@ -0,0 +1,15 @@
1
+ import Autoconsent from "../lib/web";
2
+ import { DomActions } from "../lib/dom-actions";
3
+ import * as rules from '../rules/rules.json';
4
+
5
+ declare global {
6
+ interface Window {
7
+ Autoconsent: typeof Autoconsent;
8
+ DomActions: typeof DomActions;
9
+ rules: typeof rules;
10
+ }
11
+ }
12
+
13
+ window.Autoconsent = Autoconsent;
14
+ window.DomActions = DomActions;
15
+ window.rules = rules;
package/readme.md CHANGED
@@ -56,7 +56,7 @@ By default, rules will be executed in all top-level documents. Some rules are de
56
56
  An autoconsent CMP rule can be written as either:
57
57
  * a JSON file adhering to the `AutoConsentCMPRule` type.
58
58
  * a class implementing the `AutoCMP` interface, or
59
- * common JSON rules are available as reusable functions in [rule-executors.ts](/lib/rule-executors.ts). You can also use existing class-based rules as reference.
59
+ * common JSON rules are available as reusable functions in [dom-actions.ts](/lib/dom-actions.ts). You can also use existing class-based rules as reference.
60
60
 
61
61
  In most cases the JSON syntax should be sufficient, unless some complex non-linear logic is required, in which case a class is required.
62
62
 
@@ -1,5 +1,11 @@
1
1
  import { test, expect } from "@playwright/test";
2
- import { click, elementSelector, querySelectorChain, querySingleReplySelector } from "../lib/rule-executors";
2
+ import { DomActions } from "../lib/dom-actions";
3
+
4
+ declare global {
5
+ interface Window {
6
+ domActions: DomActions;
7
+ }
8
+ }
3
9
 
4
10
  const mockPage = `<!DOCTYPE html>
5
11
  <html>
@@ -11,17 +17,6 @@ const mockPage = `<!DOCTYPE html>
11
17
  </body>
12
18
  </html>`
13
19
 
14
- function buildRuleEvalString(ruleFn: string, args: any[]) {
15
- // console.log(`(${ruleFn})(${args.map(s => JSON.stringify(s)).join(',')})`)
16
- return `(() => {
17
- const _config = { enableLogs: false };
18
- const querySelectorChain = ${querySelectorChain.toString()};
19
- const querySingleReplySelector = ${querySingleReplySelector.toString()};
20
- const elementSelector = ${elementSelector.toString()};
21
- return (${ruleFn})(${args.map(s => JSON.stringify(s)).join(',')})
22
- })()`
23
- }
24
-
25
20
  async function getButtonClickCount(page, id) {
26
21
  return await page.evaluate((id) => parseInt(document.querySelector(`#${id} > button`)?.innerHTML || '0', 10), id)
27
22
  }
@@ -31,40 +26,44 @@ test.beforeEach(async ({ page }) => {
31
26
  // set up a click counter on the buttons
32
27
  await page.evaluate(() => {
33
28
  document.querySelectorAll('button').forEach(el => el.addEventListener('click', () => el.innerText = JSON.stringify(parseInt(el.innerText, 10) + 1)))
34
- })
29
+ });
30
+ await page.addScriptTag({
31
+ path: 'dist/autoconsent.unit.js'
32
+ });
33
+ await page.evaluate('window.domActions = new window.DomActions({ config: { logs: { lifecycle: false, rulesteps: false, evals: false, errors: false, messages: false } } })');
35
34
  })
36
35
 
37
36
  test.describe("click", () => {
38
37
  test("click on button", async ({ page }) => {
39
- expect(await page.evaluate(buildRuleEvalString(click.toString(), ['#test']))).toBe(true);
40
- expect(await getButtonClickCount(page, 'first')).toEqual(1)
38
+ expect(await page.evaluate(() => window.domActions.click('#test'))).toBe(true);
39
+ expect(await getButtonClickCount(page, 'first')).toEqual(1);
41
40
  });
42
41
 
43
42
  test('click all', async ({page}) => {
44
- expect(await page.evaluate(buildRuleEvalString(click.toString(), ['button', true]))).toBe(true);
43
+ expect(await page.evaluate(() => window.domActions.click('button', true))).toBe(true);
45
44
  expect(await getButtonClickCount(page, 'first')).toEqual(1)
46
45
  expect(await getButtonClickCount(page, 'second')).toEqual(1)
47
46
  })
48
47
 
49
48
  test('click single with multiple matches', async ({page}) => {
50
- expect(await page.evaluate(buildRuleEvalString(click.toString(), ['button']))).toBe(true);
49
+ expect(await page.evaluate(() => window.domActions.click('button'))).toBe(true);
51
50
  expect(await getButtonClickCount(page, 'first')).toEqual(1)
52
51
  expect(await getButtonClickCount(page, 'second')).toEqual(0)
53
52
  })
54
53
 
55
54
  test('chained selector', async ({ page }) => {
56
- expect(await page.evaluate(buildRuleEvalString(click.toString(), [['#second', 'button']]))).toBe(true);
55
+ expect(await page.evaluate(() => window.domActions.click(['#second', 'button']))).toBe(true);
57
56
  expect(await getButtonClickCount(page, 'first')).toEqual(0)
58
57
  expect(await getButtonClickCount(page, 'second')).toEqual(1)
59
58
  })
60
59
 
61
60
  test('xpath selector', async ({ page }) => {
62
- expect(await page.evaluate(buildRuleEvalString(click.toString(), ['xpath///*[@id="second"]/button']))).toBe(true);
61
+ expect(await page.evaluate(() => window.domActions.click(['xpath///*[@id="second"]/button']))).toBe(true);
63
62
  expect(await page.evaluate(() => document.querySelector('#first > button')?.innerHTML)).toEqual('0')
64
63
  expect(await page.evaluate(() => document.querySelector('#second > button')?.innerHTML)).toEqual('1')
65
64
  })
66
65
 
67
- test('click open shadow dom element', async ({ page}) => {
66
+ test('click open shadow dom element', async ({ page }) => {
68
67
  const shadowRoot = await page.evaluateHandle(() => {
69
68
  const shadowDiv = document.createElement('div')
70
69
  shadowDiv.id = 'shadow'
@@ -76,7 +75,7 @@ test.describe("click", () => {
76
75
  shadowButton.addEventListener('click', () => shadowButton.innerText = JSON.stringify(parseInt(shadowButton.innerText, 10) + 1))
77
76
  return shadow
78
77
  })
79
- expect(await page.evaluate(buildRuleEvalString(click.toString(), [['#shadow', 'button']]))).toBe(true)
78
+ expect(await page.evaluate(() => window.domActions.click(['#shadow', 'button']))).toBe(true)
80
79
  expect(await page.evaluate(() => document.querySelector('#first > button')?.innerHTML)).toEqual('0')
81
80
  expect(await page.evaluate(() => document.querySelector('#second > button')?.innerHTML)).toEqual('0')
82
81
  expect(await page.evaluate((shadow) => shadow.querySelector('button')?.innerHTML || '0', shadowRoot)).toEqual('1')
@@ -5,5 +5,6 @@ generateCMPTests('Klaro', [
5
5
  'https://www.zeitraum-moebel.de/',
6
6
  'https://repisalud.isciii.es/',
7
7
  'https://www.innogames.com/',
8
+ 'https://www.lebenslauf.de/',
8
9
  // 'https://dspace.library.stonybrook.edu', // polyfills Promise so playwright doesn't work
9
10
  ]);
package/lib/config.ts DELETED
@@ -1,2 +0,0 @@
1
- // TODO: make this configurable through DevTools (and possibly toggleable per rule step type)
2
- export const enableLogs = false; // change this to enable debug logs
@@ -1,147 +0,0 @@
1
- import { enableLogs } from "./config";
2
- import { ElementSelector, HideMethod, VisibilityCheck } from "./rules";
3
- import { getStyleElement, hideElements, isElementVisible, waitFor } from "./utils";
4
-
5
- export function click(selector: ElementSelector, all = false): boolean {
6
- const elem = elementSelector(selector)
7
- enableLogs && console.log("[click]", selector, all, elem);
8
- if (elem.length > 0) {
9
- if (all) {
10
- elem.forEach((e) => e.click());
11
- } else {
12
- elem[0].click();
13
- }
14
- }
15
- return elem.length > 0;
16
- }
17
-
18
- export function elementExists(selector: ElementSelector): boolean {
19
- const exists = elementSelector(selector).length > 0;
20
- // enableLogs && console.log("[exists?]", selector, exists);
21
- return exists;
22
- }
23
-
24
- export function elementVisible(selector: ElementSelector, check: VisibilityCheck): boolean {
25
- const elem = elementSelector(selector);
26
- const results = new Array(elem.length);
27
- elem.forEach((e, i) => {
28
- // check for display: none
29
- results[i] = isElementVisible(e);
30
- });
31
- // enableLogs && console.log("[visible?]", selector, check, elem, results);
32
- if (check === "none") {
33
- return results.every(r => !r);
34
- } else if (results.length === 0) {
35
- return false;
36
- } else if (check === "any") {
37
- return results.some(r => r);
38
- }
39
- // all
40
- return results.every(r => r);
41
- }
42
-
43
- export function waitForElement(selector: ElementSelector, timeout = 10000): Promise<boolean> {
44
- const interval = 200;
45
- const times = Math.ceil((timeout) / interval);
46
- enableLogs && console.log("[waitForElement]", selector);
47
- return waitFor(
48
- () => elementSelector(selector).length > 0,
49
- times,
50
- interval
51
- );
52
- }
53
-
54
- export function waitForVisible(selector: ElementSelector, timeout = 10000, check: VisibilityCheck = 'any'): Promise<boolean> {
55
- const interval = 200;
56
- const times = Math.ceil((timeout) / interval);
57
- // enableLogs && console.log("[waitForVisible]", ruleStep.waitFor);
58
- return waitFor(
59
- () => elementVisible(selector, check),
60
- times,
61
- interval
62
- );
63
- }
64
-
65
- export async function waitForThenClick(selector: ElementSelector, timeout = 10000, all = false): Promise<boolean> {
66
- // enableLogs && console.log("[waitForThenClick]", ruleStep.waitForThenClick);
67
- await waitForElement(selector, timeout);
68
- return click(selector, all);
69
- }
70
-
71
- export function wait(ms: number): Promise<true> {
72
- // enableLogs && console.log(`waiting for ${ruleStep.wait}ms`);
73
- return new Promise(resolve => {
74
- setTimeout(() => {
75
- // enableLogs && console.log(`done waiting`);
76
- resolve(true);
77
- }, ms);
78
- });
79
- }
80
-
81
- export function hide(selector: string, method: HideMethod): boolean {
82
- // enableLogs && console.log("[hide]", ruleStep.hide, ruleStep.method);
83
- const styleEl = getStyleElement();
84
- return hideElements(styleEl, selector, method);
85
- }
86
-
87
- export function prehide(selector: string): boolean {
88
- const styleEl = getStyleElement('autoconsent-prehide');
89
- enableLogs && console.log("[prehide]", styleEl, location.href);
90
- return hideElements(styleEl, selector, "opacity");
91
- }
92
-
93
- export function undoPrehide(): boolean {
94
- const existingElement = getStyleElement('autoconsent-prehide');
95
- enableLogs && console.log("[undoprehide]", existingElement, location.href);
96
- if (existingElement) {
97
- existingElement.remove();
98
- }
99
- return !!existingElement;
100
- }
101
-
102
- export function querySingleReplySelector(selector: string, parent: any = document): HTMLElement[] {
103
- if (selector.startsWith('aria/')) {
104
- return []
105
- }
106
- if (selector.startsWith('xpath/')) {
107
- const xpath = selector.slice(6)
108
- const result = document.evaluate(xpath, parent, null, XPathResult.ANY_TYPE, null)
109
- let node: Node = null
110
- const elements: HTMLElement[] = []
111
- // eslint-disable-next-line no-cond-assign
112
- while (node = result.iterateNext()) {
113
- elements.push(node as HTMLElement)
114
- }
115
- return elements
116
- }
117
- if (selector.startsWith('text/')) {
118
- return []
119
- }
120
- if (selector.startsWith('pierce/')) {
121
- return []
122
- }
123
- if (parent.shadowRoot) {
124
- return Array.from(parent.shadowRoot.querySelectorAll(selector))
125
- }
126
- return Array.from(parent.querySelectorAll(selector))
127
- }
128
-
129
- export function querySelectorChain(selectors: string[]): HTMLElement[] {
130
- let parent: ParentNode = document
131
- let matches: HTMLElement[]
132
- for (const selector of selectors) {
133
- matches = querySingleReplySelector(selector, parent)
134
- if (matches.length === 0) {
135
- return []
136
- }
137
- parent = matches[0]
138
- }
139
- return matches;
140
- }
141
-
142
- export function elementSelector(selector: ElementSelector): HTMLElement[] {
143
- if (typeof selector === 'string') {
144
- return querySingleReplySelector(selector)
145
- }
146
- return querySelectorChain(selector)
147
- }