fix(stealth): 修复 headed 模式下 stealth 失效并统一策略

- 修复 launch 协议未透传 stealth 导致 --headed 下补丁失效的问题\n- 在 BrowserManager 引入 StealthPolicy,统一 local/CDP/provider 能力决策\n- 增加 launch 返回 stealth 状态并在 --debug 输出连接类型与能力\n- 补充 local/CDP 回归测试与 bot.sannysoft.com 自动检查脚本\n- 同步 README、CLI help、技能文档与 CDP 文档中的 stealth 能力矩阵
This commit is contained in:
leeguooooo
2026-02-24 12:18:47 +09:00
parent 2fe7394dbe
commit 8932f28926
15 changed files with 985 additions and 89 deletions
+54
View File
@@ -0,0 +1,54 @@
import { afterEach, describe, expect, it } from 'vitest';
import { BrowserManager } from './browser.js';
async function readWebdriverSignals(browser: BrowserManager): Promise<{
value: boolean | undefined;
inNavigator: boolean;
ownNavigator: boolean;
ownPrototype: boolean;
}> {
const page = browser.getPage();
await page.goto('about:blank');
return page.evaluate(() => {
const prototype = Object.getPrototypeOf(navigator);
return {
value: navigator.webdriver,
inNavigator: 'webdriver' in navigator,
ownNavigator: Object.prototype.hasOwnProperty.call(navigator, 'webdriver'),
ownPrototype: Object.prototype.hasOwnProperty.call(prototype, 'webdriver'),
};
});
}
describe('Stealth mode', () => {
let browser: BrowserManager;
afterEach(async () => {
if (browser?.isLaunched()) {
await browser.close();
}
});
it('removes navigator.webdriver when stealth is enabled', async () => {
browser = new BrowserManager();
await browser.launch({ headless: true, stealth: true });
const signals = await readWebdriverSignals(browser);
expect(signals.value).toBeUndefined();
expect(signals.inNavigator).toBe(false);
expect(signals.ownNavigator).toBe(false);
expect(signals.ownPrototype).toBe(false);
});
it('applies stealth patches to contexts created by newWindow', async () => {
browser = new BrowserManager();
await browser.launch({ headless: true, stealth: true });
await browser.newWindow();
const signals = await readWebdriverSignals(browser);
expect(signals.value).toBeUndefined();
expect(signals.inNavigator).toBe(false);
expect(signals.ownNavigator).toBe(false);
expect(signals.ownPrototype).toBe(false);
});
});