Skip to content

Commit

Permalink
Opt in improvements (#17)
Browse files Browse the repository at this point in the history
* Add missing opt-in rules

* Do not request self-test for opt-in

* Fix existing opt-in rules

* Allow passing declarative rules in constructor without config

* test opt-in

* Remove standalone bundle (use playwright instead)

* Fix tests for C-O-M rules

* Disable opt-in check

* Improve sourcepoint rule

* Fix test failures around opt-in
  • Loading branch information
muodov authored Jul 19, 2022
1 parent 518e082 commit 5c2f913
Show file tree
Hide file tree
Showing 28 changed files with 182 additions and 157 deletions.
5 changes: 5 additions & 0 deletions lib/cmps/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ async function evaluateRuleStep(rule: AutoConsentRuleStep) {
results.push(hide(rule.hide, rule.method));
}

if (results.length === 0) {
enableLogs && console.warn('Unrecognized rule', rule);
return false;
}

// boolean and of results
const all = await Promise.all(results);
return all.reduce((a, b) => a && b, true);
Expand Down
2 changes: 1 addition & 1 deletion lib/cmps/onetrust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default class Onetrust extends AutoConsentCMPBase {
}

async optIn() {
return click("onetrust-accept-btn-handler,js-accept-cookies");
return click("#onetrust-accept-btn-handler,.js-accept-cookies");
}

async test() {
Expand Down
16 changes: 9 additions & 7 deletions lib/cmps/sourcepoint-frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default class SourcePoint extends AutoConsentCMPBase {
}

async optIn() {
await waitForElement(".sp_choice_type_11,.sp_choice_type_ACCEPT_ALL", 2000);
if (click(".sp_choice_type_11")) {
return true;
}
Expand All @@ -56,10 +57,13 @@ export default class SourcePoint extends AutoConsentCMPBase {

async optOut() {
if (!this.isManagerOpen()) {
const actionable = await waitForElement('button.sp_choice_type_12,button.sp_choice_type_13');
if (!actionable) {
return false;
}
if (!elementExists("button.sp_choice_type_12")) {
// do not sell button
click("button.sp_choice_type_13");
return true;
return click("button.sp_choice_type_13");
}

click("button.sp_choice_type_12");
Expand All @@ -82,10 +86,9 @@ export default class SourcePoint extends AutoConsentCMPBase {
]);
if (path === 0) {
await wait(1000);
click(rejectSelector1);
return true;
return click(rejectSelector1);
} else if (path === 1) {
click(rejectSelector2);
return click(rejectSelector2);
} else if (path === 2) {
// TODO: check if this is still working
await waitForElement('.pm-features', 10000);
Expand All @@ -96,7 +99,6 @@ export default class SourcePoint extends AutoConsentCMPBase {
} catch (e) {
enableLogs && console.warn(e);
}
click('.sp_choice_type_SAVE_AND_EXIT');
return true;
return click('.sp_choice_type_SAVE_AND_EXIT');
}
}
6 changes: 6 additions & 0 deletions lib/cmps/trustarc-top.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@ export default class TrustArcTop extends AutoConsentCMPBase {
}

_shortcutButton: HTMLElement;
_optInDone: boolean;

constructor() {
super("TrustArc-top");
this._shortcutButton = null; // indicates if the "reject all" button is detected
this._optInDone = false;
}

get hasSelfTest(): boolean {
return false;
}

get isIntermediate(): boolean {
if (this._optInDone) {
return false;
}
return !this._shortcutButton;
}

Expand Down Expand Up @@ -76,6 +81,7 @@ export default class TrustArcTop extends AutoConsentCMPBase {
}

async optIn() {
this._optInDone = true; // just a hack to force autoconsentDone
return click(shortcutOptIn);
}

Expand Down
9 changes: 7 additions & 2 deletions lib/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export default class AutoConsent {
if (config) {
this.initialize(config, declarativeRules);
} else {
if (declarativeRules) {
this.parseRules(declarativeRules);
}
const initMsg: InitMessage = {
type: "init",
url: window.location.href,
Expand All @@ -39,7 +42,9 @@ export default class AutoConsent {
return;
}

this.parseRules(declarativeRules);
if (declarativeRules) {
this.parseRules(declarativeRules);
}
if (config.disabledCmps?.length > 0) {
this.disableCMPs(config.disabledCmps);
}
Expand Down Expand Up @@ -241,7 +246,7 @@ export default class AutoConsent {
type: 'optInResult',
cmp: this.foundCmp ? this.foundCmp.name : 'none',
result: optInResult,
scheduleSelfTest: this.foundCmp && this.foundCmp.hasSelfTest,
scheduleSelfTest: false, // self-tests are only for opt-out at the moment
url: location.href,
});

Expand Down
8 changes: 1 addition & 7 deletions playwright/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ declare global {
}

if (!window.autoconsentReceiveMessage) {
const consent = new AutoConsent(window.autoconsentSendMessage, {
enabled: true,
autoAction: 'optOut',
disabledCmps: [],
enablePrehide: true,
detectRetries: 20,
}, <RuleBundle>rules);
const consent = new AutoConsent(window.autoconsentSendMessage, null, <RuleBundle>rules);

window.autoconsentReceiveMessage = (message: BackgroundMessage) => {
return Promise.resolve(consent.receiveMessageCallback(message));
Expand Down
177 changes: 104 additions & 73 deletions playwright/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import { test, expect, Page, Frame } from "@playwright/test";
import { waitFor } from "../lib/utils";
import { ContentScriptMessage } from "../lib/messages";
import { enableLogs } from "../lib/config";
import { AutoAction } from "../lib/types";

const testRegion = (process.env.REGION || "NA").trim();

type TestOptions = {
testOptOut: boolean;
testSelfTest: boolean;
testOptIn: boolean;
skipRegions?: string[];
onlyRegions?: string[];
};
const defaultOptions: TestOptions = {
testOptOut: true,
testOptIn: true,
testSelfTest: true,
skipRegions: [],
onlyRegions: [],
Expand All @@ -37,85 +40,113 @@ export async function injectContentScript(page: Page | Frame) {
export function generateTest(
url: string,
expectedCmp: string,
options: TestOptions = { testOptOut: true, testSelfTest: true }
options: TestOptions = defaultOptions
) {
test(`${url.split("://")[1]} .${testRegion}`, async ({ page }) => {
if (options.onlyRegions && options.onlyRegions.length > 0 && !options.onlyRegions.includes(testRegion)) {
test.skip();
}
if (options.skipRegions && options.skipRegions.includes(testRegion)) {
test.skip();
}
enableLogs && page.on('console', async msg => {
console.log(` page log:`, msg.text());
});
await page.exposeBinding("autoconsentSendMessage", messageCallback);
await page.goto(url, { waitUntil: "commit" });

// set up a messaging function
const received: ContentScriptMessage[] = [];

function isMessageReceived(msg: Partial<ContentScriptMessage>, partial = true) {
return received.some((m) => {
const keysMatch = partial || Object.keys(m).length === Object.keys(msg).length;
return keysMatch && Object.keys(msg).every(
(k) => (<any>m)[k] === (<any>msg)[k]
);
function genTest(autoAction: AutoAction) {
test(`${url.split("://")[1]} .${testRegion} ${autoAction}`, async ({ page }) => {
if (options.onlyRegions && options.onlyRegions.length > 0 && !options.onlyRegions.includes(testRegion)) {
test.skip();
}
if (options.skipRegions && options.skipRegions.includes(testRegion)) {
test.skip();
}
enableLogs && page.on('console', async msg => {
console.log(` page log:`, msg.text());
});
}

let selfTestFrame: Frame = null;
async function messageCallback({ frame }: { frame: Frame }, msg: ContentScriptMessage) {
enableLogs && msg.type !== 'eval' && console.log(msg);
received.push(msg);
switch (msg.type) {
case 'optInResult':
case 'optOutResult': {
if (msg.scheduleSelfTest) {
selfTestFrame = frame;
await page.exposeBinding("autoconsentSendMessage", messageCallback);
await page.goto(url, { waitUntil: "commit" });

// set up a messaging function
const received: ContentScriptMessage[] = [];

function isMessageReceived(msg: Partial<ContentScriptMessage>, partial = true) {
return received.some((m) => {
const keysMatch = partial || Object.keys(m).length === Object.keys(msg).length;
return keysMatch && Object.keys(msg).every(
(k) => (<any>m)[k] === (<any>msg)[k]
);
});
}

let selfTestFrame: Frame = null;
async function messageCallback({ frame }: { frame: Frame }, msg: ContentScriptMessage) {
enableLogs && msg.type !== 'eval' && console.log(msg);
received.push(msg);
switch (msg.type) {
case 'init': {
await frame.evaluate(`autoconsentReceiveMessage({ type: "initResp", config: ${JSON.stringify({
enabled: true,
autoAction: autoAction,
disabledCmps: [],
enablePrehide: true,
detectRetries: 20,
})} })`);
break;
}
break;
}
case 'autoconsentDone': {
if (selfTestFrame && options.testSelfTest) {
await selfTestFrame.evaluate(`autoconsentReceiveMessage({ type: "selfTest" })`);
case 'optInResult':
case 'optOutResult': {
if (msg.scheduleSelfTest) {
selfTestFrame = frame;
}
break;
}
case 'autoconsentDone': {
if (selfTestFrame && options.testSelfTest) {
await selfTestFrame.evaluate(`autoconsentReceiveMessage({ type: "selfTest" })`);
}
break;
}
case 'eval': {
const result = await frame.evaluate(msg.code);
await frame.evaluate(`autoconsentReceiveMessage({ id: "${msg.id}", type: "evalResp", result: ${JSON.stringify(result)} })`);
break;
}
case 'autoconsentError': {
console.error(url, msg.details);
break;
}
break;
}
case 'eval': {
const result = await frame.evaluate(msg.code);
await frame.evaluate(`autoconsentReceiveMessage({ id: "${msg.id}", type: "evalResp", result: ${JSON.stringify(result)} })`);
break;
}
case 'autoconsentError': {
console.error(url, msg.details);
break;
}
}
}

// inject content scripts into every frame
await injectContentScript(page);
page.frames().forEach(injectContentScript);
page.on("framenavigated", injectContentScript);

// wait for all messages and assertions
await waitFor(() => isMessageReceived({ type: "popupFound", cmp: expectedCmp }), 50, 500);
expect(isMessageReceived({ type: "popupFound", cmp: expectedCmp })).toBe(true);

if (options.testOptOut) {
await waitFor(() => isMessageReceived({ type: "optOutResult", result: true }), 50, 300);
expect(isMessageReceived({ type: "optOutResult", result: true })).toBe(true);
}
if (options.testSelfTest && selfTestFrame) {
await waitFor(() => isMessageReceived({ type: "selfTestResult", result: true }), 50, 300);
expect(isMessageReceived({ type: "selfTestResult", result: true })).toBe(true);
}
await waitFor(() => isMessageReceived({ type: "autoconsentDone" }), 10, 500);
expect(isMessageReceived({ type: "autoconsentDone" })).toBe(true);

expect(isMessageReceived({ type: "autoconsentError" })).toBe(false);
});

// inject content scripts into every frame
await injectContentScript(page);
page.frames().forEach(injectContentScript);
page.on("framenavigated", injectContentScript);

// wait for all messages and assertions
await waitFor(() => isMessageReceived({ type: "popupFound", cmp: expectedCmp }), 50, 500);
expect(isMessageReceived({ type: "popupFound", cmp: expectedCmp })).toBe(true);

if (autoAction === 'optOut') {
await waitFor(() => isMessageReceived({ type: "optOutResult", result: true }), 50, 300);
expect(isMessageReceived({ type: "optOutResult", result: true })).toBe(true);
}
if (autoAction === 'optIn') {
await waitFor(() => isMessageReceived({ type: "optInResult", result: true }), 50, 300);
expect(isMessageReceived({ type: "optInResult", result: true })).toBe(true);
}
if (options.testSelfTest && selfTestFrame) {
await waitFor(() => isMessageReceived({ type: "selfTestResult", result: true }), 50, 300);
expect(isMessageReceived({ type: "selfTestResult", result: true })).toBe(true);
}
await waitFor(() => isMessageReceived({ type: "autoconsentDone" }), 10, 500);
expect(isMessageReceived({ type: "autoconsentDone" })).toBe(true);

expect(isMessageReceived({ type: "autoconsentError" })).toBe(false);
})
}

if (!options.testOptIn && !options.testOptOut) {
genTest(null);
}

if (options.testOptIn) {
genTest('optIn');
}

if (options.testOptOut) {
genTest('optOut');
}
}

export default function generateCMPTests(
Expand Down
36 changes: 0 additions & 36 deletions playwright/standalone.ts

This file was deleted.

Loading

0 comments on commit 5c2f913

Please sign in to comment.