From abd24d381bcb0f1dc5a91b76d10c51998c8cbbd6 Mon Sep 17 00:00:00 2001 From: Ruzzgar Date: Mon, 6 Apr 2026 20:50:38 +0300 Subject: [PATCH] Implement comprehensive browser path discovery for Windows --- cli.py | 77 +++++++++++++++++++++++-------- tests/test_cli_browser_connect.py | 43 +++++++++++++++++ 2 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 tests/test_cli_browser_connect.py diff --git a/cli.py b/cli.py index ed9e08131..29e6257d1 100644 --- a/cli.py +++ b/cli.py @@ -120,6 +120,63 @@ def _parse_reasoning_config(effort: str) -> dict | None: return result +def _get_chrome_debug_candidates(system: str) -> list[str]: + """Return likely browser executables for local CDP auto-launch.""" + candidates: list[str] = [] + seen: set[str] = set() + + def _add_candidate(path: str | None) -> None: + if not path: + return + normalized = os.path.normcase(os.path.normpath(path)) + if normalized in seen: + return + if os.path.isfile(path): + candidates.append(path) + seen.add(normalized) + + def _add_from_path(*names: str) -> None: + for name in names: + _add_candidate(shutil.which(name)) + + if system == "Darwin": + for app in ( + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + "/Applications/Chromium.app/Contents/MacOS/Chromium", + "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", + "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", + ): + _add_candidate(app) + elif system == "Windows": + _add_from_path( + "chrome.exe", "msedge.exe", "brave.exe", "chromium.exe", + "chrome", "msedge", "brave", "chromium", + ) + + for base in ( + os.environ.get("ProgramFiles"), + os.environ.get("ProgramFiles(x86)"), + os.environ.get("LOCALAPPDATA"), + ): + if not base: + continue + for parts in ( + ("Google", "Chrome", "Application", "chrome.exe"), + ("Chromium", "Application", "chrome.exe"), + ("Chromium", "Application", "chromium.exe"), + ("BraveSoftware", "Brave-Browser", "Application", "brave.exe"), + ("Microsoft", "Edge", "Application", "msedge.exe"), + ): + _add_candidate(os.path.join(base, *parts)) + else: + _add_from_path( + "google-chrome", "google-chrome-stable", "chromium-browser", + "chromium", "brave-browser", "microsoft-edge", + ) + + return candidates + + def load_cli_config() -> Dict[str, Any]: """ Load CLI configuration from config files. @@ -4838,27 +4895,9 @@ class HermesCLI: Returns True if a launch command was executed (doesn't guarantee success). """ - import shutil import subprocess as _sp - candidates = [] - if system == "Darwin": - # macOS: try common app bundle locations - for app in ( - "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", - "/Applications/Chromium.app/Contents/MacOS/Chromium", - "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", - "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", - ): - if os.path.isfile(app): - candidates.append(app) - else: - # Linux: try common binary names - for name in ("google-chrome", "google-chrome-stable", "chromium-browser", - "chromium", "brave-browser", "microsoft-edge"): - path = shutil.which(name) - if path: - candidates.append(path) + candidates = _get_chrome_debug_candidates(system) if not candidates: return False diff --git a/tests/test_cli_browser_connect.py b/tests/test_cli_browser_connect.py new file mode 100644 index 000000000..a913d96fe --- /dev/null +++ b/tests/test_cli_browser_connect.py @@ -0,0 +1,43 @@ +"""Tests for CLI browser CDP auto-launch helpers.""" + +from unittest.mock import patch + +from cli import HermesCLI + + +class TestChromeDebugLaunch: + def test_windows_launch_uses_browser_found_on_path(self): + captured = {} + + def fake_popen(cmd, **kwargs): + captured["cmd"] = cmd + captured["kwargs"] = kwargs + return object() + + with patch("cli.shutil.which", side_effect=lambda name: r"C:\Chrome\chrome.exe" if name == "chrome.exe" else None), \ + patch("cli.os.path.isfile", side_effect=lambda path: path == r"C:\Chrome\chrome.exe"), \ + patch("subprocess.Popen", side_effect=fake_popen): + assert HermesCLI._try_launch_chrome_debug(9333, "Windows") is True + + assert captured["cmd"] == [r"C:\Chrome\chrome.exe", "--remote-debugging-port=9333"] + assert captured["kwargs"]["start_new_session"] is True + + def test_windows_launch_falls_back_to_common_install_dirs(self, monkeypatch): + captured = {} + installed = r"C:\Program Files\Google\Chrome\Application\chrome.exe" + + def fake_popen(cmd, **kwargs): + captured["cmd"] = cmd + captured["kwargs"] = kwargs + return object() + + monkeypatch.setenv("ProgramFiles", r"C:\Program Files") + monkeypatch.delenv("ProgramFiles(x86)", raising=False) + monkeypatch.delenv("LOCALAPPDATA", raising=False) + + with patch("cli.shutil.which", return_value=None), \ + patch("cli.os.path.isfile", side_effect=lambda path: path == installed), \ + patch("subprocess.Popen", side_effect=fake_popen): + assert HermesCLI._try_launch_chrome_debug(9222, "Windows") is True + + assert captured["cmd"] == [installed, "--remote-debugging-port=9222"]