13
13
from time import sleep
14
14
from typing import Dict , List , Optional
15
15
16
+ import browsers
16
17
import requests
17
- from requests import Response
18
+ from requests import RequestException , Response
18
19
from selenium import webdriver
19
20
from selenium .webdriver .chrome .options import Options
20
21
from selenium .webdriver .chrome .service import Service
21
- from webdriver_manager .core .os_manager import ChromeType , OperationSystemManager
22
22
23
23
__version__ = "0.0.15"
24
24
39
39
40
40
41
41
class ChromeDriverManager :
42
- def get_chrome_driver (self , path_to_cache_dir : str ):
43
- chrome_version = self .get_chrome_version ()
44
-
42
+ def get_chrome_driver (self , chrome_version : str , path_to_cache_dir : str ):
45
43
# If Web Driver Manager cannot detect Chrome, it returns None.
46
44
if chrome_version is None :
47
45
raise RuntimeError (
@@ -104,13 +102,24 @@ def get_chrome_driver(self, path_to_cache_dir: str):
104
102
105
103
@staticmethod
106
104
def _download_chromedriver (
107
- chrome_major_version ,
105
+ chrome_major_version : str ,
108
106
os_type : str ,
109
- path_to_driver_cache_dir ,
110
- path_to_cached_chrome_driver ,
111
- ):
107
+ path_to_driver_cache_dir : str ,
108
+ path_to_cached_chrome_driver : str ,
109
+ ) -> str :
112
110
url = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
113
- response = ChromeDriverManager .send_http_get_request (url ).json ()
111
+ response = ChromeDriverManager .send_http_get_request (url )
112
+ if response is None :
113
+ raise RuntimeError (
114
+ "Could not download known-good-versions-with-downloads.json"
115
+ )
116
+
117
+ response = response .json ()
118
+ if response is None :
119
+ raise RuntimeError (
120
+ "Could not parse known-good-versions-with-downloads.json"
121
+ )
122
+ assert isinstance (response , dict )
114
123
115
124
matching_versions = [
116
125
item
@@ -143,6 +152,11 @@ def _download_chromedriver(
143
152
)
144
153
response = ChromeDriverManager .send_http_get_request (driver_url )
145
154
155
+ if response is None :
156
+ raise Exception (
157
+ f"Could not download ChromeDriver from { driver_url } "
158
+ )
159
+
146
160
Path (path_to_driver_cache_dir ).mkdir (parents = True , exist_ok = True )
147
161
zip_path = os .path .join (path_to_driver_cache_dir , "chromedriver.zip" )
148
162
print ( # noqa: T201
@@ -180,9 +194,12 @@ def send_http_get_request(url, params=None, **kwargs) -> Response:
180
194
f"html2print: "
181
195
f"failed to get response for URL: { url } with error: { last_error } "
182
196
)
197
+ raise RequestException (
198
+ f"GET request failed after 3 attempts: { url } "
199
+ ) from last_error
183
200
184
201
@staticmethod
185
- def get_chrome_version () :
202
+ def get_browser_info () -> browsers . Browser :
186
203
# Special case: GitHub Actions macOS CI machines have both
187
204
# Google Chrome for Testing and normal Google Chrome installed, and
188
205
# sometimes their versions are of different major version families.
@@ -216,17 +233,24 @@ def get_chrome_version():
216
233
f"html2print: Google Chrome for Testing Version: { chrome_version } "
217
234
)
218
235
219
- return chrome_version
236
+ return browsers .Browser (
237
+ browser_type = "chrome-for-testing" ,
238
+ path = chrome_path ,
239
+ version = chrome_version ,
240
+ display_name = "Google Chrome for Testing" ,
241
+ )
220
242
except FileNotFoundError :
221
243
print ("html2print: Chrome for Testing not available." ) # noqa: T201
222
244
except Exception as e :
223
245
print ( # noqa: T201
224
246
f"html2print: Error getting Google Chrome for Testing version: { e } "
225
247
)
248
+ else :
249
+ chrome_browser_info = browsers .get ("chrome" ) or browsers .get (
250
+ "chromium"
251
+ )
226
252
227
- os_manager = OperationSystemManager (os_type = None )
228
- version = os_manager .get_browser_version_from_os (ChromeType .GOOGLE )
229
- return version
253
+ return chrome_browser_info
230
254
231
255
232
256
def get_inches_from_millimeters (mm : float ) -> float :
@@ -305,34 +329,44 @@ class Done(Exception):
305
329
306
330
307
331
def create_webdriver (
332
+ browser_info : browsers .Browser ,
308
333
chromedriver : Optional [str ],
309
334
path_to_cache_dir : str ,
310
335
page_load_timeout : int ,
311
336
debug : bool = False ,
312
337
) -> webdriver .Chrome :
313
338
print ("html2print: creating ChromeDriver service." , flush = True ) # noqa: T201
339
+
314
340
if chromedriver is None :
315
- path_to_chrome = ChromeDriverManager ().get_chrome_driver (
316
- path_to_cache_dir
341
+ path_to_chrome_driver = ChromeDriverManager ().get_chrome_driver (
342
+ chrome_version = browser_info ["version" ],
343
+ path_to_cache_dir = path_to_cache_dir ,
317
344
)
318
345
else :
319
- path_to_chrome = chromedriver
320
- print (f"html2print: ChromeDriver available at path: { path_to_chrome } " ) # noqa: T201
346
+ path_to_chrome_driver = chromedriver
347
+ print ( # noqa: T201
348
+ f"html2print: ChromeDriver available at path: { path_to_chrome_driver } "
349
+ )
321
350
322
351
if debug :
323
352
service = Service (
324
- path_to_chrome , log_output = PATH_TO_CHROME_DRIVER_DEBUG_LOG
353
+ path_to_chrome_driver , log_output = PATH_TO_CHROME_DRIVER_DEBUG_LOG
325
354
)
326
355
else :
327
- service = Service (path_to_chrome )
356
+ service = Service (path_to_chrome_driver )
328
357
329
358
webdriver_options = Options ()
359
+ # Workaround for Windows: chrome is not typically found in the PATH, so need to supply the exact binary location manually
360
+ if platform .system () == "Windows" :
361
+ webdriver_options .binary_location = browser_info ["path" ]
330
362
webdriver_options .add_argument ("start-maximized" )
331
363
webdriver_options .add_argument ("disable-infobars" )
332
364
# Doesn't seem to be needed.
333
365
# webdriver_options.add_argument('--disable-gpu') # noqa: ERA001
334
366
webdriver_options .add_argument ("--disable-extensions" )
335
- webdriver_options .add_argument ("--headless=chrome" )
367
+ # Use --headless=new, as it seems to be more stable on Windows (available since Chrome 109).
368
+ # see https://www.selenium.dev/blog/2023/headless-is-going-away/
369
+ webdriver_options .add_argument ("--headless=new" )
336
370
# FIXME: This is not nice but otherwise it does not work in Ubuntu 24-based Docker image.
337
371
# https://github.com/SeleniumHQ/selenium/issues/15327#issuecomment-2689287561
338
372
webdriver_options .add_argument ("--no-sandbox" )
@@ -441,16 +475,31 @@ def main():
441
475
442
476
args = parser .parse_args ()
443
477
478
+ # Look for Chrome on the System...
479
+ browser_info = ChromeDriverManager .get_browser_info ()
480
+
444
481
path_to_cache_dir : str
445
482
if args .command == "get_driver" :
446
483
path_to_cache_dir = (
447
484
args .cache_dir if args .cache_dir is not None else DEFAULT_CACHE_DIR
448
485
)
449
486
450
- path_to_chrome = ChromeDriverManager ().get_chrome_driver (
451
- path_to_cache_dir
487
+ path_to_chrome_browser = browser_info ["path" ]
488
+ print ( # noqa: T201
489
+ f"html2print: Chrome available at path: { path_to_chrome_browser } "
490
+ )
491
+ chrome_version = browser_info ["version" ]
492
+ print ( # noqa: T201
493
+ f"html2print: Chrome version : { chrome_version } "
494
+ )
495
+
496
+ path_to_chrome_driver = ChromeDriverManager ().get_chrome_driver (
497
+ chrome_version = browser_info ["version" ],
498
+ path_to_cache_dir = path_to_cache_dir ,
499
+ )
500
+ print ( # noqa: T201
501
+ f"html2print: ChromeDriver available at path: { path_to_chrome_driver } "
452
502
)
453
- print (f"html2print: ChromeDriver available at path: { path_to_chrome } " ) # noqa: T201
454
503
sys .exit (0 )
455
504
456
505
elif args .command == "print" :
@@ -462,6 +511,7 @@ def main():
462
511
args .cache_dir if args .cache_dir is not None else DEFAULT_CACHE_DIR
463
512
)
464
513
driver = create_webdriver (
514
+ browser_info ,
465
515
args .chromedriver ,
466
516
path_to_cache_dir ,
467
517
page_load_timeout ,
0 commit comments