@@ -1118,6 +1118,7 @@ mod tests {
|
|||||||
proxy_bypass: None,
|
proxy_bypass: None,
|
||||||
args: None,
|
args: None,
|
||||||
user_agent: None,
|
user_agent: None,
|
||||||
|
provider: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,388 +0,0 @@
|
|||||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
|
|
||||||
import { execSync } from 'child_process';
|
|
||||||
import { resolve } from 'path';
|
|
||||||
import { existsSync } from 'fs';
|
|
||||||
|
|
||||||
const CLI_PATH = resolve(__dirname, '../cli/target/release/agent-browser');
|
|
||||||
|
|
||||||
// Check if binary exists before running any tests
|
|
||||||
beforeAll(() => {
|
|
||||||
if (!existsSync(CLI_PATH)) {
|
|
||||||
throw new Error(`CLI binary not found at: ${CLI_PATH}. Please build the project first with 'cargo build --release'.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate unique session for each test
|
|
||||||
let testCounter = 0;
|
|
||||||
function getUniqueSession(): string {
|
|
||||||
return `e2e-${Date.now()}-${testCounter++}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function runCli(session: string, args: string): string {
|
|
||||||
return execSync(`${CLI_PATH} --session ${session} ${args}`, {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
timeout: 30000,
|
|
||||||
}).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function runCliWithEnv(session: string, args: string, envVars: Record<string, string>): string {
|
|
||||||
const env = { ...process.env, ...envVars };
|
|
||||||
return execSync(`${CLI_PATH} --session ${session} ${args}`, {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
env,
|
|
||||||
timeout: 30000,
|
|
||||||
}).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function runCliJson(session: string, args: string): {
|
|
||||||
success: boolean;
|
|
||||||
data?: Record<string, unknown>;
|
|
||||||
error?: string;
|
|
||||||
} {
|
|
||||||
const output = runCli(session, `${args} --json`);
|
|
||||||
return JSON.parse(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeBrowser(session: string) {
|
|
||||||
try {
|
|
||||||
execSync(`${CLI_PATH} --session ${session} close`, {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
timeout: 10000,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// Ignore if already closed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to ensure browser is closed before test
|
|
||||||
function ensureBrowserClosed(session: string) {
|
|
||||||
closeBrowser(session);
|
|
||||||
// Wait a bit to ensure daemon is fully stopped
|
|
||||||
execSync('sleep 0.2', { timeout: 1000 });
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('E2E: Launch Options', () => {
|
|
||||||
let session: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
session = getUniqueSession();
|
|
||||||
ensureBrowserClosed(session);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
closeBrowser(session);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('--args flag', () => {
|
|
||||||
it('should disable webdriver detection with --args', () => {
|
|
||||||
runCli(session, '--args "--disable-blink-features=AutomationControlled" open https://example.com');
|
|
||||||
const result = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support multiple comma-separated args', () => {
|
|
||||||
runCli(session, '--args "--disable-blink-features=AutomationControlled,--disable-dev-shm-usage" open https://example.com');
|
|
||||||
const result = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have webdriver=true without --args (default behavior)', () => {
|
|
||||||
runCli(session, 'open https://example.com');
|
|
||||||
const result = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.data?.result).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('--user-agent flag', () => {
|
|
||||||
it('should set custom user-agent', () => {
|
|
||||||
const customUA = 'E2ETestBot/1.0';
|
|
||||||
runCli(session, `--user-agent "${customUA}" open https://example.com`);
|
|
||||||
const result = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.data?.result).toBe(customUA);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use default Chrome user-agent when not specified', () => {
|
|
||||||
runCli(session, 'open https://example.com');
|
|
||||||
const result = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(String(result.data?.result)).toContain('Chrome');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('--proxy flag', () => {
|
|
||||||
it('should fail navigation when proxy is unreachable (proves proxy is used)', () => {
|
|
||||||
// Launch with unreachable proxy - navigation should fail immediately
|
|
||||||
// This proves proxy is being used
|
|
||||||
let failed = false;
|
|
||||||
try {
|
|
||||||
runCli(session, '--proxy "http://127.0.0.1:59999" open https://example.com');
|
|
||||||
} catch (e) {
|
|
||||||
failed = true;
|
|
||||||
expect(String(e)).toContain('ERR_PROXY_CONNECTION_FAILED');
|
|
||||||
}
|
|
||||||
expect(failed).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('environment variables', () => {
|
|
||||||
it('should read AGENT_BROWSER_ARGS from environment', () => {
|
|
||||||
runCliWithEnv(session, 'open https://example.com', {
|
|
||||||
AGENT_BROWSER_ARGS: '--disable-blink-features=AutomationControlled',
|
|
||||||
});
|
|
||||||
const result = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should read AGENT_BROWSER_USER_AGENT from environment', () => {
|
|
||||||
const customUA = 'EnvTestBot/2.0';
|
|
||||||
runCliWithEnv(session, 'open https://example.com', {
|
|
||||||
AGENT_BROWSER_USER_AGENT: customUA,
|
|
||||||
});
|
|
||||||
const result = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.data?.result).toBe(customUA);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('warning for already running daemon', () => {
|
|
||||||
it('should warn when launch-time options are ignored', () => {
|
|
||||||
// First, start daemon with default options
|
|
||||||
runCli(session, 'open https://example.com');
|
|
||||||
// Try to use --user-agent with already running daemon
|
|
||||||
const output = execSync(
|
|
||||||
`${CLI_PATH} --session ${session} --user-agent "IgnoredUA" get url 2>&1`,
|
|
||||||
{ encoding: 'utf-8' }
|
|
||||||
);
|
|
||||||
expect(output).toContain('--user-agent ignored');
|
|
||||||
expect(output).toContain('daemon already running');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Single parameter tests', () => {
|
|
||||||
it('--args only', () => {
|
|
||||||
runCli(session, '--args "--disable-blink-features=AutomationControlled" open https://example.com');
|
|
||||||
const result = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('--user-agent only', () => {
|
|
||||||
const ua = 'SingleParam/1.0';
|
|
||||||
runCli(session, `--user-agent "${ua}" open https://example.com`);
|
|
||||||
const result = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.data?.result).toBe(ua);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('--proxy only', () => {
|
|
||||||
// Use real proxy to verify it works
|
|
||||||
runCli(session, '--proxy "http://localhost:7890" open https://httpbin.org/ip');
|
|
||||||
const urlResult = runCliJson(session, 'get url');
|
|
||||||
expect(urlResult.success).toBe(true);
|
|
||||||
expect(urlResult.data?.url).toBe('https://httpbin.org/ip');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Two parameter combinations', () => {
|
|
||||||
it('--args + --user-agent', () => {
|
|
||||||
const ua = 'TwoParam/1.0';
|
|
||||||
runCli(session, `--args "--disable-blink-features=AutomationControlled" --user-agent "${ua}" open https://example.com`);
|
|
||||||
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(ua);
|
|
||||||
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('--proxy + --user-agent', () => {
|
|
||||||
const ua = 'ProxyUA/1.0';
|
|
||||||
runCli(session, `--proxy "http://localhost:7890" --user-agent "${ua}" open https://example.com`);
|
|
||||||
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(ua);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('--proxy + --args', () => {
|
|
||||||
runCli(session, '--proxy "http://localhost:7890" --args "--disable-blink-features=AutomationControlled" open https://example.com');
|
|
||||||
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('--proxy + --proxy-bypass', () => {
|
|
||||||
// example.com should bypass proxy
|
|
||||||
runCli(session, '--proxy "http://localhost:7890" --proxy-bypass "example.com" open https://example.com');
|
|
||||||
|
|
||||||
const urlResult = runCliJson(session, 'get url');
|
|
||||||
expect(urlResult.success).toBe(true);
|
|
||||||
expect(urlResult.data?.url).toBe('https://example.com/');
|
|
||||||
|
|
||||||
const titleResult = runCliJson(session, 'get title');
|
|
||||||
expect(titleResult.success).toBe(true);
|
|
||||||
expect(String(titleResult.data?.title)).toContain('Example Domain');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Three parameter combinations', () => {
|
|
||||||
it('--proxy + --user-agent + --args', () => {
|
|
||||||
const ua = 'ThreeParam/1.0';
|
|
||||||
runCli(session, `--proxy "http://localhost:7890" --user-agent "${ua}" --args "--disable-blink-features=AutomationControlled" open https://example.com`);
|
|
||||||
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(ua);
|
|
||||||
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('--proxy + --proxy-bypass + --user-agent', () => {
|
|
||||||
const ua = 'BypassUA/1.0';
|
|
||||||
runCli(session, `--proxy "http://localhost:7890" --proxy-bypass "example.com" --user-agent "${ua}" open https://example.com`);
|
|
||||||
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(ua);
|
|
||||||
|
|
||||||
const urlResult = runCliJson(session, 'get url');
|
|
||||||
expect(urlResult.success).toBe(true);
|
|
||||||
expect(urlResult.data?.url).toBe('https://example.com/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('--proxy + --proxy-bypass + --args', () => {
|
|
||||||
runCli(session, '--proxy "http://localhost:7890" --proxy-bypass "example.com" --args "--disable-blink-features=AutomationControlled" open https://example.com');
|
|
||||||
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('--user-agent + --args + --proxy-bypass (no proxy)', () => {
|
|
||||||
const ua = 'NoProxy/1.0';
|
|
||||||
// Without proxy, proxy-bypass should be ignored
|
|
||||||
runCli(session, `--user-agent "${ua}" --args "--disable-blink-features=AutomationControlled" open https://example.com`);
|
|
||||||
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(ua);
|
|
||||||
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Four parameter combinations (all parameters)', () => {
|
|
||||||
it('--proxy + --proxy-bypass + --user-agent + --args', () => {
|
|
||||||
const ua = 'AllParams/1.0';
|
|
||||||
runCli(session, `--proxy "http://localhost:7890" --proxy-bypass "example.com" --user-agent "${ua}" --args "--disable-blink-features=AutomationControlled" open https://example.com`);
|
|
||||||
|
|
||||||
// Verify user-agent
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(ua);
|
|
||||||
|
|
||||||
// Verify webdriver hidden (args works)
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
|
|
||||||
// Verify page loaded (proxy + bypass works)
|
|
||||||
const urlResult = runCliJson(session, 'get url');
|
|
||||||
expect(urlResult.success).toBe(true);
|
|
||||||
expect(urlResult.data?.url).toBe('https://example.com/');
|
|
||||||
|
|
||||||
// Navigate to httpbin through proxy (not bypassed)
|
|
||||||
runCli(session, 'goto https://httpbin.org/headers');
|
|
||||||
const bodyResult = runCliJson(session, 'get text body');
|
|
||||||
expect(bodyResult.success).toBe(true);
|
|
||||||
const text = String(bodyResult.data?.text);
|
|
||||||
expect(text).toContain(ua);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Multiple args formats', () => {
|
|
||||||
it('comma-separated args with proxy and user-agent', () => {
|
|
||||||
const ua = 'CommaArgs/1.0';
|
|
||||||
runCli(session, `--proxy "http://localhost:7890" --user-agent "${ua}" --args "--disable-blink-features=AutomationControlled,--disable-dev-shm-usage" open https://example.com`);
|
|
||||||
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(ua);
|
|
||||||
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Real-world scenarios', () => {
|
|
||||||
it('mobile device simulation with proxy', () => {
|
|
||||||
const mobileUA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15';
|
|
||||||
runCli(session, `--proxy "http://localhost:7890" --proxy-bypass "example.com" --user-agent "${mobileUA}" --args "--disable-blink-features=AutomationControlled" open https://example.com`);
|
|
||||||
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(String(uaResult.data?.result)).toContain('iPhone');
|
|
||||||
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('stealth browsing with all anti-detection features', () => {
|
|
||||||
const stealthUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
||||||
runCli(session, `--proxy "http://localhost:7890" --proxy-bypass "example.com,localhost" --user-agent "${stealthUA}" --args "--disable-blink-features=AutomationControlled,--disable-web-security" open https://example.com`);
|
|
||||||
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(stealthUA);
|
|
||||||
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
|
|
||||||
const pluginsResult = runCliJson(session, 'eval "typeof navigator.plugins"');
|
|
||||||
expect(pluginsResult.success).toBe(true);
|
|
||||||
expect(pluginsResult.data?.result).toBe('object');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('combine proxy with user-agent and args', () => {
|
|
||||||
const customUA = 'ProxyTestBot/1.0';
|
|
||||||
runCli(session, `--proxy "http://localhost:7890" --user-agent "${customUA}" --args "--disable-blink-features=AutomationControlled" open https://example.com`);
|
|
||||||
|
|
||||||
// navigator.userAgent
|
|
||||||
const uaResult = runCliJson(session, 'eval "navigator.userAgent"');
|
|
||||||
expect(uaResult.success).toBe(true);
|
|
||||||
expect(uaResult.data?.result).toBe(customUA);
|
|
||||||
|
|
||||||
// webdriver
|
|
||||||
const wdResult = runCliJson(session, 'eval "navigator.webdriver"');
|
|
||||||
expect(wdResult.success).toBe(true);
|
|
||||||
expect(wdResult.data?.result).toBe(false);
|
|
||||||
|
|
||||||
// proxy well
|
|
||||||
const urlResult = runCliJson(session, 'get url');
|
|
||||||
expect(urlResult.success).toBe(true);
|
|
||||||
expect(urlResult.data?.url).toBe('https://example.com/');
|
|
||||||
|
|
||||||
// proxy httpbin validate User-Agent
|
|
||||||
runCli(session, 'goto https://httpbin.org/headers');
|
|
||||||
const bodyResult = runCliJson(session, 'get text body');
|
|
||||||
expect(bodyResult.success).toBe(true);
|
|
||||||
const text = String(bodyResult.data?.text);
|
|
||||||
expect(text).toContain(customUA);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user