55import os .path
66import platform
77import re
8+ import shutil
89import subprocess
910import sys
1011import zipfile
3940
4041
4142class ChromeDriverManager :
42- def get_chrome_driver (self , path_to_cache_dir : str ):
43+ def get_chrome_driver (self , path_to_cache_dir : str ) -> str :
44+ """Return path to downloaded chromedriver."""
4345 chrome_version = self .get_chrome_version ()
4446
4547 # If Web Driver Manager cannot detect Chrome, it returns None.
@@ -102,15 +104,69 @@ def get_chrome_driver(self, path_to_cache_dir: str):
102104
103105 return path_to_downloaded_chrome_driver
104106
107+ @staticmethod
108+ def find_chrome () -> str :
109+ """Find path to Chrome browser across Windows, macOS, and Linux."""
110+
111+ os_name = platform .system ()
112+ if os_name == "Windows" :
113+ # Check Windows standard locations for chrome and chromium
114+ userprofile = os .environ .get ("LOCALAPPDATA" , r"C:\Users\Default\AppData\Local" )
115+ locations = [
116+ os .path .join (os .environ .get ("PROGRAMFILES" , r"C:\Program Files" ), "Google" , "Chrome" , "Application" , "chrome.exe" ),
117+ os .path .join (os .environ .get ("PROGRAMFILES(X86)" , r"C:\Program Files (x86)" ), "Google" , "Chrome" , "Application" , "chrome.exe" ),
118+ os .path .join (os .environ .get ("PROGRAMFILES" , r"C:\Program Files" ), "Chromium" , "Application" , "chrome.exe" ),
119+ os .path .join (os .environ .get ("PROGRAMFILES(X86)" , r"C:\Program Files (x86)" ), "Chromium" , "Application" , "chrome.exe" ),
120+ os .path .join (userprofile , "Chromium" ,"Application" ,"chrome.exe" ),
121+ os .path .join (userprofile , "Chromium" ,"Application" ,"chromium.exe" ),
122+ ]
123+ elif os_name == "Darwin" :
124+ # macOS standard locations
125+ locations = [
126+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ,
127+ os .path .expanduser ("~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" )
128+ ]
129+ elif os_name == "Linux" :
130+ # Linux: check if chrome or chromium is in PATH
131+ chrome_path = shutil .which ("google-chrome" ) or shutil .which ("chrome" ) or shutil .which ("chromium" ) or shutil .which ("chromium-browser" )
132+ if chrome_path :
133+ return chrome_path
134+ locations = [
135+ "/usr/bin/google-chrome" ,
136+ "/usr/local/bin/google-chrome" ,
137+ "/usr/bin/chromium-browser" ,
138+ "/snap/bin/chromium" ,
139+ ]
140+ else :
141+ raise Exception (f"Unsupported platform: { sys .platform } " )
142+
143+ for path in locations :
144+ if path and os .path .exists (path ):
145+ return path
146+
147+ print ("html2print: Chrome executable was not found in any usual location - falling back to webdriver's autodetection" ) # noqa: T201
148+ return ""
149+
105150 @staticmethod
106151 def _download_chromedriver (
107152 chrome_major_version ,
108153 os_type : str ,
109154 path_to_driver_cache_dir ,
110155 path_to_cached_chrome_driver ,
111- ):
156+ ) -> str :
112157 url = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
113- response = ChromeDriverManager .send_http_get_request (url ).json ()
158+ response = ChromeDriverManager .send_http_get_request (url )
159+ if response is None :
160+ raise RuntimeError (
161+ "Could not download known-good-versions-with-downloads.json"
162+ )
163+
164+ response = response .json ()
165+ if response is None :
166+ raise RuntimeError (
167+ "Could not parse known-good-versions-with-downloads.json"
168+ )
169+ assert isinstance (response , dict )
114170
115171 matching_versions = [
116172 item
@@ -143,6 +199,11 @@ def _download_chromedriver(
143199 )
144200 response = ChromeDriverManager .send_http_get_request (driver_url )
145201
202+ if response is None :
203+ raise Exception (
204+ f"Could not download ChromeDriver from { driver_url } "
205+ )
206+
146207 Path (path_to_driver_cache_dir ).mkdir (parents = True , exist_ok = True )
147208 zip_path = os .path .join (path_to_driver_cache_dir , "chromedriver.zip" )
148209 print ( # noqa: T201
@@ -160,7 +221,7 @@ def _download_chromedriver(
160221 return path_to_cached_chrome_driver
161222
162223 @staticmethod
163- def send_http_get_request (url , params = None , ** kwargs ) -> Response :
224+ def send_http_get_request (url , params = None , ** kwargs ) -> Optional [ Response ] :
164225 last_error : Optional [Exception ] = None
165226 for attempt in range (1 , 4 ):
166227 print ( # noqa: T201
@@ -180,9 +241,10 @@ def send_http_get_request(url, params=None, **kwargs) -> Response:
180241 f"html2print: "
181242 f"failed to get response for URL: { url } with error: { last_error } "
182243 )
244+ return None
183245
184246 @staticmethod
185- def get_chrome_version ():
247+ def get_chrome_version () -> str :
186248 # Special case: GitHub Actions macOS CI machines have both
187249 # Google Chrome for Testing and normal Google Chrome installed, and
188250 # sometimes their versions are of different major version families.
@@ -312,21 +374,25 @@ def create_webdriver(
312374) -> webdriver .Chrome :
313375 print ("html2print: creating ChromeDriver service." , flush = True ) # noqa: T201
314376 if chromedriver is None :
315- path_to_chrome = ChromeDriverManager ().get_chrome_driver (
377+ path_to_chromedriver = ChromeDriverManager ().get_chrome_driver (
316378 path_to_cache_dir
317379 )
318380 else :
319- path_to_chrome = chromedriver
320- print (f"html2print: ChromeDriver available at path: { path_to_chrome } " ) # noqa: T201
381+ path_to_chromedriver = chromedriver
382+ print (f"html2print: ChromeDriver available at path: { path_to_chromedriver } " ) # noqa: T201
321383
322384 if debug :
323385 service = Service (
324- path_to_chrome , log_output = PATH_TO_CHROME_DRIVER_DEBUG_LOG
386+ path_to_chromedriver , log_output = PATH_TO_CHROME_DRIVER_DEBUG_LOG
325387 )
326388 else :
327- service = Service (path_to_chrome )
389+ service = Service (path_to_chromedriver )
390+
391+ path_to_chrome = ChromeDriverManager .find_chrome ()
392+ print (f"html2print: Chrome available at path: { path_to_chrome } " ) # noqa: T201
328393
329394 webdriver_options = Options ()
395+ webdriver_options .binary_location = path_to_chrome
330396 webdriver_options .add_argument ("start-maximized" )
331397 webdriver_options .add_argument ("disable-infobars" )
332398 # Doesn't seem to be needed.
0 commit comments