diff --git a/.gitignore b/.gitignore
index 86fc05b17c1..5d8549990c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,6 +82,20 @@ msedgedriver.exe
operadriver.exe
uc_driver.exe
+# Chrome for Testing folders
+chrome-mac-arm64
+chrome-mac-x64
+chrome-linux64
+chrome-win64
+chrome-win32
+
+# Chrome-Headless-Shell folders
+chrome-headless-shell-mac-arm64
+chrome-headless-shell-mac-x64
+chrome-headless-shell-linux64
+chrome-headless-shell-win64
+chrome-headless-shell-win32
+
# msedgedriver requirements
libc++.dylib
diff --git a/examples/proxy_test.py b/examples/proxy_test.py
index 25aaf05d9e3..08088617338 100644
--- a/examples/proxy_test.py
+++ b/examples/proxy_test.py
@@ -13,12 +13,24 @@ def test_proxy(self):
if not self.page_load_strategy == "none" and not self.undetectable:
# This page takes too long to load otherwise
self.get_new_driver(page_load_strategy="none")
+ self.open("https://api.ipify.org/")
+ ip_address = self.get_text("body")
self.open("https://ipinfo.io/")
- self.wait_for_non_empty_text("form input", timeout=20)
- ip_address = self.get_text('#ip-string span[class*="primary"] span')
+ self.type('input[name="search"]', ip_address, timeout=20)
+ self.click("form button span")
+ self.sleep(2)
+ self.click_if_visible("span.cursor-pointer", timeout=4)
print("\n\nMy IP Address = %s\n" % ip_address)
print("Displaying Host Info:")
- text = self.get_text("#widget-scrollable-container").split("asn:")[0]
+ text = self.get_text("#block-summary").split("Hosted domains")[0]
+ rows = text.split("\n")
+ data = []
+ for row in rows:
+ if row.strip() != "":
+ data.append(row.strip())
+ print("\n".join(data).replace('\n"', " "))
+ print("\nDisplaying Geolocation Info:")
+ text = self.get_text("#block-geolocation").split("Coordinates")[0]
rows = text.split("\n")
data = []
for row in rows:
@@ -26,5 +38,5 @@ def test_proxy(self):
data.append(row.strip())
print("\n".join(data).replace('\n"', " "))
if not self.headless:
- print("\nThe browser will close automatically in 7 seconds...")
- self.sleep(7)
+ print("\nThe browser will close automatically in 3 seconds...")
+ self.sleep(3)
diff --git a/examples/test_cdp_ad_blocking.py b/examples/test_cdp_ad_blocking.py
index 94ffd80808b..3ff4648df5e 100644
--- a/examples/test_cdp_ad_blocking.py
+++ b/examples/test_cdp_ad_blocking.py
@@ -24,6 +24,7 @@ def test_cdp_network_blocking(self):
]})
self.execute_cdp_cmd('Network.enable', {})
self.open('https://www.w3schools.com/jquery/default.asp')
+ self.ad_block()
source = self.get_page_source()
self.assert_true("doubleclick.net" not in source)
self.assert_true("google-analytics.com" not in source)
diff --git a/examples/youtube_search_test.py b/examples/youtube_search_test.py
index 96f119340f1..2e102082b5c 100644
--- a/examples/youtube_search_test.py
+++ b/examples/youtube_search_test.py
@@ -11,7 +11,7 @@ def test_youtube_autocomplete_results(self):
self.skip("Unsupported mode for this test.")
self.open("https://www.youtube.com/c/MichaelMintz")
search_term = "seleniumbase"
- search_selector = "input#search"
+ search_selector = 'input[name="search_query"]'
results_selector = '[role="listbox"]'
self.click_if_visible('button[aria-label="Close"]')
self.double_click(search_selector)
diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md
index 66afbd4af19..615960ab905 100644
--- a/help_docs/customizing_test_runs.md
+++ b/help_docs/customizing_test_runs.md
@@ -337,6 +337,22 @@ pytest --headless -n8 --dashboard --html=report.html -v --rs --crumbs
The above not only runs tests in parallel processes, but it also tells tests in the same process to share the same browser session, runs the tests in headless mode, displays the full name of each test on a separate line, creates a real-time dashboard of the test results, and creates a full report after all tests complete.
+🎛️ For extra speed, run your tests using `chrome-headless-shell`:
+
+First, get `chrome-headless-shell` if you don't already have it:
+
+```bash
+sbase get chs
+```
+
+Then, run scripts with `binary_location` / `bl` set to `"chs"`:
+
+```bash
+pytest --bl="chs" -n8 --dashboard --html=report.html -v --rs
+```
+
+That makes your tests run very quickly in headless mode.
+
--------
The SeleniumBase Dashboard:
@@ -449,6 +465,27 @@ Note that different options could lead to the same result. (Eg. If you have the
--------
+ Setting the binary location:
+
+🔵 By default, SeleniumBase uses the browser binary detected on the System PATH.
+
+🎛️ To change this default behavior, you can use:
+
+```bash
+pytest --binary-location=PATH
+```
+
+The `PATH` in `--binary-location=PATH` / `--bl=PATH` can be:
+* A relative or exact path to the browser binary.
+* `"cft"` as a special option for `Chrome for Testing`.
+* `"chs"` as a special option for `Chrome-Headless-Shell`.
+
+Before using the `"cft"` / `"chs"` options, call `sbase get cft` / `sbase get chs` in order to download the specified binaries into the `seleniumbase/drivers` folder. The default version is the latest stable version on https://googlechromelabs.github.io/chrome-for-testing/. You can change that by specifying the arg as a parameter. (Eg. `sbase get cft 131`, `sbase get chs 132`, etc.)
+
+With the `SB()` and `Driver()` formats, the binary location is set via the `binary_location` parameter.
+
+--------
+
Customizing default settings:
🎛️ An easy way to override [seleniumbase/config/settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) is by using a custom settings file.
diff --git a/help_docs/webdriver_installation.md b/help_docs/webdriver_installation.md
index f5b8dd16c2b..cbae48fa4f7 100644
--- a/help_docs/webdriver_installation.md
+++ b/help_docs/webdriver_installation.md
@@ -4,7 +4,7 @@
To run web automation, you need webdrivers for each browser you plan on using. With SeleniumBase, drivers are downloaded automatically (as needed) into the SeleniumBase `drivers/` folder.
-You can also download drivers manually with these commands:
+🎛️ You can also download drivers manually with these commands:
```bash
seleniumbase get chromedriver
@@ -16,7 +16,7 @@ After running the commands above, web drivers will get downloaded into the `sele
If the necessary driver is not found in this location while running tests, SeleniumBase will instead look for the driver on the System PATH. If the necessary driver is not on the System PATH either, SeleniumBase will automatically attempt to download the required driver.
-* You can also download specific versions of drivers. Examples:
+🎛️ You can also download specific versions of drivers. Examples:
```bash
sbase get chromedriver 114
@@ -30,7 +30,7 @@ sbase get chromedriver mlatest # Milestone latest version for detected browser
sbase get edgedriver 115.0.1901.183
```
-(NOTE: ``sbase`` is a shortcut for ``seleniumbase``)
+(NOTE: `sbase` is a shortcut for `seleniumbase`)
--------
@@ -48,7 +48,7 @@ Here's where you can go to manually get web drivers from the source:
**macOS shortcuts**:
-* You can also install drivers by using ``brew`` (aka ``homebrew``):
+🎛️ You can also install drivers by using ``brew`` (aka ``homebrew``):
```bash
brew install --cask chromedriver
@@ -56,7 +56,7 @@ brew install --cask chromedriver
brew install geckodriver
```
-You can also upgrade existing webdrivers:
+🎛️ You can also upgrade existing webdrivers:
```bash
brew upgrade --cask chromedriver
@@ -66,7 +66,7 @@ brew upgrade geckodriver
**Linux shortcuts**:
-If you still need drivers, these scripts download ``chromedriver`` and ``geckodriver`` to a Linux machine:
+🎛️ If you still need drivers, these scripts download `chromedriver` and `geckodriver` to a Linux machine:
```bash
wget https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip
@@ -76,12 +76,30 @@ chmod +x /usr/local/bin/chromedriver
```
```bash
-wget https://github.com/mozilla/geckodriver/releases/download/v0.34.0/geckodriver-v0.34.0-linux64.tar.gz
-tar xvfz geckodriver-v0.34.0-linux64.tar.gz
+wget https://github.com/mozilla/geckodriver/releases/download/v0.35.0/geckodriver-v0.35.0-linux64.tar.gz
+tar xvfz geckodriver-v0.35.0-linux64.tar.gz
mv geckodriver /usr/local/bin/
chmod +x /usr/local/bin/geckodriver
```
To verify that web drivers are working, **[follow these instructions](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/verify_webdriver.md)**.
+--------
+
+**Browser Binaries**:
+
+🎛️ Use the `sbase get` command to download the `Chrome for Testing` and `Chrome-Headless-Shell` browser binaries. Example:
+
+```bash
+sbase get cft # (For `Chrome for Testing`)
+sbase get chs # (For `Chrome-Headless-Shell`)
+```
+
+Those commands download those binaries into the `seleniumbase/drivers` folder.
+To use the binaries from there in SeleniumBase scripts, set the `binary_location` to `cft` or `chs`.
+
+(Source: https://googlechromelabs.github.io/chrome-for-testing/)
+
+--------
+
[](https://github.com/seleniumbase/SeleniumBase)
diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt
index 5104e0fa319..96a64770f55 100644
--- a/mkdocs_build/requirements.txt
+++ b/mkdocs_build/requirements.txt
@@ -6,7 +6,6 @@ pymdown-extensions>=10.14.1
pipdeptree>=2.24.0
python-dateutil>=2.8.2
Markdown==3.7
-markdown2==2.5.2
click==8.1.8
ghp-import==2.1.0
watchdog==6.0.0
@@ -14,9 +13,6 @@ cairocffi==1.7.1
pathspec==0.12.1
Babel==2.16.0
paginate==0.5.7
-lxml==5.3.0
-pyquery==2.0.1
-readtime==3.0.0
mkdocs==1.6.1
mkdocs-material==9.5.50
mkdocs-exclude-search==0.6.6
diff --git a/requirements.txt b/requirements.txt
index d3037254d8c..9d0d4f974b6 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -43,7 +43,7 @@ trio-websocket==0.11.1
wsproto==1.2.0
websocket-client==1.8.0
selenium==4.27.1;python_version<"3.9"
-selenium==4.28.0;python_version>="3.9"
+selenium==4.28.1;python_version>="3.9"
cssselect==1.2.0
sortedcontainers==2.4.0
execnet==2.1.1
diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py
index ad05ff79990..7772e500ce6 100755
--- a/seleniumbase/__version__.py
+++ b/seleniumbase/__version__.py
@@ -1,2 +1,2 @@
# seleniumbase package
-__version__ = "4.34.1"
+__version__ = "4.34.2"
diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py
index 6274432b838..ac7bd6001d3 100644
--- a/seleniumbase/behave/behave_sb.py
+++ b/seleniumbase/behave/behave_sb.py
@@ -482,8 +482,8 @@ def get_configured_sb(context):
extension_dir = sb.extension_dir # revert to default
sb.extension_dir = extension_dir
continue
- # Handle: -D binary-location=PATH / binary_location=PATH
- if low_key in ["binary-location", "binary_location"]:
+ # Handle: -D binary-location=PATH / binary_location=PATH / bl=PATH
+ if low_key in ["binary-location", "binary_location", "bl"]:
binary_location = userdata[key]
if binary_location == "true":
binary_location = sb.binary_location # revert to default
@@ -884,6 +884,14 @@ def get_configured_sb(context):
sb.headless = True # Firefox has regular headless
elif sb.browser not in ["chrome", "edge"]:
sb.headless2 = False # Only for Chromium browsers
+ if (
+ sb.binary_location
+ and sb.binary_location.lower() == "chs"
+ and sb.browser == "chrome"
+ ):
+ sb.headless = True
+ sb.headless1 = False
+ sb.headless2 = False
# Recorder Mode only supports Chromium browsers.
if sb.recorder_ext and (sb.browser not in ["chrome", "edge"]):
raise Exception(
diff --git a/seleniumbase/console_scripts/ReadMe.md b/seleniumbase/console_scripts/ReadMe.md
index 43faa057e6c..58cf7190164 100644
--- a/seleniumbase/console_scripts/ReadMe.md
+++ b/seleniumbase/console_scripts/ReadMe.md
@@ -68,10 +68,12 @@ sbase get chromedriver 114.0.5735.90
sbase get chromedriver stable
sbase get chromedriver beta
sbase get chromedriver -p
+sbase get cft 131
+sbase get chs
```
-(Drivers: ``chromedriver``, ``geckodriver``, ``edgedriver``,
- ``iedriver``, ``uc_driver``)
+(Drivers: ``chromedriver``, ``cft``, ``uc_driver``,
+ ``edgedriver``, ``chs``, ``geckodriver``)
(Options: A specific driver version or major version integer.
If not set, the driver version matches the browser.
diff --git a/seleniumbase/console_scripts/run.py b/seleniumbase/console_scripts/run.py
index 57c15cb041d..962a7ad5c03 100644
--- a/seleniumbase/console_scripts/run.py
+++ b/seleniumbase/console_scripts/run.py
@@ -139,8 +139,8 @@ def show_install_usage():
print(" OR: seleniumbase get [DRIVER_NAME] [OPTIONS]")
print(" OR: sbase install [DRIVER_NAME] [OPTIONS]")
print(" OR: sbase get [DRIVER_NAME] [OPTIONS]")
- print(" (Drivers: chromedriver, geckodriver,")
- print(" edgedriver, iedriver, uc_driver)")
+ print(" (Drivers: chromedriver, cft, uc_driver,")
+ print(" edgedriver, chs, geckodriver)")
print(" Options:")
print(" VERSION Specify the version to download.")
print(" Tries to detect the needed version.")
@@ -157,11 +157,15 @@ def show_install_usage():
print(" sbase get chromedriver stable")
print(" sbase get chromedriver beta")
print(" sbase get chromedriver -p")
+ print(" sbase get cft 131")
+ print(" sbase get chs")
print(" Output:")
print(" Downloads the webdriver to seleniumbase/drivers/")
print(" (chromedriver is required for Chrome automation)")
print(" (geckodriver is required for Firefox automation)")
print(" (edgedriver is required for MS__Edge automation)")
+ print(" (cft is for the `Chrome for Testing` binary exe)")
+ print(" (chs is for the `Chrome-Headless-Shell` binary.)")
print("")
diff --git a/seleniumbase/console_scripts/sb_install.py b/seleniumbase/console_scripts/sb_install.py
index 8d55ac87bb1..5d6de44bcc9 100644
--- a/seleniumbase/console_scripts/sb_install.py
+++ b/seleniumbase/console_scripts/sb_install.py
@@ -3,7 +3,7 @@
Usage:
sbase get {chromedriver|geckodriver|edgedriver|
- iedriver|uc_driver} [OPTIONS]
+ iedriver|uc_driver|cft|chs} [OPTIONS]
Options:
VERSION Specify the version.
Tries to detect the needed version.
@@ -19,6 +19,8 @@
sbase get chromedriver stable
sbase get chromedriver beta
sbase get chromedriver -p
+ sbase get cft 131
+ sbase get chs
Output:
Downloads the webdriver to seleniumbase/drivers/
(chromedriver is required for Chrome automation)
@@ -31,6 +33,7 @@
import platform
import requests
import shutil
+import subprocess
import sys
import time
import tarfile
@@ -61,8 +64,8 @@ def invalid_run_command():
exp += " OR sbase install [DRIVER_NAME] [OPTIONS]\n"
exp += " OR seleniumbase get [DRIVER_NAME] [OPTIONS]\n"
exp += " OR sbase get [DRIVER_NAME] [OPTIONS]\n"
- exp += " (Drivers: chromedriver, geckodriver,\n"
- exp += " edgedriver, iedriver, uc_driver)\n"
+ exp += " (Drivers: chromedriver, cft, uc_driver,\n"
+ exp += " edgedriver, chs, geckodriver)\n"
exp += " Options:\n"
exp += " VERSION Specify the version.\n"
exp += " Tries to detect the needed version.\n"
@@ -79,11 +82,15 @@ def invalid_run_command():
exp += " sbase get chromedriver stable\n"
exp += " sbase get chromedriver beta\n"
exp += " sbase get chromedriver -p\n"
+ exp += " sbase get cft 131\n"
+ exp += " sbase get chs\n"
exp += " Output:\n"
exp += " Downloads the webdriver to seleniumbase/drivers/\n"
exp += " (chromedriver is required for Chrome automation)\n"
exp += " (geckodriver is required for Firefox automation)\n"
exp += " (edgedriver is required for MS__Edge automation)\n"
+ exp += " (cft is for the `Chrome for Testing` binary exe)\n"
+ exp += " (chs is for the `Chrome-Headless-Shell` binary.)\n"
print("")
raise Exception("%s\n\n%s" % (constants.Warnings.INVALID_RUN_COMMAND, exp))
@@ -265,6 +272,16 @@ def main(override=None, intel_for_uc=None, force_uc=None):
elif override.startswith("iedriver "):
extra = override.split("iedriver ")[1]
sys.argv = ["seleniumbase", "get", "iedriver", extra]
+ elif override == "cft":
+ sys.argv = ["seleniumbase", "get", "cft"]
+ elif override.startswith("cft "):
+ extra = override.split("cft ")[1]
+ sys.argv = ["seleniumbase", "get", "cft", extra]
+ elif override == "chs":
+ sys.argv = ["seleniumbase", "get", "chs"]
+ elif override.startswith("chs "):
+ extra = override.split("chs ")[1]
+ sys.argv = ["seleniumbase", "get", "chs", extra]
if found_proxy:
sys.argv.append(found_proxy)
@@ -550,6 +567,134 @@ def main(override=None, intel_for_uc=None, force_uc=None):
raise Exception("Could not find chromedriver to download!\n")
if not get_latest:
pass
+ elif name == "chrome" or name == "cft":
+ set_version = None
+ found_version = None
+ use_version = None
+ major_version = None
+ if num_args >= 4:
+ set_version = sys.argv[3]
+ if (
+ set_version
+ and set_version.split(".")[0].isnumeric()
+ and int(set_version.split(".")[0]) >= 113
+ ):
+ major_version = set_version.split(".")[0]
+ elif (
+ not set_version
+ or set_version.lower() == "latest"
+ or set_version.lower() == "stable"
+ ):
+ found_version = get_latest_stable_chromedriver_version()
+ elif (
+ set_version and (
+ set_version.lower() == "latest-1"
+ or set_version.lower() == "previous"
+ )
+ ):
+ found_version = get_latest_stable_chromedriver_version()
+ major_version = str(int(found_version.split(".")[0]) - 1)
+ found_version = None
+ elif (set_version and set_version.lower() == "beta"):
+ found_version = get_latest_beta_chromedriver_version()
+ elif (set_version and set_version.lower() == "dev"):
+ found_version = get_latest_dev_chromedriver_version()
+ elif (set_version and set_version.lower() == "canary"):
+ found_version = get_latest_canary_chromedriver_version()
+ if found_version and found_version.split(".")[0].isnumeric():
+ major_version = found_version.split(".")[0]
+ use_version = found_version
+ if not use_version:
+ use_version = get_cft_latest_version_from_milestone(major_version)
+ msg = c2 + "Chrome for Testing to download" + cr
+ p_version = c3 + use_version + cr
+ log_d("\n*** %s = %s" % (msg, p_version))
+ if IS_MAC:
+ if IS_ARM_MAC:
+ platform_code = "mac-arm64"
+ file_name = "chrome-mac-arm64.zip"
+ else:
+ platform_code = "mac-x64"
+ file_name = "chrome-mac-x64.zip"
+ elif IS_LINUX:
+ platform_code = "linux64"
+ file_name = "chrome-linux64.zip"
+ elif IS_WINDOWS:
+ if "64" in ARCH:
+ platform_code = "win64"
+ file_name = "chrome-win64.zip"
+ else:
+ platform_code = "win32"
+ file_name = "chrome-win32.zip"
+ plat_arch = file_name.split(".zip")[0]
+ download_url = (
+ "https://storage.googleapis.com/chrome-for-testing-public/"
+ "%s/%s/%s" % (use_version, platform_code, file_name)
+ )
+ elif name == "chrome-headless-shell" or name == "chs":
+ set_version = None
+ found_version = None
+ use_version = None
+ major_version = None
+ if num_args >= 4:
+ set_version = sys.argv[3]
+ if (
+ set_version
+ and set_version.split(".")[0].isnumeric()
+ and int(set_version.split(".")[0]) >= 113
+ ):
+ major_version = set_version.split(".")[0]
+ elif (
+ not set_version
+ or set_version.lower() == "latest"
+ or set_version.lower() == "stable"
+ ):
+ found_version = get_latest_stable_chromedriver_version()
+ elif (
+ set_version and (
+ set_version.lower() == "latest-1"
+ or set_version.lower() == "previous"
+ )
+ ):
+ found_version = get_latest_stable_chromedriver_version()
+ major_version = str(int(found_version.split(".")[0]) - 1)
+ found_version = None
+ elif (set_version and set_version.lower() == "beta"):
+ found_version = get_latest_beta_chromedriver_version()
+ elif (set_version and set_version.lower() == "dev"):
+ found_version = get_latest_dev_chromedriver_version()
+ elif (set_version and set_version.lower() == "canary"):
+ found_version = get_latest_canary_chromedriver_version()
+ if found_version and found_version.split(".")[0].isnumeric():
+ major_version = found_version.split(".")[0]
+ use_version = found_version
+ if not use_version:
+ use_version = get_cft_latest_version_from_milestone(major_version)
+ msg = c2 + "Chrome-Headless-Shell to download" + cr
+ p_version = c3 + use_version + cr
+ log_d("\n*** %s = %s" % (msg, p_version))
+ if IS_MAC:
+ if IS_ARM_MAC:
+ platform_code = "mac-arm64"
+ file_name = "chrome-headless-shell-mac-arm64.zip"
+ else:
+ platform_code = "mac-x64"
+ file_name = "chrome-headless-shell-mac-x64.zip"
+ elif IS_LINUX:
+ platform_code = "linux64"
+ file_name = "chrome-headless-shell-linux64.zip"
+ elif IS_WINDOWS:
+ if "64" in ARCH:
+ platform_code = "win64"
+ file_name = "chrome-headless-shell-win64.zip"
+ else:
+ platform_code = "win32"
+ file_name = "chrome-headless-shell-win32.zip"
+ plat_arch = file_name.split(".zip")[0]
+ download_url = (
+ "https://storage.googleapis.com/chrome-for-testing-public/"
+ "%s/%s/%s" % (use_version, platform_code, file_name)
+ )
elif name == "geckodriver" or name == "firefoxdriver":
use_version = DEFAULT_GECKODRIVER_VERSION
found_geckodriver = False
@@ -1029,10 +1174,14 @@ def main(override=None, intel_for_uc=None, force_uc=None):
if os.path.exists(os.path.join(downloads_folder, "Driver_Notes/")):
# Only works if the directory is empty
os.rmdir(os.path.join(downloads_folder, "Driver_Notes/"))
- pr_driver_path = c3 + driver_path + cr
+ driver_base = os.sep.join(driver_path.split(os.sep)[:-1])
+ driver_file = driver_path.split(os.sep)[-1]
+ pr_driver_base = c3 + driver_base + cr
+ pr_sep = c3 + os.sep + cr
+ pr_driver_file = c3 + driver_file + cr
log_d(
- "The file [%s] was saved to:\n%s\n"
- % (driver_file, pr_driver_path)
+ "The file [%s] was saved to:\n%s%s\n%s\n"
+ % (driver_file, pr_driver_base, pr_sep, pr_driver_file)
)
log_d("Making [%s %s] executable ..." % (driver_file, use_version))
make_executable(driver_path)
@@ -1046,6 +1195,86 @@ def main(override=None, intel_for_uc=None, force_uc=None):
make_executable(path_file)
log_d("Also copied to: %s%s%s" % (c3, path_file, cr))
log_d("")
+ elif name == "chrome" or name == "cft":
+ # Zip file is valid. Proceed.
+ driver_path = None
+ driver_file = None
+ base_path = os.sep.join(zip_file_path.split(os.sep)[:-1])
+ folder_name = contents[0].split(os.sep)[0]
+ folder_path = os.path.join(base_path, folder_name)
+ if IS_MAC or IS_LINUX:
+ if (
+ "chrome-" in folder_path
+ and "drivers" in folder_path
+ and os.path.exists(folder_path)
+ ):
+ shutil.rmtree(folder_path)
+ subprocess.run(
+ ["unzip", zip_file_path, "-d", downloads_folder]
+ )
+ elif IS_WINDOWS:
+ subprocess.run(
+ [
+ "powershell",
+ "Expand-Archive",
+ "-Path",
+ zip_file_path,
+ "-DestinationPath",
+ downloads_folder,
+ ]
+ )
+ else:
+ zip_ref.extractall(downloads_folder)
+ zip_ref.close()
+ os.remove(zip_file_path)
+ log_d("%sUnzip Complete!%s\n" % (c2, cr))
+ pr_base_path = c3 + base_path + cr
+ pr_sep = c3 + os.sep + cr
+ pr_folder_name = c3 + folder_name + cr
+ log_d(
+ "Chrome for Testing was saved inside:\n%s%s\n%s\n"
+ % (pr_base_path, pr_sep, pr_folder_name)
+ )
+ elif name == "chrome-headless-shell" or name == "chs":
+ # Zip file is valid. Proceed.
+ driver_path = None
+ driver_file = None
+ base_path = os.sep.join(zip_file_path.split(os.sep)[:-1])
+ folder_name = contents[0].split(os.sep)[0]
+ folder_path = os.path.join(base_path, folder_name)
+ if IS_MAC or IS_LINUX:
+ if (
+ "chrome-headless-shell-" in folder_path
+ and "drivers" in folder_path
+ and os.path.exists(folder_path)
+ ):
+ shutil.rmtree(folder_path)
+ subprocess.run(
+ ["unzip", zip_file_path, "-d", downloads_folder]
+ )
+ elif IS_WINDOWS:
+ subprocess.run(
+ [
+ "powershell",
+ "Expand-Archive",
+ "-Path",
+ zip_file_path,
+ "-DestinationPath",
+ downloads_folder,
+ ]
+ )
+ else:
+ zip_ref.extractall(downloads_folder)
+ zip_ref.close()
+ os.remove(zip_file_path)
+ log_d("%sUnzip Complete!%s\n" % (c2, cr))
+ pr_base_path = c3 + base_path + cr
+ pr_sep = c3 + os.sep + cr
+ pr_folder_name = c3 + folder_name + cr
+ log_d(
+ "Chrome-Headless-Shell was saved inside:\n%s%s\n%s\n"
+ % (pr_base_path, pr_sep, pr_folder_name)
+ )
elif len(contents) == 0:
raise Exception("Zip file %s is empty!" % zip_file_path)
else:
diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py
index b91cad8530c..434275ef926 100644
--- a/seleniumbase/core/browser_launcher.py
+++ b/seleniumbase/core/browser_launcher.py
@@ -1,6 +1,7 @@
import fasteners
import logging
import os
+import platform
import re
import shutil
import subprocess
@@ -64,6 +65,7 @@
LOCAL_IEDRIVER = None
LOCAL_HEADLESS_IEDRIVER = None
LOCAL_UC_DRIVER = None
+ARCH = platform.architecture()[0]
IS_ARM_MAC = shared_utils.is_arm_mac()
IS_MAC = shared_utils.is_mac()
IS_LINUX = shared_utils.is_linux()
@@ -2657,6 +2659,118 @@ def get_driver(
or browser_name == constants.Browser.EDGE
)
):
+ if (
+ binary_location.lower() == "cft"
+ and browser_name == constants.Browser.GOOGLE_CHROME
+ ):
+ binary_folder = None
+ if IS_MAC:
+ if IS_ARM_MAC:
+ binary_folder = "chrome-mac-arm64"
+ else:
+ binary_folder = "chrome-mac-x64"
+ elif IS_LINUX:
+ binary_folder = "chrome-linux64"
+ elif IS_WINDOWS:
+ if "64" in ARCH:
+ binary_folder = "chrome-win64"
+ else:
+ binary_folder = "chrome-win32"
+ if binary_folder:
+ binary_location = os.path.join(DRIVER_DIR, binary_folder)
+ if not os.path.exists(binary_location):
+ 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
+ log_d(
+ "\nWarning: Chrome for Testing binary not found..."
+ )
+ try:
+ sb_install.main(override="cft")
+ except Exception as e:
+ log_d("\nWarning: Chrome download failed: %s" % e)
+ sys.argv = sys_args # Put back the original sys args
+ else:
+ chrome_fixing_lock = fasteners.InterProcessLock(
+ constants.MultiBrowser.DRIVER_FIXING_LOCK
+ )
+ with chrome_fixing_lock:
+ with suppress(Exception):
+ shared_utils.make_writable(
+ constants.MultiBrowser.DRIVER_FIXING_LOCK
+ )
+ if not os.path.exists(binary_location):
+ sys_args = sys.argv # Save a copy of sys args
+ log_d(
+ "\nWarning: "
+ "Chrome for Testing binary not found..."
+ )
+ sb_install.main(override="cft")
+ sys.argv = sys_args # Put back original args
+ else:
+ binary_location = None
+ if (
+ binary_location.lower() == "chs"
+ and browser_name == constants.Browser.GOOGLE_CHROME
+ ):
+ binary_folder = None
+ if IS_MAC:
+ if IS_ARM_MAC:
+ binary_folder = "chrome-headless-shell-mac-arm64"
+ else:
+ binary_folder = "chrome-headless-shell-mac-x64"
+ elif IS_LINUX:
+ binary_folder = "chrome-headless-shell-linux64"
+ elif IS_WINDOWS:
+ if "64" in ARCH:
+ binary_folder = "chrome-headless-shell-win64"
+ else:
+ binary_folder = "chrome-headless-shell-win32"
+ if binary_folder:
+ binary_location = os.path.join(DRIVER_DIR, binary_folder)
+ if not os.path.exists(binary_location):
+ 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
+ log_d(
+ "\nWarning: "
+ "Chrome-Headless-Shell binary not found..."
+ )
+ try:
+ sb_install.main(override="chs")
+ except Exception as e:
+ log_d(
+ "\nWarning: "
+ "Chrome-Headless-Shell download failed: %s" % e
+ )
+ sys.argv = sys_args # Put back the original sys args
+ else:
+ chrome_fixing_lock = fasteners.InterProcessLock(
+ constants.MultiBrowser.DRIVER_FIXING_LOCK
+ )
+ with chrome_fixing_lock:
+ with suppress(Exception):
+ shared_utils.make_writable(
+ constants.MultiBrowser.DRIVER_FIXING_LOCK
+ )
+ if not os.path.exists(binary_location):
+ sys_args = sys.argv # Save a copy of sys args
+ log_d(
+ "\nWarning: "
+ "Chrome-Headless-Shell binary not found..."
+ )
+ sb_install.main(override="chs")
+ sys.argv = sys_args # Put back original args
+ else:
+ binary_location = None
if not os.path.exists(binary_location):
log_d(
"\nWarning: The Chromium binary specified (%s) was NOT found!"
@@ -2666,7 +2780,7 @@ def get_driver(
elif binary_location.endswith("/") or binary_location.endswith("\\"):
log_d(
"\nWarning: The Chromium binary path must be a full path "
- "that includes the driver filename at the end of it!"
+ "that includes the browser filename at the end of it!"
"\n(Will use default settings...)\n" % binary_location
)
# Example of a valid binary location path - MacOS:
@@ -2675,6 +2789,34 @@ def get_driver(
else:
binary_name = binary_location.split("/")[-1].split("\\")[-1]
valid_names = get_valid_binary_names_for_browser(browser_name)
+ if binary_name == "Google Chrome for Testing.app":
+ binary_name = "Google Chrome for Testing"
+ binary_location += "/Contents/MacOS/Google Chrome for Testing"
+ elif binary_name in ["chrome-mac-arm64", "chrome-mac-x64"]:
+ binary_name = "Google Chrome for Testing"
+ binary_location += "/Google Chrome for Testing.app"
+ binary_location += "/Contents/MacOS/Google Chrome for Testing"
+ elif binary_name == "chrome-linux64":
+ binary_name = "chrome"
+ binary_location += "/chrome"
+ elif binary_name in ["chrome-win32", "chrome-win64"]:
+ binary_name = "chrome.exe"
+ binary_location += "\\chrome.exe"
+ elif binary_name in [
+ "chrome-headless-shell-mac-arm64",
+ "chrome-headless-shell-mac-x64",
+ ]:
+ binary_name = "chrome-headless-shell"
+ binary_location += "/chrome-headless-shell"
+ elif binary_name == "chrome-headless-shell-linux64":
+ binary_name = "chrome-headless-shell"
+ binary_location += "/chrome-headless-shell"
+ elif binary_name in [
+ "chrome-headless-shell-win32",
+ "chrome-headless-shell-win64",
+ ]:
+ binary_name = "chrome-headless-shell.exe"
+ binary_location += "\\chrome-headless-shell.exe"
if binary_name not in valid_names:
log_d(
"\nWarning: The Chromium binary specified is NOT valid!"
@@ -2683,6 +2825,8 @@ def get_driver(
"" % (binary_name, valid_names)
)
binary_location = None
+ elif binary_location.lower() == "chs":
+ headless = True
if (uc_cdp_events or uc_subprocess) and not undetectable:
undetectable = True
if mobile_emulator and not user_agent:
diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py
index 65a36be93e1..f35b1a3b60a 100644
--- a/seleniumbase/core/sb_cdp.py
+++ b/seleniumbase/core/sb_cdp.py
@@ -1604,7 +1604,7 @@ def is_checked(self, selector):
"""Return True if checkbox (or radio button) is checked."""
selector = self.__convert_to_css_if_xpath(selector)
self.find_element(selector, timeout=settings.SMALL_TIMEOUT)
- return self.get_element_attribute(selector, "checked")
+ return bool(self.get_element_attribute(selector, "checked"))
def is_selected(self, selector):
selector = self.__convert_to_css_if_xpath(selector)
diff --git a/seleniumbase/drivers/ReadMe.md b/seleniumbase/drivers/ReadMe.md
index 0ac22a0184e..bb699e57a98 100644
--- a/seleniumbase/drivers/ReadMe.md
+++ b/seleniumbase/drivers/ReadMe.md
@@ -1,8 +1,10 @@
-### SeleniumBase webdriver storage
+
-To run web automation, you'll need webdrivers for each browser you plan on using. With SeleniumBase, drivers are downloaded automatically as needed into the SeleniumBase ``drivers`` folder.
+## SeleniumBase driver storage
-You can also download drivers manually with these commands:
+To run web automation, you'll need webdrivers for each browser you plan on using. With SeleniumBase, drivers are downloaded automatically as needed into the SeleniumBase `drivers` folder.
+
+🎛️ You can also download drivers manually with these commands:
```bash
seleniumbase get chromedriver
@@ -10,18 +12,42 @@ seleniumbase get geckodriver
seleniumbase get edgedriver
```
-After running the commands above, web drivers will get downloaded into the ``seleniumbase/drivers/`` folder. SeleniumBase uses those drivers during tests. (The drivers don't come with SeleniumBase by default.)
+After running the commands above, web drivers will get downloaded into the `seleniumbase/drivers/` folder. SeleniumBase uses those drivers during tests. (The drivers don't come with SeleniumBase by default.)
If the necessary driver is not found in this location while running tests, SeleniumBase will instead look for the driver on the System PATH. If the necessary driver is not on the System PATH either, SeleniumBase will automatically attempt to download the required driver.
-* You can also download specific versions of drivers. Examples:
+🎛️ You can also download specific versions of drivers. Examples:
```bash
-sbase get chromedriver 107
-sbase get chromedriver 107.0.5304.62
-sbase get chromedriver latest
-sbase get chromedriver latest-1
-sbase get edgedriver 106.0.1370.42
+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
```
-(NOTE: ``sbase`` is a shortcut for ``seleniumbase``)
+(NOTE: `sbase` is a shortcut for `seleniumbase`)
+
+--------
+
+**Browser Binaries**:
+
+🎛️ Use the `sbase get` command to download the `Chrome for Testing` and `Chrome-Headless-Shell` browser binaries. Example:
+
+```bash
+sbase get cft # (For `Chrome for Testing`)
+sbase get chs # (For `Chrome-Headless-Shell`)
+```
+
+Those commands download those binaries into the `seleniumbase/drivers` folder.
+To use the binaries from there in SeleniumBase scripts, set the `binary_location` to `cft` or `chs`.
+
+(Source: https://googlechromelabs.github.io/chrome-for-testing/)
+
+--------
+
+[](https://github.com/seleniumbase/SeleniumBase)
diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py
index 7c9d659555e..81696196ce8 100644
--- a/seleniumbase/fixtures/base_case.py
+++ b/seleniumbase/fixtures/base_case.py
@@ -2494,8 +2494,10 @@ def is_checked(self, selector, by="css selector", timeout=None):
kind = self.get_attribute(selector, "type", by=by, timeout=timeout)
if kind != "checkbox" and kind != "radio":
raise Exception("Expecting a checkbox or a radio button element!")
- return self.get_attribute(
- selector, "checked", by=by, timeout=timeout, hard_fail=False
+ return bool(
+ self.get_attribute(
+ selector, "checked", by=by, timeout=timeout, hard_fail=False
+ )
)
def is_selected(self, selector, by="css selector", timeout=None):
@@ -15166,9 +15168,12 @@ def setUp(self, masterqa_mode=False):
self.driver.close()
self.switch_to_window(0)
if self._crumbs:
- self.wait_for_ready_state_complete()
- with suppress(Exception):
- self.driver.delete_all_cookies()
+ if self.binary_location == "chs":
+ self.delete_session_storage()
+ else:
+ self.wait_for_ready_state_complete()
+ with suppress(Exception):
+ self.driver.delete_all_cookies()
if self._reuse_session and sb_config.shared_driver and has_url:
good_start_page = False
if self.recorder_ext:
diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py
index 9fd3131c732..16d300a4449 100644
--- a/seleniumbase/fixtures/constants.py
+++ b/seleniumbase/fixtures/constants.py
@@ -400,6 +400,7 @@ class ValidBinaries:
"google-chrome-beta",
"google-chrome-dev",
"google-chrome-unstable",
+ "chrome-headless-shell",
"brave-browser",
"brave-browser-stable",
"brave",
@@ -418,6 +419,7 @@ class ValidBinaries:
"Google Chrome",
"Chromium",
"Google Chrome for Testing",
+ "chrome-headless-shell",
"Google Chrome Beta",
"Google Chrome Dev",
"Brave Browser",
@@ -429,6 +431,7 @@ class ValidBinaries:
valid_chrome_binaries_on_windows = [
"chrome.exe",
"chromium.exe",
+ "chrome-headless-shell.exe",
"brave.exe",
"opera.exe",
]
diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py
index 2b7928c68da..384e30d38c4 100644
--- a/seleniumbase/plugins/driver_manager.py
+++ b/seleniumbase/plugins/driver_manager.py
@@ -530,6 +530,34 @@ def Driver(
break
count += 1
user_agent = agent
+ found_bl = None
+ if binary_location is None and "--binary-location" in arg_join:
+ count = 0
+ for arg in sys_argv:
+ if arg.startswith("--binary-location="):
+ found_bl = arg.split("--binary-location=")[1]
+ break
+ elif arg == "--binary-location" and len(sys_argv) > count + 1:
+ found_bl = sys_argv[count + 1]
+ if found_bl.startswith("-"):
+ found_bl = None
+ break
+ count += 1
+ if found_bl:
+ binary_location = found_bl
+ if binary_location is None and "--bl=" in arg_join:
+ for arg in sys_argv:
+ if arg.startswith("--bl="):
+ binary_location = arg.split("--bl=")[1]
+ break
+ if (
+ binary_location
+ and binary_location.lower() == "chs"
+ and browser == "chrome"
+ ):
+ headless = True
+ headless1 = False
+ headless2 = False
recorder_mode = False
if recorder_ext:
recorder_mode = True
diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py
index e18a57360fe..4887d837248 100644
--- a/seleniumbase/plugins/pytest_plugin.py
+++ b/seleniumbase/plugins/pytest_plugin.py
@@ -656,6 +656,7 @@ def pytest_addoption(parser):
parser.addoption(
"--binary_location",
"--binary-location",
+ "--bl",
action="store",
dest="binary_location",
default=None,
@@ -1574,6 +1575,14 @@ def pytest_configure(config):
sb_config.extension_dir = config.getoption("extension_dir")
sb_config.disable_features = config.getoption("disable_features")
sb_config.binary_location = config.getoption("binary_location")
+ if (
+ sb_config.binary_location
+ and sb_config.binary_location.lower() == "chs"
+ and sb_config.browser == "chrome"
+ ):
+ sb_config.headless = True
+ sb_config.headless1 = False
+ sb_config.headless2 = False
sb_config.driver_version = config.getoption("driver_version")
sb_config.page_load_strategy = config.getoption("page_load_strategy")
sb_config.with_testing_base = config.getoption("with_testing_base")
diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py
index 96f916cf2e0..1f60832281a 100644
--- a/seleniumbase/plugins/sb_manager.py
+++ b/seleniumbase/plugins/sb_manager.py
@@ -568,6 +568,34 @@ def SB(
break
count += 1
user_agent = agent
+ found_bl = None
+ if binary_location is None and "--binary-location" in arg_join:
+ count = 0
+ for arg in sys_argv:
+ if arg.startswith("--binary-location="):
+ found_bl = arg.split("--binary-location=")[1]
+ break
+ elif arg == "--binary-location" and len(sys_argv) > count + 1:
+ found_bl = sys_argv[count + 1]
+ if found_bl.startswith("-"):
+ found_bl = None
+ break
+ count += 1
+ if found_bl:
+ binary_location = found_bl
+ if binary_location is None and "--bl=" in arg_join:
+ for arg in sys_argv:
+ if arg.startswith("--bl="):
+ binary_location = arg.split("--bl=")[1]
+ break
+ if (
+ binary_location
+ and binary_location.lower() == "chs"
+ and browser == "chrome"
+ ):
+ headless = True
+ headless1 = False
+ headless2 = False
recorder_mode = False
if recorder_ext:
recorder_mode = True
diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py
index 149b7084a87..2666e91c4a9 100644
--- a/seleniumbase/plugins/selenium_plugin.py
+++ b/seleniumbase/plugins/selenium_plugin.py
@@ -397,6 +397,7 @@ def options(self, parser, env):
parser.addoption(
"--binary_location",
"--binary-location",
+ "--bl",
action="store",
dest="binary_location",
default=None,
@@ -1202,6 +1203,14 @@ def beforeTest(self, test):
test.test.extension_dir = self.options.extension_dir
test.test.disable_features = self.options.disable_features
test.test.binary_location = self.options.binary_location
+ if (
+ test.test.binary_location
+ and test.test.binary_location.lower() == "chs"
+ and test.test.browser == "chrome"
+ ):
+ test.test.headless = True
+ test.test.headless1 = False
+ test.test.headless2 = False
test.test.driver_version = self.options.driver_version
test.test.page_load_strategy = self.options.page_load_strategy
test.test.chromium_arg = self.options.chromium_arg
diff --git a/setup.py b/setup.py
index 5b4929ca4c7..b09da705aa5 100755
--- a/setup.py
+++ b/setup.py
@@ -192,7 +192,7 @@
'wsproto==1.2.0',
'websocket-client==1.8.0',
'selenium==4.27.1;python_version<"3.9"',
- 'selenium==4.28.0;python_version>="3.9"',
+ 'selenium==4.28.1;python_version>="3.9"',
'cssselect==1.2.0',
"sortedcontainers==2.4.0",
'execnet==2.1.1',