Merge pull request #3452 from seleniumbase/download-browsers-and-more

Add support for downloading browsers, and more
This commit is contained in:
Michael Mintz 2025-01-24 19:40:35 -05:00 committed by GitHub
commit 36bdf1499c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 624 additions and 51 deletions

14
.gitignore vendored
View File

@ -82,6 +82,20 @@ msedgedriver.exe
operadriver.exe operadriver.exe
uc_driver.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 # msedgedriver requirements
libc++.dylib libc++.dylib

View File

@ -13,12 +13,24 @@ class ProxyTests(BaseCase):
if not self.page_load_strategy == "none" and not self.undetectable: if not self.page_load_strategy == "none" and not self.undetectable:
# This page takes too long to load otherwise # This page takes too long to load otherwise
self.get_new_driver(page_load_strategy="none") 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.open("https://ipinfo.io/")
self.wait_for_non_empty_text("form input", timeout=20) self.type('input[name="search"]', ip_address, timeout=20)
ip_address = self.get_text('#ip-string span[class*="primary"] span') 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("\n\nMy IP Address = %s\n" % ip_address)
print("Displaying Host Info:") 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") rows = text.split("\n")
data = [] data = []
for row in rows: for row in rows:
@ -26,5 +38,5 @@ class ProxyTests(BaseCase):
data.append(row.strip()) data.append(row.strip())
print("\n".join(data).replace('\n"', " ")) print("\n".join(data).replace('\n"', " "))
if not self.headless: if not self.headless:
print("\nThe browser will close automatically in 7 seconds...") print("\nThe browser will close automatically in 3 seconds...")
self.sleep(7) self.sleep(3)

View File

@ -24,6 +24,7 @@ class CDPNetworkBlockingTests(BaseCase):
]}) ]})
self.execute_cdp_cmd('Network.enable', {}) self.execute_cdp_cmd('Network.enable', {})
self.open('https://www.w3schools.com/jquery/default.asp') self.open('https://www.w3schools.com/jquery/default.asp')
self.ad_block()
source = self.get_page_source() source = self.get_page_source()
self.assert_true("doubleclick.net" not in source) self.assert_true("doubleclick.net" not in source)
self.assert_true("google-analytics.com" not in source) self.assert_true("google-analytics.com" not in source)

View File

@ -11,7 +11,7 @@ class YouTubeSearchTests(BaseCase):
self.skip("Unsupported mode for this test.") self.skip("Unsupported mode for this test.")
self.open("https://www.youtube.com/c/MichaelMintz") self.open("https://www.youtube.com/c/MichaelMintz")
search_term = "seleniumbase" search_term = "seleniumbase"
search_selector = "input#search" search_selector = 'input[name="search_query"]'
results_selector = '[role="listbox"]' results_selector = '[role="listbox"]'
self.click_if_visible('button[aria-label="Close"]') self.click_if_visible('button[aria-label="Close"]')
self.double_click(search_selector) self.double_click(search_selector)

View File

