real-browser-mcp-server 1.3.4 → 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 +229 -184
  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 -352
  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 -267
  117. package/test/esm/package.json +0 -13
  118. package/test/esm/test.js +0 -235
package/test/cjs/test.js DELETED
@@ -1,267 +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: true,
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
- if (realBrowserOption.headless) {
52
- console.log('⚠️ Skipping Cloudflare WAF on headless mode (nopecha Cloudflare demo blocks standard headless runs)');
53
- return;
54
- }
55
- await page.goto("https://nopecha.com/demo/cloudflare", { timeout: 60000 });
56
- let verify = null
57
- let startDate = Date.now()
58
- // Increased timeout to 60 seconds to allow turnstile to be solved
59
- while (!verify && (Date.now() - startDate) < 50000) {
60
- verify = await page.evaluate(() => {
61
- // Check if we passed the challenge - look for main content
62
- return document.querySelector('.link_row') || document.querySelector('a[href*="nopecha"]') ? true : null
63
- }).catch(() => null)
64
- await new Promise(r => setTimeout(r, 2000));
65
- }
66
- assert.strictEqual(verify === true, true, "Cloudflare WAF test failed! (Site may be blocking automated access)")
67
- })
68
-
69
-
70
- test('Cloudflare Turnstile', async () => {
71
- await page.goto("https://2captcha.com/demo/cloudflare-turnstile", { timeout: 60000 });
72
- await page.waitForSelector('.cf-turnstile')
73
- let token = null
74
- let startDate = Date.now()
75
- while (!token && (Date.now() - startDate) < 30000) {
76
- token = await page.evaluate(() => {
77
- try {
78
- let item = document.querySelector('[name="cf-turnstile-response"]')?.value
79
- return item && item.length > 20 ? item : null
80
- } catch (e) {
81
- return null
82
- }
83
- })
84
- await new Promise(r => setTimeout(r, 1000));
85
- }
86
- assert.strictEqual(token !== null, true, "Cloudflare turnstile test failed!")
87
- })
88
-
89
-
90
-
91
- test('Fingerprint JS Bot Detector', async () => {
92
- // Use domcontentloaded + higher timeout to avoid timeout on heavy pages
93
- await page.goto("https://fingerprint.com/products/bot-detection/", { waitUntil: 'domcontentloaded', timeout: 60000 });
94
- await new Promise(r => setTimeout(r, 5000));
95
- const detect = await page.evaluate(() => {
96
- // Check for bot detection result in page content
97
- const pageText = document.body.innerText.toLowerCase();
98
-
99
- // If page loaded successfully without bot block, test passes
100
- // Fingerprint.com shows their product page, not a block page
101
- const isProductPage = pageText.includes('bot detection') ||
102
- pageText.includes('fingerprint') ||
103
- document.querySelector('h1') !== null;
104
-
105
- // Check if we're NOT blocked (no captcha, no access denied)
106
- const isNotBlocked = !pageText.includes('access denied') &&
107
- !pageText.includes('blocked') &&
108
- !pageText.includes('captcha');
109
-
110
- // Check in pre/code blocks for notDetected result or in page text
111
- const preElements = document.querySelectorAll('pre, code');
112
- for (const el of preElements) {
113
- if (el.textContent.includes('notDetected') || el.textContent.includes('"result": "notDetected"')) {
114
- return true;
115
- }
116
- }
117
- // Fallback: check any element with partial class match
118
- const allElements = document.querySelectorAll('*');
119
- for (const el of allElements) {
120
- for (const cls of el.classList) {
121
- if (cls.includes('botSubTitle') && el.textContent.toLowerCase().includes('not')) {
122
- return true;
123
- }
124
- }
125
- }
126
-
127
- // If product page loaded and not blocked, we passed
128
- return isProductPage && isNotBlocked;
129
- })
130
- assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
131
- })
132
-
133
-
134
- // Datadome Bot Detector - Tests against a real Datadome-protected website (hermes.com)
135
- test('Datadome Bot Detector', async (t) => {
136
- // Navigate to hermes.com which is protected by Datadome
137
- await page.goto("https://www.hermes.com/us/en/", { waitUntil: 'domcontentloaded', timeout: 60000 });
138
- await new Promise(r => setTimeout(r, 2000 + Math.random() * 1000));
139
-
140
- // Human-like behavior on the page
141
- await page.realCursor.move('body', { paddingPercentage: 20 });
142
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
143
- await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
144
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
145
-
146
- // Check if main page content loaded (Datadome bypass successful)
147
- // Look for Hermès logo or main navigation elements that only appear after Datadome clears
148
- const check = await page.evaluate(() => {
149
- // Check for Hermès header/logo/navigation elements
150
- const hasLogo = !!document.querySelector('a[href*="hermes"]') || !!document.querySelector('[class*="logo"]');
151
- const hasNav = !!document.querySelector('nav') || !!document.querySelector('[class*="header"]') || !!document.querySelector('[class*="navigation"]');
152
- const hasContent = document.body.innerText.length > 500; // Real page has significant content
153
- const notBlocked = !document.body.innerText.toLowerCase().includes('blocked') &&
154
- !document.body.innerText.toLowerCase().includes('captcha');
155
- return (hasLogo || hasNav) && hasContent && notBlocked;
156
- }).catch(() => false);
157
-
158
- 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.]");
159
- })
160
-
161
- // If this test fails, please first check if you can access https://antcpt.com/score_detector/
162
- // Note: ReCAPTCHA V3 score depends heavily on IP reputation, browser history, and Google's algorithms.
163
- // A score >= 0.3 indicates the browser is not detected as an obvious bot.
164
- test('Recaptcha V3 Score', async () => {
165
- await page.goto("https://antcpt.com/score_detector/", { timeout: 60000 });
166
-
167
- // Human-like warm-up interactions before clicking
168
- // 1. Random mouse movements using realCursor (Bézier curves via ghost-cursor)
169
- await page.realCursor.move('body', { paddingPercentage: 20 });
170
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
171
-
172
- // 2. Scroll down a bit to simulate reading
173
- await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
174
- await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
175
-
176
- // 3. Move mouse towards button area naturally
177
- await page.realCursor.move('button', { paddingPercentage: 10 });
178
- await new Promise(r => setTimeout(r, 300 + Math.random() * 300));
179
-
180
- // 4. Now click the button
181
- await page.realClick("button")
182
- await new Promise(r => setTimeout(r, 5000));
183
-
184
- const score = await page.evaluate(() => {
185
- return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
186
- })
187
- // 0.3+ means browser is not obviously a bot. Higher scores depend on IP reputation.
188
- 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)
189
- })
190
-
191
- // Pixelscan Fingerprint Consistency Check
192
- // Checks browser fingerprint consistency, automation detection, and proxy detection
193
- test('Pixelscan Fingerprint Check', async () => {
194
- if (realBrowserOption.headless) {
195
- console.log('⚠️ Skipping Pixelscan Fingerprint Check in headless mode (Pixelscan always detects hardware-level headless signatures)');
196
- return;
197
- }
198
- await page.goto("https://pixelscan.net/fingerprint-check", { waitUntil: 'domcontentloaded', timeout: 60000 });
199
-
200
- // Poll for the final status. We look specifically at the green header and the fingerprint checker card.
201
- let result = false;
202
- const startTime = Date.now();
203
- while (!result && (Date.now() - startTime) < 30000) {
204
- result = await page.evaluate(() => {
205
- const statusBar = document.querySelector('.status-content');
206
- if (!statusBar) return false;
207
-
208
- const statusText = statusBar.innerText.toLowerCase();
209
- const isConsistent = statusText.includes('consistent') && !statusText.includes('inconsistent');
210
-
211
- const cards = Array.from(document.querySelectorAll('.checker-card'));
212
- if (cards.length === 0) return false;
213
-
214
- // Check if scanning is still in progress
215
- const isScanning = cards.some(c => c.innerText.toLowerCase().includes('scanning') || c.innerText.toLowerCase().includes('collecting'));
216
- if (isScanning) return false;
217
-
218
- // Verify if fingerprint card shows masking detected
219
- const fingerprintCard = cards.find(c => c.innerText.toLowerCase().includes('fingerprint'));
220
- if (!fingerprintCard) return false;
221
- const hasMasking = fingerprintCard.innerText.toLowerCase().includes('masking detected') && !fingerprintCard.innerText.toLowerCase().includes('no masking');
222
-
223
- return isConsistent && !hasMasking;
224
- }).catch(() => false);
225
- if (!result) await new Promise(r => setTimeout(r, 1000));
226
- }
227
-
228
- // Wait 10 seconds so the user can clearly see the final green scan results on screen
229
- await new Promise(r => setTimeout(r, 10000));
230
-
231
- assert.strictEqual(result, true, "Pixelscan Fingerprint Check failed! Browser fingerprint is inconsistent or masking was detected.")
232
- })
233
-
234
- // CreepJS Deep Fingerprint Analysis
235
- // The most comprehensive fingerprint analyzer - checks lies, headless, stealth, trust score
236
- test('CreepJS Fingerprint Analysis', async () => {
237
- await page.goto("https://abrahamjuliot.github.io/creepjs/", { waitUntil: 'domcontentloaded', timeout: 60000 });
238
- await new Promise(r => setTimeout(r, 15000)); // CreepJS needs time to run all checks
239
-
240
- const result = await page.evaluate((isHeadlessOption) => {
241
- const pageText = document.body.innerText;
242
-
243
- // Check headless detection section
244
- // Look for "0% headless" which means not detected as headless
245
- const headlessSection = pageText.match(/(\d+)%\s*headless/i);
246
- const headlessPercent = headlessSection ? parseInt(headlessSection[1]) : 100;
247
-
248
- // Check stealth detection
249
- const stealthSection = pageText.match(/(\d+)%\s*stealth/i);
250
- const stealthPercent = stealthSection ? parseInt(stealthSection[1]) : 100;
251
-
252
- // Check lies detection - "0 lies" or low count is good
253
- const liesMatch = pageText.match(/(\d+)\s*lie/i);
254
- const liesCount = liesMatch ? parseInt(liesMatch[1]) : 0;
255
-
256
- return {
257
- headlessPercent,
258
- stealthPercent,
259
- liesCount,
260
- // Pass if: 0% headless (or <= 67% in headless mode), 0% stealth, and lies count is low
261
- passed: (headlessPercent === 0 || (isHeadlessOption && headlessPercent <= 67)) && stealthPercent === 0 && liesCount <= 2
262
- };
263
- }, realBrowserOption.headless).catch(() => ({ passed: false, headlessPercent: -1, stealthPercent: -1, liesCount: -1 }));
264
-
265
- assert.strictEqual(result.passed, true,
266
- `CreepJS Fingerprint Analysis failed! Headless: ${result.headlessPercent}%, Stealth: ${result.stealthPercent}%, Lies: ${result.liesCount}`)
267
- })
@@ -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,235 +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: true,
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
- if (realBrowserOption.headless) {
52
- console.log('⚠️ Skipping Cloudflare WAF on headless mode (nopecha Cloudflare demo blocks standard headless runs)');
53
- return;
54
- }
55
- await page.goto("https://nopecha.com/demo/cloudflare", { timeout: 60000 });
56
- let verify = null
57
- let startDate = Date.now()
58
- while (!verify && (Date.now() - startDate) < 50000) {
59
- verify = await page.evaluate(() => {
60
- return document.querySelector('.link_row') || document.querySelector('a[href*="nopecha"]') ? true : null
61
- }).catch(() => null)
62
- await new Promise(r => setTimeout(r, 2000));
63
- }
64
- assert.strictEqual(verify === true, true, "Cloudflare WAF test failed! (Site may be blocking automated access)")
65
- })
66
-
67
-
68
- test('Cloudflare Turnstile', async () => {
69
- await page.goto("https://2captcha.com/demo/cloudflare-turnstile", { timeout: 60000 });
70
- await page.waitForSelector('.cf-turnstile')
71
- let token = null
72
- let startDate = Date.now()
73
- while (!token && (Date.now() - startDate) < 30000) {
74
- token = await page.evaluate(() => {
75
- try {
76
- let item = document.querySelector('[name="cf-turnstile-response"]')?.value
77
- return item && item.length > 20 ? item : null
78
- } catch (e) {
79
- return null
80
- }
81
- })
82
- await new Promise(r => setTimeout(r, 1000));
83
- }
84
- assert.strictEqual(token !== null, true, "Cloudflare turnstile test failed!")
85
- })
86
-
87
-
88
-
89
- test('Fingerprint JS Bot Detector', async () => {
90
- await page.goto("https://fingerprint.com/products/bot-detection/", { waitUntil: 'domcontentloaded', timeout: 60000 });
91
- await new Promise(r => setTimeout(r, 5000));
92
- const detect = await page.evaluate(() => {
93
- const pageText = document.body.innerText.toLowerCase();
94
-
95
- const isProductPage = pageText.includes('bot detection') ||
96
- pageText.includes('fingerprint') ||
97
- document.querySelector('h1') !== null;
98
-
99
- const isNotBlocked = !pageText.includes('access denied') &&
100
- !pageText.includes('blocked') &&
101
- !pageText.includes('captcha');
102
-
103
- const preElements = document.querySelectorAll('pre, code');
104
- for (const el of preElements) {
105
- if (el.textContent.includes('notDetected') || el.textContent.includes('"result": "notDetected"')) {
106
- return true;
107
- }
108
- }
109
- const allElements = document.querySelectorAll('*');
110
- for (const el of allElements) {
111
- for (const cls of el.classList) {
112
- if (cls.includes('botSubTitle') && el.textContent.toLowerCase().includes('not')) {
113
- return true;
114
- }
115
- }
116
- }
117
-
118
- return isProductPage && isNotBlocked;
119
- })
120
- assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
121
- })
122
-
123
-
124
- // Datadome Bot Detector - Tests against a real Datadome-protected website (hermes.com)
125
- test('Datadome Bot Detector', async (t) => {
126
- await page.goto("https://www.hermes.com/us/en/", { waitUntil: 'domcontentloaded', timeout: 60000 });
127
- await new Promise(r => setTimeout(r, 2000 + Math.random() * 1000));
128
-
129
- await page.realCursor.move('body', { paddingPercentage: 20 });
130
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
131
- await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
132
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
133
-
134
- const check = await page.evaluate(() => {
135
- const hasLogo = !!document.querySelector('a[href*="hermes"]') || !!document.querySelector('[class*="logo"]');
136
- const hasNav = !!document.querySelector('nav') || !!document.querySelector('[class*="header"]') || !!document.querySelector('[class*="navigation"]');
137
- const hasContent = document.body.innerText.length > 500;
138
- const notBlocked = !document.body.innerText.toLowerCase().includes('blocked') &&
139
- !document.body.innerText.toLowerCase().includes('captcha');
140
- return (hasLogo || hasNav) && hasContent && notBlocked;
141
- }).catch(() => false);
142
-
143
- 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.]");
144
- })
145
-
146
- test('Recaptcha V3 Score', async () => {
147
- await page.goto("https://antcpt.com/score_detector/", { timeout: 60000 });
148
-
149
- await page.realCursor.move('body', { paddingPercentage: 20 });
150
- await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
151
-
152
- await page.mouse.wheel({ deltaY: 100 + Math.random() * 100 });
153
- await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
154
-
155
- await page.realCursor.move('button', { paddingPercentage: 10 });
156
- await new Promise(r => setTimeout(r, 300 + Math.random() * 300));
157
-
158
- await page.realClick("button")
159
- await new Promise(r => setTimeout(r, 5000));
160
-
161
- const score = await page.evaluate(() => {
162
- return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
163
- })
164
- 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)
165
- })
166
-
167
- test('Pixelscan Fingerprint Check', async () => {
168
- if (realBrowserOption.headless) {
169
- console.log('⚠️ Skipping Pixelscan Fingerprint Check in headless mode (Pixelscan always detects hardware-level headless signatures)');
170
- return;
171
- }
172
- await page.goto("https://pixelscan.net/fingerprint-check", { waitUntil: 'domcontentloaded', timeout: 60000 });
173
-
174
- // Poll for the final status. We look specifically at the green header and the fingerprint checker card.
175
- let result = false;
176
- const startTime = Date.now();
177
- while (!result && (Date.now() - startTime) < 30000) {
178
- result = await page.evaluate(() => {
179
- const statusBar = document.querySelector('.status-content');
180
- if (!statusBar) return false;
181
-
182
- const statusText = statusBar.innerText.toLowerCase();
183
- const isConsistent = statusText.includes('consistent') && !statusText.includes('inconsistent');
184
-
185
- const cards = Array.from(document.querySelectorAll('.checker-card'));
186
- if (cards.length === 0) return false;
187
-
188
- // Check if scanning is still in progress
189
- const isScanning = cards.some(c => c.innerText.toLowerCase().includes('scanning') || c.innerText.toLowerCase().includes('collecting'));
190
- if (isScanning) return false;
191
-
192
- // Verify if fingerprint card shows masking detected
193
- const fingerprintCard = cards.find(c => c.innerText.toLowerCase().includes('fingerprint'));
194
- if (!fingerprintCard) return false;
195
- const hasMasking = fingerprintCard.innerText.toLowerCase().includes('masking detected') && !fingerprintCard.innerText.toLowerCase().includes('no masking');
196
-
197
- return isConsistent && !hasMasking;
198
- }).catch(() => false);
199
- if (!result) await new Promise(r => setTimeout(r, 1000));
200
- }
201
-
202
- // Wait 10 seconds so the user can clearly see the final green scan results on screen
203
- await new Promise(r => setTimeout(r, 10000));
204
-
205
- assert.strictEqual(result, true, "Pixelscan Fingerprint Check failed! Browser fingerprint is inconsistent or masking was detected.")
206
- })
207
-
208
- test('CreepJS Fingerprint Analysis', async () => {
209
- await page.goto("https://abrahamjuliot.github.io/creepjs/", { waitUntil: 'domcontentloaded', timeout: 60000 });
210
- await new Promise(r => setTimeout(r, 15000));
211
-
212
- const result = await page.evaluate((isHeadlessOption) => {
213
- const pageText = document.body.innerText;
214
-
215
- const headlessSection = pageText.match(/(\d+)%\s*headless/i);
216
- const headlessPercent = headlessSection ? parseInt(headlessSection[1]) : 100;
217
-
218
- const stealthSection = pageText.match(/(\d+)%\s*stealth/i);
219
- const stealthPercent = stealthSection ? parseInt(stealthSection[1]) : 100;
220
-
221
- const liesMatch = pageText.match(/(\d+)\s*lie/i);
222
- const liesCount = liesMatch ? parseInt(liesMatch[1]) : 0;
223
-
224
- return {
225
- headlessPercent,
226
- stealthPercent,
227
- liesCount,
228
- // Pass if: 0% headless (or <= 67% in headless mode), 0% stealth, and lies count is low
229
- passed: (headlessPercent === 0 || (isHeadlessOption && headlessPercent <= 67)) && stealthPercent === 0 && liesCount <= 2
230
- };
231
- }, realBrowserOption.headless).catch(() => ({ passed: false, headlessPercent: -1, stealthPercent: -1, liesCount: -1 }));
232
-
233
- assert.strictEqual(result.passed, true,
234
- `CreepJS Fingerprint Analysis failed! Headless: ${result.headlessPercent}%, Stealth: ${result.stealthPercent}%, Lies: ${result.liesCount}`)
235
- })