From 16f7f7c917fabf421b207c3879503b3b4ce44029 Mon Sep 17 00:00:00 2001 From: Br1an67 <932039080@qq.com> Date: Mon, 2 Mar 2026 00:10:23 +0800 Subject: [PATCH] fix: auto-update ChromeDriver when version mismatches Chrome Add version compatibility check before using existing ChromeDriver. When the installed ChromeDriver major version does not match Chrome, chromedriver_autoinstaller downloads the correct version automatically instead of failing with a version mismatch error. Add helper functions get_chromedriver_version() and is_chromedriver_compatible() with tests for version matching logic. --- sources/browser.py | 42 ++++++++++++++-- tests/test_chromedriver_update.py | 82 +++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 tests/test_chromedriver_update.py diff --git a/sources/browser.py b/sources/browser.py index 92d7244..ba3d756 100644 --- a/sources/browser.py +++ b/sources/browser.py @@ -15,6 +15,7 @@ import undetected_chromedriver as uc import chromedriver_autoinstaller import certifi import ssl +import subprocess import time import random import os @@ -76,20 +77,51 @@ def get_random_user_agent() -> str: ] return random.choice(user_agents) +def get_chromedriver_version(chromedriver_path: str) -> str: + """Get the major version of a chromedriver binary. Returns empty string on failure.""" + try: + result = subprocess.run( + [chromedriver_path, "--version"], + capture_output=True, text=True, timeout=10 + ) + # Output format: "ChromeDriver 125.0.6422.78 (...)" + return result.stdout.strip().split()[1].split('.')[0] + except Exception: + return "" + +def is_chromedriver_compatible(chromedriver_path: str) -> bool: + """Check if a chromedriver binary is compatible with the installed Chrome version.""" + try: + chrome_version = chromedriver_autoinstaller.get_chrome_version() + if not chrome_version: + return True # Can't determine Chrome version, assume compatible + chrome_major = chrome_version.split('.')[0] + driver_major = get_chromedriver_version(chromedriver_path) + if not driver_major: + return True # Can't determine driver version, assume compatible + return chrome_major == driver_major + except Exception: + return True # On any error, assume compatible to avoid blocking + def install_chromedriver() -> str: """ Install the ChromeDriver if not already installed. Return the path. + Automatically updates the driver if the version does not match the installed Chrome. """ # First try to use chromedriver in the project root directory (as per README) project_root_chromedriver = "./chromedriver" if os.path.exists(project_root_chromedriver) and os.access(project_root_chromedriver, os.X_OK): - print(f"Using ChromeDriver from project root: {project_root_chromedriver}") - return project_root_chromedriver + if is_chromedriver_compatible(project_root_chromedriver): + print(f"Using ChromeDriver from project root: {project_root_chromedriver}") + return project_root_chromedriver + print("ChromeDriver in project root is outdated, attempting auto-update...") # Then try to use the system-installed chromedriver chromedriver_path = shutil.which("chromedriver") if chromedriver_path: - return chromedriver_path + if is_chromedriver_compatible(chromedriver_path): + return chromedriver_path + print(f"System ChromeDriver at {chromedriver_path} is outdated, attempting auto-update...") # In Docker environment, try the fixed path if os.path.exists('/.dockerenv'): @@ -98,9 +130,9 @@ def install_chromedriver() -> str: print(f"Using Docker ChromeDriver at {docker_chromedriver_path}") return docker_chromedriver_path - # Fallback to auto-installer only if no other option works + # Auto-install matching ChromeDriver version try: - print("ChromeDriver not found, attempting to install automatically...") + print("Installing matching ChromeDriver version automatically...") chromedriver_path = chromedriver_autoinstaller.install() except Exception as e: raise FileNotFoundError( diff --git a/tests/test_chromedriver_update.py b/tests/test_chromedriver_update.py new file mode 100644 index 0000000..3204564 --- /dev/null +++ b/tests/test_chromedriver_update.py @@ -0,0 +1,82 @@ +import unittest +import os +import sys +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from unittest.mock import patch, MagicMock + +# Mock heavy dependencies +for mod_name in [ + 'torch', 'transformers', 'kokoro', 'adaptive_classifier', 'text2emotion', + 'ollama', 'openai', 'together', 'IPython', 'IPython.display', + 'playsound3', 'soundfile', 'pyaudio', 'librosa', + 'pypdf', 'langid', 'pypinyin', 'fake_useragent', + 'num2words', 'sentencepiece', 'sacremoses', + 'scipy', 'numpy', 'selenium_stealth', 'undetected_chromedriver', + 'markdownify', 'chromedriver_autoinstaller', +]: + if mod_name not in sys.modules: + sys.modules[mod_name] = MagicMock() + +os.environ.setdefault('WORK_DIR', '/tmp') + +from sources.browser import get_chromedriver_version, is_chromedriver_compatible + + +class TestChromedriverVersionCheck(unittest.TestCase): + """Test suite for ChromeDriver version checking and auto-update logic.""" + + @patch('sources.browser.subprocess.run') + def test_get_chromedriver_version_success(self, mock_run): + """Test extracting major version from chromedriver --version output.""" + mock_run.return_value = MagicMock( + stdout="ChromeDriver 125.0.6422.78 (abc123)\n" + ) + self.assertEqual(get_chromedriver_version("/usr/bin/chromedriver"), "125") + + @patch('sources.browser.subprocess.run') + def test_get_chromedriver_version_failure(self, mock_run): + """Test graceful failure when chromedriver --version fails.""" + mock_run.side_effect = FileNotFoundError("not found") + self.assertEqual(get_chromedriver_version("/nonexistent"), "") + + @patch('sources.browser.subprocess.run') + def test_get_chromedriver_version_timeout(self, mock_run): + """Test graceful failure on timeout.""" + import subprocess + mock_run.side_effect = subprocess.TimeoutExpired(cmd="chromedriver", timeout=10) + self.assertEqual(get_chromedriver_version("/usr/bin/chromedriver"), "") + + @patch('sources.browser.chromedriver_autoinstaller.get_chrome_version') + @patch('sources.browser.get_chromedriver_version') + def test_compatible_versions(self, mock_driver_ver, mock_chrome_ver): + """Test that matching major versions are compatible.""" + mock_chrome_ver.return_value = "125.0.6422.78" + mock_driver_ver.return_value = "125" + self.assertTrue(is_chromedriver_compatible("/usr/bin/chromedriver")) + + @patch('sources.browser.chromedriver_autoinstaller.get_chrome_version') + @patch('sources.browser.get_chromedriver_version') + def test_incompatible_versions(self, mock_driver_ver, mock_chrome_ver): + """Test that mismatched major versions are incompatible.""" + mock_chrome_ver.return_value = "126.0.6478.55" + mock_driver_ver.return_value = "125" + self.assertFalse(is_chromedriver_compatible("/usr/bin/chromedriver")) + + @patch('sources.browser.chromedriver_autoinstaller.get_chrome_version') + def test_no_chrome_version_assumes_compatible(self, mock_chrome_ver): + """Test that missing Chrome version defaults to compatible.""" + mock_chrome_ver.return_value = None + self.assertTrue(is_chromedriver_compatible("/usr/bin/chromedriver")) + + @patch('sources.browser.chromedriver_autoinstaller.get_chrome_version') + @patch('sources.browser.get_chromedriver_version') + def test_no_driver_version_assumes_compatible(self, mock_driver_ver, mock_chrome_ver): + """Test that missing driver version defaults to compatible.""" + mock_chrome_ver.return_value = "125.0.6422.78" + mock_driver_ver.return_value = "" + self.assertTrue(is_chromedriver_compatible("/usr/bin/chromedriver")) + + +if __name__ == '__main__': + unittest.main()