diff --git a/browser_use/browser/browser.py b/browser_use/browser/browser.py index 9278ac34c..7cb6bd75d 100644 --- a/browser_use/browser/browser.py +++ b/browser_use/browser/browser.py @@ -6,6 +6,7 @@ import asyncio import gc import logging from dataclasses import dataclass, field +from typing import Literal from playwright._impl._api_structures import ProxySettings from playwright.async_api import Browser as PlaywrightBrowser @@ -32,7 +33,7 @@ class BrowserConfig: disable_security: True Disable browser security features - extra_chromium_args: [] + extra_browser_args: [] Extra arguments to pass to the browser wss_url: None @@ -41,15 +42,15 @@ class BrowserConfig: cdp_url: None Connect to a browser instance via CDP - chrome_instance_path: None - Path to a Chrome instance to use to connect to your normal browser + browser_instance_path: None + Path to a Browser instance to use to connect to your normal browser e.g. '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome' """ headless: bool = False disable_security: bool = True - extra_chromium_args: list[str] = field(default_factory=list) - chrome_instance_path: str | None = None + extra_browser_args: list[str] = field(default_factory=list) + browser_instance_path: str | None = None wss_url: str | None = None cdp_url: str | None = None @@ -57,6 +58,8 @@ class BrowserConfig: new_context_config: BrowserContextConfig = field(default_factory=BrowserContextConfig) _force_keep_browser_alive: bool = False + browser_class: Literal['chromium', 'firefox', 'webkit'] = 'chromium' + # @singleton: TODO - think about id singleton makes sense here @@ -80,11 +83,11 @@ class Browser: self.disable_security_args = [] if self.config.disable_security: - self.disable_security_args = [ - '--disable-web-security', - '--disable-site-isolation-trials', - '--disable-features=IsolateOrigins,site-per-process', - ] + self.disable_security_args = ['--disable-web-security', '--disable-site-isolation-trials'] + if self.config.browser_class == 'chromium': + self.disable_security_args += [ + '--disable-features=IsolateOrigins,site-per-process', + ] async def new_context(self, config: BrowserContextConfig = BrowserContextConfig()) -> BrowserContext: """Create a browser context""" @@ -113,7 +116,8 @@ class Browser: if not self.config.cdp_url: raise ValueError('CDP URL is required') logger.info(f'Connecting to remote browser via CDP {self.config.cdp_url}') - browser = await playwright.chromium.connect_over_cdp(self.config.cdp_url) + browser_class = getattr(playwright, self.config.browser_class) + browser = await browser_class.connect_over_cdp(self.config.cdp_url) return browser async def _setup_wss(self, playwright: Playwright) -> PlaywrightBrowser: @@ -121,12 +125,13 @@ class Browser: if not self.config.wss_url: raise ValueError('WSS URL is required') logger.info(f'Connecting to remote browser via WSS {self.config.wss_url}') - browser = await playwright.chromium.connect(self.config.wss_url) + browser_class = getattr(playwright, self.config.browser_class) + browser = await browser_class.connect(self.config.wss_url) return browser async def _setup_browser_with_instance(self, playwright: Playwright) -> PlaywrightBrowser: """Sets up and returns a Playwright Browser instance with anti-detection measures.""" - if not self.config.chrome_instance_path: + if not self.config.browser_instance_path: raise ValueError('Chrome instance path is required') import subprocess @@ -137,7 +142,8 @@ class Browser: response = requests.get('http://localhost:9222/json/version', timeout=2) if response.status_code == 200: logger.info('Reusing existing Chrome instance') - browser = await playwright.chromium.connect_over_cdp( + browser_class = getattr(playwright, self.config.browser_class) + browser = await browser_class.connect_over_cdp( endpoint_url='http://localhost:9222', timeout=20000, # 20 second timeout for connection ) @@ -148,10 +154,10 @@ class Browser: # Start a new Chrome instance subprocess.Popen( [ - self.config.chrome_instance_path, + self.config.browser_instance_path, '--remote-debugging-port=9222', ] - + self.config.extra_chromium_args, + + self.config.extra_browser_args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) @@ -168,7 +174,8 @@ class Browser: # Attempt to connect again after starting a new instance try: - browser = await playwright.chromium.connect_over_cdp( + browser_class = getattr(playwright, self.config.browser_class) + browser = await browser_class.connect_over_cdp( endpoint_url='http://localhost:9222', timeout=20000, # 20 second timeout for connection ) @@ -181,9 +188,9 @@ class Browser: async def _setup_standard_browser(self, playwright: Playwright) -> PlaywrightBrowser: """Sets up and returns a Playwright Browser instance with anti-detection measures.""" - browser = await playwright.chromium.launch( - headless=self.config.headless, - args=[ + browser_class = getattr(playwright, self.config.browser_class) + args = { + 'chromium': [ '--no-sandbox', '--disable-blink-features=AutomationControlled', '--disable-infobars', @@ -197,10 +204,17 @@ class Browser: '--no-default-browser-check', '--no-startup-window', '--window-position=0,0', - # '--window-size=1280,1000', - ] - + self.disable_security_args - + self.config.extra_chromium_args, + ], + 'firefox': [ + '-no-remote', + ], + 'webkit': [ + '--no-startup-window', + ], + } + browser = await browser_class.launch( + headless=self.config.headless, + args=args[self.config.browser_class] + self.disable_security_args + self.config.extra_browser_args, proxy=self.config.proxy, ) # convert to Browser @@ -213,7 +227,7 @@ class Browser: return await self._setup_cdp(playwright) if self.config.wss_url: return await self._setup_wss(playwright) - elif self.config.chrome_instance_path: + elif self.config.browser_instance_path: return await self._setup_browser_with_instance(playwright) else: return await self._setup_standard_browser(playwright) diff --git a/browser_use/browser/context.py b/browser_use/browser/context.py index 44b7595d5..2f2291026 100644 --- a/browser_use/browser/context.py +++ b/browser_use/browser/context.py @@ -311,7 +311,7 @@ class BrowserContext: """Creates a new browser context with anti-detection measures and loads cookies if available.""" if self.browser.config.cdp_url and len(browser.contexts) > 0: context = browser.contexts[0] - elif self.browser.config.chrome_instance_path and len(browser.contexts) > 0: + elif self.browser.config.browser_instance_path and len(browser.contexts) > 0: # Connect to existing Chrome instance instead of creating new one context = browser.contexts[0] else: diff --git a/docs/customize/browser-settings.mdx b/docs/customize/browser-settings.mdx index 41995f9e1..de5dbf9ed 100644 --- a/docs/customize/browser-settings.mdx +++ b/docs/customize/browser-settings.mdx @@ -43,7 +43,7 @@ agent = Agent( ### Additional Settings -- **extra_chromium_args** (default: `[]`) +- **extra_browser_args** (default: `[]`) Additional arguments are passed to the browser at launch. See the [full list of available arguments](https://github.com/browser-use/browser-use/blob/main/browser_use/browser/browser.py#L180). - **proxy** (default: `None`) @@ -98,12 +98,12 @@ Connect to your existing Chrome installation to access saved states and cookies. ```python config = BrowserConfig( - chrome_instance_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + browser_instance_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ) ``` -- **chrome_instance_path** (default: `None`) - Path to connect to an existing Chrome installation. Particularly useful for workflows requiring existing login states or browser preferences. +- **browser_instance_path** (default: `None`) + Path to connect to an existing Browser installation. Particularly useful for workflows requiring existing login states or browser preferences. This will overwrite other browser settings. diff --git a/docs/customize/real-browser.mdx b/docs/customize/real-browser.mdx index aafb92f91..c026f95ac 100644 --- a/docs/customize/real-browser.mdx +++ b/docs/customize/real-browser.mdx @@ -24,7 +24,7 @@ import asyncio browser = Browser( config=BrowserConfig( # Specify the path to your Chrome executable - chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', # macOS path + browser_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', # macOS path # For Windows, typically: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe' # For Linux, typically: '/usr/bin/google-chrome' ) diff --git a/examples/browser/real_browser.py b/examples/browser/real_browser.py index 1bd255ae8..c81569741 100644 --- a/examples/browser/real_browser.py +++ b/examples/browser/real_browser.py @@ -16,7 +16,7 @@ from browser_use.browser.context import BrowserContext browser = Browser( config=BrowserConfig( # NOTE: you need to close your chrome browser - so that this can open your browser in debug mode - chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + browser_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ) ) diff --git a/examples/custom-functions/file_upload.py b/examples/custom-functions/file_upload.py index f1efdf6c7..f60c996e3 100644 --- a/examples/custom-functions/file_upload.py +++ b/examples/custom-functions/file_upload.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) browser = Browser( config=BrowserConfig( headless=False, - chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + browser_instance_path=='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ) ) controller = Controller() diff --git a/examples/features/custom_user_agent.py b/examples/features/custom_user_agent.py index f832d92ad..ba5de1254 100644 --- a/examples/features/custom_user_agent.py +++ b/examples/features/custom_user_agent.py @@ -49,7 +49,7 @@ llm = get_llm(args.provider) browser = Browser( config=BrowserConfig( - # chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + #browser_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ) ) diff --git a/examples/features/restrict_urls.py b/examples/features/restrict_urls.py index 398c78559..f174263f7 100644 --- a/examples/features/restrict_urls.py +++ b/examples/features/restrict_urls.py @@ -23,7 +23,7 @@ allowed_domains = ['google.com'] browser = Browser( config=BrowserConfig( - chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + browser_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', new_context_config=BrowserContextConfig( allowed_domains=allowed_domains, ), diff --git a/examples/features/result_processing.py b/examples/features/result_processing.py index 53177f4ee..8a1c33802 100644 --- a/examples/features/result_processing.py +++ b/examples/features/result_processing.py @@ -23,7 +23,7 @@ browser = Browser( config=BrowserConfig( headless=False, disable_security=True, - extra_chromium_args=['--window-size=2000,2000'], + extra_browser_args=['--window-size=2000,2000'], ) ) diff --git a/examples/models/bedrock_claude.py b/examples/models/bedrock_claude.py index eaf4e2047..8be73ca2f 100644 --- a/examples/models/bedrock_claude.py +++ b/examples/models/bedrock_claude.py @@ -42,7 +42,7 @@ llm = get_llm() browser = Browser( config=BrowserConfig( - # chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + # browser_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ) ) diff --git a/examples/use-cases/find_and_apply_to_jobs.py b/examples/use-cases/find_and_apply_to_jobs.py index daf65897b..73dbcf0b2 100644 --- a/examples/use-cases/find_and_apply_to_jobs.py +++ b/examples/use-cases/find_and_apply_to_jobs.py @@ -110,7 +110,7 @@ async def upload_cv(index: int, browser: BrowserContext): browser = Browser( config=BrowserConfig( - chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + browser_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', disable_security=True, ) ) diff --git a/examples/use-cases/post-twitter.py b/examples/use-cases/post-twitter.py index 86caef1a0..c453769cb 100644 --- a/examples/use-cases/post-twitter.py +++ b/examples/use-cases/post-twitter.py @@ -71,7 +71,7 @@ def create_twitter_agent(config: TwitterConfig) -> Agent: browser = Browser( config=BrowserConfig( headless=config.headless, - chrome_instance_path=config.chrome_path, + browser_instance_path=config.chrome_path, ) ) diff --git a/examples/use-cases/twitter_post_using_cookies.py b/examples/use-cases/twitter_post_using_cookies.py index 72ac98cea..cbf336ece 100644 --- a/examples/use-cases/twitter_post_using_cookies.py +++ b/examples/use-cases/twitter_post_using_cookies.py @@ -21,7 +21,7 @@ llm = ChatGoogleGenerativeAI(model='gemini-2.0-flash-exp', api_key=SecretStr(api browser = Browser( config=BrowserConfig( - # chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + # browser_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ) ) file_path = os.path.join(os.path.dirname(__file__), 'twitter_cookies.txt') diff --git a/tests/test_browser.py b/tests/test_browser.py index b3acf344f..3f17c7a62 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -27,7 +27,7 @@ async def test_standard_browser_launch(monkeypatch): async def start(self): return DummyPlaywright() monkeypatch.setattr("browser_use.browser.browser.async_playwright", lambda: DummyAsyncPlaywrightContext()) - config = BrowserConfig(headless=True, disable_security=False, extra_chromium_args=["--test"]) + config = BrowserConfig(headless=True, disable_security=False, extra_browser_args=["--test"]) browser_obj = Browser(config=config) result_browser = await browser_obj.get_playwright_browser() assert isinstance(result_browser, DummyBrowser), "Expected DummyBrowser from _setup_standard_browser" @@ -114,7 +114,7 @@ async def test_chrome_instance_browser_launch(monkeypatch): async def start(self): return DummyPlaywright() monkeypatch.setattr("browser_use.browser.browser.async_playwright", lambda: DummyAsyncPlaywrightContext()) - config = BrowserConfig(chrome_instance_path="dummy/chrome", extra_chromium_args=["--dummy-arg"]) + config = BrowserConfig(browser_instance_path="dummy/chrome", extra_browser_args=["--dummy-arg"]) browser_obj = Browser(config=config) result_browser = await browser_obj.get_playwright_browser() assert isinstance(result_browser, DummyBrowser), "Expected DummyBrowser from _setup_browser_with_instance" @@ -169,7 +169,7 @@ async def test_standard_browser_disable_security_args(monkeypatch): async def start(self): return DummyPlaywright() monkeypatch.setattr("browser_use.browser.browser.async_playwright", lambda: DummyAsyncPlaywrightContext()) - config = BrowserConfig(headless=True, disable_security=True, extra_chromium_args=extra_args) + config = BrowserConfig(headless=True, disable_security=True, extra_browser_args=extra_args) browser_obj = Browser(config=config) result_browser = await browser_obj.get_playwright_browser() assert isinstance(result_browser, DummyBrowser), "Expected DummyBrowser from _setup_standard_browser with disable_security active" @@ -218,7 +218,7 @@ async def test_chrome_instance_browser_launch_failure(monkeypatch): async def start(self): return DummyPlaywright() monkeypatch.setattr("browser_use.browser.browser.async_playwright", lambda: DummyAsyncPlaywrightContext()) - config = BrowserConfig(chrome_instance_path="dummy/chrome", extra_chromium_args=["--dummy-arg"]) + config = BrowserConfig(browser_instance_path="dummy/chrome", extra_browser_args=["--dummy-arg"]) browser_obj = Browser(config=config) with pytest.raises(RuntimeError, match="To start chrome in Debug mode"): await browser_obj.get_playwright_browser() @@ -244,7 +244,7 @@ async def test_get_playwright_browser_caching(monkeypatch): async def start(self): return DummyPlaywright() monkeypatch.setattr("browser_use.browser.browser.async_playwright", lambda: DummyAsyncPlaywrightContext()) - config = BrowserConfig(headless=True, disable_security=False, extra_chromium_args=["--test"]) + config = BrowserConfig(headless=True, disable_security=False, extra_browser_args=["--test"]) browser_obj = Browser(config=config) first_browser = await browser_obj.get_playwright_browser() second_browser = await browser_obj.get_playwright_browser()