Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 106 additions & 20 deletions injected/integration-test/breakage-reporting.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,104 @@ import { ResultsCollector } from './page-objects/results-collector.js';

const HTML = '/breakage-reporting/index.html';
const CONFIG = './integration-test/test-pages/breakage-reporting/config/config.json';
test('Breakage Reporting Feature', async ({ page }, testInfo) => {
const collector = ResultsCollector.create(page, testInfo.project.use);
await collector.load(HTML, CONFIG);

const breakageFeature = new BreakageReportingSpec(page);
await breakageFeature.navigate();

await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
await collector.waitForMessage('breakageReportResult');
const calls = await collector.outgoingMessages();

expect(calls.length).toBe(1);
const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
expect(result.params?.jsPerformance.length).toBe(1);
expect(result.params?.jsPerformance[0]).toBeGreaterThan(0);
expect(result.params?.referrer).toBe('http://localhost:3220/breakage-reporting/index.html');

test.describe('Breakage Reporting Feature', () => {
test('collects basic metrics without detectors', async ({ page }, testInfo) => {
const collector = ResultsCollector.create(page, testInfo.project.use);
await collector.load(HTML, CONFIG);

const breakageFeature = new BreakageReportingSpec(page);
await breakageFeature.navigate();

await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
await collector.waitForMessage('breakageReportResult');
const calls = await collector.outgoingMessages();

expect(calls.length).toBe(1);
const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
expect(result.params?.jsPerformance.length).toBe(1);
expect(result.params?.jsPerformance[0]).toBeGreaterThan(0);
expect(result.params?.referrer).toBe('http://localhost:3220/breakage-reporting/index.html');
});

test('detects no challenges on clean page', async ({ page }, testInfo) => {
const collector = ResultsCollector.create(page, testInfo.project.use);
await collector.load(HTML, CONFIG);

const breakageFeature = new BreakageReportingSpec(page);
await breakageFeature.navigateToPage('/breakage-reporting/pages/no-challenge.html');

await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
await collector.waitForMessage('breakageReportResult');
const calls = await collector.outgoingMessages();

const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
expect(result.params?.detectorData).toBeDefined();
expect(result.params?.detectorData?.botDetection.detected).toBe(false);
expect(result.params?.detectorData?.fraudDetection.detected).toBe(false);
});

test('detects Cloudflare challenge', async ({ page }, testInfo) => {
const collector = ResultsCollector.create(page, testInfo.project.use);
await collector.load(HTML, CONFIG);

const breakageFeature = new BreakageReportingSpec(page);
await breakageFeature.navigateToPage('/breakage-reporting/pages/captcha-cloudflare.html');

await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
await collector.waitForMessage('breakageReportResult');
const calls = await collector.outgoingMessages();

const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
expect(result.params?.detectorData).toBeDefined();
expect(result.params?.detectorData?.botDetection.detected).toBe(true);
expect(result.params?.detectorData?.botDetection.results.length).toBeGreaterThan(0);

const cloudflareResult = result.params?.detectorData?.botDetection.results[0];
expect(cloudflareResult.vendor).toBe('Cloudflare');
expect(cloudflareResult.challengeType).toBe('cloudflare');
expect(cloudflareResult.detected).toBe(true);
});

test('detects reCAPTCHA challenge', async ({ page }, testInfo) => {
const collector = ResultsCollector.create(page, testInfo.project.use);
await collector.load(HTML, CONFIG);

const breakageFeature = new BreakageReportingSpec(page);
await breakageFeature.navigateToPage('/breakage-reporting/pages/captcha-recaptcha.html');

await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
await collector.waitForMessage('breakageReportResult');
const calls = await collector.outgoingMessages();

const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
expect(result.params?.detectorData).toBeDefined();
expect(result.params?.detectorData?.botDetection.detected).toBe(true);

const recaptchaResult = result.params?.detectorData?.botDetection.results.find((r) => r.challengeType === 'recaptcha');
expect(recaptchaResult).toBeDefined();
expect(recaptchaResult.vendor).toBe('reCAPTCHA');
expect(recaptchaResult.detected).toBe(true);
});

test('detects Fraud challenge (PerimeterX)', async ({ page }, testInfo) => {
const collector = ResultsCollector.create(page, testInfo.project.use);
await collector.load(HTML, CONFIG);

const breakageFeature = new BreakageReportingSpec(page);
await breakageFeature.navigateToPage('/breakage-reporting/pages/fraud-px.html');

await collector.simulateSubscriptionMessage('breakageReporting', 'getBreakageReportValues', {});
await collector.waitForMessage('breakageReportResult');
const calls = await collector.outgoingMessages();

const result = /** @type {import("@duckduckgo/messaging").NotificationMessage} */ (calls[0].payload);
expect(result.params?.detectorData).toBeDefined();
expect(result.params?.detectorData?.fraudDetection.detected).toBe(true);

const fraudResult = result.params?.detectorData?.fraudDetection.results[0];
expect(fraudResult.alertId).toBe('px');
});
});

export class BreakageReportingSpec {
Expand All @@ -30,10 +112,14 @@ export class BreakageReportingSpec {
}

async navigate() {
await this.page.evaluate(() => {
window.location.href = '/breakage-reporting/pages/ref.html';
});
await this.page.waitForURL('**/ref.html');
await this.navigateToPage('/breakage-reporting/pages/ref.html');
}

async navigateToPage(url) {
await this.page.evaluate((targetUrl) => {
window.location.href = targetUrl;
}, url);
await this.page.waitForURL(`**${url}`);

// Wait for first paint event to ensure we can get the performance metrics
await this.page.evaluate(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@
"breakageReporting": {
"state": "enabled",
"exceptions": []
},
"webInterferenceDetection": {
"state": "enabled",
"exceptions": [],
"settings": {
"interferenceTypes": {
"botDetection": {
"cloudflare": {
"state": "enabled",
"vendor": "Cloudflare",
"selectors": ["#challenge-running", ".cf-browser-verification"]
},
"recaptcha": {
"state": "enabled",
"vendor": "reCAPTCHA",
"selectors": [".g-recaptcha", "iframe[src*='google.com/recaptcha']"]
}
},
"fraudDetection": {
"px": {
"state": "enabled",
"vendor": "PerimeterX",
"selectors": ["#px-captcha", "[id^='px-']"]
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mock Cloudflare Challenge</title>
</head>
<body>
<h1>Mock Cloudflare Challenge Page</h1>

<!-- Mock Cloudflare challenge elements -->
<div id="challenge-running" class="cf-browser-verification">
<p>Checking your browser before accessing example.com</p>
<p>This process is automatic. Your browser will redirect shortly.</p>
</div>

<script>
// Mock some common bot detection properties
window.__CF$cv$params = { r: 'mock-ray-id' };
</script>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mock reCAPTCHA Challenge</title>
</head>
<body>
<h1>Mock reCAPTCHA Challenge Page</h1>

<!-- Mock reCAPTCHA elements -->
<div class="g-recaptcha" data-sitekey="mock-site-key"></div>
<iframe src="https://www.google.com/recaptcha/api2/anchor" style="width: 304px; height: 78px;"></iframe>

<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mock PerimeterX Fraud Page</title>
<style>
#px-captcha {
width: 100px;
height: 100px;
background-color: red;
}
</style>
</head>
<body>
<h1>Mock PerimeterX Page</h1>
<!-- Matches "selectors": ["#px-captcha", ...] in config -->
<div id="px-captcha"></div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>No Challenge Page</title>
</head>
<body>
<h1>Clean Page - No Challenges</h1>
<p>This page has no bot detection or fraud detection challenges.</p>
</body>
</html>

Loading
Loading