@ -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. 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.
-------- --------
<h3><img src="https://seleniumbase.github.io/img/green_logo.png" title="SeleniumBase" width="32" /> The SeleniumBase Dashboard:</h3> <h3><img src="https://seleniumbase.github.io/img/green_logo.png" title="SeleniumBase" width="32" /> The SeleniumBase Dashboard:</h3>
@ -449,6 +465,27 @@ Note that different options could lead to the same result. (Eg. If you have the
-------- --------
<h3><img src="https://seleniumbase.github.io/img/green_logo.png" title="SeleniumBase" width="32" /> Setting the binary location:</h3>
🔵 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.
--------
<h3><img src="https://seleniumbase.github.io/img/green_logo.png" title="SeleniumBase" width="32" /> Customizing default settings:</h3> <h3><img src="https://seleniumbase.github.io/img/green_logo.png" title="SeleniumBase" width="32" /> Customizing default settings:</h3>
🎛️ 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. 🎛️ 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.

View File

@ -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. 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 ```bash
seleniumbase get chromedriver 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. 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 ```bash
sbase get chromedriver 114 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 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**: **macOS shortcuts**:
* You can also install drivers by using ``brew`` (aka ``homebrew``): 🎛️ You can also install drivers by using ``brew`` (aka ``homebrew``):
```bash ```bash
brew install --cask chromedriver brew install --cask chromedriver
@ -56,7 +56,7 @@ brew install --cask chromedriver
brew install geckodriver brew install geckodriver
``` ```
You can also upgrade existing webdrivers: 🎛️ You can also upgrade existing webdrivers:
```bash ```bash
brew upgrade --cask chromedriver brew upgrade --cask chromedriver
@ -66,7 +66,7 @@ brew upgrade geckodriver
**Linux shortcuts**: **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 ```bash
wget https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip wget https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip
@ -76,12 +76,30 @@ chmod +x /usr/local/bin/chromedriver
``` ```
```bash ```bash
wget https://github.com/mozilla/geckodriver/releases/download/v0.34.0/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.34.0-linux64.tar.gz tar xvfz geckodriver-v0.35.0-linux64.tar.gz
mv geckodriver /usr/local/bin/ mv geckodriver /usr/local/bin/
chmod +x /usr/local/bin/geckodriver 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)**. 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/)
--------
[<img src="https://seleniumbase.github.io/cdn/img/sb_logo_b.png" title="SeleniumBase" width="280">](https://github.com/seleniumbase/SeleniumBase) [<img src="https://seleniumbase.github.io/cdn/img/sb_logo_b.png" title="SeleniumBase" width="280">](https://github.com/seleniumbase/SeleniumBase)

View File

@ -6,7 +6,6 @@ pymdown-extensions>=10.14.1
pipdeptree>=2.24.0 pipdeptree>=2.24.0
python-dateutil>=2.8.2 python-dateutil>=2.8.2
Markdown==3.7 Markdown==3.7
markdown2==2.5.2
click==8.1.8 click==8.1.8
ghp-import==2.1.0 ghp-import==2.1.0
watchdog==6.0.0 watchdog==6.0.0
@ -14,9 +13,6 @@ cairocffi==1.7.1
pathspec==0.12.1 pathspec==0.12.1
Babel==2.16.0 Babel==2.16.0
paginate==0.5.7 paginate==0.5.7
lxml==5.3.0
pyquery==2.0.1
readtime==3.0.0
mkdocs==1.6.1 mkdocs==1.6.1
mkdocs-material==9.5.50 mkdocs-material==9.5.50
mkdocs-exclude-search==0.6.6 mkdocs-exclude-search==0.6.6

View File

@ -43,7 +43,7 @@ trio-websocket==0.11.1
wsproto==1.2.0 wsproto==1.2.0
websocket-client==1.8.0 websocket-client==1.8.0
selenium==4.27.1;python_version<"3.9" 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 cssselect==1.2.0
sortedcontainers==2.4.0 sortedcontainers==2.4.0
execnet==2.1.1 execnet==2.1.1

View File

@ -1,2 +1,2 @@
# seleniumbase package # seleniumbase package
__version__ = "4.34.1" __version__ = "4.34.2"

View File

@ -482,8 +482,8 @@ def get_configured_sb(context):
extension_dir = sb.extension_dir # revert to default extension_dir = sb.extension_dir # revert to default
sb.extension_dir = extension_dir sb.extension_dir = extension_dir
continue continue
# Handle: -D binary-location=PATH / binary_location=PATH # Handle: -D binary-location=PATH / binary_location=PATH / bl=PATH
if low_key in ["binary-location", "binary_location"]: if low_key in ["binary-location", "binary_location", "bl"]:
binary_location = userdata[key] binary_location = userdata[key]
if binary_location == "true": if binary_location == "true":
binary_location = sb.binary_location # revert to default binary_location = sb.binary_location # revert to default
@ -884,6 +884,14 @@ def get_configured_sb(context):
sb.headless = True # Firefox has regular headless sb.headless = True # Firefox has regular headless
elif sb.browser not in ["chrome", "edge"]: elif sb.browser not in ["chrome", "edge"]:
sb.headless2 = False # Only for Chromium browsers 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. # Recorder Mode only supports Chromium browsers.
if sb.recorder_ext and (sb.browser not in ["chrome", "edge"]): if sb.recorder_ext and (sb.browser not in ["chrome", "edge"]):
raise Exception( raise Exception(

View File

@ -68,10 +68,12 @@ sbase get chromedriver 114.0.5735.90
sbase get chromedriver stable sbase get chromedriver stable
sbase get chromedriver beta sbase get chromedriver beta
sbase get chromedriver -p sbase get chromedriver -p
sbase get cft 131
sbase get chs
``` ```
(Drivers: ``chromedriver``, ``geckodriver``, ``edgedriver``, (Drivers: ``chromedriver``, ``cft``, ``uc_driver``,
``iedriver``, ``uc_driver``) ``edgedriver``, ``chs``, ``geckodriver``)
(Options: A specific driver version or major version integer. (Options: A specific driver version or major version integer.
If not set, the driver version matches the browser. If not set, the driver version matches the browser.

View File

@ -139,8 +139,8 @@ def show_install_usage():
print(" OR: seleniumbase get [DRIVER_NAME] [OPTIONS]") print(" OR: seleniumbase get [DRIVER_NAME] [OPTIONS]")
print(" OR: sbase install [DRIVER_NAME] [OPTIONS]") print(" OR: sbase install [DRIVER_NAME] [OPTIONS]")
print(" OR: sbase get [DRIVER_NAME] [OPTIONS]") print(" OR: sbase get [DRIVER_NAME] [OPTIONS]")
print(" (Drivers: chromedriver, geckodriver,") print(" (Drivers: chromedriver, cft, uc_driver,")
print(" edgedriver, iedriver, uc_driver)") print(" edgedriver, chs, geckodriver)")
print(" Options:") print(" Options:")
print(" VERSION Specify the version to download.") print(" VERSION Specify the version to download.")
print(" Tries to detect the needed version.") print(" Tries to detect the needed version.")
@ -157,11 +157,15 @@ def show_install_usage():
print(" sbase get chromedriver stable") print(" sbase get chromedriver stable")
print(" sbase get chromedriver beta") print(" sbase get chromedriver beta")
print(" sbase get chromedriver -p") print(" sbase get chromedriver -p")
print(" sbase get cft 131")
print(" sbase get chs")
print(" Output:") print(" Output:")
print(" Downloads the webdriver to seleniumbase/drivers/") print(" Downloads the webdriver to seleniumbase/drivers/")
print(" (chromedriver is required for Chrome automation)") print(" (chromedriver is required for Chrome automation)")
print(" (geckodriver is required for Firefox automation)") print(" (geckodriver is required for Firefox automation)")
print(" (edgedriver is required for MS__Edge 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("") print("")

View File

@ -3,7 +3,7 @@ Downloads the specified webdriver to "seleniumbase/drivers/"
Usage: Usage:
sbase get {chromedriver|geckodriver|edgedriver| sbase get {chromedriver|geckodriver|edgedriver|
iedriver|uc_driver} [OPTIONS] iedriver|uc_driver|cft|chs} [OPTIONS]
Options: Options:
VERSION Specify the version. VERSION Specify the version.
Tries to detect the needed version. Tries to detect the needed version.
@ -19,6 +19,8 @@ Examples:
sbase get chromedriver stable sbase get chromedriver stable
sbase get chromedriver beta sbase get chromedriver beta
sbase get chromedriver -p sbase get chromedriver -p
sbase get cft 131
sbase get chs
Output: Output:
Downloads the webdriver to seleniumbase/drivers/ Downloads the webdriver to seleniumbase/drivers/
(chromedriver is required for Chrome automation) (chromedriver is required for Chrome automation)
@ -31,6 +33,7 @@ import os
import platform import platform
import requests import requests
import shutil import shutil
import subprocess
import sys import sys
import time import time
import tarfile import tarfile
@ -61,8 +64,8 @@ def invalid_run_command():
exp += " OR sbase install [DRIVER_NAME] [OPTIONS]\n" exp += " OR sbase install [DRIVER_NAME] [OPTIONS]\n"
exp += " OR seleniumbase get [DRIVER_NAME] [OPTIONS]\n" exp += " OR seleniumbase get [DRIVER_NAME] [OPTIONS]\n"
exp += " OR sbase get [DRIVER_NAME] [OPTIONS]\n" exp += " OR sbase get [DRIVER_NAME] [OPTIONS]\n"
exp += " (Drivers: chromedriver, geckodriver,\n" exp += " (Drivers: chromedriver, cft, uc_driver,\n"
exp += " edgedriver, iedriver, uc_driver)\n" exp += " edgedriver, chs, geckodriver)\n"
exp += " Options:\n" exp += " Options:\n"
exp += " VERSION Specify the version.\n" exp += " VERSION Specify the version.\n"
exp += " Tries to detect the needed 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 stable\n"
exp += " sbase get chromedriver beta\n" exp += " sbase get chromedriver beta\n"
exp += " sbase get chromedriver -p\n" exp += " sbase get chromedriver -p\n"
exp += " sbase get cft 131\n"
exp += " sbase get chs\n"
exp += " Output:\n" exp += " Output:\n"
exp += " Downloads the webdriver to seleniumbase/drivers/\n" exp += " Downloads the webdriver to seleniumbase/drivers/\n"
exp += " (chromedriver is required for Chrome automation)\n" exp += " (chromedriver is required for Chrome automation)\n"
exp += " (geckodriver is required for Firefox automation)\n" exp += " (geckodriver is required for Firefox automation)\n"
exp += " (edgedriver is required for MS__Edge 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("") print("")
raise Exception("%s\n\n%s" % (constants.Warnings.INVALID_RUN_COMMAND, exp)) 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 "): elif override.startswith("iedriver "):
extra = override.split("iedriver ")[1] extra = override.split("iedriver ")[1]
sys.argv = ["seleniumbase", "get", "iedriver", extra] 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: if found_proxy:
sys.argv.append(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") raise Exception("Could not find chromedriver to download!\n")
if not get_latest: if not get_latest:
pass 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": elif name == "geckodriver" or name == "firefoxdriver":
use_version = DEFAULT_GECKODRIVER_VERSION use_version = DEFAULT_GECKODRIVER_VERSION
found_geckodriver = False 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/")): if os.path.exists(os.path.join(downloads_folder, "Driver_Notes/")):
# Only works if the directory is empty # Only works if the directory is empty
os.rmdir(os.path.join(downloads_folder, "Driver_Notes/")) 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( log_d(
"The file [%s] was saved to:\n%s\n" "The file [%s] was saved to:\n%s%s\n%s\n"
% (driver_file, pr_driver_path) % (driver_file, pr_driver_base, pr_sep, pr_driver_file)
) )
log_d("Making [%s %s] executable ..." % (driver_file, use_version)) log_d("Making [%s %s] executable ..." % (driver_file, use_version))
make_executable(driver_path) make_executable(driver_path)
@ -1046,6 +1195,86 @@ def main(override=None, intel_for_uc=None, force_uc=None):
make_executable(path_file) make_executable(path_file)
log_d("Also copied to: %s%s%s" % (c3, path_file, cr)) log_d("Also copied to: %s%s%s" % (c3, path_file, cr))
log_d("") 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: elif len(contents) == 0:
raise Exception("Zip file %s is empty!" % zip_file_path) raise Exception("Zip file %s is empty!" % zip_file_path)
else: else:

View File

@ -1,6 +1,7 @@
import fasteners import fasteners
import logging import logging
import os import os
import platform
import re import re
import shutil import shutil
import subprocess import subprocess
@ -64,6 +65,7 @@ LOCAL_EDGEDRIVER = None
LOCAL_IEDRIVER = None LOCAL_IEDRIVER = None
LOCAL_HEADLESS_IEDRIVER = None LOCAL_HEADLESS_IEDRIVER = None
LOCAL_UC_DRIVER = None LOCAL_UC_DRIVER = None
ARCH = platform.architecture()[0]
IS_ARM_MAC = shared_utils.is_arm_mac() IS_ARM_MAC = shared_utils.is_arm_mac()
IS_MAC = shared_utils.is_mac() IS_MAC = shared_utils.is_mac()
IS_LINUX = shared_utils.is_linux() IS_LINUX = shared_utils.is_linux()
@ -2657,6 +2659,118 @@ def get_driver(
or browser_name == constants.Browser.EDGE 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): if not os.path.exists(binary_location):
log_d( log_d(
"\nWarning: The Chromium binary specified (%s) was NOT found!" "\nWarning: The Chromium binary specified (%s) was NOT found!"
@ -2666,7 +2780,7 @@ def get_driver(
elif binary_location.endswith("/") or binary_location.endswith("\\"): elif binary_location.endswith("/") or binary_location.endswith("\\"):
log_d( log_d(
"\nWarning: The Chromium binary path must be a full path " "\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 "\n(Will use default settings...)\n" % binary_location
) )
# Example of a valid binary location path - MacOS: # Example of a valid binary location path - MacOS:
@ -2675,6 +2789,34 @@ def get_driver(
else: else:
binary_name = binary_location.split("/")[-1].split("\\")[-1] binary_name = binary_location.split("/")[-1].split("\\")[-1]
valid_names = get_valid_binary_names_for_browser(browser_name) 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: if binary_name not in valid_names:
log_d( log_d(
"\nWarning: The Chromium binary specified is NOT valid!" "\nWarning: The Chromium binary specified is NOT valid!"
@ -2683,6 +2825,8 @@ def get_driver(
"" % (binary_name, valid_names) "" % (binary_name, valid_names)
) )
binary_location = None binary_location = None
elif binary_location.lower() == "chs":
headless = True
if (uc_cdp_events or uc_subprocess) and not undetectable: if (uc_cdp_events or uc_subprocess) and not undetectable:
undetectable = True undetectable = True
if mobile_emulator and not user_agent: if mobile_emulator and not user_agent:

View File

@ -1604,7 +1604,7 @@ class CDPMethods():
"""Return True if checkbox (or radio button) is checked.""" """Return True if checkbox (or radio button) is checked."""
selector = self.__convert_to_css_if_xpath(selector) selector = self.__convert_to_css_if_xpath(selector)
self.find_element(selector, timeout=settings.SMALL_TIMEOUT) 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): def is_selected(self, selector):
selector = self.__convert_to_css_if_xpath(selector) selector = self.__convert_to_css_if_xpath(selector)

View File

@ -1,8 +1,10 @@
### <img src="https://seleniumbase.github.io/img/logo6.png" title="SeleniumBase" width="32" /> SeleniumBase webdriver storage <!-- SeleniumBase Docs -->
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. ## <img src="https://seleniumbase.github.io/img/logo6.png" title="SeleniumBase" width="32" /> 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 ```bash
seleniumbase get chromedriver seleniumbase get chromedriver
@ -10,18 +12,42 @@ seleniumbase get geckodriver
seleniumbase get edgedriver 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. 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 ```bash
sbase get chromedriver 107 sbase get chromedriver 114
sbase get chromedriver 107.0.5304.62 sbase get chromedriver 114.0.5735.90
sbase get chromedriver latest sbase get chromedriver stable
sbase get chromedriver latest-1 sbase get chromedriver beta
sbase get edgedriver 106.0.1370.42 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/)
--------
[<img src="https://seleniumbase.github.io/cdn/img/sb_logo_b.png" title="SeleniumBase" width="280">](https://github.com/seleniumbase/SeleniumBase)

View File

@ -2494,9 +2494,11 @@ class BaseCase(unittest.TestCase):
kind = self.get_attribute(selector, "type", by=by, timeout=timeout) kind = self.get_attribute(selector, "type", by=by, timeout=timeout)
if kind != "checkbox" and kind != "radio": if kind != "checkbox" and kind != "radio":
raise Exception("Expecting a checkbox or a radio button element!") raise Exception("Expecting a checkbox or a radio button element!")
return self.get_attribute( return bool(
self.get_attribute(
selector, "checked", by=by, timeout=timeout, hard_fail=False selector, "checked", by=by, timeout=timeout, hard_fail=False
) )
)
def is_selected(self, selector, by="css selector", timeout=None): def is_selected(self, selector, by="css selector", timeout=None):
"""Same as is_checked()""" """Same as is_checked()"""
@ -15166,6 +15168,9 @@ class BaseCase(unittest.TestCase):
self.driver.close() self.driver.close()
self.switch_to_window(0) self.switch_to_window(0)
if self._crumbs: if self._crumbs:
if self.binary_location == "chs":
self.delete_session_storage()
else:
self.wait_for_ready_state_complete() self.wait_for_ready_state_complete()
with suppress(Exception): with suppress(Exception):
self.driver.delete_all_cookies() self.driver.delete_all_cookies()

View File

@ -400,6 +400,7 @@ class ValidBinaries:
"google-chrome-beta", "google-chrome-beta",
"google-chrome-dev", "google-chrome-dev",
"google-chrome-unstable", "google-chrome-unstable",
"chrome-headless-shell",
"brave-browser", "brave-browser",
"brave-browser-stable", "brave-browser-stable",
"brave", "brave",
@ -418,6 +419,7 @@ class ValidBinaries:
"Google Chrome", "Google Chrome",
"Chromium", "Chromium",
"Google Chrome for Testing", "Google Chrome for Testing",
"chrome-headless-shell",
"Google Chrome Beta", "Google Chrome Beta",
"Google Chrome Dev", "Google Chrome Dev",
"Brave Browser", "Brave Browser",
@ -429,6 +431,7 @@ class ValidBinaries:
valid_chrome_binaries_on_windows = [ valid_chrome_binaries_on_windows = [
"chrome.exe", "chrome.exe",
"chromium.exe", "chromium.exe",
"chrome-headless-shell.exe",
"brave.exe", "brave.exe",
"opera.exe", "opera.exe",
] ]

View File

@ -530,6 +530,34 @@ def Driver(
break break
count += 1 count += 1
user_agent = agent 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 recorder_mode = False
if recorder_ext: if recorder_ext:
recorder_mode = True recorder_mode = True

View File

@ -656,6 +656,7 @@ def pytest_addoption(parser):
parser.addoption( parser.addoption(
"--binary_location", "--binary_location",
"--binary-location", "--binary-location",
"--bl",
action="store", action="store",
dest="binary_location", dest="binary_location",
default=None, default=None,
@ -1574,6 +1575,14 @@ def pytest_configure(config):
sb_config.extension_dir = config.getoption("extension_dir") sb_config.extension_dir = config.getoption("extension_dir")
sb_config.disable_features = config.getoption("disable_features") sb_config.disable_features = config.getoption("disable_features")
sb_config.binary_location = config.getoption("binary_location") 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.driver_version = config.getoption("driver_version")
sb_config.page_load_strategy = config.getoption("page_load_strategy") sb_config.page_load_strategy = config.getoption("page_load_strategy")
sb_config.with_testing_base = config.getoption("with_testing_base") sb_config.with_testing_base = config.getoption("with_testing_base")

View File

@ -568,6 +568,34 @@ def SB(
break break
count += 1 count += 1
user_agent = agent 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 recorder_mode = False
if recorder_ext: if recorder_ext:
recorder_mode = True recorder_mode = True

View File

@ -397,6 +397,7 @@ class SeleniumBrowser(Plugin):
parser.addoption( parser.addoption(
"--binary_location", "--binary_location",
"--binary-location", "--binary-location",
"--bl",
action="store", action="store",
dest="binary_location", dest="binary_location",
default=None, default=None,
@ -1202,6 +1203,14 @@ class SeleniumBrowser(Plugin):
test.test.extension_dir = self.options.extension_dir test.test.extension_dir = self.options.extension_dir
test.test.disable_features = self.options.disable_features test.test.disable_features = self.options.disable_features
test.test.binary_location = self.options.binary_location 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.driver_version = self.options.driver_version
test.test.page_load_strategy = self.options.page_load_strategy test.test.page_load_strategy = self.options.page_load_strategy
test.test.chromium_arg = self.options.chromium_arg test.test.chromium_arg = self.options.chromium_arg

View File

@ -192,7 +192,7 @@ setup(
'wsproto==1.2.0', 'wsproto==1.2.0',
'websocket-client==1.8.0', 'websocket-client==1.8.0',
'selenium==4.27.1;python_version<"3.9"', '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', 'cssselect==1.2.0',
"sortedcontainers==2.4.0", "sortedcontainers==2.4.0",
'execnet==2.1.1', 'execnet==2.1.1',