real-browser-mcp-server 1.3.3 → 1.4.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 (118) hide show
  1. package/README.md +96 -9
  2. package/dist/lib/cjs/index.d.ts +2 -0
  3. package/dist/lib/cjs/index.d.ts.map +1 -0
  4. package/dist/lib/cjs/index.js +386 -0
  5. package/dist/lib/cjs/index.js.map +1 -0
  6. package/dist/lib/cjs/module/pageController.d.ts +2 -0
  7. package/dist/lib/cjs/module/pageController.d.ts.map +1 -0
  8. package/{lib → dist/lib}/cjs/module/pageController.js +28 -29
  9. package/dist/lib/cjs/module/pageController.js.map +1 -0
  10. package/dist/lib/cjs/module/turnstile.d.ts +2 -0
  11. package/dist/lib/cjs/module/turnstile.d.ts.map +1 -0
  12. package/{lib → dist/lib}/cjs/module/turnstile.js +24 -12
  13. package/dist/lib/cjs/module/turnstile.js.map +1 -0
  14. package/dist/src/index.d.ts +11 -0
  15. package/dist/src/index.d.ts.map +1 -0
  16. package/dist/src/index.js +118 -0
  17. package/dist/src/index.js.map +1 -0
  18. package/dist/src/mcp/handlers/browser.d.ts +30 -0
  19. package/dist/src/mcp/handlers/browser.d.ts.map +1 -0
  20. package/dist/src/mcp/handlers/browser.js +232 -0
  21. package/dist/src/mcp/handlers/browser.js.map +1 -0
  22. package/dist/src/mcp/handlers/dom.d.ts +149 -0
  23. package/dist/src/mcp/handlers/dom.d.ts.map +1 -0
  24. package/dist/src/mcp/handlers/dom.js +577 -0
  25. package/dist/src/mcp/handlers/dom.js.map +1 -0
  26. package/dist/src/mcp/handlers/extract.d.ts +59 -0
  27. package/dist/src/mcp/handlers/extract.d.ts.map +1 -0
  28. package/dist/src/mcp/handlers/extract.js +455 -0
  29. package/dist/src/mcp/handlers/extract.js.map +1 -0
  30. package/dist/src/mcp/handlers/form-handlers.d.ts +9 -0
  31. package/dist/src/mcp/handlers/form-handlers.d.ts.map +1 -0
  32. package/dist/src/mcp/handlers/form-handlers.js +56 -0
  33. package/dist/src/mcp/handlers/form-handlers.js.map +1 -0
  34. package/dist/src/mcp/handlers/helpers.d.ts +47 -0
  35. package/dist/src/mcp/handlers/helpers.d.ts.map +1 -0
  36. package/dist/src/mcp/handlers/helpers.js +515 -0
  37. package/dist/src/mcp/handlers/helpers.js.map +1 -0
  38. package/dist/src/mcp/handlers/index.d.ts +6 -0
  39. package/dist/src/mcp/handlers/index.d.ts.map +1 -0
  40. package/dist/src/mcp/handlers/index.js +61 -0
  41. package/dist/src/mcp/handlers/index.js.map +1 -0
  42. package/dist/src/mcp/handlers/media-handlers.d.ts +10 -0
  43. package/dist/src/mcp/handlers/media-handlers.d.ts.map +1 -0
  44. package/dist/src/mcp/handlers/media-handlers.js +535 -0
  45. package/dist/src/mcp/handlers/media-handlers.js.map +1 -0
  46. package/dist/src/mcp/handlers/network.d.ts +147 -0
  47. package/dist/src/mcp/handlers/network.d.ts.map +1 -0
  48. package/dist/src/mcp/handlers/network.js +1135 -0
  49. package/dist/src/mcp/handlers/network.js.map +1 -0
  50. package/dist/src/mcp/handlers/state.d.ts +34 -0
  51. package/dist/src/mcp/handlers/state.d.ts.map +1 -0
  52. package/dist/src/mcp/handlers/state.js +226 -0
  53. package/dist/src/mcp/handlers/state.js.map +1 -0
  54. package/dist/src/mcp/handlers/utility-handlers.d.ts +167 -0
  55. package/dist/src/mcp/handlers/utility-handlers.d.ts.map +1 -0
  56. package/dist/src/mcp/handlers/utility-handlers.js +280 -0
  57. package/dist/src/mcp/handlers/utility-handlers.js.map +1 -0
  58. package/dist/src/mcp/handlers/vision.d.ts +127 -0
  59. package/dist/src/mcp/handlers/vision.d.ts.map +1 -0
  60. package/dist/src/mcp/handlers/vision.js +549 -0
  61. package/dist/src/mcp/handlers/vision.js.map +1 -0
  62. package/dist/src/mcp/index.d.ts +3 -0
  63. package/dist/src/mcp/index.d.ts.map +1 -0
  64. package/dist/src/mcp/index.js +166 -0
  65. package/dist/src/mcp/index.js.map +1 -0
  66. package/dist/src/mcp/server.d.ts +2 -0
  67. package/dist/src/mcp/server.d.ts.map +1 -0
  68. package/dist/src/mcp/server.js +117 -0
  69. package/dist/src/mcp/server.js.map +1 -0
  70. package/dist/src/mcp/tools.d.ts +8 -0
  71. package/dist/src/mcp/tools.d.ts.map +1 -0
  72. package/{src → dist/src}/mcp/tools.js +12 -11
  73. package/dist/src/mcp/tools.js.map +1 -0
  74. package/dist/src/shared/cache-manager.d.ts +80 -0
  75. package/dist/src/shared/cache-manager.d.ts.map +1 -0
  76. package/dist/src/shared/cache-manager.js +221 -0
  77. package/dist/src/shared/cache-manager.js.map +1 -0
  78. package/dist/src/shared/tools.d.ts +2 -0
  79. package/dist/src/shared/tools.d.ts.map +1 -0
  80. package/dist/src/shared/tools.js +606 -0
  81. package/dist/src/shared/tools.js.map +1 -0
  82. package/dist/src/types.d.ts +376 -0
  83. package/dist/src/types.d.ts.map +1 -0
  84. package/dist/src/types.js +9 -0
  85. package/dist/src/types.js.map +1 -0
  86. package/dist/test/cjs/test.d.ts +11 -0
  87. package/dist/test/cjs/test.d.ts.map +1 -0
  88. package/dist/test/cjs/test.js +289 -0
  89. package/dist/test/cjs/test.js.map +1 -0
  90. package/dist/test/mcp/smoke-test.d.ts +29 -0
  91. package/dist/test/mcp/smoke-test.d.ts.map +1 -0
  92. package/dist/test/mcp/smoke-test.js +132 -0
  93. package/dist/test/mcp/smoke-test.js.map +1 -0
  94. package/lib/esm/index.mjs +230 -154
  95. package/lib/esm/module/pageController.mjs +21 -18
  96. package/lib/esm/module/turnstile.mjs +7 -0
  97. package/package.json +25 -16
  98. package/typings.d.ts +7 -1
  99. package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
  100. package/.github/SETUP.md +0 -111
  101. package/.github/workflows/publish.yml +0 -135
  102. package/Dockerfile +0 -79
  103. package/lib/cjs/adblocker.bin +0 -0
  104. package/lib/cjs/index.js +0 -321
  105. package/src/ai/action-parser.js +0 -274
  106. package/src/ai/core.js +0 -378
  107. package/src/ai/element-finder.js +0 -466
  108. package/src/ai/index.js +0 -82
  109. package/src/ai/page-analyzer.js +0 -304
  110. package/src/ai/selector-healer.js +0 -236
  111. package/src/index.js +0 -121
  112. package/src/mcp/handlers.js +0 -5071
  113. package/src/mcp/index.js +0 -190
  114. package/src/mcp/server.js +0 -144
  115. package/src/shared/tools.js +0 -618
  116. package/test/cjs/test.js +0 -259
  117. package/test/esm/package.json +0 -13
  118. package/test/esm/test.js +0 -226
