real-browser-mcp-server 1.2.0 → 1.2.2
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.
- package/README.md +96 -9
- package/dist/lib/cjs/index.d.ts +2 -0
- package/dist/lib/cjs/index.d.ts.map +1 -0
- package/dist/lib/cjs/index.js +385 -0
- package/dist/lib/cjs/index.js.map +1 -0
- package/dist/lib/cjs/module/pageController.d.ts +2 -0
- package/dist/lib/cjs/module/pageController.d.ts.map +1 -0
- package/{lib → dist/lib}/cjs/module/pageController.js +28 -29
- package/dist/lib/cjs/module/pageController.js.map +1 -0
- package/dist/lib/cjs/module/turnstile.d.ts +2 -0
- package/dist/lib/cjs/module/turnstile.d.ts.map +1 -0
- package/{lib → dist/lib}/cjs/module/turnstile.js +24 -12
- package/dist/lib/cjs/module/turnstile.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +118 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/handlers/browser.d.ts +30 -0
- package/dist/src/mcp/handlers/browser.d.ts.map +1 -0
- package/dist/src/mcp/handlers/browser.js +231 -0
- package/dist/src/mcp/handlers/browser.js.map +1 -0
- package/dist/src/mcp/handlers/dom.d.ts +134 -0
- package/dist/src/mcp/handlers/dom.d.ts.map +1 -0
- package/dist/src/mcp/handlers/dom.js +551 -0
- package/dist/src/mcp/handlers/dom.js.map +1 -0
- package/dist/src/mcp/handlers/extract.d.ts +59 -0
- package/dist/src/mcp/handlers/extract.d.ts.map +1 -0
- package/dist/src/mcp/handlers/extract.js +455 -0
- package/dist/src/mcp/handlers/extract.js.map +1 -0
- package/dist/src/mcp/handlers/form-handlers.d.ts +9 -0
- package/dist/src/mcp/handlers/form-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/form-handlers.js +56 -0
- package/dist/src/mcp/handlers/form-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/helpers.d.ts +47 -0
- package/dist/src/mcp/handlers/helpers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/helpers.js +515 -0
- package/dist/src/mcp/handlers/helpers.js.map +1 -0
- package/dist/src/mcp/handlers/index.d.ts +6 -0
- package/dist/src/mcp/handlers/index.d.ts.map +1 -0
- package/dist/src/mcp/handlers/index.js +61 -0
- package/dist/src/mcp/handlers/index.js.map +1 -0
- package/dist/src/mcp/handlers/media-handlers.d.ts +10 -0
- package/dist/src/mcp/handlers/media-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/media-handlers.js +535 -0
- package/dist/src/mcp/handlers/media-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/network.d.ts +147 -0
- package/dist/src/mcp/handlers/network.d.ts.map +1 -0
- package/dist/src/mcp/handlers/network.js +1135 -0
- package/dist/src/mcp/handlers/network.js.map +1 -0
- package/dist/src/mcp/handlers/state.d.ts +34 -0
- package/dist/src/mcp/handlers/state.d.ts.map +1 -0
- package/dist/src/mcp/handlers/state.js +225 -0
- package/dist/src/mcp/handlers/state.js.map +1 -0
- package/dist/src/mcp/handlers/utility-handlers.d.ts +167 -0
- package/dist/src/mcp/handlers/utility-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/utility-handlers.js +280 -0
- package/dist/src/mcp/handlers/utility-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/vision.d.ts +127 -0
- package/dist/src/mcp/handlers/vision.d.ts.map +1 -0
- package/dist/src/mcp/handlers/vision.js +483 -0
- package/dist/src/mcp/handlers/vision.js.map +1 -0
- package/dist/src/mcp/index.d.ts +3 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +166 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +117 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools.d.ts +8 -0
- package/dist/src/mcp/tools.d.ts.map +1 -0
- package/{src → dist/src}/mcp/tools.js +12 -11
- package/dist/src/mcp/tools.js.map +1 -0
- package/dist/src/shared/cache-manager.d.ts +80 -0
- package/dist/src/shared/cache-manager.d.ts.map +1 -0
- package/dist/src/shared/cache-manager.js +221 -0
- package/dist/src/shared/cache-manager.js.map +1 -0
- package/dist/src/shared/tools.d.ts +2 -0
- package/dist/src/shared/tools.d.ts.map +1 -0
- package/dist/src/shared/tools.js +599 -0
- package/dist/src/shared/tools.js.map +1 -0
- package/dist/src/types.d.ts +365 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/test/cjs/test.d.ts +11 -0
- package/dist/test/cjs/test.d.ts.map +1 -0
- package/dist/test/cjs/test.js +289 -0
- package/dist/test/cjs/test.js.map +1 -0
- package/dist/test/mcp/smoke-test.d.ts +29 -0
- package/dist/test/mcp/smoke-test.d.ts.map +1 -0
- package/dist/test/mcp/smoke-test.js +132 -0
- package/dist/test/mcp/smoke-test.js.map +1 -0
- package/lib/esm/index.mjs +232 -79
- package/lib/esm/module/pageController.mjs +21 -18
- package/lib/esm/module/turnstile.mjs +7 -0
- package/package.json +25 -15
- package/typings.d.ts +12 -6
- package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
- package/.github/SETUP.md +0 -111
- package/.github/workflows/publish.yml +0 -135
- package/Dockerfile +0 -79
- package/lib/cjs/adblocker.bin +0 -0
- package/lib/cjs/index.js +0 -249
- package/src/ai/action-parser.js +0 -274
- package/src/ai/core.js +0 -378
- package/src/ai/element-finder.js +0 -466
- package/src/ai/index.js +0 -82
- package/src/ai/page-analyzer.js +0 -304
- package/src/ai/selector-healer.js +0 -236
- package/src/index.js +0 -121
- package/src/mcp/handlers.js +0 -5071
- package/src/mcp/index.js +0 -190
- package/src/mcp/server.js +0 -144
- package/src/shared/tools.js +0 -618
- package/test/cjs/test.js +0 -259
- package/test/esm/package.json +0 -13
- package/test/esm/test.js +0 -226
- package/test/esm/test_option2.js +0 -46
- package/test/esm/test_playwright_ghost.js +0 -30
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
|
-
})
|
package/test/esm/package.json
DELETED
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
|
-
})
|
package/test/esm/test_option2.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import playwright from "playwright-ghost/patchright";
|
|
2
|
-
import recommended from "playwright-ghost/plugins/recommended";
|
|
3
|
-
|
|
4
|
-
console.log("🚀 Starting Option 2 Demonstration...");
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
// Launch browser with playwright-ghost overlay & recommended stealth/humanize plugins
|
|
8
|
-
console.log("Launching browser with Option 2 (playwright-ghost overlay & recommended plugins)...");
|
|
9
|
-
const browser = await playwright.chromium.launch({
|
|
10
|
-
headless: false, // Set to false so you can see it if run locally, but works in headless too
|
|
11
|
-
plugins: [recommended()]
|
|
12
|
-
});
|
|
13
|
-
console.log("✅ Browser launched successfully!");
|
|
14
|
-
|
|
15
|
-
const context = await browser.newContext();
|
|
16
|
-
const page = await context.newPage();
|
|
17
|
-
console.log("✅ Page created!");
|
|
18
|
-
|
|
19
|
-
// Go to DrissionPage Detector
|
|
20
|
-
console.log("Navigating to DrissionPage Detector page...");
|
|
21
|
-
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout: 60000 });
|
|
22
|
-
console.log("✅ Page loaded successfully!");
|
|
23
|
-
|
|
24
|
-
console.log("Triggering normal, standard page.click('#detector')!");
|
|
25
|
-
console.log("👉 Option 2 will intercept this click, calculate a Bézier curve path, scroll it into view, move the cursor humanly, and then click!");
|
|
26
|
-
|
|
27
|
-
const startTime = Date.now();
|
|
28
|
-
await page.click("#detector");
|
|
29
|
-
console.log(`✅ Standard click intercepted and completed in ${(Date.now() - startTime) / 1000}s!`);
|
|
30
|
-
|
|
31
|
-
// Verify the detector says "not a bot"
|
|
32
|
-
const isNotBot = await page.evaluate(() => {
|
|
33
|
-
return document.querySelector('#isBot span').textContent.includes("not");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
if (isNotBot) {
|
|
37
|
-
console.log("🎉 SUCCESS! The detector passed: 'not a bot'.");
|
|
38
|
-
} else {
|
|
39
|
-
console.log("❌ FAILED! Detected as bot.");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
await browser.close();
|
|
43
|
-
console.log("🏁 Browser closed. Demonstration completed successfully.");
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error("❌ An error occurred during the demonstration:", error);
|
|
46
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import playwright from "playwright-ghost/patchright";
|
|
2
|
-
import recommended from "playwright-ghost/plugins/recommended";
|
|
3
|
-
|
|
4
|
-
console.log("🚀 Testing playwright-ghost integration...");
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
const browser = await playwright.chromium.launch({
|
|
8
|
-
headless: true,
|
|
9
|
-
plugins: [recommended()]
|
|
10
|
-
});
|
|
11
|
-
console.log("✅ Browser launched successfully with playwright-ghost!");
|
|
12
|
-
|
|
13
|
-
const context = await browser.newContext();
|
|
14
|
-
const page = await context.newPage();
|
|
15
|
-
console.log("✅ Context and page created!");
|
|
16
|
-
|
|
17
|
-
console.log("Navigating to simple website...");
|
|
18
|
-
await page.goto("https://example.com");
|
|
19
|
-
console.log("✅ Navigated successfully!");
|
|
20
|
-
|
|
21
|
-
console.log("Attempting humanized click on link...");
|
|
22
|
-
const link = page.locator("a");
|
|
23
|
-
await link.click();
|
|
24
|
-
console.log("✅ Clicked successfully!");
|
|
25
|
-
|
|
26
|
-
await browser.close();
|
|
27
|
-
console.log("🎉 Test completed successfully!");
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error("❌ Test failed:", error);
|
|
30
|
-
}
|