real-browser-mcp-server 1.1.1 β 1.1.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/Dockerfile +0 -1
- package/README.md +62 -2
- package/lib/cjs/index.js +231 -84
- package/lib/esm/index.mjs +232 -79
- package/package.json +5 -6
- package/src/ai/core.js +1 -1
- package/src/index.js +8 -1
- package/src/mcp/handlers.js +33 -17
- package/src/mcp/server.js +1 -1
- package/test/cjs/test.js +143 -66
- package/test/esm/{test.js β test.mjs} +131 -58
- package/typings.d.ts +12 -6
- package/test/esm/package.json +0 -13
- package/test/esm/test_option2.js +0 -46
- package/test/esm/test_playwright_ghost.js +0 -30
package/test/cjs/test.js
CHANGED
|
@@ -30,6 +30,126 @@ test.after(async () => {
|
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
test('Human-like Move & Click', async () => {
|
|
34
|
+
await page.goto("https://www.google.com", { timeout: 40000 });
|
|
35
|
+
const selector = 'textarea[name="q"], input[name="q"]';
|
|
36
|
+
await page.realCursor.move(selector);
|
|
37
|
+
await page.realClick(selector);
|
|
38
|
+
assert.ok(true);
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('Human-like Typing', async () => {
|
|
42
|
+
await page.goto("https://www.google.com", { timeout: 40000 });
|
|
43
|
+
const selector = 'textarea[name="q"], input[name="q"]';
|
|
44
|
+
await page.realCursor.move(selector);
|
|
45
|
+
await page.realClick(selector);
|
|
46
|
+
await page.type(selector, 'Real Browser MCP Server', { delay: 150 });
|
|
47
|
+
const val = await page.inputValue(selector);
|
|
48
|
+
assert.strictEqual(val, 'Real Browser MCP Server');
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('Human-like Scrolling', async () => {
|
|
52
|
+
await page.goto("https://www.google.com/search?q=Real+Browser+MCP+Server", { timeout: 40000, waitUntil: 'domcontentloaded' });
|
|
53
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
54
|
+
|
|
55
|
+
console.log('π Scrolling down smoothly and fast (400px)...');
|
|
56
|
+
await page.realScroll(400, 500);
|
|
57
|
+
await new Promise(r => setTimeout(r, 600));
|
|
58
|
+
|
|
59
|
+
console.log('π Scrolling down smoothly and fast (300px)...');
|
|
60
|
+
await page.realScroll(300, 400);
|
|
61
|
+
await new Promise(r => setTimeout(r, 600));
|
|
62
|
+
|
|
63
|
+
console.log('π Scrolling up smoothly and fast (-500px)...');
|
|
64
|
+
await page.realScroll(-500, 600);
|
|
65
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
66
|
+
|
|
67
|
+
assert.ok(true);
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('Form Automation Demonstration', async () => {
|
|
71
|
+
console.log('\n㪠DEMO: Form Automation');
|
|
72
|
+
try {
|
|
73
|
+
await page.goto('https://httpbin.org/forms/post', { timeout: 30000 });
|
|
74
|
+
console.log('\n4οΈβ£ Filling out form...');
|
|
75
|
+
|
|
76
|
+
// 1. Customer Name
|
|
77
|
+
await page.type('input[name="custname"]', 'John Doe', { delay: 100 });
|
|
78
|
+
console.log('β
Customer Name filled');
|
|
79
|
+
|
|
80
|
+
// 2. Telephone
|
|
81
|
+
await page.type('input[name="custtel"]', '+1-555-0199', { delay: 100 });
|
|
82
|
+
console.log('β
Telephone filled');
|
|
83
|
+
|
|
84
|
+
// 3. Email address
|
|
85
|
+
await page.type('input[name="custemail"]', 'john.doe@example.com', { delay: 100 });
|
|
86
|
+
console.log('β
Email field filled');
|
|
87
|
+
|
|
88
|
+
// 4. Pizza Size (Radio Button)
|
|
89
|
+
await page.realClick('input[value="medium"]');
|
|
90
|
+
console.log('β
Pizza Size selected (Medium)');
|
|
91
|
+
|
|
92
|
+
// 5. Pizza Toppings (Checkboxes)
|
|
93
|
+
await page.realClick('input[value="bacon"]');
|
|
94
|
+
await page.realClick('input[value="onion"]');
|
|
95
|
+
console.log('β
Toppings selected (Bacon, Onion)');
|
|
96
|
+
|
|
97
|
+
// 6. Preferred Delivery Time
|
|
98
|
+
await page.type('input[name="delivery"]', '13:00', { delay: 100 });
|
|
99
|
+
console.log('β
Delivery time filled');
|
|
100
|
+
|
|
101
|
+
// 7. Delivery Instructions (Comments)
|
|
102
|
+
await page.type('textarea[name="comments"]', 'Leave at the front door, please.', { delay: 100 });
|
|
103
|
+
console.log('β
Delivery instructions filled');
|
|
104
|
+
|
|
105
|
+
// Wait 2 seconds for visual demonstration
|
|
106
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
107
|
+
|
|
108
|
+
// 8. Submit Order
|
|
109
|
+
await page.realClick('form button');
|
|
110
|
+
console.log('β
Form submitted successfully');
|
|
111
|
+
|
|
112
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
113
|
+
console.log('\nπ FORM AUTOMATION COMPLETE!');
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('β Form automation test failed:', error);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('Content Strategy Demonstration', async () => {
|
|
121
|
+
console.log('\n㪠DEMO: Content Analysis & Token Management');
|
|
122
|
+
console.log('π Watch browser analyze content from different websites');
|
|
123
|
+
try {
|
|
124
|
+
const testSites = [
|
|
125
|
+
{ url: 'https://httpbin.org/html', description: 'Simple HTML page' },
|
|
126
|
+
{ url: 'https://example.com', description: 'Minimal content page' }
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
for (const [index, site] of testSites.entries()) {
|
|
130
|
+
console.log(`\n${index + 2}οΈβ£ Testing ${site.description}: ${site.url}`);
|
|
131
|
+
await page.goto(site.url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
133
|
+
|
|
134
|
+
console.log(` π Getting HTML content...`);
|
|
135
|
+
const htmlContent = await page.content();
|
|
136
|
+
console.log(` β
HTML analyzed: ${htmlContent.length} characters`);
|
|
137
|
+
|
|
138
|
+
console.log(` π Getting text content...`);
|
|
139
|
+
const textContent = await page.evaluate(() => document.body.innerText);
|
|
140
|
+
console.log(` β
Text analyzed: ${textContent.length} characters`);
|
|
141
|
+
|
|
142
|
+
assert.ok(htmlContent.length > 0);
|
|
143
|
+
assert.ok(textContent.length > 0);
|
|
144
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
145
|
+
}
|
|
146
|
+
console.log('\nπ CONTENT ANALYSIS COMPLETE!');
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('β Content strategy test failed:', error);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
33
153
|
test('DrissionPage Detector', async () => {
|
|
34
154
|
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout: 60000 });
|
|
35
155
|
await page.realClick("#detector")
|
|
@@ -38,7 +158,7 @@ test('DrissionPage Detector', async () => {
|
|
|
38
158
|
})
|
|
39
159
|
|
|
40
160
|
test('Sannysoft WebDriver Detector', async () => {
|
|
41
|
-
await page.goto("https://bot.sannysoft.com/", { timeout:
|
|
161
|
+
await page.goto("https://bot.sannysoft.com/", { timeout: 70000 });
|
|
42
162
|
await new Promise(r => setTimeout(r, 3000));
|
|
43
163
|
let result = await page.evaluate(() => {
|
|
44
164
|
const webdriverEl = document.getElementById('webdriver-result');
|
|
@@ -48,7 +168,7 @@ test('Sannysoft WebDriver Detector', async () => {
|
|
|
48
168
|
})
|
|
49
169
|
|
|
50
170
|
test('Cloudflare WAF', async () => {
|
|
51
|
-
await page.goto("https://nopecha.com/demo/cloudflare", { timeout:
|
|
171
|
+
await page.goto("https://nopecha.com/demo/cloudflare", { timeout: 70000 });
|
|
52
172
|
let verify = null
|
|
53
173
|
let startDate = Date.now()
|
|
54
174
|
// Increased timeout to 60 seconds to allow turnstile to be solved
|
|
@@ -64,7 +184,7 @@ test('Cloudflare WAF', async () => {
|
|
|
64
184
|
|
|
65
185
|
|
|
66
186
|
test('Cloudflare Turnstile', async () => {
|
|
67
|
-
await page.goto("https://2captcha.com/demo/cloudflare-turnstile", { timeout:
|
|
187
|
+
await page.goto("https://2captcha.com/demo/cloudflare-turnstile", { timeout: 70000 });
|
|
68
188
|
await page.waitForSelector('.cf-turnstile')
|
|
69
189
|
let token = null
|
|
70
190
|
let startDate = Date.now()
|
|
@@ -86,7 +206,7 @@ test('Cloudflare Turnstile', async () => {
|
|
|
86
206
|
|
|
87
207
|
test('Fingerprint JS Bot Detector', async () => {
|
|
88
208
|
// Use domcontentloaded + higher timeout to avoid timeout on heavy pages
|
|
89
|
-
await page.goto("https://fingerprint.com/products/bot-detection/", { waitUntil: 'domcontentloaded', timeout:
|
|
209
|
+
await page.goto("https://fingerprint.com/products/bot-detection/", { waitUntil: 'domcontentloaded', timeout: 70000 });
|
|
90
210
|
await new Promise(r => setTimeout(r, 5000));
|
|
91
211
|
const detect = await page.evaluate(() => {
|
|
92
212
|
// Check for bot detection result in page content
|
|
@@ -126,39 +246,11 @@ test('Fingerprint JS Bot Detector', async () => {
|
|
|
126
246
|
assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
|
|
127
247
|
})
|
|
128
248
|
|
|
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
249
|
// If this test fails, please first check if you can access https://antcpt.com/score_detector/
|
|
158
250
|
// Note: ReCAPTCHA V3 score depends heavily on IP reputation, browser history, and Google's algorithms.
|
|
159
251
|
// A score >= 0.3 indicates the browser is not detected as an obvious bot.
|
|
160
252
|
test('Recaptcha V3 Score', async () => {
|
|
161
|
-
await page.goto("https://antcpt.com/score_detector/", { timeout:
|
|
253
|
+
await page.goto("https://antcpt.com/score_detector/", { timeout: 70000 });
|
|
162
254
|
|
|
163
255
|
// Human-like warm-up interactions before clicking
|
|
164
256
|
// 1. Random mouse movements using realCursor (BΓ©zier curves via ghost-cursor)
|
|
@@ -166,7 +258,7 @@ test('Recaptcha V3 Score', async () => {
|
|
|
166
258
|
await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
|
|
167
259
|
|
|
168
260
|
// 2. Scroll down a bit to simulate reading
|
|
169
|
-
await page.mouse.wheel(
|
|
261
|
+
await page.mouse.wheel(0, 100 + Math.random() * 100);
|
|
170
262
|
await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
|
|
171
263
|
|
|
172
264
|
// 3. Move mouse towards button area naturally
|
|
@@ -187,7 +279,7 @@ test('Recaptcha V3 Score', async () => {
|
|
|
187
279
|
// Pixelscan Fingerprint Consistency Check
|
|
188
280
|
// Checks browser fingerprint consistency, automation detection, and proxy detection
|
|
189
281
|
test('Pixelscan Fingerprint Check', async () => {
|
|
190
|
-
await page.goto("https://pixelscan.net/fingerprint-check", { waitUntil: 'domcontentloaded', timeout:
|
|
282
|
+
await page.goto("https://pixelscan.net/fingerprint-check", { waitUntil: 'domcontentloaded', timeout: 70000 });
|
|
191
283
|
|
|
192
284
|
// Poll for the final status. We look specifically at the green header and the fingerprint checker card.
|
|
193
285
|
let result = false;
|
|
@@ -217,43 +309,28 @@ test('Pixelscan Fingerprint Check', async () => {
|
|
|
217
309
|
if (!result) await new Promise(r => setTimeout(r, 1000));
|
|
218
310
|
}
|
|
219
311
|
|
|
312
|
+
// Capture diagnostic info before asserting
|
|
313
|
+
const debugInfo = await page.evaluate(() => {
|
|
314
|
+
const statusBar = document.querySelector('.status-content');
|
|
315
|
+
const cards = Array.from(document.querySelectorAll('.checker-card'));
|
|
316
|
+
return {
|
|
317
|
+
statusBarExists: !!statusBar,
|
|
318
|
+
statusText: statusBar ? statusBar.innerText : 'NOT FOUND',
|
|
319
|
+
cardCount: cards.length,
|
|
320
|
+
cardTexts: cards.map(c => c.innerText.substring(0, 200)),
|
|
321
|
+
pageTitle: document.title,
|
|
322
|
+
bodySnippet: document.body.innerText.substring(0, 500)
|
|
323
|
+
};
|
|
324
|
+
}).catch(e => ({ error: e.message }));
|
|
325
|
+
console.log('π Pixelscan Debug Info:', JSON.stringify(debugInfo, null, 2));
|
|
326
|
+
|
|
220
327
|
// Wait 10 seconds so the user can clearly see the final green scan results on screen
|
|
221
328
|
await new Promise(r => setTimeout(r, 10000));
|
|
222
329
|
|
|
223
|
-
assert.strictEqual(result, true, "Pixelscan Fingerprint Check failed! Browser fingerprint is inconsistent or masking was detected.")
|
|
330
|
+
assert.strictEqual(result, true, "Pixelscan Fingerprint Check failed! Browser fingerprint is inconsistent or masking was detected.");
|
|
224
331
|
})
|
|
225
332
|
|
|
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
333
|
|
|
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
334
|
|
|
240
|
-
// Check stealth detection
|
|
241
|
-
const stealthSection = pageText.match(/(\d+)%\s*stealth/i);
|
|
242
|
-
const stealthPercent = stealthSection ? parseInt(stealthSection[1]) : 100;
|
|
243
335
|
|
|
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
336
|
|
|
257
|
-
assert.strictEqual(result.passed, true,
|
|
258
|
-
`CreepJS Fingerprint Analysis failed! Headless: ${result.headlessPercent}%, Stealth: ${result.stealthPercent}%, Lies: ${result.liesCount}`)
|
|
259
|
-
})
|
|
@@ -30,15 +30,135 @@ test.after(async () => {
|
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
test('Human-like Move & Click', async () => {
|
|
34
|
+
await page.goto("https://www.google.com", { timeout: 40000 });
|
|
35
|
+
const selector = 'textarea[name="q"], input[name="q"]';
|
|
36
|
+
await page.realCursor.move(selector);
|
|
37
|
+
await page.realClick(selector);
|
|
38
|
+
assert.ok(true);
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('Human-like Typing', async () => {
|
|
42
|
+
await page.goto("https://www.google.com", { timeout: 40000 });
|
|
43
|
+
const selector = 'textarea[name="q"], input[name="q"]';
|
|
44
|
+
await page.realCursor.move(selector);
|
|
45
|
+
await page.realClick(selector);
|
|
46
|
+
await page.type(selector, 'Real Browser MCP Server', { delay: 150 });
|
|
47
|
+
const val = await page.inputValue(selector);
|
|
48
|
+
assert.strictEqual(val, 'Real Browser MCP Server');
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('Human-like Scrolling', async () => {
|
|
52
|
+
await page.goto("https://www.google.com/search?q=Real+Browser+MCP+Server", { timeout: 40000, waitUntil: 'domcontentloaded' });
|
|
53
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
54
|
+
|
|
55
|
+
console.log('π Scrolling down smoothly and fast (400px)...');
|
|
56
|
+
await page.realScroll(400, 500);
|
|
57
|
+
await new Promise(r => setTimeout(r, 600));
|
|
58
|
+
|
|
59
|
+
console.log('π Scrolling down smoothly and fast (300px)...');
|
|
60
|
+
await page.realScroll(300, 400);
|
|
61
|
+
await new Promise(r => setTimeout(r, 600));
|
|
62
|
+
|
|
63
|
+
console.log('π Scrolling up smoothly and fast (-500px)...');
|
|
64
|
+
await page.realScroll(-500, 600);
|
|
65
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
66
|
+
|
|
67
|
+
assert.ok(true);
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('Form Automation Demonstration', async () => {
|
|
71
|
+
console.log('\n㪠DEMO: Form Automation');
|
|
72
|
+
try {
|
|
73
|
+
await page.goto('https://httpbin.org/forms/post', { timeout: 30000 });
|
|
74
|
+
console.log('\n4οΈβ£ Filling out form...');
|
|
75
|
+
|
|
76
|
+
// 1. Customer Name
|
|
77
|
+
await page.type('input[name="custname"]', 'John Doe', { delay: 100 });
|
|
78
|
+
console.log('β
Customer Name filled');
|
|
79
|
+
|
|
80
|
+
// 2. Telephone
|
|
81
|
+
await page.type('input[name="custtel"]', '+1-555-0199', { delay: 100 });
|
|
82
|
+
console.log('β
Telephone filled');
|
|
83
|
+
|
|
84
|
+
// 3. Email address
|
|
85
|
+
await page.type('input[name="custemail"]', 'john.doe@example.com', { delay: 100 });
|
|
86
|
+
console.log('β
Email field filled');
|
|
87
|
+
|
|
88
|
+
// 4. Pizza Size (Radio Button)
|
|
89
|
+
await page.realClick('input[value="medium"]');
|
|
90
|
+
console.log('β
Pizza Size selected (Medium)');
|
|
91
|
+
|
|
92
|
+
// 5. Pizza Toppings (Checkboxes)
|
|
93
|
+
await page.realClick('input[value="bacon"]');
|
|
94
|
+
await page.realClick('input[value="onion"]');
|
|
95
|
+
console.log('β
Toppings selected (Bacon, Onion)');
|
|
96
|
+
|
|
97
|
+
// 6. Preferred Delivery Time
|
|
98
|
+
await page.type('input[name="delivery"]', '13:00', { delay: 100 });
|
|
99
|
+
console.log('β
Delivery time filled');
|
|
100
|
+
|
|
101
|
+
// 7. Delivery Instructions (Comments)
|
|
102
|
+
await page.type('textarea[name="comments"]', 'Leave at the front door, please.', { delay: 100 });
|
|
103
|
+
console.log('β
Delivery instructions filled');
|
|
104
|
+
|
|
105
|
+
// Wait 2 seconds for visual demonstration
|
|
106
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
107
|
+
|
|
108
|
+
// 8. Submit Order
|
|
109
|
+
await page.realClick('form button');
|
|
110
|
+
console.log('β
Form submitted successfully');
|
|
111
|
+
|
|
112
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
113
|
+
console.log('\nπ FORM AUTOMATION COMPLETE!');
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('β Form automation test failed:', error);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('Content Strategy Demonstration', async () => {
|
|
121
|
+
console.log('\n㪠DEMO: Content Analysis & Token Management');
|
|
122
|
+
console.log('π Watch browser analyze content from different websites');
|
|
123
|
+
try {
|
|
124
|
+
const testSites = [
|
|
125
|
+
{ url: 'https://httpbin.org/html', description: 'Simple HTML page' },
|
|
126
|
+
{ url: 'https://example.com', description: 'Minimal content page' }
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
for (const [index, site] of testSites.entries()) {
|
|
130
|
+
console.log(`\n${index + 2}οΈβ£ Testing ${site.description}: ${site.url}`);
|
|
131
|
+
await page.goto(site.url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
133
|
+
|
|
134
|
+
console.log(` π Getting HTML content...`);
|
|
135
|
+
const htmlContent = await page.content();
|
|
136
|
+
console.log(` β
HTML analyzed: ${htmlContent.length} characters`);
|
|
137
|
+
|
|
138
|
+
console.log(` π Getting text content...`);
|
|
139
|
+
const textContent = await page.evaluate(() => document.body.innerText);
|
|
140
|
+
console.log(` β
Text analyzed: ${textContent.length} characters`);
|
|
141
|
+
|
|
142
|
+
assert.ok(htmlContent.length > 0);
|
|
143
|
+
assert.ok(textContent.length > 0);
|
|
144
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
145
|
+
}
|
|
146
|
+
console.log('\nπ CONTENT ANALYSIS COMPLETE!');
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('β Content strategy test failed:', error);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
33
153
|
test('DrissionPage Detector', async () => {
|
|
34
|
-
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout:
|
|
154
|
+
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/", { timeout: 70000 });
|
|
35
155
|
await page.realClick("#detector")
|
|
36
156
|
let result = await page.evaluate(() => { return document.querySelector('#isBot span').textContent.includes("not") ? true : false })
|
|
37
157
|
assert.strictEqual(result, true, "DrissionPage Detector test failed!")
|
|
38
158
|
})
|
|
39
159
|
|
|
40
160
|
test('Sannysoft WebDriver Detector', async () => {
|
|
41
|
-
await page.goto("https://bot.sannysoft.com/", { timeout:
|
|
161
|
+
await page.goto("https://bot.sannysoft.com/", { timeout: 70000 });
|
|
42
162
|
await new Promise(r => setTimeout(r, 3000));
|
|
43
163
|
let result = await page.evaluate(() => {
|
|
44
164
|
const webdriverEl = document.getElementById('webdriver-result');
|
|
@@ -48,7 +168,7 @@ test('Sannysoft WebDriver Detector', async () => {
|
|
|
48
168
|
})
|
|
49
169
|
|
|
50
170
|
test('Cloudflare WAF', async () => {
|
|
51
|
-
await page.goto("https://nopecha.com/demo/cloudflare", { timeout:
|
|
171
|
+
await page.goto("https://nopecha.com/demo/cloudflare", { timeout: 70000 });
|
|
52
172
|
let verify = null
|
|
53
173
|
let startDate = Date.now()
|
|
54
174
|
while (!verify && (Date.now() - startDate) < 50000) {
|
|
@@ -62,7 +182,7 @@ test('Cloudflare WAF', async () => {
|
|
|
62
182
|
|
|
63
183
|
|
|
64
184
|
test('Cloudflare Turnstile', async () => {
|
|
65
|
-
await page.goto("https://2captcha.com/demo/cloudflare-turnstile", { timeout:
|
|
185
|
+
await page.goto("https://2captcha.com/demo/cloudflare-turnstile", { timeout: 70000 });
|
|
66
186
|
await page.waitForSelector('.cf-turnstile')
|
|
67
187
|
let token = null
|
|
68
188
|
let startDate = Date.now()
|
|
@@ -83,7 +203,7 @@ test('Cloudflare Turnstile', async () => {
|
|
|
83
203
|
|
|
84
204
|
|
|
85
205
|
test('Fingerprint JS Bot Detector', async () => {
|
|
86
|
-
await page.goto("https://fingerprint.com/products/bot-detection/", { waitUntil: 'domcontentloaded', timeout:
|
|
206
|
+
await page.goto("https://fingerprint.com/products/bot-detection/", { waitUntil: 'domcontentloaded', timeout: 70000 });
|
|
87
207
|
await new Promise(r => setTimeout(r, 5000));
|
|
88
208
|
const detect = await page.evaluate(() => {
|
|
89
209
|
const pageText = document.body.innerText.toLowerCase();
|
|
@@ -93,8 +213,8 @@ test('Fingerprint JS Bot Detector', async () => {
|
|
|
93
213
|
document.querySelector('h1') !== null;
|
|
94
214
|
|
|
95
215
|
const isNotBlocked = !pageText.includes('access denied') &&
|
|
96
|
-
|
|
97
|
-
|
|
216
|
+
!pageText.includes('blocked') &&
|
|
217
|
+
!pageText.includes('captcha');
|
|
98
218
|
|
|
99
219
|
const preElements = document.querySelectorAll('pre, code');
|
|
100
220
|
for (const el of preElements) {
|
|
@@ -116,36 +236,13 @@ test('Fingerprint JS Bot Detector', async () => {
|
|
|
116
236
|
assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
|
|
117
237
|
})
|
|
118
238
|
|
|
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
239
|
test('Recaptcha V3 Score', async () => {
|
|
143
|
-
await page.goto("https://antcpt.com/score_detector/", { timeout:
|
|
240
|
+
await page.goto("https://antcpt.com/score_detector/", { timeout: 70000 });
|
|
144
241
|
|
|
145
242
|
await page.realCursor.move('body', { paddingPercentage: 20 });
|
|
146
243
|
await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
|
|
147
244
|
|
|
148
|
-
await page.mouse.wheel(
|
|
245
|
+
await page.mouse.wheel(0, 100 + Math.random() * 100);
|
|
149
246
|
await new Promise(r => setTimeout(r, 800 + Math.random() * 400));
|
|
150
247
|
|
|
151
248
|
await page.realCursor.move('button', { paddingPercentage: 10 });
|
|
@@ -161,7 +258,7 @@ test('Recaptcha V3 Score', async () => {
|
|
|
161
258
|
})
|
|
162
259
|
|
|
163
260
|
test('Pixelscan Fingerprint Check', async () => {
|
|
164
|
-
await page.goto("https://pixelscan.net/fingerprint-check", { waitUntil: 'domcontentloaded', timeout:
|
|
261
|
+
await page.goto("https://pixelscan.net/fingerprint-check", { waitUntil: 'domcontentloaded', timeout: 70000 });
|
|
165
262
|
|
|
166
263
|
// Poll for the final status. We look specifically at the green header and the fingerprint checker card.
|
|
167
264
|
let result = false;
|
|
@@ -194,33 +291,9 @@ test('Pixelscan Fingerprint Check', async () => {
|
|
|
194
291
|
// Wait 10 seconds so the user can clearly see the final green scan results on screen
|
|
195
292
|
await new Promise(r => setTimeout(r, 10000));
|
|
196
293
|
|
|
197
|
-
assert.strictEqual(result, true, "Pixelscan Fingerprint Check failed! Browser fingerprint is inconsistent or masking was detected.")
|
|
294
|
+
assert.strictEqual(result, true, "Pixelscan Fingerprint Check failed! Browser fingerprint is inconsistent or masking was detected.");
|
|
198
295
|
})
|
|
199
296
|
|
|
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
297
|
|
|
213
|
-
const liesMatch = pageText.match(/(\d+)\s*lie/i);
|
|
214
|
-
const liesCount = liesMatch ? parseInt(liesMatch[1]) : 0;
|
|
215
298
|
|
|
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
299
|
|
|
224
|
-
assert.strictEqual(result.passed, true,
|
|
225
|
-
`CreepJS Fingerprint Analysis failed! Headless: ${result.headlessPercent}%, Stealth: ${result.stealthPercent}%, Lies: ${result.liesCount}`)
|
|
226
|
-
})
|
package/typings.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
declare module "
|
|
2
|
-
import type { Browser, Page } from "
|
|
3
|
-
import type { GhostCursor } from "ghost-cursor";
|
|
1
|
+
declare module "real-browser-mcp-server" {
|
|
2
|
+
import type { Browser, Page } from "patchright";
|
|
3
|
+
import type { GhostCursor } from "ghost-cursor-patchright";
|
|
4
4
|
|
|
5
5
|
export function connect(options?: Options): Promise<ConnectResult>;
|
|
6
6
|
|
|
@@ -19,13 +19,14 @@ declare module "brave-real-browser-mcp-server" {
|
|
|
19
19
|
interface Options {
|
|
20
20
|
args?: string[];
|
|
21
21
|
headless?: boolean;
|
|
22
|
-
customConfig?:
|
|
22
|
+
customConfig?: any;
|
|
23
23
|
proxy?: ProxyOptions;
|
|
24
24
|
turnstile?: boolean;
|
|
25
|
-
connectOption?:
|
|
25
|
+
connectOption?: any;
|
|
26
26
|
disableXvfb?: boolean;
|
|
27
|
-
plugins?: import("puppeteer-extra").PuppeteerExtraPlugin[];
|
|
28
27
|
ignoreAllFlags?: boolean;
|
|
28
|
+
/** Path to the browser executable (defaults to auto-detected Brave browser or falls back to Chromium) */
|
|
29
|
+
executablePath?: string;
|
|
29
30
|
/** Enable blocker on all pages (default: true) */
|
|
30
31
|
enableBlocker?: boolean;
|
|
31
32
|
/** Blocker configuration options */
|
|
@@ -77,3 +78,8 @@ declare module "brave-real-browser-mcp-server" {
|
|
|
77
78
|
shouldBlock(url: string): boolean;
|
|
78
79
|
}
|
|
79
80
|
}
|
|
81
|
+
|
|
82
|
+
declare module "real-browser-mcp-server" {
|
|
83
|
+
export * from "real-browser-mcp-server";
|
|
84
|
+
}
|
|
85
|
+
|
package/test/esm/package.json
DELETED
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
|
-
}
|