Merge pull request #438 from Br1an67/fix/issue-356-auto-update-chromedriver

fix: auto-update ChromeDriver when version mismatches Chrome
This commit is contained in:
Martin
2026-03-02 19:43:39 +01:00
committed by GitHub
2 changed files with 119 additions and 5 deletions
+34 -2
View File
@@ -15,6 +15,7 @@ import undetected_chromedriver as uc
import chromedriver_autoinstaller import chromedriver_autoinstaller
import certifi import certifi
import ssl import ssl
import subprocess
import time import time
import random import random
import os import os
@@ -76,20 +77,51 @@ def get_random_user_agent() -> str:
] ]
return random.choice(user_agents) 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: def install_chromedriver() -> str:
""" """
Install the ChromeDriver if not already installed. Return the path. 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) # First try to use chromedriver in the project root directory (as per README)
project_root_chromedriver = "./chromedriver" project_root_chromedriver = "./chromedriver"
if os.path.exists(project_root_chromedriver) and os.access(project_root_chromedriver, os.X_OK): if os.path.exists(project_root_chromedriver) and os.access(project_root_chromedriver, os.X_OK):
if is_chromedriver_compatible(project_root_chromedriver):
print(f"Using ChromeDriver from project root: {project_root_chromedriver}") print(f"Using ChromeDriver from project root: {project_root_chromedriver}")
return 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 # Then try to use the system-installed chromedriver
chromedriver_path = shutil.which("chromedriver") chromedriver_path = shutil.which("chromedriver")
if chromedriver_path: if chromedriver_path:
if is_chromedriver_compatible(chromedriver_path):
return chromedriver_path return chromedriver_path
print(f"System ChromeDriver at {chromedriver_path} is outdated, attempting auto-update...")
# In Docker environment, try the fixed path # In Docker environment, try the fixed path
if os.path.exists('/.dockerenv'): if os.path.exists('/.dockerenv'):
@@ -98,9 +130,9 @@ def install_chromedriver() -> str:
print(f"Using Docker ChromeDriver at {docker_chromedriver_path}") print(f"Using Docker ChromeDriver at {docker_chromedriver_path}")
return docker_chromedriver_path return docker_chromedriver_path
# Fallback to auto-installer only if no other option works # Auto-install matching ChromeDriver version
try: try:
print("ChromeDriver not found, attempting to install automatically...") print("Installing matching ChromeDriver version automatically...")
chromedriver_path = chromedriver_autoinstaller.install() chromedriver_path = chromedriver_autoinstaller.install()
except Exception as e: except Exception as e:
raise FileNotFoundError( raise FileNotFoundError(
+82
View File
@@ -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()