brave-real-browser 1.5.106 → 2.0.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 +22 -54
- package/lib/cjs/index.js +13 -40
- package/lib/cjs/module/pageController.js +35 -142
- package/lib/esm/index.mjs +12 -35
- package/lib/esm/module/pageController.mjs +34 -144
- package/lib/esm/module/turnstile.mjs +3 -4
- package/package.json +8 -15
- package/test/cjs/test.js +49 -57
- package/test/esm/test.js +48 -58
- package/AUTO_UPDATE_GUIDE.md +0 -197
- package/FEATURES.md +0 -265
- package/FIXES_NEEDED.md +0 -131
- package/lib/cjs/errors.js +0 -299
- package/renovate.json +0 -59
- package/scripts/auto-update.js +0 -119
|
@@ -2,175 +2,65 @@ import { createCursor } from 'ghost-cursor';
|
|
|
2
2
|
import { checkTurnstile } from './turnstile.mjs';
|
|
3
3
|
import kill from 'tree-kill';
|
|
4
4
|
|
|
5
|
-
// Configuration constants
|
|
6
|
-
const TURNSTILE_CHECK_INTERVAL = 1000; // 1 second
|
|
7
|
-
const TURNSTILE_MAX_ATTEMPTS = 300; // 5 minutes max (300 attempts * 1 second)
|
|
8
|
-
const TURNSTILE_TIMEOUT = 300000; // 5 minutes in milliseconds
|
|
9
|
-
|
|
10
5
|
function getRandomInt(min, max) {
|
|
11
6
|
min = Math.ceil(min);
|
|
12
7
|
max = Math.floor(max);
|
|
13
8
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
14
9
|
}
|
|
15
10
|
|
|
16
|
-
async function pageController({ browser, page, proxy, turnstile, xvfbsession, pid, plugins, killProcess = false,
|
|
17
|
-
|
|
18
|
-
let solveStatus = turnstile;
|
|
19
|
-
let turnstileSolverRunning = false;
|
|
11
|
+
export async function pageController({ browser, page, proxy, turnstile, xvfbsession, pid, plugins, killProcess = false, brave }) {
|
|
20
12
|
|
|
21
|
-
|
|
22
|
-
const cleanup = async () => {
|
|
23
|
-
solveStatus = false;
|
|
24
|
-
if (killProcess === true) {
|
|
25
|
-
if (xvfbsession) {
|
|
26
|
-
try {
|
|
27
|
-
xvfbsession.stopSync();
|
|
28
|
-
} catch (err) {
|
|
29
|
-
console.error('Error stopping Xvfb session:', err.message);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (chrome) {
|
|
33
|
-
try {
|
|
34
|
-
chrome.kill();
|
|
35
|
-
} catch (err) {
|
|
36
|
-
console.error('Error killing Chrome process:', err.message);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (pid) {
|
|
40
|
-
try {
|
|
41
|
-
kill(pid, 'SIGKILL', (err) => {
|
|
42
|
-
if (err) console.error('Error killing process:', err.message);
|
|
43
|
-
});
|
|
44
|
-
} catch (err) {
|
|
45
|
-
console.error('Error in kill command:', err.message);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
13
|
+
let solveStatus = turnstile
|
|
50
14
|
|
|
51
15
|
page.on('close', () => {
|
|
52
|
-
solveStatus = false
|
|
16
|
+
solveStatus = false
|
|
53
17
|
});
|
|
54
18
|
|
|
19
|
+
|
|
55
20
|
browser.on('disconnected', async () => {
|
|
56
|
-
|
|
21
|
+
solveStatus = false
|
|
22
|
+
if (killProcess === true) {
|
|
23
|
+
if (xvfbsession) try { xvfbsession.stopSync() } catch (err) { }
|
|
24
|
+
if (brave) try { brave.kill() } catch (err) { console.log(err); }
|
|
25
|
+
if (pid) try { kill(pid, 'SIGKILL', () => { }) } catch (err) { }
|
|
26
|
+
}
|
|
57
27
|
});
|
|
58
28
|
|
|
59
|
-
// Enhanced turnstile solver with timeout and max attempts protection
|
|
60
29
|
async function turnstileSolver() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
turnstileSolverRunning = true;
|
|
67
|
-
const startTime = Date.now();
|
|
68
|
-
let attempts = 0;
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
while (solveStatus && attempts < TURNSTILE_MAX_ATTEMPTS) {
|
|
72
|
-
// Check timeout
|
|
73
|
-
if (Date.now() - startTime > TURNSTILE_TIMEOUT) {
|
|
74
|
-
console.warn('Turnstile solver timeout reached after', attempts, 'attempts');
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Check if page is still valid
|
|
79
|
-
if (page.isClosed()) {
|
|
80
|
-
console.log('Page closed, stopping turnstile solver');
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
attempts++;
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
await checkTurnstile({ page });
|
|
88
|
-
} catch (err) {
|
|
89
|
-
// Silent fail for individual checks, log only if debug enabled
|
|
90
|
-
if (process.env.DEBUG === 'true') {
|
|
91
|
-
console.debug('Turnstile check failed:', err.message);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Wait before next check
|
|
96
|
-
await new Promise(r => setTimeout(r, TURNSTILE_CHECK_INTERVAL));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (attempts >= TURNSTILE_MAX_ATTEMPTS) {
|
|
100
|
-
console.warn('Turnstile solver stopped: max attempts reached');
|
|
101
|
-
}
|
|
102
|
-
} catch (err) {
|
|
103
|
-
console.error('Error in turnstile solver:', err.message);
|
|
104
|
-
} finally {
|
|
105
|
-
turnstileSolverRunning = false;
|
|
30
|
+
while (solveStatus) {
|
|
31
|
+
await checkTurnstile({ page }).catch(() => { });
|
|
32
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
106
33
|
}
|
|
34
|
+
return
|
|
107
35
|
}
|
|
108
36
|
|
|
109
|
-
|
|
110
|
-
if (turnstile) {
|
|
111
|
-
turnstileSolver().catch(err => {
|
|
112
|
-
console.error('Failed to start turnstile solver:', err.message);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
37
|
+
turnstileSolver()
|
|
115
38
|
|
|
116
|
-
|
|
117
|
-
if (proxy && proxy.username && proxy.password) {
|
|
118
|
-
try {
|
|
119
|
-
await page.authenticate({
|
|
120
|
-
username: proxy.username,
|
|
121
|
-
password: proxy.password
|
|
122
|
-
});
|
|
123
|
-
} catch (err) {
|
|
124
|
-
console.error('Proxy authentication failed:', err.message);
|
|
125
|
-
throw new Error(`Failed to authenticate proxy: ${err.message}`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
39
|
+
if (proxy.username && proxy.password) await page.authenticate({ username: proxy.username, password: proxy.password });
|
|
128
40
|
|
|
129
|
-
|
|
130
|
-
if (plugins && plugins.length > 0) {
|
|
41
|
+
if (plugins.length > 0) {
|
|
131
42
|
for (const plugin of plugins) {
|
|
132
|
-
|
|
133
|
-
if (plugin && typeof plugin.onPageCreated === 'function') {
|
|
134
|
-
await plugin.onPageCreated(page);
|
|
135
|
-
}
|
|
136
|
-
} catch (err) {
|
|
137
|
-
console.error('Plugin initialization failed:', err.message);
|
|
138
|
-
// Continue with other plugins even if one fails
|
|
139
|
-
}
|
|
43
|
+
plugin.onPageCreated(page)
|
|
140
44
|
}
|
|
141
45
|
}
|
|
142
46
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return this.clientX + window.screenX;
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
Object.defineProperty(MouseEvent.prototype, 'screenY', {
|
|
153
|
-
get: function () {
|
|
154
|
-
return this.clientY + window.screenY;
|
|
155
|
-
}
|
|
156
|
-
});
|
|
47
|
+
await page.evaluateOnNewDocument(() => {
|
|
48
|
+
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
|
49
|
+
get: function () {
|
|
50
|
+
return this.clientX + window.screenX;
|
|
51
|
+
}
|
|
157
52
|
});
|
|
158
|
-
} catch (err) {
|
|
159
|
-
console.error('Failed to setup mouse event handlers:', err.message);
|
|
160
|
-
// Non-critical, continue anyway
|
|
161
|
-
}
|
|
162
53
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
} catch (err) {
|
|
169
|
-
console.error('Failed to create ghost cursor:', err.message);
|
|
170
|
-
// Non-critical, continue anyway
|
|
171
|
-
}
|
|
54
|
+
Object.defineProperty(MouseEvent.prototype, 'screenY', {
|
|
55
|
+
get: function () {
|
|
56
|
+
return this.clientY + window.screenY;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
172
59
|
|
|
173
|
-
|
|
174
|
-
}
|
|
60
|
+
});
|
|
175
61
|
|
|
176
|
-
|
|
62
|
+
const cursor = createCursor(page);
|
|
63
|
+
page.realCursor = cursor
|
|
64
|
+
page.realClick = cursor.click
|
|
65
|
+
return page
|
|
66
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
const checkTurnstile = ({ page }) => {
|
|
1
|
+
export const checkTurnstile = ({ page }) => {
|
|
2
2
|
return new Promise(async (resolve, reject) => {
|
|
3
3
|
var waitInterval = setTimeout(() => { clearInterval(waitInterval); resolve(false) }, 5000);
|
|
4
|
+
|
|
4
5
|
try {
|
|
5
6
|
const elements = await page.$$('[name="cf-turnstile-response"]');
|
|
6
7
|
if (elements.length <= 0) {
|
|
@@ -58,6 +59,4 @@ const checkTurnstile = ({ page }) => {
|
|
|
58
59
|
resolve(false)
|
|
59
60
|
}
|
|
60
61
|
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export { checkTurnstile };
|
|
62
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "This package is designed to bypass puppeteer's bot-detecting captchas such as Cloudflare. It acts like a real browser and can be managed with puppeteer.",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/esm/index.mjs",
|
|
@@ -14,19 +14,12 @@
|
|
|
14
14
|
"typings": "typings.d.ts",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"esm_test": "node ./test/esm/test.js",
|
|
17
|
-
"cjs_test": "node ./test/cjs/test.js"
|
|
18
|
-
"update-deps": "npm install brave-real-launcher@latest brave-real-puppeteer-core@latest ghost-cursor@latest puppeteer-extra@latest tree-kill@latest xvfb@latest && npm list --depth=0",
|
|
19
|
-
"preinstall": "node scripts/auto-update.js",
|
|
20
|
-
"postinstall": "echo \"✅ Installation complete with latest dependencies!\"",
|
|
21
|
-
"check-updates": "npm outdated || true",
|
|
22
|
-
"upgrade-all": "npm run update-deps && echo \"📦 All dependencies updated to latest versions!\""
|
|
17
|
+
"cjs_test": "node ./test/cjs/test.js"
|
|
23
18
|
},
|
|
24
19
|
"keywords": [
|
|
25
|
-
"cf-bypass",
|
|
26
|
-
"cloudflare-bypass",
|
|
27
20
|
"puppeteer-fingerprint",
|
|
28
21
|
"puppeteer-cloudflare",
|
|
29
|
-
"
|
|
22
|
+
"brave-real-browser",
|
|
30
23
|
"undetectable-puppeteer",
|
|
31
24
|
"undetect",
|
|
32
25
|
"undetectable",
|
|
@@ -40,11 +33,11 @@
|
|
|
40
33
|
"puppeteer-undetect-cf",
|
|
41
34
|
"puppeteer-undetectable-cf-bypass"
|
|
42
35
|
],
|
|
43
|
-
"author": "
|
|
36
|
+
"author": "codeiva4u",
|
|
44
37
|
"license": "ISC",
|
|
45
38
|
"dependencies": {
|
|
46
|
-
"brave-real-launcher": "^1.2.
|
|
47
|
-
"brave-real-puppeteer-core": "^24.34.0",
|
|
39
|
+
"brave-real-launcher": "^1.2.40",
|
|
40
|
+
"brave-real-puppeteer-core": "^24.34.0-patch.5",
|
|
48
41
|
"ghost-cursor": "^1.4.1",
|
|
49
42
|
"puppeteer-extra": "^3.3.6",
|
|
50
43
|
"tree-kill": "^1.2.2",
|
|
@@ -52,8 +45,8 @@
|
|
|
52
45
|
},
|
|
53
46
|
"repository": {
|
|
54
47
|
"type": "git",
|
|
55
|
-
"url": "https://github.com/
|
|
48
|
+
"url": "https://github.com/codeiva4u/Brave-Real-Browser.git"
|
|
56
49
|
},
|
|
57
50
|
"readme": "README.md",
|
|
58
|
-
"homepage": "https://github.com/
|
|
51
|
+
"homepage": "https://github.com/codeiva4u/Brave-Real-Browser"
|
|
59
52
|
}
|
package/test/cjs/test.js
CHANGED
|
@@ -2,75 +2,74 @@ const test = require('node:test');
|
|
|
2
2
|
const assert = require('node:assert');
|
|
3
3
|
const { connect } = require('../../lib/cjs/index.js');
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
const realBrowserOption = {
|
|
7
|
-
args: ["--start-maximized"],
|
|
8
6
|
turnstile: true,
|
|
9
7
|
headless: false,
|
|
10
|
-
// disableXvfb: true,
|
|
11
8
|
customConfig: {},
|
|
12
|
-
connectOption: {
|
|
13
|
-
defaultViewport: null
|
|
14
|
-
},
|
|
15
9
|
plugins: []
|
|
16
10
|
}
|
|
17
11
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
// Shared browser instance for all tests
|
|
13
|
+
let browser = null;
|
|
14
|
+
let page = null;
|
|
15
|
+
|
|
16
|
+
// Setup - Run once before all tests
|
|
17
|
+
test.before(async () => {
|
|
18
|
+
console.log('🚀 Starting browser for all tests...');
|
|
19
|
+
const result = await connect(realBrowserOption);
|
|
20
|
+
browser = result.browser;
|
|
21
|
+
page = result.page;
|
|
22
|
+
console.log('✅ Browser started successfully');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Teardown - Run once after all tests
|
|
26
|
+
test.after(async () => {
|
|
27
|
+
console.log('🏁 Closing browser after all tests...');
|
|
28
|
+
if (browser) {
|
|
29
|
+
await browser.close();
|
|
30
|
+
console.log('✅ Browser closed successfully');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
31
33
|
|
|
32
34
|
test('DrissionPage Detector', async () => {
|
|
33
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
34
35
|
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/");
|
|
35
36
|
await page.realClick("#detector")
|
|
36
37
|
let result = await page.evaluate(() => { return document.querySelector('#isBot span').textContent.includes("not") ? true : false })
|
|
37
|
-
await browser.close()
|
|
38
38
|
assert.strictEqual(result, true, "DrissionPage Detector test failed!")
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
test('Brotector, a webdriver detector', async () => {
|
|
42
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
43
42
|
await page.goto("https://kaliiiiiiiiii.github.io/brotector/");
|
|
44
43
|
await new Promise(r => setTimeout(r, 3000));
|
|
45
44
|
let result = await page.evaluate(() => { return document.querySelector('#table-keys').getAttribute('bgcolor') })
|
|
46
|
-
await browser.close()
|
|
47
45
|
assert.strictEqual(result === "darkgreen", true, "Brotector, a webdriver detector test failed!")
|
|
48
46
|
})
|
|
49
47
|
|
|
50
48
|
test('Cloudflare WAF', async () => {
|
|
51
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
52
49
|
await page.goto("https://nopecha.com/demo/cloudflare");
|
|
53
50
|
let verify = null
|
|
54
51
|
let startDate = Date.now()
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
await
|
|
52
|
+
// Increased timeout to 60 seconds to allow turnstile to be solved
|
|
53
|
+
while (!verify && (Date.now() - startDate) < 60000) {
|
|
54
|
+
verify = await page.evaluate(() => {
|
|
55
|
+
// Check if we passed the challenge - look for main content
|
|
56
|
+
return document.querySelector('.link_row') || document.querySelector('a[href*="nopecha"]') ? true : null
|
|
57
|
+
}).catch(() => null)
|
|
58
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
-
assert.strictEqual(verify === true, true, "Cloudflare WAF test failed!")
|
|
60
|
+
assert.strictEqual(verify === true, true, "Cloudflare WAF test failed! (Site may be blocking automated access)")
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
test('Cloudflare Turnstile', async () => {
|
|
65
|
-
|
|
66
|
-
await page.
|
|
67
|
-
await page.waitForSelector('[type="submit"]')
|
|
65
|
+
await page.goto("https://2captcha.com/demo/cloudflare-turnstile");
|
|
66
|
+
await page.waitForSelector('.cf-turnstile')
|
|
68
67
|
let token = null
|
|
69
68
|
let startDate = Date.now()
|
|
70
69
|
while (!token && (Date.now() - startDate) < 30000) {
|
|
71
70
|
token = await page.evaluate(() => {
|
|
72
71
|
try {
|
|
73
|
-
let item = document.querySelector('[name="cf-turnstile-response"]')
|
|
72
|
+
let item = document.querySelector('[name="cf-turnstile-response"]')?.value
|
|
74
73
|
return item && item.length > 20 ? item : null
|
|
75
74
|
} catch (e) {
|
|
76
75
|
return null
|
|
@@ -78,57 +77,50 @@ test('Cloudflare Turnstile', async () => {
|
|
|
78
77
|
})
|
|
79
78
|
await new Promise(r => setTimeout(r, 1000));
|
|
80
79
|
}
|
|
81
|
-
await browser.close()
|
|
82
|
-
// if (token !== null) console.log('Cloudflare Turnstile Token: ' + token);
|
|
83
80
|
assert.strictEqual(token !== null, true, "Cloudflare turnstile test failed!")
|
|
84
81
|
})
|
|
85
82
|
|
|
86
83
|
|
|
87
84
|
test('Fingerprint JS Bot Detector', async () => {
|
|
88
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
89
85
|
await page.goto("https://fingerprint.com/products/bot-detection/");
|
|
90
|
-
|
|
91
|
-
// Wait for the bot detection section to load
|
|
92
|
-
await page.waitForSelector('.HeroSection-module--botSubTitle--2711e', { timeout: 15000 });
|
|
93
|
-
|
|
94
|
-
// Give extra time for bot detection to complete
|
|
95
|
-
await new Promise(r => setTimeout(r, 7000));
|
|
96
|
-
|
|
86
|
+
await new Promise(r => setTimeout(r, 8000));
|
|
97
87
|
const detect = await page.evaluate(() => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
88
|
+
// Check in pre/code blocks for notDetected result or in page text
|
|
89
|
+
const preElements = document.querySelectorAll('pre, code');
|
|
90
|
+
for (const el of preElements) {
|
|
91
|
+
if (el.textContent.includes('notDetected') || el.textContent.includes('"result": "notDetected"')) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Fallback: check any element with partial class match
|
|
96
|
+
const allElements = document.querySelectorAll('*');
|
|
97
|
+
for (const el of allElements) {
|
|
98
|
+
for (const cls of el.classList) {
|
|
99
|
+
if (cls.includes('botSubTitle') && el.textContent.toLowerCase().includes('not')) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
console.log('Bot detection text:', textContent);
|
|
105
|
-
// Check if text contains "not" which indicates we are not detected as a bot
|
|
106
|
-
return textContent.includes("not") ? true : false;
|
|
104
|
+
return false;
|
|
107
105
|
})
|
|
108
|
-
await browser.close()
|
|
109
106
|
assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
|
|
110
107
|
})
|
|
111
108
|
|
|
112
109
|
|
|
113
110
|
// If you fail this test, your ip address probably has a high spam score. Please use a mobile or clean ip address.
|
|
114
111
|
test('Datadome Bot Detector', async (t) => {
|
|
115
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
116
112
|
await page.goto("https://antoinevastel.com/bots/datadome");
|
|
117
113
|
const check = await page.waitForSelector('nav #navbarCollapse').catch(() => null)
|
|
118
|
-
await browser.close()
|
|
119
114
|
assert.strictEqual(check ? true : false, 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.]")
|
|
120
115
|
})
|
|
121
116
|
|
|
122
117
|
// If this test fails, please first check if you can access https://antcpt.com/score_detector/
|
|
123
118
|
test('Recaptcha V3 Score (hard)', async () => {
|
|
124
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
125
119
|
await page.goto("https://antcpt.com/score_detector/");
|
|
126
120
|
await page.realClick("button")
|
|
127
121
|
await new Promise(r => setTimeout(r, 5000));
|
|
128
122
|
const score = await page.evaluate(() => {
|
|
129
123
|
return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
|
|
130
124
|
})
|
|
131
|
-
await browser.close()
|
|
132
|
-
// if (Number(score) >= 0.7) console.log('Recaptcha V3 Score: ' + score);
|
|
133
125
|
assert.strictEqual(Number(score) >= 0.7, true, "(please first check if you can access https://antcpt.com/score_detector/.) Recaptcha V3 Score (hard) should be >=0.7. Score Result: " + score)
|
|
134
126
|
})
|
package/test/esm/test.js
CHANGED
|
@@ -3,76 +3,73 @@ import assert from 'node:assert';
|
|
|
3
3
|
import { connect } from '../../lib/esm/index.mjs';
|
|
4
4
|
|
|
5
5
|
const realBrowserOption = {
|
|
6
|
-
args: ["--start-maximized"],
|
|
7
6
|
turnstile: true,
|
|
8
7
|
headless: false,
|
|
9
|
-
// disableXvfb: true,
|
|
10
|
-
// ignoreAllFlags:true,
|
|
11
8
|
customConfig: {},
|
|
12
|
-
connectOption: {
|
|
13
|
-
defaultViewport: null
|
|
14
|
-
},
|
|
15
9
|
plugins: []
|
|
16
10
|
}
|
|
17
11
|
|
|
12
|
+
// Shared browser instance for all tests
|
|
13
|
+
let browser = null;
|
|
14
|
+
let page = null;
|
|
18
15
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
16
|
+
// Setup - Run once before all tests
|
|
17
|
+
test.before(async () => {
|
|
18
|
+
console.log('🚀 Starting browser for all tests...');
|
|
19
|
+
const result = await connect(realBrowserOption);
|
|
20
|
+
browser = result.browser;
|
|
21
|
+
page = result.page;
|
|
22
|
+
console.log('✅ Browser started successfully');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Teardown - Run once after all tests
|
|
26
|
+
test.after(async () => {
|
|
27
|
+
console.log('🏁 Closing browser after all tests...');
|
|
28
|
+
if (browser) {
|
|
29
|
+
await browser.close();
|
|
30
|
+
console.log('✅ Browser closed successfully');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
33
|
|
|
34
34
|
test('DrissionPage Detector', async () => {
|
|
35
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
36
35
|
await page.goto("https://web.archive.org/web/20240913054632/https://drissionpage.pages.dev/");
|
|
37
36
|
await page.realClick("#detector")
|
|
38
37
|
let result = await page.evaluate(() => { return document.querySelector('#isBot span').textContent.includes("not") ? true : false })
|
|
39
|
-
await browser.close()
|
|
40
38
|
assert.strictEqual(result, true, "DrissionPage Detector test failed!")
|
|
41
39
|
})
|
|
42
40
|
|
|
43
41
|
test('Brotector, a webdriver detector', async () => {
|
|
44
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
45
42
|
await page.goto("https://kaliiiiiiiiii.github.io/brotector/");
|
|
46
43
|
await new Promise(r => setTimeout(r, 3000));
|
|
47
44
|
let result = await page.evaluate(() => { return document.querySelector('#table-keys').getAttribute('bgcolor') })
|
|
48
|
-
await browser.close()
|
|
49
45
|
assert.strictEqual(result === "darkgreen", true, "Brotector, a webdriver detector test failed!")
|
|
50
46
|
})
|
|
51
47
|
|
|
52
48
|
test('Cloudflare WAF', async () => {
|
|
53
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
54
49
|
await page.goto("https://nopecha.com/demo/cloudflare");
|
|
55
50
|
let verify = null
|
|
56
51
|
let startDate = Date.now()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
await
|
|
52
|
+
// Increased timeout to 60 seconds to allow turnstile to be solved
|
|
53
|
+
while (!verify && (Date.now() - startDate) < 60000) {
|
|
54
|
+
verify = await page.evaluate(() => {
|
|
55
|
+
// Check if we passed the challenge - look for main content
|
|
56
|
+
return document.querySelector('.link_row') || document.querySelector('a[href*="nopecha"]') ? true : null
|
|
57
|
+
}).catch(() => null)
|
|
58
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
60
59
|
}
|
|
61
|
-
|
|
62
|
-
assert.strictEqual(verify === true, true, "Cloudflare WAF test failed!")
|
|
60
|
+
assert.strictEqual(verify === true, true, "Cloudflare WAF test failed! (Site may be blocking automated access)")
|
|
63
61
|
})
|
|
64
62
|
|
|
65
63
|
|
|
66
64
|
test('Cloudflare Turnstile', async () => {
|
|
67
|
-
|
|
68
|
-
await page.
|
|
69
|
-
await page.waitForSelector('[type="submit"]')
|
|
65
|
+
await page.goto("https://2captcha.com/demo/cloudflare-turnstile");
|
|
66
|
+
await page.waitForSelector('.cf-turnstile')
|
|
70
67
|
let token = null
|
|
71
68
|
let startDate = Date.now()
|
|
72
69
|
while (!token && (Date.now() - startDate) < 30000) {
|
|
73
70
|
token = await page.evaluate(() => {
|
|
74
71
|
try {
|
|
75
|
-
let item = document.querySelector('[name="cf-turnstile-response"]')
|
|
72
|
+
let item = document.querySelector('[name="cf-turnstile-response"]')?.value
|
|
76
73
|
return item && item.length > 20 ? item : null
|
|
77
74
|
} catch (e) {
|
|
78
75
|
return null
|
|
@@ -80,58 +77,51 @@ test('Cloudflare Turnstile', async () => {
|
|
|
80
77
|
})
|
|
81
78
|
await new Promise(r => setTimeout(r, 1000));
|
|
82
79
|
}
|
|
83
|
-
await browser.close()
|
|
84
|
-
// if (token !== null) console.log('Cloudflare Turnstile Token: ' + token);
|
|
85
80
|
assert.strictEqual(token !== null, true, "Cloudflare turnstile test failed!")
|
|
86
81
|
})
|
|
87
82
|
|
|
88
83
|
|
|
89
84
|
|
|
90
85
|
test('Fingerprint JS Bot Detector', async () => {
|
|
91
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
92
86
|
await page.goto("https://fingerprint.com/products/bot-detection/");
|
|
93
|
-
|
|
94
|
-
// Wait for the bot detection section to load
|
|
95
|
-
await page.waitForSelector('.HeroSection-module--botSubTitle--2711e', { timeout: 15000 });
|
|
96
|
-
|
|
97
|
-
// Give extra time for bot detection to complete
|
|
98
|
-
await new Promise(r => setTimeout(r, 7000));
|
|
99
|
-
|
|
87
|
+
await new Promise(r => setTimeout(r, 8000));
|
|
100
88
|
const detect = await page.evaluate(() => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
89
|
+
// Check in pre/code blocks for notDetected result or in page text
|
|
90
|
+
const preElements = document.querySelectorAll('pre, code');
|
|
91
|
+
for (const el of preElements) {
|
|
92
|
+
if (el.textContent.includes('notDetected') || el.textContent.includes('"result": "notDetected"')) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Fallback: check any element with partial class match
|
|
97
|
+
const allElements = document.querySelectorAll('*');
|
|
98
|
+
for (const el of allElements) {
|
|
99
|
+
for (const cls of el.classList) {
|
|
100
|
+
if (cls.includes('botSubTitle') && el.textContent.toLowerCase().includes('not')) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
105
104
|
}
|
|
106
|
-
|
|
107
|
-
console.log('Bot detection text:', textContent);
|
|
108
|
-
// Check if text contains "not" which indicates we are not detected as a bot
|
|
109
|
-
return textContent.includes("not") ? true : false;
|
|
105
|
+
return false;
|
|
110
106
|
})
|
|
111
|
-
await browser.close()
|
|
112
107
|
assert.strictEqual(detect, true, "Fingerprint JS Bot Detector test failed!")
|
|
113
108
|
})
|
|
114
109
|
|
|
115
110
|
|
|
116
111
|
// If you fail this test, your ip address probably has a high spam score. Please use a mobile or clean ip address.
|
|
117
112
|
test('Datadome Bot Detector', async (t) => {
|
|
118
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
119
113
|
await page.goto("https://antoinevastel.com/bots/datadome");
|
|
120
114
|
const check = await page.waitForSelector('nav #navbarCollapse').catch(() => null)
|
|
121
|
-
await browser.close()
|
|
122
115
|
assert.strictEqual(check ? true : false, 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.]")
|
|
123
116
|
})
|
|
124
117
|
|
|
125
118
|
// If this test fails, please first check if you can access https://antcpt.com/score_detector/
|
|
126
119
|
test('Recaptcha V3 Score (hard)', async () => {
|
|
127
|
-
const { page, browser } = await connect(realBrowserOption)
|
|
128
120
|
await page.goto("https://antcpt.com/score_detector/");
|
|
129
121
|
await page.realClick("button")
|
|
130
122
|
await new Promise(r => setTimeout(r, 5000));
|
|
131
123
|
const score = await page.evaluate(() => {
|
|
132
124
|
return document.querySelector('big').textContent.replace(/[^0-9.]/g, '')
|
|
133
125
|
})
|
|
134
|
-
await browser.close()
|
|
135
|
-
// if (Number(score) >= 0.7) console.log('Recaptcha V3 Score: ' + score);
|
|
136
126
|
assert.strictEqual(Number(score) >= 0.7, true, "(please first check if you can access https://antcpt.com/score_detector/.) Recaptcha V3 Score (hard) should be >=0.7. Score Result: " + score)
|
|
137
127
|
})
|