@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.
- package/CHANGELOG.md +29 -0
- package/build.sh +1 -0
- package/dist/addon-firefox/background.bundle.js +60 -43
- package/dist/addon-firefox/content.bundle.js +484 -382
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-mv3/background.bundle.js +60 -43
- package/dist/addon-mv3/content.bundle.js +484 -382
- package/dist/addon-mv3/manifest.json +1 -1
- package/dist/addon-mv3/popup.bundle.js +71 -33
- package/dist/addon-mv3/popup.html +28 -0
- package/dist/autoconsent.cjs.js +484 -382
- package/dist/autoconsent.esm.js +484 -382
- package/dist/autoconsent.playwright.js +1 -1
- package/dist/autoconsent.unit.js +10370 -0
- package/lib/cmps/airbnb.ts +5 -6
- package/lib/cmps/base.ts +97 -41
- package/lib/cmps/consentmanager.ts +13 -14
- package/lib/cmps/conversant.ts +8 -9
- package/lib/cmps/cookiebot.ts +8 -9
- package/lib/cmps/evidon.ts +7 -8
- package/lib/cmps/klaro.ts +13 -14
- package/lib/cmps/onetrust.ts +15 -16
- package/lib/cmps/sourcepoint-frame.ts +25 -26
- package/lib/cmps/tiktok.ts +7 -7
- package/lib/cmps/trustarc-frame.ts +27 -28
- package/lib/cmps/trustarc-top.ts +5 -6
- package/lib/cmps/uniconsent.ts +9 -10
- package/lib/dom-actions.ts +145 -0
- package/lib/eval-snippets.ts +17 -2
- package/lib/types.ts +24 -1
- package/lib/utils.ts +32 -1
- package/lib/web.ts +46 -34
- package/package.json +4 -4
- package/playwright/runner.ts +11 -3
- package/playwright/unit.ts +15 -0
- package/readme.md +1 -1
- package/tests/{rule-executors.spec.ts → dom-actions.spec.ts} +20 -21
- package/tests/klaro.spec.ts +1 -0
- package/lib/config.ts +0 -2
- 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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
253
|
+
logsConfig.errors && console.log('no CMP to opt out');
|
|
247
254
|
optOutResult = false;
|
|
248
255
|
} else {
|
|
249
|
-
|
|
256
|
+
logsConfig.lifecycle && console.log(`CMP ${this.foundCmp.name}: opt out on ${window.location.href}`);
|
|
250
257
|
optOutResult = await this.foundCmp.optOut();
|
|
251
|
-
|
|
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
|
-
|
|
293
|
+
logsConfig.errors && console.log('no CMP to opt in');
|
|
286
294
|
optInResult = false;
|
|
287
295
|
} else {
|
|
288
|
-
|
|
296
|
+
logsConfig.lifecycle && console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`);
|
|
289
297
|
optInResult = await this.foundCmp.optIn();
|
|
290
|
-
|
|
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
|
-
|
|
332
|
+
logsConfig.errors && console.log('no CMP to self test');
|
|
324
333
|
selfTestResult = false;
|
|
325
334
|
} else {
|
|
326
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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/
|
|
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.
|
|
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",
|
package/playwright/runner.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 [
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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')
|
package/tests/klaro.spec.ts
CHANGED
package/lib/config.ts
DELETED
package/lib/rule-executors.ts
DELETED
|
@@ -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
|
-
}
|