281 lines
9.1 KiB
TypeScript
281 lines
9.1 KiB
TypeScript
/**
|
|
* Full stealth test suite — validates CloakBrowser against live detection services.
|
|
* Mirrors Python examples/stealth_test.py.
|
|
*
|
|
* Usage:
|
|
* CLOAKBROWSER_BINARY_PATH=/path/to/chrome npx tsx examples/stealth-test.ts
|
|
* CLOAKBROWSER_BINARY_PATH=/path/to/chrome npx tsx examples/stealth-test.ts --proxy http://10.50.96.5:8888
|
|
*/
|
|
|
|
import { launch } from "../src/index.js";
|
|
|
|
const PROXY = process.argv.includes("--proxy")
|
|
? process.argv[process.argv.indexOf("--proxy") + 1]
|
|
: undefined;
|
|
|
|
interface TestResult {
|
|
name: string;
|
|
status: "PASS" | "FAIL" | "ERROR";
|
|
verdict: string;
|
|
}
|
|
|
|
const results: TestResult[] = [];
|
|
|
|
console.log("=".repeat(60));
|
|
console.log("CloakBrowser JS — Stealth Test Suite");
|
|
console.log("=".repeat(60));
|
|
console.log(`Proxy: ${PROXY || "none"}\n`);
|
|
|
|
const browser = await launch({ headless: true, proxy: PROXY });
|
|
const page = await browser.newPage();
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 1: bot.sannysoft.com
|
|
// ---------------------------------------------------------------------------
|
|
async function testSannysoft() {
|
|
console.log("--- bot.sannysoft.com ---");
|
|
await page.goto("https://bot.sannysoft.com", {
|
|
waitUntil: "networkidle",
|
|
timeout: 30000,
|
|
});
|
|
await page.waitForTimeout(3000);
|
|
|
|
const result = await page.evaluate(() => {
|
|
const rows = document.querySelectorAll("table tr");
|
|
let passed = 0;
|
|
let total = 0;
|
|
const failed: string[] = [];
|
|
rows.forEach((r) => {
|
|
const cells = r.querySelectorAll("td");
|
|
if (cells.length >= 2) {
|
|
total++;
|
|
const key = cells[0]!.innerText.trim();
|
|
const cls = cells[1]!.className || "";
|
|
if (cls.includes("failed")) {
|
|
failed.push(key);
|
|
} else {
|
|
passed++;
|
|
}
|
|
}
|
|
});
|
|
return { passed, total, failed };
|
|
});
|
|
|
|
const verdict =
|
|
result.failed.length === 0
|
|
? `${result.passed}/${result.total} — ALL GREEN`
|
|
: `${result.passed}/${result.total} (FAILED: ${result.failed.join(", ")})`;
|
|
const status = result.failed.length === 0 ? "PASS" : "FAIL";
|
|
console.log(`Result: [${status}] ${verdict}\n`);
|
|
results.push({ name: "bot.sannysoft.com", status, verdict });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 2: bot.incolumitas.com
|
|
// ---------------------------------------------------------------------------
|
|
async function testIncolumitas() {
|
|
console.log("--- bot.incolumitas.com ---");
|
|
await page.goto("https://bot.incolumitas.com", {
|
|
waitUntil: "networkidle",
|
|
timeout: 30000,
|
|
});
|
|
await page.waitForTimeout(12000); // needs time for all detection tests
|
|
|
|
const result = await page.evaluate(() => {
|
|
const text = document.body.innerText;
|
|
const okMatches = text.match(/"(\w+)":\s*"OK"/g) || [];
|
|
const failMatches = text.match(/"(\w+)":\s*"FAIL"/g) || [];
|
|
const failedTests = failMatches.map((m) => {
|
|
const match = m.match(/"(\w+)"/);
|
|
return match ? match[1] : m;
|
|
});
|
|
return {
|
|
passed: okMatches.length,
|
|
failed: failMatches.length,
|
|
failedTests,
|
|
total: okMatches.length + failMatches.length,
|
|
};
|
|
});
|
|
|
|
const verdict =
|
|
result.failed === 0
|
|
? `${result.passed}/${result.total} — ALL GREEN`
|
|
: `${result.passed}/${result.total} (FAILED: ${result.failedTests.join(", ")})`;
|
|
// WEBDRIVER false positive is expected
|
|
const status = result.failed <= 1 ? "PASS" : "FAIL";
|
|
console.log(`Result: [${status}] ${verdict}\n`);
|
|
results.push({ name: "bot.incolumitas.com", status, verdict });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 3: BrowserScan
|
|
// ---------------------------------------------------------------------------
|
|
async function testBrowserScan() {
|
|
console.log("--- BrowserScan ---");
|
|
await page.goto("https://www.browserscan.net/bot-detection", {
|
|
waitUntil: "networkidle",
|
|
timeout: 30000,
|
|
});
|
|
await page.waitForTimeout(5000);
|
|
|
|
const result = await page.evaluate(() => {
|
|
const text = document.body.innerText;
|
|
const normalMatches = text.match(/Normal/g);
|
|
const abnormalMatches = text.match(/Abnormal/g);
|
|
return {
|
|
normal: normalMatches ? normalMatches.length : 0,
|
|
abnormal: abnormalMatches ? abnormalMatches.length : 0,
|
|
};
|
|
});
|
|
|
|
const verdict = `Normal: ${result.normal}, Abnormal: ${result.abnormal}`;
|
|
const status = result.abnormal === 0 ? "PASS" : "FAIL";
|
|
console.log(`Result: [${status}] ${verdict}\n`);
|
|
results.push({ name: "BrowserScan", status, verdict });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 4: deviceandbrowserinfo.com
|
|
// ---------------------------------------------------------------------------
|
|
async function testDeviceAndBrowserInfo() {
|
|
console.log("--- deviceandbrowserinfo.com ---");
|
|
await page.goto("https://deviceandbrowserinfo.com/are_you_a_bot", {
|
|
waitUntil: "domcontentloaded",
|
|
timeout: 30000,
|
|
});
|
|
await page.waitForTimeout(8000);
|
|
|
|
const result = await page.evaluate(() => {
|
|
const text = document.body.innerText;
|
|
const botMatch = text.match(/"isBot":\s*(true|false)/);
|
|
const isBot = botMatch ? botMatch[1] === "true" : null;
|
|
const checks: Record<string, boolean> = {};
|
|
const patterns = [
|
|
"isBot",
|
|
"hasBotUserAgent",
|
|
"hasWebdriverTrue",
|
|
"isHeadlessChrome",
|
|
"isAutomatedWithCDP",
|
|
"hasSuspiciousWeakSignals",
|
|
"isPlaywright",
|
|
"hasInconsistentChromeObject",
|
|
];
|
|
patterns.forEach((p) => {
|
|
const match = text.match(new RegExp('"' + p + '":\\s*(true|false)'));
|
|
if (match) checks[p] = match[1] === "true";
|
|
});
|
|
return { isBot, checks };
|
|
});
|
|
|
|
const trueFlags = Object.entries(result.checks)
|
|
.filter(([, v]) => v)
|
|
.map(([k]) => k);
|
|
const verdict =
|
|
`isBot: ${result.isBot}` +
|
|
(trueFlags.length > 0 ? ` (flagged: ${trueFlags.join(", ")})` : " — all clear");
|
|
const status = !result.isBot ? "PASS" : "FAIL";
|
|
console.log(`Result: [${status}] ${verdict}\n`);
|
|
results.push({ name: "deviceandbrowserinfo.com", status, verdict });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 5: FingerprintJS
|
|
// ---------------------------------------------------------------------------
|
|
async function testFingerprintJS() {
|
|
console.log("--- FingerprintJS ---");
|
|
await page.goto("https://demo.fingerprint.com/web-scraping", {
|
|
waitUntil: "networkidle",
|
|
timeout: 30000,
|
|
});
|
|
await page.waitForTimeout(5000);
|
|
|
|
try {
|
|
await page.click("button:has-text('Search')", { timeout: 5000 });
|
|
await page.waitForTimeout(5000);
|
|
} catch {
|
|
// Search button may not be present
|
|
}
|
|
|
|
const result = await page.evaluate(() => {
|
|
const text = document.body.innerText;
|
|
const hasFlights =
|
|
text.includes("Price per adult") || text.includes("$");
|
|
const isBlocked =
|
|
text.includes("request was blocked") ||
|
|
text.includes("bot visit detected");
|
|
return { passed: hasFlights && !isBlocked, isBlocked, hasFlights };
|
|
});
|
|
|
|
const verdict = result.passed
|
|
? "PASSED (flights shown)"
|
|
: result.isBlocked
|
|
? "BLOCKED"
|
|
: "NO FLIGHTS";
|
|
const status = result.passed ? "PASS" : "FAIL";
|
|
console.log(`Result: [${status}] ${verdict}\n`);
|
|
results.push({ name: "FingerprintJS", status, verdict });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 6: reCAPTCHA v3
|
|
// ---------------------------------------------------------------------------
|
|
async function testRecaptcha() {
|
|
console.log("--- reCAPTCHA v3 (Google) ---");
|
|
await page.goto(
|
|
"https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php",
|
|
{ waitUntil: "networkidle", timeout: 30000 }
|
|
);
|
|
await page.waitForTimeout(8000);
|
|
|
|
const result = await page.evaluate(() => {
|
|
const text = document.body.innerText;
|
|
const scoreMatch = text.match(/"score":\s*(\d+\.\d+)/);
|
|
return {
|
|
score: scoreMatch ? parseFloat(scoreMatch[1]) : null,
|
|
};
|
|
});
|
|
|
|
const verdict = `Score: ${result.score ?? "N/A"}`;
|
|
const status = (result.score ?? 0) >= 0.7 ? "PASS" : "FAIL";
|
|
console.log(`Result: [${status}] ${verdict}\n`);
|
|
results.push({ name: "reCAPTCHA v3", status, verdict });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Run all tests
|
|
// ---------------------------------------------------------------------------
|
|
const tests = [
|
|
testSannysoft,
|
|
testIncolumitas,
|
|
testBrowserScan,
|
|
testDeviceAndBrowserInfo,
|
|
testFingerprintJS,
|
|
testRecaptcha,
|
|
];
|
|
|
|
for (const test of tests) {
|
|
try {
|
|
await test();
|
|
} catch (err) {
|
|
const name = test.name.replace("test", "");
|
|
console.log(`Error: ${err}\n`);
|
|
results.push({ name, status: "ERROR", verdict: String(err) });
|
|
}
|
|
}
|
|
|
|
await browser.close();
|
|
|
|
// Summary
|
|
console.log("=".repeat(60));
|
|
console.log("RESULTS SUMMARY");
|
|
console.log("=".repeat(60));
|
|
for (const r of results) {
|
|
const icon = { PASS: "+", FAIL: "!", ERROR: "x" }[r.status];
|
|
console.log(` [${icon}] ${r.name}: ${r.verdict}`);
|
|
}
|
|
const passedCount = results.filter((r) => r.status === "PASS").length;
|
|
console.log(`\n ${passedCount}/${results.length} tests passed`);
|
|
console.log("=".repeat(60));
|
|
|
|
process.exit(passedCount === results.length ? 0 : 1);
|