Skip to content

fix(browser): let ManagedBrowser use the arguments it documents - #2272

Open
L4XB wants to merge 1 commit into
unclecode:mainfrom
L4XB:fix/managed-browser-ignores-its-arguments
Open

L4XB wants to merge 1 commit into
unclecode:mainfrom
L4XB:fix/managed-browser-ignores-its-arguments

Conversation

@L4XB

@L4XB L4XB commented Sep 16, 2026

Copy link
Copy Markdown

Summary

ManagedBrowser.__init__ documents eight arguments and then reads every attribute off
browser_config, which defaults to None:

def __init__(self, browser_type="chromium", user_data_dir=None, headless=False,
             logger=None, host="localhost", debugging_port=9222, cdp_url=None,
             browser_config: Optional[BrowserConfig] = None):
    ...
    self.browser_type = browser_config.browser_type      # None when no config was passed

Two consequences, both reproducible:

Constructing it the documented way raises.

>>> ManagedBrowser(browser_type='chromium', user_data_dir='/tmp/x', headless=True, debugging_port=9222)
AttributeError: 'NoneType' object has no attribute 'browser_type'

That is the exact call BrowserProfiler.launch_builtin_browser makes
(browser_profiler.py:1205), and it sits outside the try: below it, so it propagates.
crwl browser start (cli.py:717) and crwl browser restart (cli.py:913) both go through
it, so neither can launch the builtin browser.

With a config, the arguments beside it are silently ignored.

>>> cfg = BrowserConfig(browser_type='chromium', debugging_port=9222)
>>> b = ManagedBrowser(browser_type='firefox', debugging_port=9333, host='0.0.0.0', browser_config=cfg)
>>> b.browser_type, b.debugging_port, b.host
('chromium', 9222, 'localhost')

browser_profiler.py shows the confusion in the repository itself: the call at line 481 has
its arguments commented out next to browser_config=, while the one at line 1020 still
passes user_data_dir, headless and debugging_port alongside a config — they do nothing
there, and only happen to agree with it.

List of files changed and why

  • crawl4ai/browser_manager.py — when browser_config is None, build one from the
    arguments. Nothing else changes: a supplied config is still used as-is, so every existing
    call site (browser_manager.py:777, the three in browser_profiler.py, and the tests in
    tests/browser/) behaves exactly as before. The docstring now says which of the two wins.
  • tests/unit/test_managed_browser_arguments.py — new.

How Has This Been Tested?

tests/unit/test_managed_browser_arguments.py, 9 cases:

  • the all-defaults construction no longer raises
  • the exact argument shape launch_builtin_browser uses constructs, with its values intact
  • each of the six settings reaches the instance, one parametrized case per argument
  • a supplied config still wins, and is still stored as browser_config — the control that
    pins the existing call sites

8 of the 9 fail on main; the one that passes is that control.

tests/unit/test_managed_browser_arguments.py     9 passed
tests/unit                                       same 28 pre-existing failures as main
                                                 (pdf and sitemap optional deps), 58 passed

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added/updated unit tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Every attribute is read off browser_config, which defaults to None, so the
documented all-arguments form raised on construction:

  ManagedBrowser(browser_type='chromium', user_data_dir=..., headless=True,
                 debugging_port=9222)
  AttributeError: 'NoneType' object has no attribute 'browser_type'

That is the exact call BrowserProfiler.launch_builtin_browser makes, so
'crwl browser start' and 'crwl browser restart' fail before a browser is
launched. Where a config is passed as well, the arguments beside it are
ignored instead: browser_type='firefox', debugging_port=9333 came back as
chromium on 9222. browser_profiler.py shows both, one call site having
commented its arguments out and another still passing them.

Build a BrowserConfig from the arguments when none is given. A supplied
config still wins, so every existing call site behaves exactly as before, and
the docstring now says which of the two is the source of truth.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant