Skip to content

Commit ed5e7e8

Browse files
jdorweilermadblex
andauthored
detector service for breakage reporting (#2052)
* feat: web interference detection prototype * detector prototype v2 * init and service * update readme * update readme * update readme * add auto run * drop global domains option * update readme * remove old file * pr review changes, drop yt detector * fix type erros * remove unused' * remove service and init" * fix test * pass config * get settings from webInterference * comment auto-run * revert features * revert features * remove cache, refresh' * update readme * update features.js * fix getFeature * detection tests * add px test * lint --------- Co-authored-by: madblex <alex.buliga.92@gmail.com>
1 parent b0596b6 commit ed5e7e8

File tree

14 files changed

+672
-24
lines changed

14 files changed

+672
-24
lines changed

injected/integration-test/breakage-reporting.spec.js

Lines changed: 106 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,104 @@ import { ResultsCollector } from './page-objects/results-collector.js';
33

44
const HTML = '/breakage-reporting/index.html';
55
const CONFIG = './integration-test/test-pages/breakage-reporting/config/config.json';
6-
test('Breakage Reporting Feature', async ({ page }, testInfo) => {
7-
const collector = ResultsCollector.create(page, testInfo.project.use);
8-
await collector.load(HTML, CONFIG);
9-
10-
const breakageFeature = new BreakageReportingSpec(page);
11-
await breakageFeature.navigate();
12-
13-
await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
14-
await collector.waitForMessage('breakageReportResult');
15-
const calls = await collector.outgoingMessages();
16-
17-
expect(calls.length).toBe(1);
18-
const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
19-
expect(result.params?.jsPerformance.length).toBe(1);
20-
expect(result.params?.jsPerformance[0]).toBeGreaterThan(0);
21-
expect(result.params?.referrer).toBe('http://localhost:3220/breakage-reporting/index.html');
6+
7+
test.describe('Breakage Reporting Feature', () => {
8+
test('collects basic metrics without detectors', async ({ page }, testInfo) => {
9+
const collector = ResultsCollector.create(page, testInfo.project.use);
10+
await collector.load(HTML, CONFIG);
11+
12+
const breakageFeature = new BreakageReportingSpec(page);
13+
await breakageFeature.navigate();
14+
15+
await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
16+
await collector.waitForMessage('breakageReportResult');
17+
const calls = await collector.outgoingMessages();
18+
19+
expect(calls.length).toBe(1);
20+
const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
21+
expect(result.params?.jsPerformance.length).toBe(1);
22+
expect(result.params?.jsPerformance[0]).toBeGreaterThan(0);
23+
expect(result.params?.referrer).toBe('http://localhost:3220/breakage-reporting/index.html');
24+
});
25+
26+
test('detects no challenges on clean page', async ({ page }, testInfo) => {
27+
const collector = ResultsCollector.create(page, testInfo.project.use);
28+
await collector.load(HTML, CONFIG);
29+
30+
const breakageFeature = new BreakageReportingSpec(page);
31+
await breakageFeature.navigateToPage('/breakage-reporting/pages/no-challenge.html');
32+
33+
await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
34+
await collector.waitForMessage('breakageReportResult');
35+
const calls = await collector.outgoingMessages();
36+
37+
const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
38+
expect(result.params?.detectorData).toBeDefined();
39+
expect(result.params?.detectorData?.botDetection.detected).toBe(false);
40+
expect(result.params?.detectorData?.fraudDetection.detected).toBe(false);
41+
});
42+
43+
test('detects Cloudflare challenge', async ({ page }, testInfo) => {
44+
const collector = ResultsCollector.create(page, testInfo.project.use);
45+
await collector.load(HTML, CONFIG);
46+
47+
const breakageFeature = new BreakageReportingSpec(page);
48+
await breakageFeature.navigateToPage('/breakage-reporting/pages/captcha-cloudflare.html');
49+
50+
await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
51+
await collector.waitForMessage('breakageReportResult');
52+
const calls = await collector.outgoingMessages();
53+
54+
const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
55+
expect(result.params?.detectorData).toBeDefined();
56+
expect(result.params?.detectorData?.botDetection.detected).toBe(true);
57+
expect(result.params?.detectorData?.botDetection.results.length).toBeGreaterThan(0);
58+
59+
const cloudflareResult = result.params?.detectorData?.botDetection.results[0];
60+
expect(cloudflareResult.vendor).toBe('Cloudflare');
61+
expect(cloudflareResult.challengeType).toBe('cloudflare');
62+
expect(cloudflareResult.detected).toBe(true);
63+
});
64+
65+
test('detects reCAPTCHA challenge', async ({ page }, testInfo) => {
66+
const collector = ResultsCollector.create(page, testInfo.project.use);
67+
await collector.load(HTML, CONFIG);
68+
69+
const breakageFeature = new BreakageReportingSpec(page);
70+
await breakageFeature.navigateToPage('/breakage-reporting/pages/captcha-recaptcha.html');
71+
72+
await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
73+
await collector.waitForMessage('breakageReportResult');
74+
const calls = await collector.outgoingMessages();
75+
76+
const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
77+
expect(result.params?.detectorData).toBeDefined();
78+
expect(result.params?.detectorData?.botDetection.detected).toBe(true);
79+
80+
const recaptchaResult = result.params?.detectorData?.botDetection.results.find((r) => r.challengeType === 'recaptcha');
81+
expect(recaptchaResult).toBeDefined();
82+
expect(recaptchaResult.vendor).toBe('reCAPTCHA');
83+
expect(recaptchaResult.detected).toBe(true);
84+
});
85+
86+
test('detects Fraud challenge (PerimeterX)', async ({ page }, testInfo) => {
87+
const collector = ResultsCollector.create(page, testInfo.project.use);
88+
await collector.load(HTML, CONFIG);
89+
90+
const breakageFeature = new BreakageReportingSpec(page);
91+
await breakageFeature.navigateToPage('/breakage-reporting/pages/fraud-px.html');
92+
93+
await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
94+
await collector.waitForMessage('breakageReportResult');
95+
const calls = await collector.outgoingMessages();
96+
97+
const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
98+
expect(result.params?.detectorData).toBeDefined();
99+
expect(result.params?.detectorData?.fraudDetection.detected).toBe(true);
100+
101+
const fraudResult = result.params?.detectorData?.fraudDetection.results[0];
102+
expect(fraudResult.alertId).toBe('px');
103+
});
22104
});
23105

24106
export class BreakageReportingSpec {
@@ -30,10 +112,14 @@ export class BreakageReportingSpec {
30112
}
31113

32114
async navigate() {
33-
await this.page.evaluate(() => {
34-
window.location.href = '/breakage-reporting/pages/ref.html';
35-
});
36-
await this.page.waitForURL('**/ref.html');
115+
await this.navigateToPage('/breakage-reporting/pages/ref.html');
116+
}
117+
118+
async navigateToPage(url) {
119+
await this.page.evaluate((targetUrl) => {
120+
window.location.href = targetUrl;
121+
}, url);
122+
await this.page.waitForURL(`**${url}`);
37123

38124
// Wait for first paint event to ensure we can get the performance metrics
39125
await this.page.evaluate(() => {

injected/integration-test/test-pages/breakage-reporting/config/config.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,33 @@
66
"breakageReporting": {
77
"state": "enabled",
88
"exceptions": []
9+
},
10+
"webInterferenceDetection": {
11+
"state": "enabled",
12+
"exceptions": [],
13+
"settings": {
14+
"interferenceTypes": {
15+
"botDetection": {
16+
"cloudflare": {
17+
"state": "enabled",
18+
"vendor": "Cloudflare",
19+
"selectors": ["#challenge-running", ".cf-browser-verification"]
20+
},
21+
"recaptcha": {
22+
"state": "enabled",
23+
"vendor": "reCAPTCHA",
24+
"selectors": [".g-recaptcha", "iframe[src*='google.com/recaptcha']"]
25+
}
26+
},
27+
"fraudDetection": {
28+
"px": {
29+
"state": "enabled",
30+
"vendor": "PerimeterX",
31+
"selectors": ["#px-captcha", "[id^='px-']"]
32+
}
33+
}
34+
}
35+
}
936
}
1037
}
1138
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Mock Cloudflare Challenge</title>
7+
</head>
8+
<body>
9+
<h1>Mock Cloudflare Challenge Page</h1>
10+
11+
<!-- Mock Cloudflare challenge elements -->
12+
<div id="challenge-running" class="cf-browser-verification">
13+
<p>Checking your browser before accessing example.com</p>
14+
<p>This process is automatic. Your browser will redirect shortly.</p>
15+
</div>
16+
17+
<script>
18+
// Mock some common bot detection properties
19+
window.__CF$cv$params = { r: 'mock-ray-id' };
20+
</script>
21+
</body>
22+
</html>
23+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Mock reCAPTCHA Challenge</title>
7+
</head>
8+
<body>
9+
<h1>Mock reCAPTCHA Challenge Page</h1>
10+
11+
<!-- Mock reCAPTCHA elements -->
12+
<div class="g-recaptcha" data-sitekey="mock-site-key"></div>
13+
<iframe src="https://www.google.com/recaptcha/api2/anchor" style="width: 304px; height: 78px;"></iframe>
14+
15+
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
16+
</body>
17+
</html>
18+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Mock PerimeterX Fraud Page</title>
6+
<style>
7+
#px-captcha {
8+
width: 100px;
9+
height: 100px;
10+
background-color: red;
11+
}
12+
</style>
13+
</head>
14+
<body>
15+
<h1>Mock PerimeterX Page</h1>
16+
<!-- Matches "selectors": ["#px-captcha", ...] in config -->
17+
<div id="px-captcha"></div>
18+
</body>
19+
</html>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>No Challenge Page</title>
7+
</head>
8+
<body>
9+
<h1>Clean Page - No Challenges</h1>
10+
<p>This page has no bot detection or fraud detection challenges.</p>
11+
</body>
12+
</html>
13+

0 commit comments

Comments
 (0)