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:
+37
-5
@@ -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):
|
||||||
print(f"Using ChromeDriver from project root: {project_root_chromedriver}")
|
if is_chromedriver_compatible(project_root_chromedriver):
|
||||||
return 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
|
# Then try to use the system-installed chromedriver
|
||||||
chromedriver_path = shutil.which("chromedriver")
|
chromedriver_path = shutil.which("chromedriver")
|
||||||
if chromedriver_path:
|
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
|
# 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(
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user