package/test/cjs/test.js DELETED
@@ -1,259 +0,0 @@
1
- const test = require('node:test');
2
- const assert = require('node:assert');
3
- const { connect } = require('../../lib/cjs/index.js');
4
-
5
- const realBrowserOption = {
6
- turnstile: true,
7
- headless: false,
8
- customConfig: {}
9
- }
10
-
11
- // Shared browser instance for all tests
12
- let browser = null;
13
- let page = null;
14
-
15
- // Setup - Run once before all tests
16
- test.before(async () => {
17
- console.log('🚀 Starting browser for all tests...');
18
- const result = await connect(realBrowserOption);
19
- browser = result.browser;
20
- page = result.page;
21
- console.log('✅ Browser started successfully');
22
- });
23
-
24
- // Teardown - Run once after all tests
25
- test.after(async () => {
26
- console.log('🏁 Closing browser after all tests...');
27
- if (browser) {
28
- await browser.close();
29
- console.log('✅ Browser closed successfully');
30
- }
31
- });
32
-
33
- test('DrissionPage Detector', async () => {
34
- await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout: 60000 });
35
- await page.realClick("#detector")
36
- let result = await page.evaluate(() => { return document.querySelector('#isBot span').textContent.includes("not") ? true : false })
37
- assert.strictEqual(result, true, "DrissionPage Detector test failed!")
38
- })
39
-
40
- test('Sannysoft WebDriver Detector', async () => {
41
- await page.goto("https://bot.sannysoft.com/", { timeout: 60000 });
42
- await new Promise(r => setTimeout(r, 3000));
43
- let result = await page.evaluate(() => {
44
- const webdriverEl = document.getElementById('webdriver-result');
45
- return webdriverEl && webdriverEl.classList.contains('passed');
46
- });
47
- assert.strictEqual(result, true, "Sannysoft WebDriver Detector test failed! Browser detected as bot.")
48
- })
49
-
50
- test('Cloudflare WAF', async () => {
51
- await page.goto("https://nopecha.com/demo/cloudflare", { timeout: 60000 });
52
- let verify = null
53
- let startDate = Date.now()
54
- // Increased timeout to 60 seconds to allow turnstile to be solved
55
- while (!verify && (Date.now() - startDate) < 50000) {
56
- verify = await page.evaluate(() => {
57
- // Check if we passed the challenge - look for main content
58
- return document.querySelector('.link_row') || document.querySelector('a[href*="nopecha"]') ? true : null
59
- }).catch(() => null)
60
- await new Promise(r => setTimeout(r, 2000));
61
- }
62
- assert.strictEqual(verify === true, true, "Cloudflare WAF test failed! (Site may be blocking automated access)")
63
- })
64
-
65
-
66
- test('Cloudflare Turnstile', async () => {
67
- await page.goto("https://2captcha.com/demo/cloudflare-turnstile", { timeout: 60000 });
68
- await page.waitForSelector('.cf-turnstile')
69
- let token = null
70
- let startDate = Date.now()
71
- while (!token && (Date.now() - startDate) < 30000) {
72
- token = await page.evaluate(() => {
73
- try {
74
- let item = document.querySelector('[name="cf-turnstile-response"]')?.value
75
- return item && item.length > 20 ? item : null
76
- } catch (e) {
77
- return null
78
- }
79
- })
80
- await new Promise(r => setTimeout(r, 1000));
81
- }
82
- assert.strictEqual(token !== null, true, "Cloudflare turnstile test failed!")
83
- })
84
-
85
-
86
-
87
- test('Fingerprint JS Bot Detector', async () => {
88
- // Use domcontentloaded + higher timeout to avoid timeout on heavy pages
89
- await page.goto("https://fingerprint.com/products/bot-detection/", { waitUntil: 'domcontentloaded', timeout: 60000 });
90
- await new Promise(r => setTimeout(r, 5000));
91
- const detect = await page.evaluate(() => {
92
- // Check for bot detection result in page content
93
- const pageText = document.body.innerText.toLowerCase();
94
-
95
- // If page loaded successfully without bot block, test passes
96
- // Fingerprint.com shows their product page, not a block page
97
- const isProductPage = pageText.includes('bot detection') ||
98
- pageText.includes('fingerprint') ||
99
- document.querySelector('h1') !== null;
100
-
101
- // Check if we're NOT blocked (no captcha, no access denied)
102
- const isNotBlocked = !pageText.includes('access denied') &&
103
- !pageText.includes('blocked') &&
104
- !pageText.includes('captcha');
105
-
106
- // Check in pre/code blocks for notDetected result or in page text
107
- const preElements = document.querySelectorAll('pre, code');
108
- for (const el of preElements) {
109
- if (el.textContent.includes('notDetected') || el.textContent.includes('"result": "notDetected"')) {
110
- return true;
111
- }
112
- }
113
- // Fallback: check any element with partial class match
114
- const allElements = document.querySelectorAll('*');
115
- for (const el of allElements) {
116
- for (const cls of el.classList) {
117
- if (cls.includes('botSubTitle') && el.textContent.toLowerCase().includes('not')) {
118
- return true;
119
- }
120
- }
121
- }
122
-
123
- // If product page loaded and not blocked, we passed
124
- return isProductPage && isNotBlocked;
125
- })
126
- assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
127
- })
128
-
129
-
130
- // Datadome Bot Detector - Tests against a real Datadome-protected website (hermes.com)
131
- test('Datadome Bot Detector', async (t) => {
132
- // Navigate to hermes.com which is protected by Datadome
133
- await page.goto("https://www.hermes.com/us/en/", { waitUntil: 'domcontentloaded', timeout: 60000 });
134
- await new Promise(r => setTimeout(r, 2000 + Math.random() * 1000));
135
-
136
- // Human-like behavior on the page
137
- await page.realCursor.move('body', { paddingPercentage: 20 });
138
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
139
- await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
140
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
141
-
142
- // Check if main page content loaded (Datadome bypass successful)
143
- // Look for Hermès logo or main navigation elements that only appear after Datadome clears
144
- const check = await page.evaluate(() => {
145
- // Check for Hermès header/logo/navigation elements
146
- const hasLogo = !!document.querySelector('a[href*="hermes"]') || !!document.querySelector('[class*="logo"]');
147
- const hasNav = !!document.querySelector('nav') || !!document.querySelector('[class*="header"]') || !!document.querySelector('[class*="navigation"]');
148
- const hasContent = document.body.innerText.length > 500; // Real page has significant content
149
- const notBlocked = !document.body.innerText.toLowerCase().includes('blocked') &&
150
- !document.body.innerText.toLowerCase().includes('captcha');
151
- return (hasLogo || hasNav) && hasContent && notBlocked;
152
- }).catch(() => false);
153
-
154
- assert.strictEqual(check, true, "Datadome Bot Detector test failed! [This may also be because your ip address has a high spam score. Please try with a clean ip address.]");
155
- })
156
-
157
- // If this test fails, please first check if you can access https://antcpt.com/score_detector/
158
- // Note: ReCAPTCHA V3 score depends heavily on IP reputation, browser history, and Google's algorithms.
159
- // A score >= 0.3 indicates the browser is not detected as an obvious bot.
160
- test('Recaptcha V3 Score', async () => {
161
- await page.goto("https://antcpt.com/score_detector/", { timeout: 60000 });
162
-
163
- // Human-like warm-up interactions before clicking
164
- // 1. Random mouse movements using realCursor (Bézier curves via ghost-cursor)
165
- await page.realCursor.move('body', { paddingPercentage: 20 });
166
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
167
-
168
- // 2. Scroll down a bit to simulate reading
169
- await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
170
- await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
171
-
172
- // 3. Move mouse towards button area naturally
173
- await page.realCursor.move('button', { paddingPercentage: 10 });
174
- await new Promise(r => setTimeout(r, 300 + Math.random() * 300));
175
-
176
- // 4. Now click the button
177
- await page.realClick("button")
178
- await new Promise(r => setTimeout(r, 5000));
179
-
180
- const score = await page.evaluate(() => {
181
- return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
182
- })
183
- // 0.3+ means browser is not obviously a bot. Higher scores depend on IP reputation.
184
- assert.strictEqual(Number(score) >= 0.3, true, "(please first check if you can access https://antcpt.com/score_detector/.) Recaptcha V3 Score should be >=0.3 (not obviously a bot). Score Result: " + score)
185
- })
186
-
187
- // Pixelscan Fingerprint Consistency Check
188
- // Checks browser fingerprint consistency, automation detection, and proxy detection
189
- test('Pixelscan Fingerprint Check', async () => {
190
- await page.goto("https://pixelscan.net/fingerprint-check", { waitUntil: 'domcontentloaded', timeout: 60000 });
191
-
192
- // Poll for the final status. We look specifically at the green header and the fingerprint checker card.
193
- let result = false;
194
- const startTime = Date.now();
195
- while (!result && (Date.now() - startTime) < 30000) {
196
- result = await page.evaluate(() => {
197
- const statusBar = document.querySelector('.status-content');
198
- if (!statusBar) return false;
199
-
200
- const statusText = statusBar.innerText.toLowerCase();
201
- const isConsistent = statusText.includes('consistent') && !statusText.includes('inconsistent');
202
-
203
- const cards = Array.from(document.querySelectorAll('.checker-card'));
204
- if (cards.length === 0) return false;
205
-
206
- // Check if scanning is still in progress
207
- const isScanning = cards.some(c => c.innerText.toLowerCase().includes('scanning') || c.innerText.toLowerCase().includes('collecting'));
208
- if (isScanning) return false;
209
-
210
- // Verify if fingerprint card shows masking detected
211
- const fingerprintCard = cards.find(c => c.innerText.toLowerCase().includes('fingerprint'));
212
- if (!fingerprintCard) return false;
213
- const hasMasking = fingerprintCard.innerText.toLowerCase().includes('masking detected') && !fingerprintCard.innerText.toLowerCase().includes('no masking');
214
-
215
- return isConsistent && !hasMasking;
216
- }).catch(() => false);
217
- if (!result) await new Promise(r => setTimeout(r, 1000));
218
- }
219
-
220
- // Wait 10 seconds so the user can clearly see the final green scan results on screen
221
- await new Promise(r => setTimeout(r, 10000));
222
-
223
- assert.strictEqual(result, true, "Pixelscan Fingerprint Check failed! Browser fingerprint is inconsistent or masking was detected.")
224
- })
225
-
226
- // CreepJS Deep Fingerprint Analysis
227
- // The most comprehensive fingerprint analyzer - checks lies, headless, stealth, trust score
228
- test('CreepJS Fingerprint Analysis', async () => {
229
- await page.goto("https://abrahamjuliot.github.io/creepjs/", { waitUntil: 'domcontentloaded', timeout: 60000 });
230
- await new Promise(r => setTimeout(r, 15000)); // CreepJS needs time to run all checks
231
-
232
- const result = await page.evaluate(() => {
233
- const pageText = document.body.innerText;
234
-
235
- // Check headless detection section
236
- // Look for "0% headless" which means not detected as headless
237
- const headlessSection = pageText.match(/(\d+)%\s*headless/i);
238
- const headlessPercent = headlessSection ? parseInt(headlessSection[1]) : 100;
239
-
240
- // Check stealth detection
241
- const stealthSection = pageText.match(/(\d+)%\s*stealth/i);
242
- const stealthPercent = stealthSection ? parseInt(stealthSection[1]) : 100;
243
-
244
- // Check lies detection - "0 lies" or low count is good
245
- const liesMatch = pageText.match(/(\d+)\s*lie/i);
246
- const liesCount = liesMatch ? parseInt(liesMatch[1]) : 0;
247
-
248
- return {
249
- headlessPercent,
250
- stealthPercent,
251
- liesCount,
252
- // Pass if: 0% headless, 0% stealth, and lies count is low
253
- passed: headlessPercent === 0 && stealthPercent === 0 && liesCount <= 2
254
- };
255
- }).catch(() => ({ passed: false, headlessPercent: -1, stealthPercent: -1, liesCount: -1 }));
256
-
257
- assert.strictEqual(result.passed, true,
258
- `CreepJS Fingerprint Analysis failed! Headless: ${result.headlessPercent}%, Stealth: ${result.stealthPercent}%, Lies: ${result.liesCount}`)
259
- })
@@ -1,13 +0,0 @@
1
- {
2
- "name": "test",
3
- "type": "module",
4
- "version": "1.0.0",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "keywords": [],
10
- "author": "",
11
- "license": "ISC",
12
- "description": ""
13
- }
package/test/esm/test.js DELETED
@@ -1,226 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert';
3
- import { connect } from '../../lib/esm/index.mjs';
4
-
5
- const realBrowserOption = {
6
- turnstile: true,
7
- headless: false,
8
- customConfig: {}
9
- }
10
-
11
- // Shared browser instance for all tests
12
- let browser = null;
13
- let page = null;
14
-
15
- // Setup - Run once before all tests
16
- test.before(async () => {
17
- console.log('🚀 Starting browser for all tests...');
18
- const result = await connect(realBrowserOption);
19
- browser = result.browser;
20
- page = result.page;
21
- console.log('✅ Browser started successfully');
22
- });
23
-
24
- // Teardown - Run once after all tests
25
- test.after(async () => {
26
- console.log('🏁 Closing browser after all tests...');
27
- if (browser) {
28
- await browser.close();
29
- console.log('✅ Browser closed successfully');
30
- }
31
- });
32
-
33
- test('DrissionPage Detector', async () => {
34
- await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout: 60000 });
35
- await page.realClick("#detector")
36
- let result = await page.evaluate(() => { return document.querySelector('#isBot span').textContent.includes("not") ? true : false })
37
- assert.strictEqual(result, true, "DrissionPage Detector test failed!")
38
- })
39
-
40
- test('Sannysoft WebDriver Detector', async () => {
41
- await page.goto("https://bot.sannysoft.com/", { timeout: 60000 });
42
- await new Promise(r => setTimeout(r, 3000));
43
- let result = await page.evaluate(() => {
44
- const webdriverEl = document.getElementById('webdriver-result');
45
- return webdriverEl && webdriverEl.classList.contains('passed');
46
- });
47
- assert.strictEqual(result, true, "Sannysoft WebDriver Detector test failed! Browser detected as bot.")
48
- })
49
-
50
- test('Cloudflare WAF', async () => {
51
- await page.goto("https://nopecha.com/demo/cloudflare", { timeout: 60000 });
52
- let verify = null
53
- let startDate = Date.now()
54
- while (!verify && (Date.now() - startDate) < 50000) {
55
- verify = await page.evaluate(() => {
56
- return document.querySelector('.link_row') || document.querySelector('a[href*="nopecha"]') ? true : null
57
- }).catch(() => null)
58
- await new Promise(r => setTimeout(r, 2000));
59
- }
60
- assert.strictEqual(verify === true, true, "Cloudflare WAF test failed! (Site may be blocking automated access)")
61
- })
62
-
63
-
64
- test('Cloudflare Turnstile', async () => {
65
- await page.goto("https://2captcha.com/demo/cloudflare-turnstile", { timeout: 60000 });
66
- await page.waitForSelector('.cf-turnstile')
67
- let token = null
68
- let startDate = Date.now()
69
- while (!token && (Date.now() - startDate) < 30000) {
70
- token = await page.evaluate(() => {
71
- try {
72
- let item = document.querySelector('[name="cf-turnstile-response"]')?.value
73
- return item && item.length > 20 ? item : null
74
- } catch (e) {
75
- return null
76
- }
77
- })
78
- await new Promise(r => setTimeout(r, 1000));
79
- }
80
- assert.strictEqual(token !== null, true, "Cloudflare turnstile test failed!")
81
- })
82
-
83
-
84
-
85
- test('Fingerprint JS Bot Detector', async () => {
86
- await page.goto("https://fingerprint.com/products/bot-detection/", { waitUntil: 'domcontentloaded', timeout: 60000 });
87
- await new Promise(r => setTimeout(r, 5000));
88
- const detect = await page.evaluate(() => {
89
- const pageText = document.body.innerText.toLowerCase();
90
-
91
- const isProductPage = pageText.includes('bot detection') ||
92
- pageText.includes('fingerprint') ||
93
- document.querySelector('h1') !== null;
94
-
95
- const isNotBlocked = !pageText.includes('access denied') &&
96
- !pageText.includes('blocked') &&
97
- !pageText.includes('captcha');
98
-
99
- const preElements = document.querySelectorAll('pre, code');
100
- for (const el of preElements) {
101
- if (el.textContent.includes('notDetected') || el.textContent.includes('"result": "notDetected"')) {
102
- return true;
103
- }
104
- }
105
- const allElements = document.querySelectorAll('*');
106
- for (const el of allElements) {
107
- for (const cls of el.classList) {
108
- if (cls.includes('botSubTitle') && el.textContent.toLowerCase().includes('not')) {
109
- return true;
110
- }
111
- }
112
- }
113
-
114
- return isProductPage && isNotBlocked;
115
- })
116
- assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
117
- })
118
-
119
-
120
- // Datadome Bot Detector - Tests against a real Datadome-protected website (hermes.com)
121
- test('Datadome Bot Detector', async (t) => {
122
- await page.goto("https://www.hermes.com/us/en/", { waitUntil: 'domcontentloaded', timeout: 60000 });
123
- await new Promise(r => setTimeout(r, 2000 + Math.random() * 1000));
124
-
125
- await page.realCursor.move('body', { paddingPercentage: 20 });
126
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
127
- await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
128
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
129
-
130
- const check = await page.evaluate(() => {
131
- const hasLogo = !!document.querySelector('a[href*="hermes"]') || !!document.querySelector('[class*="logo"]');
132
- const hasNav = !!document.querySelector('nav') || !!document.querySelector('[class*="header"]') || !!document.querySelector('[class*="navigation"]');
133
- const hasContent = document.body.innerText.length > 500;
134
- const notBlocked = !document.body.innerText.toLowerCase().includes('blocked') &&
135
- !document.body.innerText.toLowerCase().includes('captcha');
136
- return (hasLogo || hasNav) && hasContent && notBlocked;
137
- }).catch(() => false);
138
-
139
- assert.strictEqual(check, true, "Datadome Bot Detector test failed! [This may also be because your ip address has a high spam score. Please try with a clean ip address.]");
140
- })
141
-
142
- test('Recaptcha V3 Score', async () => {
143
- await page.goto("https://antcpt.com/score_detector/", { timeout: 60000 });
144
-
145
- await page.realCursor.move('body', { paddingPercentage: 20 });
146
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
147
-
148
- await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
149
- await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
150
-
151
- await page.realCursor.move('button', { paddingPercentage: 10 });
152
- await new Promise(r => setTimeout(r, 300 + Math.random() * 300));
153
-
154
- await page.realClick("button")
155
- await new Promise(r => setTimeout(r, 5000));
156
-
157
- const score = await page.evaluate(() => {
158
- return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
159
- })
160
- assert.strictEqual(Number(score) >= 0.3, true, "(please first check if you can access https://antcpt.com/score_detector/.) Recaptcha V3 Score should be >=0.3 (not obviously a bot). Score Result: " + score)
161
- })
162
-
163
- test('Pixelscan Fingerprint Check', async () => {
164
- await page.goto("https://pixelscan.net/fingerprint-check", { waitUntil: 'domcontentloaded', timeout: 60000 });
165
-
166
- // Poll for the final status. We look specifically at the green header and the fingerprint checker card.
167
- let result = false;
168
- const startTime = Date.now();
169
- while (!result && (Date.now() - startTime) < 30000) {
170
- result = await page.evaluate(() => {
171
- const statusBar = document.querySelector('.status-content');
172
- if (!statusBar) return false;
173
-
174
- const statusText = statusBar.innerText.toLowerCase();
175
- const isConsistent = statusText.includes('consistent') && !statusText.includes('inconsistent');
176
-
177
- const cards = Array.from(document.querySelectorAll('.checker-card'));
178
- if (cards.length === 0) return false;
179
-
180
- // Check if scanning is still in progress
181
- const isScanning = cards.some(c => c.innerText.toLowerCase().includes('scanning') || c.innerText.toLowerCase().includes('collecting'));
182
- if (isScanning) return false;
183
-
184
- // Verify if fingerprint card shows masking detected
185
- const fingerprintCard = cards.find(c => c.innerText.toLowerCase().includes('fingerprint'));
186
- if (!fingerprintCard) return false;
187
- const hasMasking = fingerprintCard.innerText.toLowerCase().includes('masking detected') && !fingerprintCard.innerText.toLowerCase().includes('no masking');
188
-
189
- return isConsistent && !hasMasking;
190
- }).catch(() => false);
191
- if (!result) await new Promise(r => setTimeout(r, 1000));
192
- }
193
-
194
- // Wait 10 seconds so the user can clearly see the final green scan results on screen
195
- await new Promise(r => setTimeout(r, 10000));
196
-
197
- assert.strictEqual(result, true, "Pixelscan Fingerprint Check failed! Browser fingerprint is inconsistent or masking was detected.")
198
- })
199
-
200
- test('CreepJS Fingerprint Analysis', async () => {
201
- await page.goto("https://abrahamjuliot.github.io/creepjs/", { waitUntil: 'domcontentloaded', timeout: 60000 });
202
- await new Promise(r => setTimeout(r, 15000));
203
-
204
- const result = await page.evaluate(() => {
205
- const pageText = document.body.innerText;
206
-
207
- const headlessSection = pageText.match(/(\d+)%\s*headless/i);
208
- const headlessPercent = headlessSection ? parseInt(headlessSection[1]) : 100;
209
-
210
- const stealthSection = pageText.match(/(\d+)%\s*stealth/i);
211
- const stealthPercent = stealthSection ? parseInt(stealthSection[1]) : 100;
212
-
213
- const liesMatch = pageText.match(/(\d+)\s*lie/i);
214
- const liesCount = liesMatch ? parseInt(liesMatch[1]) : 0;
215
-
216
- return {
217
- headlessPercent,
218
- stealthPercent,
219
- liesCount,
220
- passed: headlessPercent === 0 && stealthPercent === 0 && liesCount <= 2
221
- };
222
- }).catch(() => ({ passed: false, headlessPercent: -1, stealthPercent: -1, liesCount: -1 }));
223
-
224
- assert.strictEqual(result.passed, true,
225
- `CreepJS Fingerprint Analysis failed! Headless: ${result.headlessPercent}%, Stealth: ${result.stealthPercent}%, Lies: ${result.liesCount}`)
226
- })