diff --git a/README.md b/README.md index 32d2dbfc54e..a5d717bcfa1 100755 --- a/README.md +++ b/README.md @@ -1332,13 +1332,14 @@ pytest --reruns=1 --reruns-delay=1 SeleniumBase Playlist on YouTube SeleniumBase on GitHub SeleniumBase on Facebook -SeleniumBase on Gitter +SeleniumBase on Gitter

-

https://github.com/mdmintz

+

https://github.com/mdmintz

-
Gitter chat
Tested with SeleniumBase
SeleniumBase Docs
+
SeleniumBase Docs
Tested with SeleniumBase
Gitter chat
SeleniumBase PyPI downloads
+visitor badge
-------- diff --git a/examples/custom_settings.py b/examples/custom_settings.py index b90e1c09f84..2eccc6bd8eb 100644 --- a/examples/custom_settings.py +++ b/examples/custom_settings.py @@ -72,6 +72,12 @@ HEADLESS_START_WIDTH = 1440 HEADLESS_START_HEIGHT = 1880 +# If True, hides messages related to downloading drivers. +# If False, you'll see details about downloading drivers. +# (This only affects driver downloads coming from tests.) +# (Eg. Using "sbase get chromedriver" won't hide output.) +HIDE_DRIVER_DOWNLOADS = False + # Changing the default behavior of MasterQA Mode. MASTERQA_DEFAULT_VALIDATION_MESSAGE = "Does the page look good?" MASTERQA_WAIT_TIME_BEFORE_VERIFY = 0.5 diff --git a/help_docs/webdriver_installation.md b/help_docs/webdriver_installation.md index af97b022fc1..6883dc9a064 100644 --- a/help_docs/webdriver_installation.md +++ b/help_docs/webdriver_installation.md @@ -21,6 +21,12 @@ If the necessary driver is not found in this location while running tests, Selen ```bash sbase get chromedriver 114 sbase get chromedriver 114.0.5735.90 +sbase get chromedriver stable +sbase get chromedriver beta +sbase get chromedriver dev +sbase get chromedriver canary +sbase get chromedriver previous # One major version before the stable version +sbase get chromedriver mlatest # Milestone latest version for detected browser sbase get edgedriver 115.0.1901.183 ``` diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index bd8fdab7c26..16231170d5b 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -20,7 +20,7 @@ paginate==0.5.6 pyquery==2.0.0 readtime==3.0.0 mkdocs==1.5.2 -mkdocs-material==9.2.3 +mkdocs-material==9.2.5 mkdocs-exclude-search==0.6.5 mkdocs-simple-hooks==0.1.5 mkdocs-material-extensions==1.1.1 diff --git a/requirements.txt b/requirements.txt index 3601a122f82..f2724ab4803 100755 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,8 @@ attrs==22.1.0;python_version<"3.7" attrs>=23.1.0;python_version>="3.7" certifi>=2023.7.22 filelock>=3.4.1;python_version<"3.7" -filelock>=3.12.2;python_version>="3.7" +filelock>=3.12.2;python_version>="3.7" and python_version<"3.8" +filelock>=3.12.3;python_version>="3.8" platformdirs>=2.4.0;python_version<"3.7" platformdirs>=3.10.0;python_version>="3.7" parse>=1.19.1 @@ -47,7 +48,8 @@ execnet==2.0.2;python_version>="3.7" iniconfig==1.1.1;python_version<"3.7" iniconfig==2.0.0;python_version>="3.7" pluggy==1.0.0;python_version<"3.7" -pluggy==1.2.0;python_version>="3.7" +pluggy==1.2.0;python_version>="3.7" and python_version<"3.8" +pluggy==1.3.0;python_version>="3.8" py==1.11.0 pytest==7.0.1;python_version<"3.7" pytest==7.4.0;python_version>="3.7" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 207949f7c09..8eb03dbe8bc 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.17.11" +__version__ = "4.17.12" diff --git a/seleniumbase/config/settings.py b/seleniumbase/config/settings.py index cc8cdffdcd5..0b2822100f3 100644 --- a/seleniumbase/config/settings.py +++ b/seleniumbase/config/settings.py @@ -118,6 +118,12 @@ HEADLESS_START_WIDTH = 1440 HEADLESS_START_HEIGHT = 1880 +# If True, hides messages related to downloading drivers. +# If False, you'll see details about downloading drivers. +# (This only affects driver downloads coming from tests.) +# (If calling "sbase get chromedriver", then won't hide.) +HIDE_DRIVER_DOWNLOADS = False + # #####>>>>>----- MasterQA SETTINGS -----<<<<<##### # ##### (Used when importing MasterQA as the parent class) diff --git a/seleniumbase/console_scripts/ReadMe.md b/seleniumbase/console_scripts/ReadMe.md index e85f27a044b..0592ba8b0d0 100644 --- a/seleniumbase/console_scripts/ReadMe.md +++ b/seleniumbase/console_scripts/ReadMe.md @@ -64,6 +64,8 @@ sbase get geckodriver sbase get edgedriver sbase get chromedriver 114 sbase get chromedriver 114.0.5735.90 +sbase get chromedriver stable +sbase get chromedriver beta sbase get chromedriver -p ``` diff --git a/seleniumbase/console_scripts/run.py b/seleniumbase/console_scripts/run.py index e98b688fe16..314c608ef60 100644 --- a/seleniumbase/console_scripts/run.py +++ b/seleniumbase/console_scripts/run.py @@ -39,6 +39,7 @@ import colorama import sys import time +from seleniumbase.config import settings from seleniumbase.fixtures import constants colorama.init(autoreset=True) @@ -143,6 +144,8 @@ def show_install_usage(): print(" sbase get edgedriver") print(" sbase get chromedriver 114") print(" sbase get chromedriver 114.0.5735.90") + print(" sbase get chromedriver stable") + print(" sbase get chromedriver beta") print(" sbase get chromedriver -p") print(" Output:") print(" Downloads the webdriver to seleniumbase/drivers/") @@ -984,6 +987,7 @@ def main(): proxy_helper.validate_proxy_string(proxy_string) break try: + settings.HIDE_DRIVER_DOWNLOADS = False sb_install.main() except Exception as e: invalid_run_cmd = constants.Warnings.INVALID_RUN_COMMAND diff --git a/seleniumbase/console_scripts/sb_install.py b/seleniumbase/console_scripts/sb_install.py index e66e6ddff7b..339556b05ce 100644 --- a/seleniumbase/console_scripts/sb_install.py +++ b/seleniumbase/console_scripts/sb_install.py @@ -24,6 +24,7 @@ (edgedriver is required for MS__Edge automation) """ import colorama +import logging import os import platform import requests @@ -46,7 +47,7 @@ IS_WINDOWS = shared_utils.is_windows() DRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__)) LOCAL_PATH = "/usr/local/bin/" # On Mac and Linux systems -DEFAULT_CHROMEDRIVER_VERSION = "72.0.3626.69" # (If can't find LATEST_STABLE) +DEFAULT_CHROMEDRIVER_VERSION = "114.0.5735.90" # (If can't find LATEST_STABLE) DEFAULT_GECKODRIVER_VERSION = "v0.33.0" DEFAULT_EDGEDRIVER_VERSION = "115.0.1901.183" # (If can't find LATEST_STABLE) DEFAULT_OPERADRIVER_VERSION = "v.96.0.4664.45" @@ -73,6 +74,8 @@ def invalid_run_command(): exp += " sbase get edgedriver\n" exp += " sbase get chromedriver 114\n" exp += " sbase get chromedriver 114.0.5735.90\n" + exp += " sbase get chromedriver stable\n" + exp += " sbase get chromedriver beta\n" exp += " sbase get chromedriver -p\n" exp += " Output:\n" exp += " Downloads the webdriver to seleniumbase/drivers/\n" @@ -159,7 +162,79 @@ def requests_get_with_retry(url): return response -def main(override=None, intel_for_uc=None): +def get_cft_known_good_versions(): + if hasattr(sb_config, "cft_kgv_json") and sb_config.cft_kgv_json: + return sb_config.cft_kgv_json + cft_ngv_url = ( + "https://googlechromelabs.github.io/" + "chrome-for-testing/known-good-versions.json" + ) + sb_config.cft_kgv_json = requests_get(cft_ngv_url) + return sb_config.cft_kgv_json + + +def get_cft_latest_versions_per_milestone(): + if hasattr(sb_config, "cft_lvpm_json") and sb_config.cft_lvpm_json: + return sb_config.cft_lvpm_json + cft_lvpm_url = ( + "https://googlechromelabs.github.io/" + "chrome-for-testing/latest-versions-per-milestone.json" + ) + sb_config.cft_lvpm_json = requests_get(cft_lvpm_url) + return sb_config.cft_lvpm_json + + +def get_cft_latest_version_from_milestone(milestone): + url_request = get_cft_latest_versions_per_milestone() + return url_request.json()["milestones"][milestone]["version"] + + +def get_latest_chromedriver_version(channel="Stable"): + try: + if hasattr(sb_config, "cft_lkgv_json") and sb_config.cft_lkgv_json: + return sb_config.cft_lkgv_json["channels"][channel]["version"] + req = requests_get( + "https://googlechromelabs.github.io/" + "chrome-for-testing/last-known-good-versions.json" + ) + if req and req.ok: + sb_config.cft_lkgv_json = req.json() + return req.json()["channels"][channel]["version"] + except Exception: + pass + # If a problem with Chrome-for-Testing JSON API: Fall back + return DEFAULT_CHROMEDRIVER_VERSION + + +def get_latest_stable_chromedriver_version(): + return get_latest_chromedriver_version(channel="Stable") + + +def get_latest_beta_chromedriver_version(): + return get_latest_chromedriver_version(channel="Beta") + + +def get_latest_dev_chromedriver_version(): + return get_latest_chromedriver_version(channel="Dev") + + +def get_latest_canary_chromedriver_version(): + return get_latest_chromedriver_version(channel="Canary") + + +def log_d(message): + """If setting sb_config.settings.HIDE_DRIVER_DOWNLOADS to True, + output from driver downloads are logged instead of printed.""" + if ( + hasattr(sb_config.settings, "HIDE_DRIVER_DOWNLOADS") + and sb_config.settings.HIDE_DRIVER_DOWNLOADS + ): + logging.debug(message) + else: + print(message) + + +def main(override=None, intel_for_uc=None, force_uc=None): if override: found_proxy = None if hasattr(sb_config, "proxy_driver") and sb_config.proxy_driver: @@ -201,6 +276,8 @@ def main(override=None, intel_for_uc=None): else: invalid_run_command() name = sys.argv[2].lower() + if force_uc: + name = "uc_driver" file_name = None download_url = None @@ -222,18 +299,22 @@ def main(override=None, intel_for_uc=None): c3 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX c4 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTWHITE_EX c5 = colorama.Fore.RED + colorama.Back.LIGHTWHITE_EX + c6 = colorama.Fore.LIGHTYELLOW_EX + colorama.Back.CYAN cr = colorama.Style.RESET_ALL if IS_LINUX: c1 = "" c2 = "" c3 = "" + c4 = "" + c5 = "" + c6 = "" cr = "" if name == "chromedriver" or name == "uc_driver": if name == "uc_driver" and IS_ARM_MAC: intel_for_uc = True # uc_driver is generated from chromedriver last = "https://chromedriver.storage.googleapis.com/LATEST_RELEASE" - use_version = DEFAULT_CHROMEDRIVER_VERSION + use_version = DEFAULT_CHROMEDRIVER_VERSION # Until get correct VER if ( not override @@ -260,16 +341,27 @@ def main(override=None, intel_for_uc=None): get_latest = False get_v_latest = False - get_latest_minus_one = False + get_previous = False + get_beta = False + get_canary = False if num_args == 4 or num_args == 5: if "-p" not in sys.argv[3].lower(): use_version = sys.argv[3] uv_low = use_version.lower() - if uv_low == "latest": + if uv_low == "latest" or uv_low == "stable": + uv_low = "latest" # If "stable", rename get_latest = True - elif uv_low == "latest-1": - get_latest_minus_one = True - elif len(uv_low) < 4 and uv_low.isdigit() and int(uv_low) > 69: + elif uv_low == "latest-1" or uv_low == "previous": + uv_low = "latest-1" # If "previous", rename + get_previous = True + elif uv_low == "beta": + get_beta = True + elif uv_low == "dev": + use_version = get_latest_dev_chromedriver_version() + sys.argv[3] = use_version + elif uv_low == "canary": + get_canary = True + elif uv_low.isdigit() and int(uv_low) > 69: get_v_latest = True else: copy_to_path = True @@ -280,10 +372,22 @@ def main(override=None, intel_for_uc=None): invalid_run_command() if IS_MAC: if IS_ARM_MAC and not intel_for_uc: - if use_version == "latest" or use_version == "latest-1": - use_version = requests_get(last).text - if use_version == "latest-1": + use_version = use_version.lower() + if ( + use_version == "latest" + or use_version == "stable" + or use_version == "latest-1" + or use_version == "previous" + or use_version == "beta" + or use_version == "canary" + ): + use_version = get_latest_stable_chromedriver_version() + if use_version == "latest-1" or use_version == "previous": use_version = str(int(use_version.split(".")[0]) - 1) + elif use_version == "beta": + use_version = str(int(use_version.split(".")[0]) + 1) + elif use_version == "canary": + use_version = str(int(use_version.split(".")[0]) + 2) if ( IS_ARM_MAC and not intel_for_uc @@ -304,19 +408,36 @@ def main(override=None, intel_for_uc=None): ) found_chromedriver = False cft = False - if get_latest or get_latest_minus_one: - url_request = requests_get(last) - if url_request.ok: - found_chromedriver = True - use_version = url_request.text - if get_latest_minus_one: - get_v_latest = True - use_version = str(int(use_version.split(".")[0]) - 1) - if get_v_latest: - url_req = requests_get(last) - if url_req.ok: - latest_version = url_req.text - if int(use_version) < 115: + if get_latest or get_previous or get_beta or get_canary: + use_version = get_latest_stable_chromedriver_version() + found_chromedriver = True + if get_previous and int(use_version.split(".")[0]) >= 115: + get_v_latest = True + use_version = str(int(use_version.split(".")[0]) - 1) + elif get_beta and int(use_version.split(".")[0]) >= 115: + get_v_latest = True + use_version = get_latest_beta_chromedriver_version() + use_version = use_version.split(".")[0] + elif get_canary and int(use_version.split(".")[0]) >= 115: + get_v_latest = True + use_version = get_latest_canary_chromedriver_version() + use_version = use_version.split(".")[0] + force_cft = False + if ( + use_version.split(".")[0].isnumeric() + and int(use_version.split(".")[0]) >= 115 + ): + force_cft = True + if (get_v_latest or force_cft): + if get_v_latest: + if not force_cft: + url_req = requests_get(last) + if url_req.ok: + latest_version = url_req.text + else: + latest_version = get_latest_stable_chromedriver_version() + force_cft = False + if not force_cft and int(use_version) < 115: last = last + "_" + use_version url_request = requests_get(last) if url_request.ok: @@ -325,15 +446,20 @@ def main(override=None, intel_for_uc=None): if use_version == latest_version: get_latest = True else: + url_request = None cft = True - cft_latest_vpm_url = ( - "https://googlechromelabs.github.io/" - "chrome-for-testing/latest-versions-per-milestone.json" - ) - url_request = requests_get(cft_latest_vpm_url) - if url_request.ok: - use_ver = use_version - fver = url_request.json()["milestones"][use_ver]["version"] + if force_cft: + url_request = get_cft_known_good_versions() + if ( + url_request.ok + and '"version":"%s"' % use_version in url_request.text + ): + fver = use_version + found_chromedriver = True + else: + url_request = get_cft_latest_versions_per_milestone() + if not force_cft and url_request.ok: + fver = get_cft_latest_version_from_milestone(use_version) found_chromedriver = True use_version = str(fver) if use_version == latest_version: @@ -380,21 +506,46 @@ def main(override=None, intel_for_uc=None): if found_chromedriver or url_request.ok: p_version = use_version p_version = c3 + use_version + cr - if get_latest or cft: - p_version = p_version + " " + c2 + "(Latest)" + cr + latest_stable = get_latest_stable_chromedriver_version() + latest_beta = get_latest_beta_chromedriver_version() + latest_dev = get_latest_dev_chromedriver_version() + latest_canary = get_latest_canary_chromedriver_version() + vint = True + int_use_ver = None + int_latest_ver = None + try: + int_use_ver = int(use_version.split(".")[0]) + int_latest_ver = int(latest_stable.split(".")[0]) + except Exception: + vint = False + on_cft = False + if int_latest_ver > 115: + on_cft = True + if cft and on_cft and use_version == latest_stable: + p_version = p_version + " " + c2 + "(Latest Stable)" + cr + " " + elif cft and on_cft and use_version == latest_beta: + p_version = p_version + " " + c2 + "(Latest Beta)" + cr + " " + elif cft and on_cft and use_version == latest_dev: + p_version = p_version + " " + c2 + "(Latest Dev)" + cr + " " + elif cft and on_cft and use_version == latest_canary: + p_version = p_version + " " + c2 + "(Latest Canary)" + cr + " " + elif not vint: + pass + elif vint and cft and on_cft and int_use_ver == int_latest_ver: + p_version = p_version + " " + c2 + "(Stable)" + cr + elif vint and cft and on_cft and int_use_ver == int_latest_ver + 1: + p_version = p_version + " " + c2 + "(Beta)" + cr + elif vint and cft and on_cft and int_use_ver == int_latest_ver + 2: + p_version = p_version + " " + c2 + "(Dev / Canary)" + cr + elif vint and cft and on_cft and int_use_ver == int_latest_ver - 1: + p_version = p_version + " " + c6 + "(Previous Version)" + cr + elif cft and not on_cft: + pass else: - n_l_s = "Not Latest" - try: - int_use_version = int(use_version.split(".")[0]) - int_latest_version = int(latest_version.split(".")[0]) - if int_use_version > int_latest_version: - n_l_s = "Not Latest Stable" - except Exception: - pass - not_latest = c5 + "(" + c4 + n_l_s + c5 + ")" + cr + not_latest = c5 + "(" + c4 + "Legacy Version" + c5 + ")" + cr p_version = p_version + " " + not_latest - msg = c2 + "chromedriver version for download" + cr - print("\n*** %s = %s" % (msg, p_version)) + msg = c2 + "chromedriver to download" + cr + log_d("\n*** %s = %s" % (msg, p_version)) else: raise Exception("Could not find chromedriver to download!\n") if not get_latest: @@ -445,9 +596,9 @@ def main(override=None, intel_for_uc=None): if not found_geckodriver: url_request = requests_get(download_url) if found_geckodriver or url_request.ok: - msg = c2 + "geckodriver version for download" + cr + msg = c2 + "geckodriver to download" + cr p_version = c3 + use_version + cr - print("\n*** %s = %s" % (msg, p_version)) + log_d("\n*** %s = %s" % (msg, p_version)) else: raise Exception( "\nCould not find the specified geckodriver " @@ -552,9 +703,9 @@ def main(override=None, intel_for_uc=None): raise Exception( "Could not find version [%s] of EdgeDriver!" % use_version ) - msg = c2 + "edgedriver version for download" + cr + msg = c2 + "edgedriver to download" + cr p_version = c3 + use_version + cr - print("\n*** %s = %s" % (msg, p_version)) + log_d("\n*** %s = %s" % (msg, p_version)) elif name == "iedriver": major_version = "3.14" full_version = "3.14.0" @@ -582,9 +733,9 @@ def main(override=None, intel_for_uc=None): url_request = requests_get_with_retry(headless_ie_url) if url_request.ok: headless_ie_exists = True - msg = c2 + "HeadlessIEDriver version for download" + cr + msg = c2 + "HeadlessIEDriver to download" + cr p_version = c3 + headless_ie_version + cr - print("\n*** %s = %s" % (msg, p_version)) + log_d("\n*** %s = %s" % (msg, p_version)) elif name == "operadriver" or name == "operachromiumdriver": name = "operadriver" use_version = DEFAULT_OPERADRIVER_VERSION @@ -647,9 +798,9 @@ def main(override=None, intel_for_uc=None): "releases/download/" "%s/%s" % (use_version, file_name) ) - msg = c2 + "operadriver version for download" + cr + msg = c2 + "operadriver to download" + cr p_version = c3 + use_version + cr - print("\n*** %s = %s" % (msg, p_version)) + log_d("\n*** %s = %s" % (msg, p_version)) else: invalid_run_command() @@ -667,14 +818,14 @@ def main(override=None, intel_for_uc=None): headless_ie_file_path = os.path.join( downloads_folder, headless_ie_file_name ) - print( + log_d( "\nDownloading %s from:\n%s ..." % (headless_ie_file_name, headless_ie_url) ) remote_file = requests_get_with_retry(headless_ie_url) with open(headless_ie_file_path, "wb") as file: file.write(remote_file.content) - print("%sDownload Complete!%s\n" % (c1, cr)) + log_d("%sDownload Complete!%s\n" % (c1, cr)) zip_file_path = headless_ie_file_path zip_ref = zipfile.ZipFile(zip_file_path, "r") contents = zip_ref.namelist() @@ -713,12 +864,12 @@ def main(override=None, intel_for_uc=None): os.remove(new_file) if not driver_file or not driver_path or not filename: raise Exception("headless_ie_selenium.exe missing from Zip file!") - print("Extracting %s from %s ..." % (filename, headless_ie_file_name)) + log_d("Extracting %s from %s ..." % (filename, headless_ie_file_name)) zip_ref.extractall(downloads_folder) zip_ref.close() os.remove(zip_file_path) shutil.copyfile(driver_path, os.path.join(downloads_folder, filename)) - print("%sUnzip Complete!%s\n" % (c2, cr)) + log_d("%sUnzip Complete!%s\n" % (c2, cr)) to_remove = [ "%s/%s/ruby_example/Gemfile" % (downloads_folder, h_ie_fn), "%s/%s/ruby_example/Gemfile.lock" % (downloads_folder, h_ie_fn), @@ -737,22 +888,22 @@ def main(override=None, intel_for_uc=None): # Only works if the directory is empty os.rmdir(os.path.join(downloads_folder, h_ie_fn)) driver_path = os.path.join(downloads_folder, filename) - print( + log_d( "The file [%s] was saved to:\n%s%s%s\n" % (filename, c3, driver_path, cr) ) - print("Making [%s %s] executable ..." % (driver_file, use_version)) + log_d("Making [%s %s] executable ..." % (driver_file, use_version)) make_executable(driver_path) - print( + log_d( "%s[%s %s] is now ready for use!%s" % (c1, driver_file, use_version, cr) ) - print("\nDownloading %s from:\n%s ..." % (file_name, download_url)) + log_d("\nDownloading %s from:\n%s ..." % (file_name, download_url)) remote_file = requests_get_with_retry(download_url) with open(file_path, "wb") as file: file.write(remote_file.content) - print("%sDownload Complete!%s\n" % (c1, cr)) + log_d("%sDownload Complete!%s\n" % (c1, cr)) if file_name.endswith(".zip"): zip_file_path = file_path @@ -774,23 +925,32 @@ def main(override=None, intel_for_uc=None): driver_contents = [driver_name] # Remove existing version if exists new_file = os.path.join(downloads_folder, str(f_name)) - if intel_for_uc and IS_MAC: + if name == "uc_driver": if new_file.endswith("drivers/chromedriver"): new_file = new_file.replace( "drivers/chromedriver", "drivers/uc_driver" ) + elif new_file.endswith("drivers/chromedriver.exe"): + new_file = new_file.replace( + "drivers/chromedriver.exe", "drivers/uc_driver.exe" + ) elif "drivers/%s/chromedriver" % plat_arch in new_file: new_file = new_file.replace( "drivers/%s/chromedriver" % plat_arch, "drivers/%s/uc_driver" % plat_arch ) + elif "drivers/%s/chromedriver.exe" % plat_arch in new_file: + new_file = new_file.replace( + "drivers/%s/chromedriver.exe" % plat_arch, + "drivers/%s/uc_driver.exe" % plat_arch + ) if "Driver" in new_file or "driver" in new_file: if os.path.exists(new_file): os.remove(new_file) # Technically the old file now if driver_contents: contents = driver_contents - print("Extracting %s from %s ..." % (contents, file_name)) - if intel_for_uc and IS_ARM_MAC: + log_d("Extracting %s from %s ..." % (contents, file_name)) + if name == "uc_driver": f_name = "uc_driver" new_file = os.path.join(downloads_folder, f_name) if os.path.exists(new_file): @@ -800,6 +960,9 @@ def main(override=None, intel_for_uc=None): if zipinfo.filename.split("/")[-1] == "chromedriver": zipinfo.filename = "uc_driver" zip_ref.extract(zipinfo, downloads_folder) + elif zipinfo.filename.split("/")[-1] == "chromedriver.exe": + zipinfo.filename = "uc_driver.exe" + zip_ref.extract(zipinfo, downloads_folder) contents = zip_ref.namelist() if driver_contents: contents = driver_contents @@ -826,19 +989,19 @@ def main(override=None, intel_for_uc=None): zip_ref.extractall(downloads_folder) zip_ref.close() os.remove(zip_file_path) - print("%sUnzip Complete!%s\n" % (c2, cr)) + log_d("%sUnzip Complete!%s\n" % (c2, cr)) for f_name in contents: - if intel_for_uc: + if name == "uc_driver": if IS_WINDOWS: f_name = "uc_driver.exe" else: f_name = "uc_driver" new_file = os.path.join(downloads_folder, str(f_name)) pr_file = c3 + new_file + cr - print("The file [%s] was saved to:\n%s\n" % (f_name, pr_file)) - print("Making [%s %s] executable ..." % (f_name, use_version)) + log_d("The file [%s] was saved to:\n%s\n" % (f_name, pr_file)) + log_d("Making [%s %s] executable ..." % (f_name, use_version)) make_executable(new_file) - print( + log_d( "%s[%s %s] is now ready for use!%s" % (c1, f_name, use_version, cr) ) @@ -846,8 +1009,8 @@ def main(override=None, intel_for_uc=None): path_file = LOCAL_PATH + f_name shutil.copyfile(new_file, path_file) make_executable(path_file) - print("Also copied to: %s%s%s" % (c3, path_file, cr)) - print("") + log_d("Also copied to: %s%s%s" % (c3, path_file, cr)) + log_d("") elif name == "edgedriver" or name == "msedgedriver": if IS_MAC or IS_LINUX: # Mac / Linux @@ -899,11 +1062,11 @@ def main(override=None, intel_for_uc=None): os.remove(new_file) if not driver_file or not driver_path: raise Exception("msedgedriver missing from Zip file!") - print("Extracting %s from %s ..." % (contents, file_name)) + log_d("Extracting %s from %s ..." % (contents, file_name)) zip_ref.extractall(downloads_folder) zip_ref.close() os.remove(zip_file_path) - print("%sUnzip Complete!%s\n" % (c2, cr)) + log_d("%sUnzip Complete!%s\n" % (c2, cr)) to_remove = [ "%s/Driver_Notes/credits.html" % downloads_folder, "%s/Driver_Notes/EULA" % downloads_folder, @@ -916,13 +1079,13 @@ def main(override=None, intel_for_uc=None): # Only works if the directory is empty os.rmdir(os.path.join(downloads_folder, "Driver_Notes/")) pr_driver_path = c3 + driver_path + cr - print( + log_d( "The file [%s] was saved to:\n%s\n" % (driver_file, pr_driver_path) ) - print("Making [%s %s] executable ..." % (driver_file, use_version)) + log_d("Making [%s %s] executable ..." % (driver_file, use_version)) make_executable(driver_path) - print( + log_d( "%s[%s %s] is now ready for use!%s" % (c1, driver_file, use_version, cr) ) @@ -930,8 +1093,8 @@ def main(override=None, intel_for_uc=None): path_file = LOCAL_PATH + f_name shutil.copyfile(new_file, path_file) make_executable(path_file) - print("Also copied to: %s%s%s" % (c3, path_file, cr)) - print("") + log_d("Also copied to: %s%s%s" % (c3, path_file, cr)) + log_d("") elif name == "operadriver": if len(contents) > 3: raise Exception("Unexpected content in OperaDriver Zip file!") @@ -949,11 +1112,11 @@ def main(override=None, intel_for_uc=None): os.remove(new_file) if not driver_file or not driver_path: raise Exception("Operadriver missing from Zip file!") - print("Extracting %s from %s ..." % (contents, file_name)) + log_d("Extracting %s from %s ..." % (contents, file_name)) zip_ref.extractall(downloads_folder) zip_ref.close() os.remove(zip_file_path) - print("%sUnzip Complete!%s\n" % (c2, cr)) + log_d("%sUnzip Complete!%s\n" % (c2, cr)) inner_driver = os.path.join( downloads_folder, inner_folder, driver_file ) @@ -962,13 +1125,13 @@ def main(override=None, intel_for_uc=None): ) shutil.copyfile(inner_driver, driver_path) pr_driver_path = c3 + driver_path + cr - print( + log_d( "The file [%s] was saved to:\n%s\n" % (driver_file, pr_driver_path) ) - print("Making [%s %s] executable ..." % (driver_file, use_version)) + log_d("Making [%s %s] executable ..." % (driver_file, use_version)) make_executable(driver_path) - print( + log_d( "%s[%s %s] is now ready for use!%s" % (c1, driver_file, use_version, cr) ) @@ -976,7 +1139,7 @@ def main(override=None, intel_for_uc=None): path_file = LOCAL_PATH + driver_file shutil.copyfile(driver_path, path_file) make_executable(path_file) - print("Also copied to: %s%s%s" % (c3, path_file, cr)) + log_d("Also copied to: %s%s%s" % (c3, path_file, cr)) # Clean up extra files if os.path.exists(inner_driver): os.remove(inner_driver) @@ -985,7 +1148,7 @@ def main(override=None, intel_for_uc=None): if os.path.exists(os.path.join(downloads_folder, inner_folder)): # Only works if the directory is empty os.rmdir(os.path.join(downloads_folder, inner_folder)) - print("") + log_d("") elif len(contents) == 0: raise Exception("Zip file %s is empty!" % zip_file_path) else: @@ -1001,18 +1164,18 @@ def main(override=None, intel_for_uc=None): if "Driver" in new_file or "driver" in new_file: if os.path.exists(new_file): os.remove(new_file) # Technically the old file now - print("Extracting %s from %s ..." % (contents, file_name)) + log_d("Extracting %s from %s ..." % (contents, file_name)) tar.extractall(downloads_folder) tar.close() os.remove(tar_file_path) - print("%sUnzip Complete!%s\n" % (c2, cr)) + log_d("%sUnzip Complete!%s\n" % (c2, cr)) for f_name in contents: new_file = os.path.join(downloads_folder, str(f_name)) pr_file = c3 + new_file + cr - print("The file [%s] was saved to:\n%s\n" % (f_name, pr_file)) - print("Making [%s %s] executable ..." % (f_name, use_version)) + log_d("The file [%s] was saved to:\n%s\n" % (f_name, pr_file)) + log_d("Making [%s %s] executable ..." % (f_name, use_version)) make_executable(new_file) - print( + log_d( "%s[%s %s] is now ready for use!%s" % (c1, f_name, use_version, cr) ) @@ -1020,8 +1183,8 @@ def main(override=None, intel_for_uc=None): path_file = LOCAL_PATH + f_name shutil.copyfile(new_file, path_file) make_executable(path_file) - print("Also copied to: %s%s%s" % (c3, path_file, cr)) - print("") + log_d("Also copied to: %s%s%s" % (c3, path_file, cr)) + log_d("") elif len(contents) == 0: raise Exception("Tar file %s is empty!" % tar_file_path) else: @@ -1029,15 +1192,10 @@ def main(override=None, intel_for_uc=None): else: # Not a .zip file or a .tar.gz file. Just a direct download. if "Driver" in file_name or "driver" in file_name: - print("Making [%s] executable ..." % file_name) + log_d("Making [%s] executable ..." % file_name) make_executable(file_path) - print("%s[%s] is now ready for use!%s" % (c1, file_name, cr)) - print("Location of [%s]:\n%s\n" % (file_name, file_path)) - if name == "uc_driver" and not IS_ARM_MAC: - print( - "%s[uc_driver] will be created from [chromedriver] at runtime!%s\n" - % (c5, cr) - ) + log_d("%s[%s] is now ready for use!%s" % (c1, file_name, cr)) + log_d("Location of [%s]:\n%s\n" % (file_name, file_path)) if __name__ == "__main__": diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 82b1ae2afae..668eeb096e7 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -8,13 +8,13 @@ import time import urllib3 import warnings -import zipfile from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.common.service import utils as service_utils from selenium.webdriver.edge.service import Service as EdgeService from selenium.webdriver.firefox.service import Service as FirefoxService from selenium.webdriver.safari.service import Service as SafariService +from seleniumbase import decorators from seleniumbase import drivers # webdriver storage folder for SeleniumBase from seleniumbase import extensions # browser extensions storage folder from seleniumbase.config import settings @@ -82,6 +82,18 @@ pass # SeleniumBase will use web drivers from the System PATH by default +def log_d(message): + """If setting sb_config.settings.HIDE_DRIVER_DOWNLOADS to True, + output from driver downloads are logged instead of printed.""" + if ( + hasattr(settings, "HIDE_DRIVER_DOWNLOADS") + and settings.HIDE_DRIVER_DOWNLOADS + ): + logging.debug(message) + else: + print(message) + + def make_executable(file_path): # Set permissions to: "If you can read it, you can execute it." mode = os.stat(file_path).st_mode @@ -97,6 +109,7 @@ def make_driver_executable_if_not(driver_path): make_executable(driver_path) +@decorators.rate_limited(4) def requests_get(url, proxy_string=None): import requests @@ -122,12 +135,8 @@ def requests_get(url, proxy_string=None): def get_latest_chromedriver_version(): - last = "https://chromedriver.storage.googleapis.com/LATEST_RELEASE" - url_request = requests_get(last) - if url_request.ok: - return url_request.text - else: - return None + from seleniumbase.console_scripts import sb_install + return sb_install.get_latest_stable_chromedriver_version() def chromedriver_on_path(): @@ -174,6 +183,27 @@ def find_chromedriver_version_to_use(use_version, driver_version): and int(str(driver_version).split(".")[0]) >= 72 ): use_version = str(driver_version) + elif driver_version and not str(driver_version).split(".")[0].isdigit(): + from seleniumbase.console_scripts import sb_install + driver_version = driver_version.lower() + if driver_version == "stable" or driver_version == "latest": + use_version = sb_install.get_latest_stable_chromedriver_version() + elif driver_version == "beta": + use_version = sb_install.get_latest_beta_chromedriver_version() + elif driver_version == "dev": + use_version = sb_install.get_latest_dev_chromedriver_version() + elif driver_version == "canary": + use_version = sb_install.get_latest_canary_chromedriver_version() + elif driver_version == "previous" or driver_version == "latest-1": + use_version = sb_install.get_latest_stable_chromedriver_version() + use_version = str(int(use_version.split(".")[0]) - 1) + elif driver_version == "mlatest": + if use_version.split(".")[0].isdigit(): + major = use_version.split(".")[0] + if int(major) >= 115: + use_version = ( + sb_install.get_cft_latest_version_from_milestone(major) + ) return use_version @@ -217,6 +247,8 @@ def uc_special_open_if_cf(driver, url, proxy_string=None): or has_cf(req_get.text) ): special = True + if status_str == "403": + time.sleep(0.06) # Forbidden / Blocked! (Wait first!) except Exception: pass if special: @@ -367,7 +399,7 @@ def _repair_chromedriver(chrome_options, headless_options, mcv=None): def _repair_edgedriver(edge_version): - print( + log_d( "\nWarning: msedgedriver version doesn't match Edge version!" "\nAttempting to install a matching version of msedgedriver:" ) @@ -483,6 +515,7 @@ def _unzip_to_new_folder(zip_file, folder): proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK) with proxy_dir_lock: if not os.path.exists(folder): + import zipfile zip_ref = zipfile.ZipFile(zip_file, "r") os.makedirs(folder) zip_ref.extractall(folder) @@ -1170,13 +1203,13 @@ def get_driver( ) ): if not os.path.exists(binary_location): - print( + log_d( "\nWarning: The Chromium binary specified (%s) was NOT found!" "\n(Will use default settings...)\n" % binary_location ) binary_location = None elif binary_location.endswith("/") or binary_location.endswith("\\"): - print( + log_d( "\nWarning: The Chromium binary path must be a full path " "that includes the driver filename at the end of it!" "\n(Will use default settings...)\n" % binary_location @@ -1188,7 +1221,7 @@ def get_driver( binary_name = binary_location.split("/")[-1].split("\\")[-1] valid_names = get_valid_binary_names_for_browser(browser_name) if binary_name not in valid_names: - print( + log_d( "\nWarning: The Chromium binary specified is NOT valid!" '\nExpecting "%s" to be found in %s for the browser / OS!' "\n(Will use default settings...)\n" @@ -2070,16 +2103,15 @@ def get_local_driver( ) elif not geckodriver_on_path(): from seleniumbase.console_scripts import sb_install - args = " ".join(sys.argv) if not ("-n" in sys.argv or " -n=" in args or args == "-c"): # (Not multithreaded) sys_args = sys.argv # Save a copy of current sys args - print("\nWarning: geckodriver not found. Getting it now:") + log_d("\nWarning: geckodriver not found. Getting it now:") try: sb_install.main(override="geckodriver") except Exception as e: - print("\nWarning: Could not install geckodriver: %s" % e) + log_d("\nWarning: Could not install geckodriver: %s" % e) sys.argv = sys_args # Put back the original sys args else: geckodriver_fixing_lock = fasteners.InterProcessLock( @@ -2088,7 +2120,7 @@ def get_local_driver( with geckodriver_fixing_lock: if not geckodriver_on_path(): sys_args = sys.argv # Save a copy of sys args - print( + log_d( "\nWarning: geckodriver not found. " "Getting it now:" ) @@ -2211,12 +2243,11 @@ def get_local_driver( ) elif not iedriver_on_path(): from seleniumbase.console_scripts import sb_install - args = " ".join(sys.argv) if not ("-n" in sys.argv or " -n=" in args or args == "-c"): # (Not multithreaded) sys_args = sys.argv # Save a copy of current sys args - print("\nWarning: IEDriver not found. Getting it now:") + log_d("\nWarning: IEDriver not found. Getting it now:") sb_install.main(override="iedriver") sys.argv = sys_args # Put back the original sys args if LOCAL_HEADLESS_IEDRIVER and os.path.exists(LOCAL_HEADLESS_IEDRIVER): @@ -2229,12 +2260,11 @@ def get_local_driver( ) elif not headless_iedriver_on_path(): from seleniumbase.console_scripts import sb_install - args = " ".join(sys.argv) if not ("-n" in sys.argv or " -n=" in args or args == "-c"): # (Not multithreaded) sys_args = sys.argv # Save a copy of current sys args - print("\nWarning: HeadlessIEDriver not found. Getting it now:") + log_d("\nWarning: HeadlessIEDriver not found. Getting it now:") sb_install.main(override="iedriver") sys.argv = sys_args # Put back the original sys args if not headless: @@ -2340,7 +2370,6 @@ def get_local_driver( ) if not local_edgedriver_exists or edgedriver_upgrade_needed: from seleniumbase.console_scripts import sb_install - args = " ".join(sys.argv) if not ("-n" in sys.argv or " -n=" in args or args == "-c"): # (Not multithreaded) @@ -2348,7 +2377,7 @@ def get_local_driver( if edgedriver_upgrade_needed: msg = "Microsoft Edge Driver update needed." sys_args = sys.argv # Save a copy of current sys args - print("\n%s Getting it now:" % msg) + log_d("\n%s Getting it now:" % msg) sb_install.main(override="edgedriver %s" % use_version) sys.argv = sys_args # Put back the original sys args else: @@ -2360,7 +2389,7 @@ def get_local_driver( if edgedriver_upgrade_needed: msg = "Microsoft Edge Driver update needed." sys_args = sys.argv # Save a copy of current sys args - print("\n%s Getting it now:" % msg) + log_d("\n%s Getting it now:" % msg) sb_install.main(override="edgedriver %s" % use_version) sys.argv = sys_args # Put back the original sys args @@ -2902,34 +2931,36 @@ def get_local_driver( ) use_version = "latest" major_chrome_version = None - if selenium4_or_newer: - try: - if chrome_options.binary_location: - try: - major_chrome_version = ( - detect_b_ver.get_browser_version_from_binary( - chrome_options.binary_location, - ) - ).split(".")[0] - if len(major_chrome_version) < 2: - major_chrome_version = None - except Exception: - major_chrome_version = None - if not major_chrome_version: - br_app = "google-chrome" + full_ch_version = None + full_ch_driver_version = None + try: + if chrome_options.binary_location: + try: major_chrome_version = ( - detect_b_ver.get_browser_version_from_os(br_app) + detect_b_ver.get_browser_version_from_binary( + chrome_options.binary_location, + ) ).split(".")[0] - if int(major_chrome_version) < 67: + if len(major_chrome_version) < 2: + major_chrome_version = None + except Exception: major_chrome_version = None - elif ( - int(major_chrome_version) >= 67 - and int(major_chrome_version) <= 72 - ): - # chromedrivers 2.41 - 2.46 could be swapped with 72 - major_chrome_version = "72" - except Exception: + if not major_chrome_version: + br_app = "google-chrome" + full_ch_version = ( + detect_b_ver.get_browser_version_from_os(br_app) + ) + major_chrome_version = full_ch_version.split(".")[0] + if int(major_chrome_version) < 67: major_chrome_version = None + elif ( + int(major_chrome_version) >= 67 + and int(major_chrome_version) <= 72 + ): + # chromedrivers 2.41 - 2.46 could be swapped with 72 + major_chrome_version = "72" + except Exception: + major_chrome_version = None if major_chrome_version: use_version = major_chrome_version ch_driver_version = None @@ -2943,12 +2974,20 @@ def get_local_driver( output = output.decode("latin1") else: output = output.decode("utf-8") - output = output.split(" ")[1].split(".")[0] + full_ch_driver_version = output.split(" ")[1] + output = full_ch_driver_version.split(".")[0] if int(output) >= 2: ch_driver_version = output except Exception: pass elif path_chromedriver: + try: + make_driver_executable_if_not(path_chromedriver) + except Exception as e: + logging.debug( + "\nWarning: Could not make chromedriver" + " executable: %s" % e + ) try: output = subprocess.check_output( '"%s" --version' % path_chromedriver, shell=True @@ -2957,7 +2996,8 @@ def get_local_driver( output = output.decode("latin1") else: output = output.decode("utf-8") - output = output.split(" ")[1].split(".")[0] + full_ch_driver_version = output.split(" ")[1] + output = full_ch_driver_version.split(".")[0] if int(output) >= 2: ch_driver_version = output except Exception: @@ -2997,23 +3037,7 @@ def get_local_driver( use_version = find_chromedriver_version_to_use( use_version, driver_version ) - if ( - LOCAL_CHROMEDRIVER - and os.path.exists(LOCAL_CHROMEDRIVER) - and ( - use_version == ch_driver_version - or use_version == "latest" - ) - and ( - not is_using_uc(undetectable, browser_name) - or not IS_ARM_MAC - ) - or ( - is_using_uc(undetectable, browser_name) - and IS_ARM_MAC - and uc_driver_version == use_version - ) - ): + if LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER): try: make_driver_executable_if_not(LOCAL_CHROMEDRIVER) except Exception as e: @@ -3021,31 +3045,73 @@ def get_local_driver( "\nWarning: Could not make chromedriver" " executable: %s" % e ) + use_uc = is_using_uc(undetectable, browser_name) + make_uc_driver_from_chromedriver = False + local_ch_exists = ( + LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER) + ) + """If no LOCAL_CHROMEDRIVER, but path_chromedriver, and the + browser version nearly matches the driver version, then use + the path_chromedriver instead of downloading a new driver. + Eg. 116.0.* for both is close, but not 116.0.* and 116.1.*""" + browser_driver_close_match = False + if ( + path_chromedriver + and full_ch_version + and full_ch_driver_version + ): + full_ch_v_p = full_ch_version.split(".")[0:2] + full_ch_driver_v_p = full_ch_driver_version.split(".")[0:2] + if full_ch_v_p == full_ch_driver_v_p: + browser_driver_close_match = True + # If not ARM MAC and need to use uc_driver (and it's missing), + # and already have chromedriver with the correct version, + # then copy chromedriver to uc_driver (and it'll get patched). + if ( + not IS_ARM_MAC + and use_uc + and ( + ( + (local_ch_exists or path_chromedriver) + and use_version == ch_driver_version + and ( + not os.path.exists(LOCAL_UC_DRIVER) + or uc_driver_version != use_version + ) + ) + or ( + local_ch_exists + and use_version == "latest" + and not os.path.exists(LOCAL_UC_DRIVER) + ) + ) + ): + make_uc_driver_from_chromedriver = True elif ( - not path_chromedriver + (use_uc and not os.path.exists(LOCAL_UC_DRIVER)) + or (not use_uc and not path_chromedriver) or ( - use_version != "latest" - and ch_driver_version - and use_version != ch_driver_version + not use_uc + and use_version != "latest" # Browser version detected + and (ch_driver_version or not local_ch_exists) and ( - selenium4_or_newer - or ch_driver_version != "72" + use_version.split(".")[0] != ch_driver_version + or ( + not local_ch_exists + and use_version.isnumeric() + and int(use_version) >= 115 + and not browser_driver_close_match + ) ) ) or ( - is_using_uc(undetectable, browser_name) - and not os.path.exists(LOCAL_UC_DRIVER) - ) - or ( - is_using_uc(undetectable, browser_name) - and IS_ARM_MAC - and use_version != "latest" + use_uc + and use_version != "latest" # Browser version detected and uc_driver_version != use_version ) ): # chromedriver download needed in the seleniumbase/drivers dir from seleniumbase.console_scripts import sb_install - args = " ".join(sys.argv) if not ("-n" in sys.argv or " -n=" in args or args == "-c"): # (Not multithreaded) @@ -3053,14 +3119,22 @@ def get_local_driver( msg = "chromedriver update needed. Getting it now:" if not path_chromedriver: msg = "chromedriver not found. Getting it now:" - print("\nWarning: %s" % msg) + if use_uc and not os.path.exists(LOCAL_UC_DRIVER): + msg = "uc_driver not found. Getting it now:" + if use_uc and os.path.exists(LOCAL_UC_DRIVER): + msg = "uc_driver update needed. Getting it now:" + log_d("\nWarning: %s" % msg) + force_uc = False intel_for_uc = False - if IS_ARM_MAC and is_using_uc(undetectable, browser_name): + if use_uc: + force_uc = True + if IS_ARM_MAC and use_uc: intel_for_uc = True # Use Intel's driver for UC Mode try: sb_install.main( override="chromedriver %s" % use_version, intel_for_uc=intel_for_uc, + force_uc=force_uc, ) except Exception: d_latest = get_latest_chromedriver_version() @@ -3092,28 +3166,60 @@ def get_local_driver( msg = "chromedriver update needed. Getting it now:" if not path_chromedriver: msg = "chromedriver not found. Getting it now:" + if use_uc and not os.path.exists(LOCAL_UC_DRIVER): + msg = "uc_driver not found. Getting it now:" + if use_uc and os.path.exists(LOCAL_UC_DRIVER): + msg = "uc_driver update needed. Getting it now:" + force_uc = False intel_for_uc = False - if ( - IS_ARM_MAC - and is_using_uc(undetectable, browser_name) - ): + if use_uc: + force_uc = True + if IS_ARM_MAC and use_uc: intel_for_uc = True # Use Intel driver for UC Mode + if os.path.exists(LOCAL_CHROMEDRIVER): + try: + output = subprocess.check_output( + '"%s" --version' % LOCAL_CHROMEDRIVER, + shell=True, + ) + if IS_WINDOWS: + output = output.decode("latin1") + else: + output = output.decode("utf-8") + full_ch_driver_version = output.split(" ")[1] + output = full_ch_driver_version.split(".")[0] + if int(output) >= 2: + ch_driver_version = output + except Exception: + pass if ( ( - not is_using_uc(undetectable, browser_name) + not use_uc and not os.path.exists(LOCAL_CHROMEDRIVER) ) + or (use_uc and not os.path.exists(LOCAL_UC_DRIVER)) or ( - is_using_uc(undetectable, browser_name) - and not os.path.exists(LOCAL_UC_DRIVER) + not use_uc + and ( + use_version.split(".")[0] + != ch_driver_version + ) + ) + or ( + use_uc + and ( + use_version.split(".")[0] + != get_uc_driver_version() + ) ) ): - print("\nWarning: %s" % msg) + log_d("\nWarning: %s" % msg) sys_args = sys.argv # Save a copy of sys args try: sb_install.main( override="chromedriver %s" % use_version, intel_for_uc=intel_for_uc, + force_uc=force_uc, ) except Exception: d_latest = get_latest_chromedriver_version() @@ -3150,20 +3256,8 @@ def get_local_driver( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with uc_lock: # Avoid multithreaded issues - uc_driver_version = get_uc_driver_version() - use_version = find_chromedriver_version_to_use( - use_version, driver_version - ) - if ( - ( - uc_driver_version != use_version - and use_version != "latest" - ) - or not os.path.exists(LOCAL_UC_DRIVER) - ): - if IS_ARM_MAC and os.path.exists(LOCAL_UC_DRIVER): - pass # Already taken care of in sb_install - elif os.path.exists(LOCAL_CHROMEDRIVER): + if make_uc_driver_from_chromedriver: + if os.path.exists(LOCAL_CHROMEDRIVER): shutil.copyfile( LOCAL_CHROMEDRIVER, LOCAL_UC_DRIVER ) diff --git a/seleniumbase/core/settings_parser.py b/seleniumbase/core/settings_parser.py index 80f92430aca..c45a20e748b 100644 --- a/seleniumbase/core/settings_parser.py +++ b/seleniumbase/core/settings_parser.py @@ -121,6 +121,8 @@ def set_settings(settings_file): settings.HEADLESS_START_WIDTH = override_settings[key] elif key == "HEADLESS_START_HEIGHT": settings.HEADLESS_START_HEIGHT = override_settings[key] + elif key == "HIDE_DRIVER_DOWNLOADS": + settings.HIDE_DRIVER_DOWNLOADS = override_settings[key] elif key == "MASTERQA_DEFAULT_VALIDATION_MESSAGE": settings.MASTERQA_DEFAULT_VALIDATION_MESSAGE = override_settings[ key diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py index 1ac9d3e746b..dbd6b944ea9 100644 --- a/seleniumbase/undetected/__init__.py +++ b/seleniumbase/undetected/__init__.py @@ -13,8 +13,6 @@ from .cdp import PageElement from .dprocess import start_detached from .options import ChromeOptions -from .patcher import IS_POSIX -from .patcher import Patcher from .reactor import Reactor from .webelement import WebElement @@ -26,6 +24,7 @@ "CDP", "find_chrome_executable", ) +IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux")) logger = logging.getLogger("uc") logger.setLevel(logging.getLogger().getEffectiveLevel()) @@ -114,14 +113,14 @@ def __init__( """ self.debug = debug self.patcher = None + import fasteners + from seleniumbase.fixtures import constants if patch_driver: - import fasteners - from seleniumbase.fixtures import constants - uc_lock = fasteners.InterProcessLock( constants.MultiBrowser.DRIVER_FIXING_LOCK ) with uc_lock: + from .patcher import Patcher self.patcher = Patcher( executable_path=driver_executable_path, force=patcher_force_close, @@ -268,50 +267,54 @@ def __init__( if "win32" in sys.platform: creationflags = subprocess.CREATE_NO_WINDOW self.options = options - if not use_subprocess: - self.browser_pid = start_detached( - options.binary_location, *options.arguments - ) - else: - browser = subprocess.Popen( - [options.binary_location, *options.arguments], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=IS_POSIX, - creationflags=creationflags, - ) - self.browser_pid = browser.pid - service_ = None - if patch_driver: - service_ = selenium.webdriver.chrome.service.Service( - executable_path=self.patcher.executable_path, - service_args=["--disable-build-check"], - port=port, - log_output=os.devnull, - ) - else: - service_ = selenium.webdriver.chrome.service.Service( - executable_path=driver_executable_path, - service_args=["--disable-build-check"], - port=port, - log_output=os.devnull, - ) - if hasattr(service_, "creationflags"): - setattr(service_, "creationflags", creationflags) - if hasattr(service_, "creation_flags"): - setattr(service_, "creation_flags", creationflags) - super().__init__(options=options, service=service_) - self.reactor = None - if enable_cdp_events: - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - logging.getLogger( - "selenium.webdriver.remote.remote_connection" - ).setLevel(20) - reactor = Reactor(self) - reactor.start() - self.reactor = reactor - self._web_element_cls = WebElement + uc_lock = fasteners.InterProcessLock( + constants.MultiBrowser.DRIVER_FIXING_LOCK + ) + with uc_lock: + if not use_subprocess: + self.browser_pid = start_detached( + options.binary_location, *options.arguments + ) + else: + browser = subprocess.Popen( + [options.binary_location, *options.arguments], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=IS_POSIX, + creationflags=creationflags, + ) + self.browser_pid = browser.pid + service_ = None + if patch_driver: + service_ = selenium.webdriver.chrome.service.Service( + executable_path=self.patcher.executable_path, + service_args=["--disable-build-check"], + port=port, + log_output=os.devnull, + ) + else: + service_ = selenium.webdriver.chrome.service.Service( + executable_path=driver_executable_path, + service_args=["--disable-build-check"], + port=port, + log_output=os.devnull, + ) + if hasattr(service_, "creationflags"): + setattr(service_, "creationflags", creationflags) + if hasattr(service_, "creation_flags"): + setattr(service_, "creation_flags", creationflags) + super().__init__(options=options, service=service_) + self.reactor = None + if enable_cdp_events: + if logging.getLogger().getEffectiveLevel() == logging.DEBUG: + logging.getLogger( + "selenium.webdriver.remote.remote_connection" + ).setLevel(20) + reactor = Reactor(self) + reactor.start() + self.reactor = reactor + self._web_element_cls = WebElement def __getattribute__(self, item): if not super().__getattribute__("debug"): diff --git a/seleniumbase/undetected/patcher.py b/seleniumbase/undetected/patcher.py index cc25084d67b..8393d1f4a01 100644 --- a/seleniumbase/undetected/patcher.py +++ b/seleniumbase/undetected/patcher.py @@ -35,7 +35,10 @@ class Patcher(object): # downloads_folder = "~/Library/Application Support/undetected_drivers" downloads_folder = download_helper.get_downloads_folder() if not os.path.exists(downloads_folder): - os.makedirs(downloads_folder) + try: + os.makedirs(downloads_folder, exist_ok=True) + except Exception: + pass # Only possible during multithreaded tests data_path = os.path.abspath(os.path.expanduser(downloads_folder)) def __init__(self, executable_path=None, force=False, version_main=0): @@ -50,7 +53,10 @@ def __init__(self, executable_path=None, force=False, version_main=0): self.executable_path = None prefix = "undetected" if not os.path.exists(self.data_path): - os.makedirs(self.data_path, exist_ok=True) + try: + os.makedirs(self.data_path, exist_ok=True) + except Exception: + pass if not executable_path: self.executable_path = os.path.join( self.data_path, "_".join([prefix, self.exe_name]) @@ -137,7 +143,10 @@ def unzip_package(self, fp): os.unlink(self.zip_path) except (FileNotFoundError, OSError): pass - os.makedirs(self.zip_path, mode=0o755, exist_ok=True) + try: + os.makedirs(self.zip_path, mode=0o755, exist_ok=True) + except Exception: + pass with zipfile.ZipFile(fp, mode="r") as zf: zf.extract(self.exe_name, self.zip_path) try: diff --git a/setup.py b/setup.py index ea7e2e2d5a9..d5165f8b520 100755 --- a/setup.py +++ b/setup.py @@ -145,7 +145,8 @@ 'attrs>=23.1.0;python_version>="3.7"', "certifi>=2023.7.22", 'filelock>=3.4.1;python_version<"3.7"', - 'filelock>=3.12.2;python_version>="3.7"', + 'filelock>=3.12.2;python_version>="3.7" and python_version<"3.8"', + 'filelock>=3.12.3;python_version>="3.8"', 'platformdirs>=2.4.0;python_version<"3.7"', 'platformdirs>=3.10.0;python_version>="3.7"', 'parse>=1.19.1', @@ -181,7 +182,8 @@ 'iniconfig==1.1.1;python_version<"3.7"', 'iniconfig==2.0.0;python_version>="3.7"', 'pluggy==1.0.0;python_version<"3.7"', - 'pluggy==1.2.0;python_version>="3.7"', + 'pluggy==1.2.0;python_version>="3.7" and python_version<"3.8"', + 'pluggy==1.3.0;python_version>="3.8"', "py==1.11.0", 'pytest==7.0.1;python_version<"3.7"', 'pytest==7.4.0;python_version>="3.7"',