From eff35ce3f6285bfba02da153a134a1713dde2548 Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 3 Jul 2025 18:34:16 -0400
Subject: [PATCH 1/8] Fix issue that may cause crashes in CDP Mode
---
seleniumbase/core/browser_launcher.py | 5 -----
seleniumbase/fixtures/base_case.py | 2 +-
2 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py
index d8872a8cfc7..6b5411a472d 100644
--- a/seleniumbase/core/browser_launcher.py
+++ b/seleniumbase/core/browser_launcher.py
@@ -543,11 +543,6 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
driver.connect()
current_url = driver.current_url
url_protocol = current_url.split(":")[0]
- if url_protocol not in ["about", "data", "chrome"]:
- script = 'window.open("data:,","_blank");'
- js_utils.call_me_later(driver, script, 3)
- time.sleep(0.012)
- driver.close()
driver.disconnect()
cdp_details = driver._get_cdp_details()
diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py
index 8714785b2e6..5b34af8ae71 100644
--- a/seleniumbase/fixtures/base_case.py
+++ b/seleniumbase/fixtures/base_case.py
@@ -4894,7 +4894,7 @@ def activate_cdp_mode(self, url=None, **kwargs):
self.driver.connect()
current_url = self.get_current_url()
if not current_url.startswith(("about", "data", "chrome")):
- self.get_new_driver(undetectable=True)
+ self.open("about:blank")
self.driver.uc_open_with_cdp_mode(url, **kwargs)
else:
self.get_new_driver(undetectable=True)
From d31889eb12aa4f8be0d1ed84f82eea5ad7c5cb03 Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 3 Jul 2025 19:32:34 -0400
Subject: [PATCH 2/8] Add support for multiple drivers
---
seleniumbase/console_scripts/sb_install.py | 15 +++++++++++++++
seleniumbase/core/browser_launcher.py | 16 ++++++++++++++++
seleniumbase/drivers/cft_drivers/__init__.py | 0
seleniumbase/drivers/chs_drivers/__init__.py | 0
4 files changed, 31 insertions(+)
create mode 100644 seleniumbase/drivers/cft_drivers/__init__.py
create mode 100644 seleniumbase/drivers/chs_drivers/__init__.py
diff --git a/seleniumbase/console_scripts/sb_install.py b/seleniumbase/console_scripts/sb_install.py
index 26981c81b03..3d33562acf3 100644
--- a/seleniumbase/console_scripts/sb_install.py
+++ b/seleniumbase/console_scripts/sb_install.py
@@ -44,6 +44,8 @@
from seleniumbase.fixtures import shared_utils
from seleniumbase import config as sb_config
from seleniumbase import drivers # webdriver storage folder for SeleniumBase
+from seleniumbase.drivers import cft_drivers # chrome-for-testing
+from seleniumbase.drivers import chs_drivers # chrome-headless-shell
urllib3.disable_warnings()
ARCH = platform.architecture()[0]
@@ -52,6 +54,8 @@
IS_LINUX = shared_utils.is_linux()
IS_WINDOWS = shared_utils.is_windows()
DRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))
+DRIVER_DIR_CFT = os.path.dirname(os.path.realpath(cft_drivers.__file__))
+DRIVER_DIR_CHS = os.path.dirname(os.path.realpath(chs_drivers.__file__))
LOCAL_PATH = "/usr/local/bin/" # On Mac and Linux systems
DEFAULT_CHROMEDRIVER_VERSION = "114.0.5735.90" # (If can't find LATEST_STABLE)
DEFAULT_GECKODRIVER_VERSION = "v0.36.0"
@@ -305,6 +309,17 @@ def main(override=None, intel_for_uc=None, force_uc=None):
headless_ie_exists = False
headless_ie_file_name = None
downloads_folder = DRIVER_DIR
+ if (
+ hasattr(sb_config, "settings")
+ and hasattr(sb_config.settings, "NEW_DRIVER_DIR")
+ and sb_config.settings.NEW_DRIVER_DIR
+ and os.path.exists(sb_config.settings.NEW_DRIVER_DIR)
+ ):
+ downloads_folder = sb_config.settings.NEW_DRIVER_DIR
+ elif override == "cft" or name == "cft":
+ downloads_folder = DRIVER_DIR_CFT
+ elif override == "chs" or name == "chs":
+ downloads_folder = DRIVER_DIR_CHS
expected_contents = None
platform_code = None
copy_to_path = False
diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py
index 6b5411a472d..d3d5de55077 100644
--- a/seleniumbase/core/browser_launcher.py
+++ b/seleniumbase/core/browser_launcher.py
@@ -25,6 +25,8 @@
from seleniumbase import config as sb_config
from seleniumbase import decorators
from seleniumbase import drivers # webdriver storage folder for SeleniumBase
+from seleniumbase.drivers import cft_drivers # chrome-for-testing
+from seleniumbase.drivers import chs_drivers # chrome-headless-shell
from seleniumbase import extensions # browser extensions storage folder
from seleniumbase.config import settings
from seleniumbase.core import detect_b_ver
@@ -39,6 +41,8 @@
urllib3.disable_warnings()
DRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))
+DRIVER_DIR_CFT = os.path.dirname(os.path.realpath(cft_drivers.__file__))
+DRIVER_DIR_CHS = os.path.dirname(os.path.realpath(chs_drivers.__file__))
# Make sure that the SeleniumBase DRIVER_DIR is at the top of the System PATH
# (Changes to the System PATH with os.environ only last during the test run)
if not os.environ["PATH"].startswith(DRIVER_DIR):
@@ -2833,6 +2837,18 @@ def get_driver(
device_pixel_ratio=None,
browser=None, # A duplicate of browser_name to avoid confusion
):
+ driver_dir = DRIVER_DIR
+ if sb_config.binary_location == "cft":
+ driver_dir = DRIVER_DIR_CFT
+ if sb_config.binary_location == "chs":
+ driver_dir = DRIVER_DIR_CHS
+ if (
+ hasattr(sb_config, "settings")
+ and hasattr(sb_config.settings, "NEW_DRIVER_DIR")
+ and sb_config.settings.NEW_DRIVER_DIR
+ and os.path.exists(sb_config.settings.NEW_DRIVER_DIR)
+ ):
+ driver_dir = sb_config.settings.NEW_DRIVER_DIR
if not browser_name:
if browser:
browser_name = browser
diff --git a/seleniumbase/drivers/cft_drivers/__init__.py b/seleniumbase/drivers/cft_drivers/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/seleniumbase/drivers/chs_drivers/__init__.py b/seleniumbase/drivers/chs_drivers/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
From 1d3b96e93413df4b6eb4b773696ea100a8152ca9 Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 3 Jul 2025 19:37:42 -0400
Subject: [PATCH 3/8] Update list of package folders
---
pyproject.toml | 2 ++
setup.py | 2 ++
2 files changed, 4 insertions(+)
diff --git a/pyproject.toml b/pyproject.toml
index 02bc74f6768..4c6c33d07b9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,6 +41,8 @@ packages = [
"seleniumbase.console_scripts",
"seleniumbase.core",
"seleniumbase.drivers",
+ "seleniumbase.drivers.cft_drivers",
+ "seleniumbase.drivers.chs_drivers",
"seleniumbase.extensions",
"seleniumbase.fixtures",
"seleniumbase.js_code",
diff --git a/setup.py b/setup.py
index 1931a3333d2..27acd4132eb 100755
--- a/setup.py
+++ b/setup.py
@@ -325,6 +325,8 @@
"seleniumbase.console_scripts",
"seleniumbase.core",
"seleniumbase.drivers",
+ "seleniumbase.drivers.cft_drivers",
+ "seleniumbase.drivers.chs_drivers",
"seleniumbase.extensions",
"seleniumbase.fixtures",
"seleniumbase.js_code",
From 33942097b34c464820e7bbff7a81271b26df05cf Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 3 Jul 2025 19:40:27 -0400
Subject: [PATCH 4/8] Add way to override the default drivers folder
---
seleniumbase/core/browser_launcher.py | 189 ++++++++++++++++++--------
1 file changed, 133 insertions(+), 56 deletions(-)
diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py
index d3d5de55077..71bf958cb61 100644
--- a/seleniumbase/core/browser_launcher.py
+++ b/seleniumbase/core/browser_launcher.py
@@ -104,6 +104,38 @@ def log_d(message):
print(message)
+def override_driver_dir(driver_dir):
+ if (
+ driver_dir
+ and isinstance(driver_dir, str)
+ and os.path.exists(driver_dir)
+ ):
+ driver_dir = os.path.realpath(driver_dir)
+ sb_config.settings.NEW_DRIVER_DIR = driver_dir
+ if (
+ not os.environ["PATH"].startswith(driver_dir)
+ and len(driver_dir) >= 4
+ ):
+ os.environ["PATH"] = os.environ["PATH"].replace(driver_dir, "")
+ os.environ["PATH"] = os.environ["PATH"].replace(
+ os.pathsep + os.pathsep, os.pathsep
+ )
+ os.environ["PATH"] = driver_dir + os.pathsep + os.environ["PATH"]
+ elif (
+ not driver_dir
+ or not isinstance(driver_dir, str)
+ or not os.path.exists(driver_dir)
+ ):
+ bad_dir = ""
+ if driver_dir and isinstance(driver_dir, str):
+ bad_dir = os.path.realpath(driver_dir)
+ log_d(
+ "\n* Warning: Cannot set driver_dir to nonexistent directory:\n%s"
+ "\n* Will use the default folder instead:\n%s)"
+ % (bad_dir, DRIVER_DIR)
+ )
+
+
def make_driver_executable_if_not(driver_path):
# Verify driver has executable permissions. If not, add them.
permissions = oct(os.stat(driver_path)[0])[-3:]
@@ -305,12 +337,14 @@ def chromedriver_on_path():
return None
-def get_uc_driver_version(full=False):
+def get_uc_driver_version(full=False, local_uc_driver=None):
+ if not local_uc_driver:
+ local_uc_driver = LOCAL_UC_DRIVER
uc_driver_version = None
- if os.path.exists(LOCAL_UC_DRIVER):
+ if os.path.exists(local_uc_driver):
with suppress(Exception):
output = subprocess.check_output(
- '"%s" --version' % LOCAL_UC_DRIVER, shell=True
+ '"%s" --version' % local_uc_driver, shell=True
)
if IS_WINDOWS:
output = output.decode("latin1")
@@ -791,6 +825,14 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
cdp.wait_for_element_visible = CDPM.wait_for_element_visible
cdp.wait_for_element_not_visible = CDPM.wait_for_element_not_visible
cdp.wait_for_element_absent = CDPM.wait_for_element_absent
+ cdp.wait_for_any_of_elements_visible = (
+ CDPM.wait_for_any_of_elements_visible
+ )
+ cdp.wait_for_any_of_elements_present = (
+ CDPM.wait_for_any_of_elements_present
+ )
+ cdp.assert_any_of_elements_visible = CDPM.assert_any_of_elements_visible
+ cdp.assert_any_of_elements_present = CDPM.assert_any_of_elements_present
cdp.assert_element = CDPM.assert_element
cdp.assert_element_visible = CDPM.assert_element_visible
cdp.assert_element_present = CDPM.assert_element_present
@@ -2893,7 +2935,7 @@ def get_driver(
else:
binary_folder = "chrome-win32"
if binary_folder:
- binary_location = os.path.join(DRIVER_DIR, 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)
@@ -2947,7 +2989,7 @@ def get_driver(
else:
binary_folder = "chrome-headless-shell-win32"
if binary_folder:
- binary_location = os.path.join(DRIVER_DIR, 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)
@@ -3783,6 +3825,35 @@ def get_local_driver(
"""Spins up a new web browser and returns the driver.
Can also be used to spin up additional browsers for the same test."""
downloads_path = DOWNLOADS_FOLDER
+ driver_dir = DRIVER_DIR
+ special_chrome = False
+ if sb_config.binary_location == "cft":
+ special_chrome = True
+ driver_dir = DRIVER_DIR_CFT
+ if sb_config.binary_location == "chs":
+ special_chrome = True
+ driver_dir = DRIVER_DIR_CHS
+ if (
+ hasattr(sb_config, "settings")
+ and hasattr(sb_config.settings, "NEW_DRIVER_DIR")
+ and sb_config.settings.NEW_DRIVER_DIR
+ and os.path.exists(sb_config.settings.NEW_DRIVER_DIR)
+ ):
+ driver_dir = sb_config.settings.NEW_DRIVER_DIR
+ elif special_chrome:
+ override_driver_dir(driver_dir)
+ if IS_MAC or IS_LINUX:
+ local_chromedriver = driver_dir + "/chromedriver"
+ local_geckodriver = driver_dir + "/geckodriver"
+ local_edgedriver = driver_dir + "/msedgedriver"
+ local_uc_driver = driver_dir + "/uc_driver"
+ elif IS_WINDOWS:
+ local_edgedriver = driver_dir + "/msedgedriver.exe"
+ local_iedriver = driver_dir + "/IEDriverServer.exe"
+ local_headless_iedriver = driver_dir + "/headless_ie_selenium.exe"
+ local_chromedriver = driver_dir + "/chromedriver.exe"
+ local_geckodriver = driver_dir + "/geckodriver.exe"
+ local_uc_driver = driver_dir + "/uc_driver.exe"
b_path = binary_location
use_uc = is_using_uc(undetectable, browser_name)
if use_wire:
@@ -3830,9 +3901,9 @@ def get_local_driver(
firefox_pref,
external_pdf,
)
- if LOCAL_GECKODRIVER and os.path.exists(LOCAL_GECKODRIVER):
+ if local_geckodriver and os.path.exists(local_geckodriver):
try:
- make_driver_executable_if_not(LOCAL_GECKODRIVER)
+ make_driver_executable_if_not(local_geckodriver)
except Exception as e:
logging.debug(
"\nWarning: Could not make geckodriver"
@@ -3868,9 +3939,9 @@ def get_local_driver(
sb_install.main(override="geckodriver")
sys.argv = sys_args # Put back original sys args
# Launch Firefox
- if os.path.exists(LOCAL_GECKODRIVER):
+ if os.path.exists(local_geckodriver):
service = FirefoxService(
- executable_path=LOCAL_GECKODRIVER,
+ executable_path=local_geckodriver,
log_output=os.devnull,
)
try:
@@ -3971,9 +4042,9 @@ def get_local_driver(
ie_options.native_events = True
ie_options.full_page_screenshot = True
ie_options.persistent_hover = True
- if LOCAL_IEDRIVER and os.path.exists(LOCAL_IEDRIVER):
+ if local_iedriver and os.path.exists(local_iedriver):
try:
- make_driver_executable_if_not(LOCAL_IEDRIVER)
+ make_driver_executable_if_not(local_iedriver)
except Exception as e:
logging.debug(
"\nWarning: Could not make IEDriver executable: %s" % e
@@ -3987,9 +4058,9 @@ def get_local_driver(
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):
+ if local_headless_iedriver and os.path.exists(local_headless_iedriver):
try:
- make_driver_executable_if_not(LOCAL_HEADLESS_IEDRIVER)
+ make_driver_executable_if_not(local_headless_iedriver)
except Exception as e:
logging.debug(
"\nWarning: Could not make HeadlessIEDriver executable: %s"
@@ -4015,7 +4086,7 @@ def get_local_driver(
else:
warnings.simplefilter("ignore", category=DeprecationWarning)
service = Service(
- executable_path=LOCAL_IEDRIVER,
+ executable_path=local_iedriver,
service_args=[d_b_c],
log_output=os.devnull,
)
@@ -4098,10 +4169,10 @@ def get_local_driver(
use_version = major_edge_version
edge_driver_version = None
edgedriver_upgrade_needed = False
- if os.path.exists(LOCAL_EDGEDRIVER):
+ if os.path.exists(local_edgedriver):
with suppress(Exception):
output = subprocess.check_output(
- '"%s" --version' % LOCAL_EDGEDRIVER, shell=True
+ '"%s" --version' % local_edgedriver, shell=True
)
if IS_WINDOWS:
output = output.decode("latin1")
@@ -4129,7 +4200,7 @@ def get_local_driver(
use_version, driver_version
)
local_edgedriver_exists = False
- if LOCAL_EDGEDRIVER and os.path.exists(LOCAL_EDGEDRIVER):
+ if local_edgedriver and os.path.exists(local_edgedriver):
local_edgedriver_exists = True
if (
use_version != "latest"
@@ -4139,7 +4210,7 @@ def get_local_driver(
edgedriver_upgrade_needed = True
else:
try:
- make_driver_executable_if_not(LOCAL_EDGEDRIVER)
+ make_driver_executable_if_not(local_edgedriver)
except Exception as e:
logging.debug(
"\nWarning: Could not make edgedriver"
@@ -4177,9 +4248,9 @@ def get_local_driver(
# For Microsoft Edge (Chromium) version 80 or higher
Edge = webdriver.edge.webdriver.WebDriver
EdgeOptions = webdriver.edge.webdriver.Options
- if LOCAL_EDGEDRIVER and os.path.exists(LOCAL_EDGEDRIVER):
+ if local_edgedriver and os.path.exists(local_edgedriver):
try:
- make_driver_executable_if_not(LOCAL_EDGEDRIVER)
+ make_driver_executable_if_not(local_edgedriver)
except Exception as e:
logging.debug(
"\nWarning: Could not make edgedriver"
@@ -4510,7 +4581,7 @@ def get_local_driver(
if binary_location:
edge_options.binary_location = binary_location
service = EdgeService(
- executable_path=LOCAL_EDGEDRIVER,
+ executable_path=local_edgedriver,
log_output=os.devnull,
service_args=["--disable-build-check"],
)
@@ -4722,10 +4793,10 @@ def get_local_driver(
use_version = major_chrome_version
ch_driver_version = None
path_chromedriver = chromedriver_on_path()
- if os.path.exists(LOCAL_CHROMEDRIVER):
+ if os.path.exists(local_chromedriver):
with suppress(Exception):
output = subprocess.check_output(
- '"%s" --version' % LOCAL_CHROMEDRIVER, shell=True
+ '"%s" --version' % local_chromedriver, shell=True
)
if IS_WINDOWS:
output = output.decode("latin1")
@@ -4763,10 +4834,14 @@ def get_local_driver(
uc_driver_version = None
if use_uc:
if use_br_version_for_uc or driver_version == "mlatest":
- uc_driver_version = get_uc_driver_version(full=True)
+ uc_driver_version = get_uc_driver_version(
+ full=True, local_uc_driver=local_uc_driver
+ )
full_ch_driver_version = uc_driver_version
else:
- uc_driver_version = get_uc_driver_version()
+ uc_driver_version = get_uc_driver_version(
+ local_uc_driver=local_uc_driver
+ )
if multi_proxy:
sb_config.multi_proxy = True
if uc_driver_version and driver_version == "keep":
@@ -4814,9 +4889,9 @@ def get_local_driver(
chrome_options.add_argument("--headless=old")
else:
chrome_options.add_argument("--headless")
- if LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER):
+ if local_chromedriver and os.path.exists(local_chromedriver):
try:
- make_driver_executable_if_not(LOCAL_CHROMEDRIVER)
+ make_driver_executable_if_not(local_chromedriver)
except Exception as e:
logging.debug(
"\nWarning: Could not make chromedriver"
@@ -4824,9 +4899,9 @@ def get_local_driver(
)
make_uc_driver_from_chromedriver = False
local_ch_exists = (
- LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER)
+ local_chromedriver and os.path.exists(local_chromedriver)
)
- """If no LOCAL_CHROMEDRIVER, but path_chromedriver, and the
+ """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.*"""
@@ -4854,20 +4929,20 @@ def get_local_driver(
(local_ch_exists or path_chromedriver)
and use_version == ch_driver_version
and (
- not os.path.exists(LOCAL_UC_DRIVER)
+ 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)
+ and not os.path.exists(local_uc_driver)
)
)
):
make_uc_driver_from_chromedriver = True
elif (
- (use_uc and not os.path.exists(LOCAL_UC_DRIVER))
+ (use_uc and not os.path.exists(local_uc_driver))
or (not use_uc and not path_chromedriver)
or (
not use_uc
@@ -4904,9 +4979,9 @@ 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):
+ 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):
+ 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
@@ -4955,9 +5030,9 @@ 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):
+ 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):
+ 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
@@ -4965,10 +5040,10 @@ def get_local_driver(
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):
+ if os.path.exists(local_chromedriver):
with suppress(Exception):
output = subprocess.check_output(
- '"%s" --version' % LOCAL_CHROMEDRIVER,
+ '"%s" --version' % local_chromedriver,
shell=True,
)
if IS_WINDOWS:
@@ -4982,9 +5057,9 @@ def get_local_driver(
if (
(
not use_uc
- and not os.path.exists(LOCAL_CHROMEDRIVER)
+ and not os.path.exists(local_chromedriver)
)
- or (use_uc and not os.path.exists(LOCAL_UC_DRIVER))
+ or (use_uc and not os.path.exists(local_uc_driver))
or (
not use_uc
and (
@@ -4996,7 +5071,9 @@ def get_local_driver(
use_uc
and (
use_version.split(".")[0]
- != get_uc_driver_version()
+ != get_uc_driver_version(
+ local_uc_driver=local_uc_driver
+ )
)
)
):
@@ -5048,20 +5125,20 @@ def get_local_driver(
constants.MultiBrowser.DRIVER_FIXING_LOCK
)
if make_uc_driver_from_chromedriver:
- if os.path.exists(LOCAL_CHROMEDRIVER):
+ if os.path.exists(local_chromedriver):
with suppress(Exception):
make_driver_executable_if_not(
- LOCAL_CHROMEDRIVER
+ local_chromedriver
)
- shutil.copy2(LOCAL_CHROMEDRIVER, LOCAL_UC_DRIVER)
+ shutil.copy2(local_chromedriver, local_uc_driver)
elif os.path.exists(path_chromedriver):
with suppress(Exception):
make_driver_executable_if_not(
path_chromedriver
)
- shutil.copy2(path_chromedriver, LOCAL_UC_DRIVER)
+ shutil.copy2(path_chromedriver, local_uc_driver)
try:
- make_driver_executable_if_not(LOCAL_UC_DRIVER)
+ make_driver_executable_if_not(local_uc_driver)
except Exception as e:
logging.debug(
"\nWarning: Could not make uc_driver"
@@ -5070,7 +5147,7 @@ def get_local_driver(
if not headless or not IS_LINUX or use_uc:
uc_activated = False
try:
- if os.path.exists(LOCAL_CHROMEDRIVER) or use_uc:
+ if os.path.exists(local_chromedriver) or use_uc:
if headless and not IS_LINUX:
undetectable = False # No support for headless
use_uc = is_using_uc(undetectable, browser_name)
@@ -5215,9 +5292,9 @@ def get_local_driver(
force_uc=False,
)
d_b_c = "--disable-build-check"
- if os.path.exists(LOCAL_CHROMEDRIVER):
+ if os.path.exists(local_chromedriver):
service = ChromeService(
- executable_path=LOCAL_CHROMEDRIVER,
+ executable_path=local_chromedriver,
log_output=os.devnull,
service_args=[d_b_c],
)
@@ -5258,8 +5335,8 @@ def get_local_driver(
sb_config.uc_agent_cache = user_agent
driver.quit()
uc_path = None
- if os.path.exists(LOCAL_UC_DRIVER):
- uc_path = LOCAL_UC_DRIVER
+ if os.path.exists(local_uc_driver):
+ uc_path = local_uc_driver
uc_path = os.path.realpath(uc_path)
try:
driver = undetected.Chrome(
@@ -5337,7 +5414,7 @@ def get_local_driver(
"w3c", True
)
service = ChromeService(
- executable_path=LOCAL_CHROMEDRIVER,
+ executable_path=local_chromedriver,
log_output=os.devnull,
service_args=service_args,
)
@@ -5472,9 +5549,9 @@ def get_local_driver(
chrome_options, headless_options, mcv
)
_mark_driver_repaired()
- if os.path.exists(LOCAL_CHROMEDRIVER):
+ if os.path.exists(local_chromedriver):
service = ChromeService(
- executable_path=LOCAL_CHROMEDRIVER,
+ executable_path=local_chromedriver,
log_output=os.devnull,
service_args=["--disable-build-check"],
)
@@ -5751,9 +5828,9 @@ def get_local_driver(
elif headless or headless2 or IS_LINUX or proxy_string or use_wire:
raise
# Try running without any options (bare bones Chrome launch)
- if LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER):
+ if local_chromedriver and os.path.exists(local_chromedriver):
try:
- make_driver_executable_if_not(LOCAL_CHROMEDRIVER)
+ make_driver_executable_if_not(local_chromedriver)
except Exception as e:
logging.debug(
"\nWarning: Could not make chromedriver"
From 378aad351cf91251651992f1143411d87e0fae7a Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 3 Jul 2025 19:43:47 -0400
Subject: [PATCH 5/8] Add methods to assist with A/B testing
---
examples/cdp_mode/ReadMe.md | 4 +
help_docs/method_summary.md | 7 ++
seleniumbase/core/sb_cdp.py | 144 +++++++++++++++++++++++
seleniumbase/fixtures/base_case.py | 162 +++++++++++++++++++++++++-
seleniumbase/fixtures/page_actions.py | 149 +++++++++++++++++++++++
5 files changed, 464 insertions(+), 2 deletions(-)
diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md
index 9ada1c2b3af..574bb0769e6 100644
--- a/examples/cdp_mode/ReadMe.md
+++ b/examples/cdp_mode/ReadMe.md
@@ -484,6 +484,10 @@ sb.cdp.wait_for_text_not_visible(text, selector="body", timeout=None)
sb.cdp.wait_for_element_visible(selector, timeout=None)
sb.cdp.wait_for_element_not_visible(selector, timeout=None)
sb.cdp.wait_for_element_absent(selector, timeout=None)
+sb.cdp.wait_for_any_of_elements_visible(*args, **kwargs)
+sb.cdp.wait_for_any_of_elements_present(*args, **kwargs)
+sb.cdp.assert_any_of_elements_visible(*args, **kwargs)
+sb.cdp.assert_any_of_elements_present(*args, **kwargs)
sb.cdp.assert_element(selector, timeout=None)
sb.cdp.assert_element_visible(selector, timeout=None)
sb.cdp.assert_element_present(selector, timeout=None)
diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md
index 3ae410edfe1..8d1a7fc5e90 100644
--- a/help_docs/method_summary.md
+++ b/help_docs/method_summary.md
@@ -544,6 +544,13 @@ self.assert_elements(*args, **kwargs)
############
+self.wait_for_any_of_elements_visible(*args, **kwargs)
+self.wait_for_any_of_elements_present(*args, **kwargs)
+self.assert_any_of_elements_visible(*args, **kwargs)
+self.assert_any_of_elements_present(*args, **kwargs)
+
+############
+
self.find_text(text, selector="html", by="css selector", timeout=None)
# Duplicates:
# self.wait_for_text(text, selector="html", by="css selector", timeout=None)
diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py
index dbace979441..d4ccaac3b0d 100644
--- a/seleniumbase/core/sb_cdp.py
+++ b/seleniumbase/core/sb_cdp.py
@@ -2014,6 +2014,150 @@ def wait_for_element_absent(self, selector, timeout=None):
% (selector, timeout, plural)
)
+ def wait_for_any_of_elements_visible(self, *args, **kwargs):
+ """Waits for at least one of the elements to be visible.
+ Returns the first element that is found.
+ The input is a list of elements. (Should be CSS selectors)
+ Optional kwargs include: "timeout" (used by all selectors).
+ Raises an exception if no elements are visible by the timeout.
+ Examples:
+ sb.cdp.wait_for_any_of_elements_visible("h1", "h2", "h3")
+ OR
+ sb.cdp.wait_for_any_of_elements_visible(["h1", "h2", "h3"]) """
+ selectors = []
+ timeout = None
+ for kwarg in kwargs:
+ if kwarg == "timeout":
+ timeout = kwargs["timeout"]
+ elif kwarg == "by":
+ pass # Autodetected
+ elif kwarg == "selector" or kwarg == "selectors":
+ selector = kwargs[kwarg]
+ if isinstance(selector, str):
+ selectors.append(selector)
+ elif isinstance(selector, list):
+ selectors_list = selector
+ for selector in selectors_list:
+ if isinstance(selector, str):
+ selectors.append(selector)
+ else:
+ raise Exception('Unknown kwarg: "%s"!' % kwarg)
+ if not timeout:
+ timeout = settings.SMALL_TIMEOUT
+ for arg in args:
+ if isinstance(arg, list):
+ for selector in arg:
+ if isinstance(selector, str):
+ selectors.append(selector)
+ elif isinstance(arg, str):
+ selectors.append(arg)
+ if not selectors:
+ raise Exception("The selectors list was empty!")
+ start_ms = time.time() * 1000.0
+ stop_ms = start_ms + (timeout * 1000.0)
+ any_present = False
+ for i in range(int(timeout * 10)):
+ for selector in selectors:
+ if self.is_element_visible(selector):
+ return self.select(selector)
+ if self.is_element_present(selector):
+ any_present = True
+ now_ms = time.time() * 1000.0
+ if now_ms >= stop_ms:
+ break
+ time.sleep(0.1)
+ plural = "s"
+ if timeout == 1:
+ plural = ""
+ if not any_present:
+ # None of the elements exist in the HTML
+ raise Exception(
+ "None of the elements {%s} were present after %s second%s!" % (
+ str(selectors),
+ timeout,
+ plural,
+ )
+ )
+ raise Exception(
+ "None of the elements %s were visible after %s second%s!" % (
+ str(selectors),
+ timeout,
+ plural,
+ )
+ )
+
+ def wait_for_any_of_elements_present(self, *args, **kwargs):
+ """Waits for at least one of the elements to be present.
+ Visibility not required, but element must be in the DOM.
+ Returns the first element that is found.
+ The input is a list of elements. (Should be CSS selectors)
+ Optional kwargs include: "timeout" (used by all selectors).
+ Raises an exception if no elements are present by the timeout.
+ Examples:
+ self.wait_for_any_of_elements_present("style", "script")
+ OR
+ self.wait_for_any_of_elements_present(["style", "script"]) """
+ selectors = []
+ timeout = None
+ for kwarg in kwargs:
+ if kwarg == "timeout":
+ timeout = kwargs["timeout"]
+ elif kwarg == "by":
+ pass # Autodetected
+ elif kwarg == "selector" or kwarg == "selectors":
+ selector = kwargs[kwarg]
+ if isinstance(selector, str):
+ selectors.append(selector)
+ elif isinstance(selector, list):
+ selectors_list = selector
+ for selector in selectors_list:
+ if isinstance(selector, str):
+ selectors.append(selector)
+ else:
+ raise Exception('Unknown kwarg: "%s"!' % kwarg)
+ if not timeout:
+ timeout = settings.SMALL_TIMEOUT
+ for arg in args:
+ if isinstance(arg, list):
+ for selector in arg:
+ if isinstance(selector, str):
+ selectors.append(selector)
+ elif isinstance(arg, str):
+ selectors.append(arg)
+ if not selectors:
+ raise Exception("The selectors list was empty!")
+ start_ms = time.time() * 1000.0
+ stop_ms = start_ms + (timeout * 1000.0)
+ for i in range(int(timeout * 10)):
+ for selector in selectors:
+ if self.is_element_present(selector):
+ return self.select(selector)
+ now_ms = time.time() * 1000.0
+ if now_ms >= stop_ms:
+ break
+ time.sleep(0.1)
+ plural = "s"
+ if timeout == 1:
+ plural = ""
+ # None of the elements exist in the HTML
+ raise Exception(
+ "None of the elements %s were present after %s second%s!" % (
+ str(selectors),
+ timeout,
+ plural,
+ )
+ )
+
+ def assert_any_of_elements_visible(self, *args, **kwargs):
+ """Like wait_for_any_of_elements_visible(), but returns nothing."""
+ self.wait_for_any_of_elements_visible(*args, **kwargs)
+ return True
+
+ def assert_any_of_elements_present(self, *args, **kwargs):
+ """Like wait_for_any_of_elements_present(), but returns nothing."""
+ self.wait_for_any_of_elements_present(*args, **kwargs)
+ return True
+
def assert_element(self, selector, timeout=None):
"""Same as assert_element_visible()"""
self.assert_element_visible(selector, timeout=timeout)
diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py
index 5b34af8ae71..2067afa0d33 100644
--- a/seleniumbase/fixtures/base_case.py
+++ b/seleniumbase/fixtures/base_case.py
@@ -9227,6 +9227,162 @@ def wait_for_element_not_present(
original_selector=original_selector,
)
+ def wait_for_any_of_elements_visible(self, *args, **kwargs):
+ """Waits for at least one of the elements to be visible.
+ Returns the first element that is found.
+ The input is a list of elements. (Should be CSS selectors or XPath)
+ Optional kwargs include: "timeout" (used by all selectors).
+ Raises an exception if no elements are visible by the timeout.
+ Allows flexible inputs (Eg. Multiple args or a list of args)
+ Examples:
+ self.wait_for_any_of_elements_visible("h1", "h2", "h3")
+ OR
+ self.wait_for_any_of_elements_visible(["h1", "h2", "h3"]) """
+ self.__check_scope()
+ selectors = []
+ timeout = None
+ for kwarg in kwargs:
+ if kwarg == "timeout":
+ timeout = kwargs["timeout"]
+ elif kwarg == "by":
+ pass # Autodetected
+ elif kwarg == "selector" or kwarg == "selectors":
+ selector = kwargs[kwarg]
+ if isinstance(selector, str):
+ selectors.append(selector)
+ elif isinstance(selector, list):
+ selectors_list = selector
+ for selector in selectors_list:
+ if isinstance(selector, str):
+ selectors.append(selector)
+ else:
+ raise Exception('Unknown kwarg: "%s"!' % kwarg)
+ if not timeout:
+ timeout = settings.LARGE_TIMEOUT
+ if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
+ timeout = self.__get_new_timeout(timeout)
+ for arg in args:
+ if isinstance(arg, list):
+ for selector in arg:
+ if isinstance(selector, str):
+ selectors.append(selector)
+ elif isinstance(arg, str):
+ selectors.append(arg)
+ if not selectors:
+ raise Exception("The selectors list was empty!")
+ original_selectors = selectors
+ updated_selectors = []
+ for selector in selectors:
+ by = "css selector"
+ if page_utils.is_xpath_selector(selector):
+ by = "xpath"
+ selector, by = self.__recalculate_selector(selector, by)
+ updated_selectors.append(selector)
+ selectors = updated_selectors
+ if self.__is_cdp_swap_needed():
+ return self.cdp.wait_for_any_of_elements_visible(
+ selectors, timeout=timeout
+ )
+ return page_actions.wait_for_any_of_elements_visible(
+ self.driver,
+ selectors,
+ timeout=timeout,
+ original_selectors=original_selectors,
+ )
+
+ def wait_for_any_of_elements_present(self, *args, **kwargs):
+ """Waits for at least one of the elements to be present.
+ Visibility not required, but element must be in the DOM.
+ Returns the first element that is found.
+ The input is a list of elements. (Should be CSS selectors or XPath)
+ Optional kwargs include: "timeout" (used by all selectors).
+ Raises an exception if no elements are present by the timeout.
+ Allows flexible inputs (Eg. Multiple args or a list of args)
+ Examples:
+ self.wait_for_any_of_elements_present("style", "script")
+ OR
+ self.wait_for_any_of_elements_present(["style", "script"]) """
+ self.__check_scope()
+ selectors = []
+ timeout = None
+ for kwarg in kwargs:
+ if kwarg == "timeout":
+ timeout = kwargs["timeout"]
+ elif kwarg == "by":
+ pass # Autodetected
+ elif kwarg == "selector" or kwarg == "selectors":
+ selector = kwargs[kwarg]
+ if isinstance(selector, str):
+ selectors.append(selector)
+ elif isinstance(selector, list):
+ selectors_list = selector
+ for selector in selectors_list:
+ if isinstance(selector, str):
+ selectors.append(selector)
+ else:
+ raise Exception('Unknown kwarg: "%s"!' % kwarg)
+ if not timeout:
+ timeout = settings.LARGE_TIMEOUT
+ if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
+ timeout = self.__get_new_timeout(timeout)
+ for arg in args:
+ if isinstance(arg, list):
+ for selector in arg:
+ if isinstance(selector, str):
+ selectors.append(selector)
+ elif isinstance(arg, str):
+ selectors.append(arg)
+ if not selectors:
+ raise Exception("The selectors list was empty!")
+ original_selectors = selectors
+ updated_selectors = []
+ for selector in selectors:
+ by = "css selector"
+ if page_utils.is_xpath_selector(selector):
+ by = "xpath"
+ selector, by = self.__recalculate_selector(selector, by)
+ updated_selectors.append(selector)
+ selectors = updated_selectors
+ if self.__is_cdp_swap_needed():
+ return self.cdp.wait_for_any_of_elements_present(
+ selectors, timeout=timeout
+ )
+ return page_actions.wait_for_any_of_elements_present(
+ self.driver,
+ selectors,
+ timeout=timeout,
+ original_selectors=original_selectors,
+ )
+
+ def assert_any_of_elements_visible(self, *args, **kwargs):
+ """Similar to wait_for_any_of_elements_visible(), but returns nothing.
+ As above, raises an exception if none of the set elements are visible.
+ Returns True if successful. Default timeout = SMALL_TIMEOUT.
+ Allows flexible inputs (Eg. Multiple args or a list of args)
+ Examples:
+ self.assert_any_of_elements_visible("h1", "h2", "h3")
+ OR
+ self.assert_any_of_elements_visible(["h1", "h2", "h3"]) """
+ if "timeout" not in kwargs:
+ kwargs["timeout"] = settings.SMALL_TIMEOUT
+ self.wait_for_any_of_elements_visible(*args, **kwargs)
+ return True
+
+ def assert_any_of_elements_present(self, *args, **kwargs):
+ """Similar to wait_for_any_of_elements_present(), but returns nothing.
+ As above, raises an exception if none of the given elements are found.
+ Visibility is not required, but element must exist in the DOM.
+ Returns True if successful. Default timeout = SMALL_TIMEOUT.
+ Allows flexible inputs (Eg. Multiple args or a list of args)
+ Examples:
+ self.assert_any_of_elements_present("h1", "h2", "h3")
+ OR
+ self.assert_any_of_elements_present(["h1", "h2", "h3"]) """
+ if "timeout" not in kwargs:
+ kwargs["timeout"] = settings.SMALL_TIMEOUT
+ self.wait_for_any_of_elements_present(*args, **kwargs)
+ return True
+
def select_all(self, selector, by="css selector", limit=0):
return self.find_elements(selector, by=by, limit=limit)
@@ -9698,6 +9854,7 @@ def assert_elements_present(self, *args, **kwargs):
The input is a list of elements.
Optional kwargs include "by" and "timeout" (used by all selectors).
Raises an exception if any of the elements are not visible.
+ Allows flexible inputs (Eg. Multiple args or a list of args)
Examples:
self.assert_elements_present("head", "style", "script", "body")
OR
@@ -9711,7 +9868,7 @@ def assert_elements_present(self, *args, **kwargs):
timeout = kwargs["timeout"]
elif kwarg == "by":
by = kwargs["by"]
- elif kwarg == "selector":
+ elif kwarg == "selector" or kwarg == "selectors":
selector = kwargs["selector"]
if isinstance(selector, str):
selectors.append(selector)
@@ -9804,6 +9961,7 @@ def assert_elements(self, *args, **kwargs):
The input is a list of elements.
Optional kwargs include "by" and "timeout" (used by all selectors).
Raises an exception if any of the elements are not visible.
+ Allows flexible inputs (Eg. Multiple args or a list of args)
Examples:
self.assert_elements("h1", "h2", "h3")
OR
@@ -9817,7 +9975,7 @@ def assert_elements(self, *args, **kwargs):
timeout = kwargs["timeout"]
elif kwarg == "by":
by = kwargs["by"]
- elif kwarg == "selector":
+ elif kwarg == "selector" or kwarg == "selectors":
selector = kwargs["selector"]
if isinstance(selector, str):
selectors.append(selector)
diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py
index bb5bf055f0b..ac84428d9be 100644
--- a/seleniumbase/fixtures/page_actions.py
+++ b/seleniumbase/fixtures/page_actions.py
@@ -751,6 +751,155 @@ def wait_for_exact_text_visible(
return element
+def wait_for_any_of_elements_visible(
+ driver,
+ selectors,
+ timeout=settings.LARGE_TIMEOUT,
+ original_selectors=[],
+ ignore_test_time_limit=False,
+):
+ """
+ Waits for at least one of the elements in the selector list to be visible.
+ Returns the first element that is found.
+ Raises NoSuchElementException if none of the elements exist in the HTML
+ within the specified timeout.
+ Raises ElementNotVisibleException if the element exists in the HTML,
+ but is not visible (eg. opacity is "0") within the specified timeout.
+ @Params
+ driver - the webdriver object (required)
+ selectors - the list of selectors for identifying page elements (required)
+ timeout - the time to wait for elements in seconds
+ original_selectors - handle pre-converted ":contains(TEXT)" selectors
+ ignore_test_time_limit - ignore test time limit (NOT related to timeout)
+ @Returns
+ A web element object
+ """
+ if not isinstance(selectors, (list, tuple)):
+ raise Exception("`selectors` must be a list or tuple!")
+ if not selectors:
+ raise Exception("`selectors` cannot be an empty list!")
+ _reconnect_if_disconnected(driver)
+ element = None
+ any_present = False
+ start_ms = time.time() * 1000.0
+ stop_ms = start_ms + (timeout * 1000.0)
+ for x in range(int(timeout * 10)):
+ if not ignore_test_time_limit:
+ shared_utils.check_if_time_limit_exceeded()
+ try:
+ for selector in selectors:
+ by = "css selector"
+ if page_utils.is_xpath_selector(selector):
+ by = "xpath"
+ try:
+ element = driver.find_element(by=by, value=selector)
+ any_present = True
+ if element.is_displayed():
+ return element
+ element = None
+ except Exception:
+ pass
+ raise Exception("Nothing found yet!")
+ except Exception:
+ now_ms = time.time() * 1000.0
+ if now_ms >= stop_ms:
+ break
+ time.sleep(0.1)
+ plural = "s"
+ if timeout == 1:
+ plural = ""
+ if original_selectors:
+ selectors = original_selectors
+ if not element:
+ if not any_present:
+ # None of the elements exist in the HTML
+ message = (
+ "None of the elements {%s} were present after %s second%s!" % (
+ str(selectors),
+ timeout,
+ plural,
+ )
+ )
+ timeout_exception(NoSuchElementException, message)
+ # At least one element exists in the HTML, but none are visible
+ message = "None of the elements %s were visible after %s second%s!" % (
+ str(selectors),
+ timeout,
+ plural,
+ )
+ timeout_exception(ElementNotVisibleException, message)
+ else:
+ return element
+
+
+def wait_for_any_of_elements_present(
+ driver,
+ selectors,
+ timeout=settings.LARGE_TIMEOUT,
+ original_selectors=[],
+ ignore_test_time_limit=False,
+):
+ """
+ Waits for at least one of the elements in the selector list to be present.
+ Visibility not required. (Eg. hidden in the HTML)
+ Returns the first element that is found.
+ Raises NoSuchElementException if none of the elements exist in the HTML
+ within the specified timeout.
+ @Params
+ driver - the webdriver object (required)
+ selectors - the list of selectors for identifying page elements (required)
+ timeout - the time to wait for elements in seconds
+ original_selectors - handle pre-converted ":contains(TEXT)" selectors
+ ignore_test_time_limit - ignore test time limit (NOT related to timeout)
+ @Returns
+ A web element object
+ """
+ if not isinstance(selectors, (list, tuple)):
+ raise Exception("`selectors` must be a list or tuple!")
+ if not selectors:
+ raise Exception("`selectors` cannot be an empty list!")
+ _reconnect_if_disconnected(driver)
+ element = None
+ start_ms = time.time() * 1000.0
+ stop_ms = start_ms + (timeout * 1000.0)
+ for x in range(int(timeout * 10)):
+ if not ignore_test_time_limit:
+ shared_utils.check_if_time_limit_exceeded()
+ try:
+ for selector in selectors:
+ by = "css selector"
+ if page_utils.is_xpath_selector(selector):
+ by = "xpath"
+ try:
+ element = driver.find_element(by=by, value=selector)
+ return element
+ except Exception:
+ pass
+ raise Exception("Nothing found yet!")
+ except Exception:
+ now_ms = time.time() * 1000.0
+ if now_ms >= stop_ms:
+ break
+ time.sleep(0.1)
+ plural = "s"
+ if timeout == 1:
+ plural = ""
+ if original_selectors:
+ selectors = original_selectors
+ if not element:
+ # None of the elements exist in the HTML
+ message = (
+ "None of the elements %s were present after %s second%s!" % (
+ str(selectors),
+ timeout,
+ plural,
+ )
+ )
+ timeout_exception(NoSuchElementException, message)
+ else:
+ return element
+
+
def wait_for_attribute(
driver,
selector,
From dc8705b856340a265e82e42b929253303c08465d Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 3 Jul 2025 19:46:56 -0400
Subject: [PATCH 6/8] Update Markdown format in the docs
---
examples/ReadMe.md | 50 +++++------
help_docs/customizing_test_runs.md | 74 ++++++++--------
help_docs/demo_mode.md | 8 +-
help_docs/desired_capabilities.md | 10 +--
help_docs/hidden_files_info.md | 2 +-
help_docs/how_it_works.md | 2 +-
help_docs/html_inspector.md | 2 +-
help_docs/install.md | 12 +--
help_docs/install_python_pip_git.md | 10 +--
help_docs/js_package_manager.md | 4 +-
help_docs/locale_codes.md | 2 +-
help_docs/mobile_testing.md | 10 +--
help_docs/mysql_installation.md | 16 ++--
help_docs/recorder_mode.md | 16 ++--
help_docs/syntax_formats.md | 2 +-
help_docs/translations.md | 4 +-
help_docs/uc_mode.md | 2 +-
help_docs/verify_webdriver.md | 10 +--
help_docs/virtualenv_instructions.md | 26 +++---
help_docs/webdriver_installation.md | 14 +--
seleniumbase/console_scripts/ReadMe.md | 114 ++++++++++++-------------
21 files changed, 195 insertions(+), 195 deletions(-)
diff --git a/examples/ReadMe.md b/examples/ReadMe.md
index a8381af1805..616086c7851 100644
--- a/examples/ReadMe.md
+++ b/examples/ReadMe.md
@@ -21,7 +21,7 @@
Run an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py): (Default option: ``--chrome``)
-```bash
+```zsh
pytest my_first_test.py
```
@@ -31,7 +31,7 @@ pytest my_first_test.py
Here's one way of changing the browser to Firefox:
-```bash
+```zsh
pytest my_first_test.py --firefox
```
@@ -39,7 +39,7 @@ pytest my_first_test.py --firefox
Another [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_demo_site.py) for a web page that has lots of different HTML items:
-```bash
+```zsh
pytest test_demo_site.py
```
@@ -49,7 +49,7 @@ pytest test_demo_site.py
Run an example test in ``--demo`` mode: (highlight assertions)
-```bash
+```zsh
pytest test_swag_labs.py --demo
```
@@ -59,7 +59,7 @@ pytest test_swag_labs.py --demo
Run [test_coffee_cart.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_coffee_cart.py) to test the [Coffee Cart](https://seleniumbase.io/coffee/) app:
-```bash
+```zsh
pytest test_coffee_cart.py --demo
```
@@ -69,7 +69,7 @@ pytest test_coffee_cart.py --demo
Run a [Wordle-solver example](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/wordle_test.py):
-```bash
+```zsh
pytest wordle_test.py
```
@@ -79,7 +79,7 @@ pytest wordle_test.py
Run an example test in ``--headless`` mode: (invisible browser)
-```bash
+```zsh
pytest my_first_test.py --headless
```
@@ -87,7 +87,7 @@ pytest my_first_test.py --headless
Run an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_swag_labs.py) using Chrome's mobile device emulator: (default settings)
-```bash
+```zsh
pytest test_swag_labs.py --mobile
```
@@ -97,7 +97,7 @@ pytest test_swag_labs.py --mobile
Run an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_xkcd.py) in ``--demo`` mode: (highlight assertions)
-```bash
+```zsh
pytest test_xkcd.py --demo
```
@@ -107,7 +107,7 @@ pytest test_xkcd.py --demo
Run a [test suite](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_suite.py) with verbose output: (see more details)
-```bash
+```zsh
pytest test_suite.py -v
```
@@ -115,7 +115,7 @@ pytest test_suite.py -v
Run a test suite using multiple parallel processes (``-n=NUM``):
-```bash
+```zsh
pytest test_suite.py -n=8
```
@@ -123,7 +123,7 @@ pytest test_suite.py -n=8
Run a [parameterized test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/parameterized_test.py): (Generates multiple tests from one)
-```bash
+```zsh
pytest parameterized_test.py -v
```
@@ -131,7 +131,7 @@ pytest parameterized_test.py -v
Run a test suite and generate a SeleniumBase Dashboard:
-```bash
+```zsh
pytest test_suite.py --dashboard
```
@@ -139,7 +139,7 @@ pytest test_suite.py --dashboard
Run a test suite and generate a ``pytest`` report:
-```bash
+```zsh
pytest test_suite.py --html=report.html
```
@@ -147,7 +147,7 @@ pytest test_suite.py --html=report.html
Run a [failing test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_fail.py): (See the ``latest_logs/`` folder for logs and screenshots)
-```bash
+```zsh
pytest test_fail.py
```
@@ -155,7 +155,7 @@ pytest test_fail.py
Run a failing test that activates ``pdb`` debug mode on failure:
-```bash
+```zsh
pytest test_fail.py --pdb -s
```
@@ -165,7 +165,7 @@ pytest test_fail.py --pdb -s
Run a test suite that demonstrates the use of ``pytest`` markers:
-```bash
+```zsh
pytest -m marker_test_suite -v
```
@@ -173,7 +173,7 @@ pytest -m marker_test_suite -v
Run a test suite that reuses the browser session between tests:
-```bash
+```zsh
pytest test_suite.py --rs
```
@@ -181,7 +181,7 @@ pytest test_suite.py --rs
Run an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/rate_limiting_test.py) demonstrating the ``rate_limited`` Python decorator:
-```bash
+```zsh
pytest rate_limiting_test.py
```
@@ -189,7 +189,7 @@ pytest rate_limiting_test.py
Run an [example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/upload_file_test.py) that demonstrates how to upload a file to a website:
-```bash
+```zsh
pytest upload_file_test.py
```
@@ -197,7 +197,7 @@ pytest upload_file_test.py
๐๏ธ **SeleniumBase Commander** is a GUI for ``pytest``:
-```bash
+```zsh
sbase gui
```
@@ -207,7 +207,7 @@ sbase gui
SeleniumBase tests can also be run with ``pynose``:
-```bash
+```zsh
pynose my_first_test.py
```
@@ -215,7 +215,7 @@ pynose my_first_test.py
Run an example test suite and generate a ``pynose`` test report:
-```bash
+```zsh
pynose test_suite.py --report --show-report
```
@@ -223,7 +223,7 @@ pynose test_suite.py --report --show-report
Run an example test using a ``pynose`` configuration file:
-```bash
+```zsh
pynose my_first_test.py --config=example_config.cfg
```
@@ -241,7 +241,7 @@ If you just need to perform some quick website verification on various devices,
To make things easier, here's a **simple GUI program** that allows you to run a few example tests by pressing a button:
-```bash
+```zsh
python gui_test_runner.py
```
diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md
index 98edf3b72f6..b82bfc1092c 100644
--- a/help_docs/customizing_test_runs.md
+++ b/help_docs/customizing_test_runs.md
@@ -26,7 +26,7 @@
๐๏ธ Here are some examples of configuring tests, which can be run from the [examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder:
-```bash
+```zsh
# Run a test in Chrome (default browser)
pytest my_first_test.py
@@ -109,7 +109,7 @@ pytest my_first_test.py --settings-file=custom_settings.py
๐๏ธ Here are some useful command-line options that come with ``pytest``:
-```bash
+```zsh
-v # Verbose mode. Prints the full name of each test and shows more details.
-q # Quiet mode. Print fewer details in the console output when running tests.
-x # Stop running the tests after the first failure is reached.
@@ -126,7 +126,7 @@ pytest my_first_test.py --settings-file=custom_settings.py
๐๏ธ SeleniumBase provides additional ``pytest`` command-line options for tests:
-```bash
+```zsh
--browser=BROWSER # (The web browser to use. Default: "chrome".)
--chrome # (Shortcut for "--browser=chrome". On by default.)
--edge # (Shortcut for "--browser=edge".)
@@ -234,13 +234,13 @@ pytest my_first_test.py --settings-file=custom_settings.py
๐๏ธ You can also view a list of popular ``pytest`` options for SeleniumBase by typing:
-```bash
+```zsh
seleniumbase options
```
Or the short form:
-```bash
+```zsh
sbase options
```
@@ -250,7 +250,7 @@ sbase options
To see logging abilities, you can run a test suite that includes tests that fail on purpose:
-```bash
+```zsh
pytest test_suite.py
```
@@ -260,13 +260,13 @@ pytest test_suite.py
๐ต If any test is moving too fast for your eyes to see what's going on, you can run it in **Demo Mode** by adding ``--demo`` on the command line, which pauses the browser briefly between actions, highlights page elements being acted on, and lets you know what test assertions are happening in real-time:
-```bash
+```zsh
pytest my_first_test.py --demo
```
๐ต You can override the default wait time by either updating [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) or by using ``--demo-sleep=NUM`` when using Demo Mode. (NOTE: If you use ``--demo-sleep=NUM`` without using ``--demo``, nothing will happen.)
-```bash
+```zsh
pytest my_first_test.py --demo --demo-sleep=1.2
```
@@ -278,7 +278,7 @@ If you want to pass additional data from the command line to your tests, you can
To run ``pytest`` with multiple processes, add ``-n=NUM``, ``-n NUM``, or ``-nNUM`` on the command line, where ``NUM`` is the number of CPUs you want to use.
-```bash
+```zsh
pytest -n=8
pytest -n 8
pytest -n8
@@ -288,7 +288,7 @@ pytest -n8
You can use pytest --reruns=NUM
to retry failing tests that many times. Add --reruns-delay=SECONDS
to wait that many seconds between retries. Example:
-```bash
+```zsh
pytest --reruns=1 --reruns-delay=1
```
@@ -304,13 +304,13 @@ import pytest; pytest.set_trace() # Debug Mode. n: next, c: continue, s: step,
๐ต To pause an active test that throws an exception or error, (*and keep the browser window open while **Debug Mode** begins in the console*), add **``--pdb``** as a ``pytest`` option:
-```bash
+```zsh
pytest test_fail.py --pdb
```
๐ต To start tests in Debug Mode, add **``--trace``** as a ``pytest`` option:
-```bash
+```zsh
pytest test_coffee_cart.py --trace
```
@@ -351,7 +351,7 @@ class Test:
๐๏ธ You might also want to combine multiple options at once. For example:
-```bash
+```zsh
pytest --headless -n8 --dashboard --html=report.html -v --rs --crumbs
```
@@ -363,13 +363,13 @@ The above not only runs tests in parallel processes, but it also tells tests in
First, get `chrome-headless-shell` if you don't already have it:
-```bash
+```zsh
sbase get chs
```
Then, run scripts with `--chs` / `chs=True`:
-```bash
+```zsh
pytest --chs -n8 --dashboard --html=report.html -v --rs
```
@@ -381,7 +381,7 @@ That makes your tests run very quickly in headless mode.
๐ต The ``--dashboard`` option for pytest generates a SeleniumBase Dashboard located at ``dashboard.html``, which updates automatically as tests run and produce results. Example:
-```bash
+```zsh
pytest --dashboard --rs --headless
```
@@ -389,7 +389,7 @@ pytest --dashboard --rs --headless
๐ต Additionally, you can host your own SeleniumBase Dashboard Server on a port of your choice. Here's an example of that using Python 3's ``http.server``:
-```bash
+```zsh
python -m http.server 1948
```
@@ -397,7 +397,7 @@ python -m http.server 1948
๐ต Here's a full example of what the SeleniumBase Dashboard may look like:
-```bash
+```zsh
pytest test_suite.py --dashboard --rs --headless
```
@@ -409,7 +409,7 @@ pytest test_suite.py --dashboard --rs --headless
๐ต Using ``--html=report.html`` gives you a fancy report of the name specified after your test suite completes.
-```bash
+```zsh
pytest test_suite.py --html=report.html
```
@@ -419,7 +419,7 @@ pytest test_suite.py --html=report.html
๐ต Here's an example of an upgraded html report:
-```bash
+```zsh
pytest test_suite.py --dashboard --html=report.html
```
@@ -429,7 +429,7 @@ If viewing pytest html reports in [Jenkins](https://www.jenkins.io/), you may ne
You can also use ``--junit-xml=report.xml`` to get an xml report instead. Jenkins can use this file to display better reporting for your tests.
-```bash
+```zsh
pytest test_suite.py --junit-xml=report.xml
```
@@ -439,7 +439,7 @@ pytest test_suite.py --junit-xml=report.xml
The ``--report`` option gives you a fancy report after your test suite completes.
-```bash
+```zsh
nosetests test_suite.py --report
```
@@ -453,7 +453,7 @@ nosetests test_suite.py --report
You can specify a Language Locale Code to customize web pages on supported websites. With SeleniumBase, you can change the web browser's Locale on the command line by doing this:
-```bash
+```zsh
pytest --locale=CODE # Example: --locale=ru
```
@@ -467,7 +467,7 @@ Visit ๐๐จ ๐ If a test runs too fast for your eyes, use Demo Mode to slow it down, highlight actions, and display assertions. Example usage:
-```bash
+```zsh
cd examples/
pytest test_coffee_cart.py --demo
```
@@ -21,7 +21,7 @@ pytest test_coffee_cart.py --demo
Another example:
-```bash
+```zsh
pytest my_first_test.py --demo
```
@@ -30,7 +30,7 @@ pytest my_first_test.py --demo
Here's how to run test_swag_labs.py from examples/ in Demo Mode:
-```bash
+```zsh
pytest test_swag_labs.py --demo
```
@@ -43,7 +43,7 @@ pytest test_swag_labs.py --demo
(test_error_page.py from examples/)
-```bash
+```zsh
pytest test_error_page.py
```
diff --git a/help_docs/desired_capabilities.md b/help_docs/desired_capabilities.md
index f7183d6b2d5..6ac4881bb71 100644
--- a/help_docs/desired_capabilities.md
+++ b/help_docs/desired_capabilities.md
@@ -6,11 +6,11 @@ You can specify browser capabilities when running SeleniumBase tests on a remote
Sample run commands may look like this when run from the [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder: (The browser is now specified in the capabilities file.)
-```bash
+```zsh
pytest test_demo_site.py --browser=remote --server=USERNAME:KEY@hub.browserstack.com --port=80 --cap_file=capabilities/sample_cap_file_BS.py
```
-```bash
+```zsh
pytest test_demo_site.py --browser=remote --server=USERNAME:KEY@ondemand.us-east-1.saucelabs.com --port=443 --protocol=https --cap_file=capabilities/sample_cap_file_SL.py
```
@@ -92,7 +92,7 @@ You'll need default SeleniumBase capabilities for:
You can also set browser desired capabilities from a command-line string. Eg:
-```bash
+```zsh
pytest test_swag_labs.py --cap-string='{"browserName":"chrome","name":"test1"}' --server="127.0.0.1" --browser=remote
```
@@ -100,7 +100,7 @@ pytest test_swag_labs.py --cap-string='{"browserName":"chrome","name":"test1"}'
If you pass ``"*"`` into the ``"name"`` field of ``--cap-string``, the name will become the test identifier. Eg:
-```bash
+```zsh
pytest my_first_test.py --cap-string='{"browserName":"chrome","name":"*"}' --server="127.0.0.1" --browser=chrome
```
@@ -110,7 +110,7 @@ Example name: ``"my_first_test.MyTestClass.test_basics"``
If using a local Selenium Grid with SeleniumBase, start up the Grid Hub and nodes first:
-```bash
+```zsh
sbase grid-hub start
sbase grid-node start
```
diff --git a/help_docs/hidden_files_info.md b/help_docs/hidden_files_info.md
index bf3d6bfbea3..8d5d3fcec9f 100644
--- a/help_docs/hidden_files_info.md
+++ b/help_docs/hidden_files_info.md
@@ -12,7 +12,7 @@ Press the **โCommandโ + โShiftโ + โ.โ (period)** keys at the same ti
* On older versions of macOS, use the following command in a Terminal window to view hidden files, and then reopen the Finder window:
-```bash
+```zsh
defaults write com.apple.finder AppleShowAllFiles -bool true
```
diff --git a/help_docs/how_it_works.md b/help_docs/how_it_works.md
index 36f44d29c76..e97a0e0607c 100644
--- a/help_docs/how_it_works.md
+++ b/help_docs/how_it_works.md
@@ -65,7 +65,7 @@ class TestSimpleLogin(BaseCase):
๐๏ธ๐ Here are some examples of running tests with ``pytest``:
-```bash
+```zsh
pytest test_mfa_login.py
pytest --headless -n8 --dashboard --html=report.html -v --rs --crumbs
pytest -m marker2
diff --git a/help_docs/html_inspector.md b/help_docs/html_inspector.md
index c7b87a0a111..6d18c8ccefc 100644
--- a/help_docs/html_inspector.md
+++ b/help_docs/html_inspector.md
@@ -20,7 +20,7 @@ class HtmlInspectorTests(BaseCase):
--------
-```bash
+```zsh
pytest test_inspect_html.py
============== test session starts ==============
diff --git a/help_docs/install.md b/help_docs/install.md
index 06b5ebb79e7..c0a5f73230e 100644
--- a/help_docs/install.md
+++ b/help_docs/install.md
@@ -4,19 +4,19 @@
If installing seleniumbase
directly from PyPI, (the Python Package Index), use:
-```bash
+```zsh
pip install seleniumbase
```
To upgrade an existing seleniumbase
install from PyPI:
-```bash
+```zsh
pip install -U seleniumbase
```
If installing seleniumbase
from a Git clone, use:
-```bash
+```zsh
git clone https://github.com/seleniumbase/SeleniumBase.git
cd SeleniumBase/
pip install .
@@ -24,7 +24,7 @@ pip install .
For a development mode install in editable mode, use:
-```bash
+```zsh
git clone https://github.com/seleniumbase/SeleniumBase.git
cd SeleniumBase/
pip install -e .
@@ -32,14 +32,14 @@ pip install -e .
To upgrade an existing seleniumbase
install from GitHub:
-```bash
+```zsh
git pull # To pull the latest version
pip install -e . # Or "pip install ."
```
If installing seleniumbase
from a GitHub branch, use:
-```bash
+```zsh
pip install git+https://github.com/seleniumbase/SeleniumBase.git@master#egg=seleniumbase
```
diff --git a/help_docs/install_python_pip_git.md b/help_docs/install_python_pip_git.md
index 31bb87b164f..ca231bc8f79 100644
--- a/help_docs/install_python_pip_git.md
+++ b/help_docs/install_python_pip_git.md
@@ -20,19 +20,19 @@ You can download Python from [https://www.python.org/downloads/](https://www.pyt
โ ๏ธ If something went wrong with your ``pip`` installation, try this:
-```bash
+```zsh
python -m ensurepip --default-pip
```
If your existing version of pip is old, upgrade to the latest version:
-```bash
+```zsh
python -m pip install --upgrade pip setuptools
```
On CentOS 7 and some versions of Linux, you may need to install pip with ``yum``:
-```bash
+```zsh
yum -y update
yum -y install python-pip
```
@@ -43,7 +43,7 @@ When done, make sure the location of pip is on your path, which is ``$PATH`` for
You can also get pip (or fix pip) by using:
-```bash
+```zsh
curl https://bootstrap.pypa.io/get-pip.py | python
```
@@ -51,7 +51,7 @@ curl https://bootstrap.pypa.io/get-pip.py | python
**Keep Pip and Setuptools up-to-date:**
-```bash
+```zsh
python -m pip install -U pip setuptools
```
diff --git a/help_docs/js_package_manager.md b/help_docs/js_package_manager.md
index 07efffe3770..270f122ab18 100644
--- a/help_docs/js_package_manager.md
+++ b/help_docs/js_package_manager.md
@@ -24,7 +24,7 @@
๐บ๏ธ This example is from maps_introjs_tour.py. (The --interval=1
makes the tour go automatically to the next step after 1 second.)
-```bash
+```zsh
cd examples/tour_examples
pytest maps_introjs_tour.py --interval=1
```
@@ -120,7 +120,7 @@ def add_css_link(driver, css_link):
Here's how to run that example:
-```bash
+```zsh
cd examples/dialog_boxes
pytest test_dialog_boxes.py
```
diff --git a/help_docs/locale_codes.md b/help_docs/locale_codes.md
index a85bbbdecce..a9aae6af33c 100644
--- a/help_docs/locale_codes.md
+++ b/help_docs/locale_codes.md
@@ -4,7 +4,7 @@
You can specify a Language Locale Code to customize web pages on supported websites. With SeleniumBase, you can change the web browser's Locale on the command-line by doing this:
-```bash
+```zsh
pytest --locale=CODE # Example: --locale=ru
```
diff --git a/help_docs/mobile_testing.md b/help_docs/mobile_testing.md
index 33abd1b2eb1..8910c7fcc06 100644
--- a/help_docs/mobile_testing.md
+++ b/help_docs/mobile_testing.md
@@ -8,7 +8,7 @@ Use ``--mobile`` to run SeleniumBase tests using Chrome's mobile device emulator
[SeleniumBase/examples/test_skype_site.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_skype_site.py)
-```bash
+```zsh
pytest test_skype_site.py --mobile
```
@@ -16,13 +16,13 @@ pytest test_skype_site.py --mobile
To configure Device Metrics, use:
-```bash
+```zsh
--metrics="CSS_Width,CSS_Height,Pixel_Ratio"
```
To configure the User-Agent, use:
-```bash
+```zsh
--agent="USER-AGENT-STRING"
```
@@ -40,7 +40,7 @@ To find real User-Agent strings, see:
[SeleniumBase/examples/test_swag_labs.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_swag_labs.py)
-```bash
+```zsh
pytest test_swag_labs.py --mobile
```
@@ -48,7 +48,7 @@ pytest test_swag_labs.py --mobile
Here's an example of configuring mobile settings for that test:
-```bash
+```zsh
# Run tests using Chrome's mobile device emulator (default settings)
pytest test_swag_labs.py --mobile
diff --git a/help_docs/mysql_installation.md b/help_docs/mysql_installation.md
index 143b8465f41..bfcda1c5a23 100644
--- a/help_docs/mysql_installation.md
+++ b/help_docs/mysql_installation.md
@@ -11,7 +11,7 @@
### GitHub Actions Ubuntu Linux MySQL Setup:
-```bash
+```zsh
sudo /etc/init.d/mysql start
mysql -e 'CREATE DATABASE IF NOT EXISTS test_db;' -uroot -proot
wget https://raw.githubusercontent.com/seleniumbase/SeleniumBase/master/seleniumbase/core/create_db_tables.sql
@@ -22,13 +22,13 @@ sudo service mysql restart
Have SeleniumBase tests write to the MySQL DB:
-```bash
+```zsh
pytest --with-db_reporting
```
Query MySQL DB Results:
-```bash
+```zsh
mysql -e 'select test_address,browser,state,start_time,runtime from test_db.test_run_data;' -uroot -ptest
```
@@ -36,7 +36,7 @@ mysql -e 'select test_address,browser,state,start_time,runtime from test_db.test
### Standard Ubuntu Linux MySQL Setup:
-```bash
+```zsh
sudo apt update
sudo apt install mysql-server
sudo mysql_secure_installation
@@ -47,20 +47,20 @@ sudo service mysql restart
To change the password from `root` to `test`:
-```bash
+```zsh
mysqladmin -u root -p'root' password 'test'
sudo service mysql restart
```
### MacOS MySQL Setup:
-```bash
+```zsh
brew install mysql
```
Then start the MySQL service:
-```bash
+```zsh
brew services start mysql
```
@@ -89,6 +89,6 @@ Update your [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/mast
Add the ``--with-db_reporting`` argument on the command-line when you want tests to write to your MySQL database. Example:
-```bash
+```zsh
pytest --with-db_reporting
```
diff --git a/help_docs/recorder_mode.md b/help_docs/recorder_mode.md
index 8f30b99cd5b..3ef50de4e18 100644
--- a/help_docs/recorder_mode.md
+++ b/help_docs/recorder_mode.md
@@ -13,7 +13,7 @@
โบ๏ธ To make a new recording from the command-line interface, use ``sbase mkrec``, ``sbase codegen``, or ``sbase record``:
-```bash
+```zsh
sbase mkrec TEST_NAME.py --url=URL
```
@@ -21,7 +21,7 @@ If the file already exists, you'll get an error. If no URL is provided, you'll s
Example:
-```bash
+```zsh
sbase mkrec new_test.py --url=wikipedia.org
* RECORDING initialized: new_test.py
@@ -50,7 +50,7 @@ pytest new_test.py --rec -q -s --url=wikipedia.org
๐ด You can also activate Recorder Mode from the Recorder Desktop App:
-```bash
+```zsh
sbase recorder
* Starting the SeleniumBase Recorder Desktop App...
```
@@ -65,7 +65,7 @@ sbase recorder
โบ๏ธ For extra flexibility, the ``sbase mkrec`` command can be split into four separate commands:
-```bash
+```zsh
sbase mkfile TEST_NAME.py --rec
pytest TEST_NAME.py --rec -q -s
@@ -85,13 +85,13 @@ import pdb; pdb.set_trace()
Now you'll be able to run your test with ``pytest``, and it will stop at the breakpoint for you to add in actions: (Press ``c`` and ``ENTER`` on the command-line to continue from the breakpoint.)
-```bash
+```zsh
pytest TEST_NAME.py --rec -s
```
โบ๏ธ You can also set a breakpoint at the start of your test by adding ``--trace`` as a ``pytest`` command-line option: (This is useful when running Recorder Mode without any ``pdb`` breakpoints.)
-```bash
+```zsh
pytest TEST_NAME.py --trace --rec -s
```
@@ -99,14 +99,14 @@ pytest TEST_NAME.py --trace --rec -s
โบ๏ธ Here's a command-line notification for a completed recording:
-```bash
+```zsh
>>> RECORDING SAVED as: recordings/TEST_NAME_rec.py
***************************************************
```
โบ๏ธ When running additional tests from the same Python module, Recordings will get added to the file that was created from the first test:
-```bash
+```zsh
>>> RECORDING ADDED to: recordings/TEST_NAME_rec.py
***************************************************
```
diff --git a/help_docs/syntax_formats.md b/help_docs/syntax_formats.md
index fa1b39fb637..ebc7e00265a 100644
--- a/help_docs/syntax_formats.md
+++ b/help_docs/syntax_formats.md
@@ -731,7 +731,7 @@ class MiClaseDePrueba(CasoDePrueba):
With [Behave's BDD Gherkin format](https://behave.readthedocs.io/en/stable/gherkin.html), you can use natural language to write tests that work with SeleniumBase methods. Behave tests are run by calling ``behave`` on the command-line. This requires some special files in a specific directory structure. Here's an example of that structure:
-```bash
+```zsh
features/
โโโ __init__.py
โโโ behave.ini
diff --git a/help_docs/translations.md b/help_docs/translations.md
index 85502ca68ce..0206f3f9499 100644
--- a/help_docs/translations.md
+++ b/help_docs/translations.md
@@ -52,11 +52,11 @@ class ็งใฎใในใใฏใฉใน(ใปใฌใใฆใ ใในใใฑใผใน):
You can use SeleniumBase to selectively translate the method names of any test from one language to another with the console scripts interface. Additionally, the ``import`` line at the top of the Python file will change to import the new ``BaseCase``. Example: ``BaseCase`` becomes ``CasoDeTeste`` when a test is translated into Portuguese.
-```bash
+```zsh
seleniumbase translate
```
-```bash
+```zsh
* Usage:
seleniumbase translate [SB_FILE.py] [LANGUAGE] [ACTION]
diff --git a/help_docs/uc_mode.md b/help_docs/uc_mode.md
index 569c73c1ffe..53e6f78cfe7 100644
--- a/help_docs/uc_mode.md
+++ b/help_docs/uc_mode.md
@@ -305,7 +305,7 @@ with SB(uc=True) as sb:
If you're using pytest
for multithreaded UC Mode (which requires using one of the pytest
[syntax formats](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md)), then all you have to do is set the number of threads when your script runs. (-n NUM
) Eg:
-```bash
+```zsh
pytest --uc -n 4
```
diff --git a/help_docs/verify_webdriver.md b/help_docs/verify_webdriver.md
index 083e5d4b6e1..2c3203d8c8b 100644
--- a/help_docs/verify_webdriver.md
+++ b/help_docs/verify_webdriver.md
@@ -6,7 +6,7 @@ On newer versions of SeleniumBase, the driver is automatically downloaded to the
Drivers can be manually downloaded to the ``seleniumbase/drivers`` folder with commands such as:
-```bash
+```zsh
sbase get chromedriver
sbase get geckodriver
sbase get edgedriver
@@ -18,7 +18,7 @@ If you want to check that you have the correct driver installed on your System P
*This assumes you've already downloaded a driver to your **System PATH** with a command such as:*
-```bash
+```zsh
sbase get chromedriver --path
```
@@ -28,7 +28,7 @@ sbase get chromedriver --path
#### Verifying ChromeDriver
-```bash
+```zsh
python
```
@@ -42,7 +42,7 @@ python
#### Verifying Geckodriver (Firefox WebDriver)
-```bash
+```zsh
python
```
@@ -56,7 +56,7 @@ python
#### Verifying WebDriver for Safari
-```bash
+```zsh
python
```
diff --git a/help_docs/virtualenv_instructions.md b/help_docs/virtualenv_instructions.md
index e92af9aa7ea..24b50ac1f39 100644
--- a/help_docs/virtualenv_instructions.md
+++ b/help_docs/virtualenv_instructions.md
@@ -18,14 +18,14 @@ There are multiple ways of creating a **[Python virtual environment](https://pac
> macOS/Linux terminal (``python3 -m venv ENV``)
-```bash
+```zsh
python3 -m venv sbase_env
source sbase_env/bin/activate
```
> Windows CMD prompt (``py -m venv ENV``):
-```bash
+```zsh
py -m venv sbase_env
call sbase_env\\Scripts\\activate
```
@@ -38,7 +38,7 @@ To exit a virtual env, type ``deactivate``.
> macOS/Linux terminal:
-```bash
+```zsh
python3 -m pip install virtualenvwrapper --force-reinstall
export WORKON_HOME=$HOME/.virtualenvs
source `which virtualenvwrapper.sh`
@@ -50,7 +50,7 @@ source `which virtualenvwrapper.sh`
> Windows CMD prompt:
-```bash
+```zsh
py -m pip install virtualenvwrapper-win --force-reinstall --user
```
@@ -61,7 +61,7 @@ py -m pip install virtualenvwrapper-win --force-reinstall --user
* ``mkvirtualenv ENV``:
-```bash
+```zsh
mkvirtualenv sbase_env
```
@@ -72,31 +72,31 @@ mkvirtualenv sbase_env
Creating a virtual environment:
-```bash
+```zsh
mkvirtualenv sbase_env
```
Leaving your virtual environment:
-```bash
+```zsh
deactivate
```
Returning to a virtual environment:
-```bash
+```zsh
workon sbase_env
```
Listing all virtual environments:
-```bash
+```zsh
workon
```
Deleting a virtual environment:
-```bash
+```zsh
rmvirtualenv sbase_env
```
@@ -104,7 +104,7 @@ rmvirtualenv sbase_env
If the ``python`` and ``python3`` versions don't match (*while in a virtualenv on macOS or Linux*), the following command will sync the versions:
-```bash
+```zsh
alias python=python3
```
@@ -114,13 +114,13 @@ alias python=python3
To verify the ``python`` version, use:
-```bash
+```zsh
python --version
```
To see the PATH of your ``python`` (macOS/Linux), use:
-```bash
+```zsh
which python
```
diff --git a/help_docs/webdriver_installation.md b/help_docs/webdriver_installation.md
index cbae48fa4f7..2ce7e703dd0 100644
--- a/help_docs/webdriver_installation.md
+++ b/help_docs/webdriver_installation.md
@@ -6,7 +6,7 @@ To run web automation, you need webdrivers for each browser you plan on using.
๐๏ธ You can also download drivers manually with these commands:
-```bash
+```zsh
seleniumbase get chromedriver
seleniumbase get geckodriver
seleniumbase get edgedriver
@@ -18,7 +18,7 @@ If the necessary driver is not found in this location while running tests, Selen
๐๏ธ You can also download specific versions of drivers. Examples:
-```bash
+```zsh
sbase get chromedriver 114
sbase get chromedriver 114.0.5735.90
sbase get chromedriver stable
@@ -50,7 +50,7 @@ Here's where you can go to manually get web drivers from the source:
๐๏ธ You can also install drivers by using ``brew`` (aka ``homebrew``):
-```bash
+```zsh
brew install --cask chromedriver
brew install geckodriver
@@ -58,7 +58,7 @@ brew install geckodriver
๐๏ธ You can also upgrade existing webdrivers:
-```bash
+```zsh
brew upgrade --cask chromedriver
brew upgrade geckodriver
@@ -68,14 +68,14 @@ brew upgrade geckodriver
๐๏ธ If you still need drivers, these scripts download `chromedriver` and `geckodriver` to a Linux machine:
-```bash
+```zsh
wget https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/
chmod +x /usr/local/bin/chromedriver
```
-```bash
+```zsh
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/
@@ -90,7 +90,7 @@ To verify that web drivers are working, **[follow these instructions](https://gi
๐๏ธ Use the `sbase get` command to download the `Chrome for Testing` and `Chrome-Headless-Shell` browser binaries. Example:
-```bash
+```zsh
sbase get cft # (For `Chrome for Testing`)
sbase get chs # (For `Chrome-Headless-Shell`)
```
diff --git a/seleniumbase/console_scripts/ReadMe.md b/seleniumbase/console_scripts/ReadMe.md
index 58cf7190164..48856616697 100644
--- a/seleniumbase/console_scripts/ReadMe.md
+++ b/seleniumbase/console_scripts/ReadMe.md
@@ -12,7 +12,7 @@
(For running tests, [use pytest with SeleniumBase](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md).)
-```bash
+```zsh
COMMANDS:
get / install [DRIVER] [OPTIONS]
methods (List common Python methods)
@@ -52,14 +52,14 @@ COMMANDS:
* Usage:
-```bash
+```zsh
sbase get [DRIVER] [OPTIONS]
sbase install [DRIVER] [OPTIONS]
```
* Examples:
-```bash
+```zsh
sbase get chromedriver
sbase get geckodriver
sbase get edgedriver
@@ -90,7 +90,7 @@ Downloads the webdriver to ``seleniumbase/drivers/``
* Usage:
-```bash
+```zsh
sbase methods
```
@@ -102,7 +102,7 @@ Displays common SeleniumBase Python methods.
* Usage:
-```bash
+```zsh
sbase options
```
@@ -111,7 +111,7 @@ sbase options
Displays common pytest command-line options
that are available when using SeleniumBase.
-```bash
+```zsh
--browser=BROWSER (The web browser to use. Default is "chrome")
--edge / --firefox / --safari (Shortcut for browser selection.)
--headless (Run tests headlessly. Default mode on Linux OS.)
@@ -169,7 +169,7 @@ For the full list of command-line options, type: "pytest --help".
* Usage:
-```bash
+```zsh
sbase behave-options
```
@@ -178,7 +178,7 @@ sbase behave-options
Displays common Behave command-line options
that are available when using SeleniumBase.
-```bash
+```zsh
-D browser=BROWSER (The web browser to use. Default is "chrome")
-D headless (Run tests headlessly. Default mode on Linux OS.)
-D demo (Slow down and visually see test actions as they occur.)
@@ -227,7 +227,7 @@ For the full list of command-line options, type: "behave --help".
* Usage:
-```bash
+```zsh
sbase gui [OPTIONAL PATH or TEST FILE]
sbase commander [OPTIONAL PATH or TEST FILE]
```
@@ -236,14 +236,14 @@ sbase commander [OPTIONAL PATH or TEST FILE]
* Usage:
-```bash
+```zsh
sbase behave-gui [OPTIONAL PATH or TEST FILE]
sbase gui-behave [OPTIONAL PATH or TEST FILE]
```
* Examples:
-```bash
+```zsh
sbase behave-gui
sbase behave-gui -i=calculator
sbase behave-gui features/
@@ -258,13 +258,13 @@ Launches SeleniumBase Commander / GUI for Behave.
* Usage:
-```bash
+```zsh
sbase caseplans [OPTIONAL PATH or TEST FILE]
```
* Examples:
-```bash
+```zsh
sbase caseplans
sbase caseplans -k agent
sbase caseplans -m marker2
@@ -280,19 +280,19 @@ Launches the SeleniumBase Case Plans Generator.
* Usage:
-```bash
+```zsh
sbase mkdir [DIRECTORY] [OPTIONS]
```
* Example:
-```bash
+```zsh
sbase mkdir ui_tests
```
* Options:
-```bash
+```zsh
-b / --basic (Only config files. No tests added.)
```
@@ -304,7 +304,7 @@ sample tests for helping new users get started,
and Python boilerplates for setting up customized
test frameworks.
-```bash
+```zsh
ui_tests/
โโโ __init__.py
โโโ my_first_test.py
@@ -330,7 +330,7 @@ ui_tests/
If running with the ``-b`` or ``--basic`` option:
-```bash
+```zsh
ui_tests/
โโโ __init__.py
โโโ pytest.ini
@@ -342,19 +342,19 @@ ui_tests/
* Usage:
-```bash
+```zsh
sbase mkfile [FILE.py] [OPTIONS]
```
* Example:
-```bash
+```zsh
sbase mkfile new_test.py
```
* Options:
-```bash
+```zsh
--uc (UC Mode boilerplate using SB context manager)
-b / --basic (Basic boilerplate / single-line test)
-r / --rec (Adds Pdb+ breakpoint for Recorder Mode)
@@ -363,7 +363,7 @@ sbase mkfile new_test.py
* Language Options:
-```bash
+```zsh
--en / --English | --zh / --Chinese
--nl / --Dutch | --fr / --French
--it / --Italian | --ja / --Japanese
@@ -373,7 +373,7 @@ sbase mkfile new_test.py
* Syntax Formats:
-```bash
+```zsh
--bc / --basecase (BaseCase class inheritance)
--pf / --pytest-fixture (sb pytest fixture)
--cf / --class-fixture (class + sb pytest fixture)
@@ -398,14 +398,14 @@ UC Mode automatically uses English with SB() format.
* Usage:
-```bash
+```zsh
sbase mkrec [FILE.py] [OPTIONS]
sbase codegen [FILE.py] [OPTIONS]
```
* Examples:
-```bash
+```zsh
sbase mkrec new_test.py
sbase mkrec new_test.py --url=seleniumbase.io
sbase codegen new_test.py
@@ -414,7 +414,7 @@ sbase codegen new_test.py --url=wikipedia.org
* Options:
-```bash
+```zsh
--url=URL (Sets the initial start page URL.)
--edge (Use Edge browser instead of Chrome.)
--gui / --headed (Use headed mode on Linux.)
@@ -433,13 +433,13 @@ If the filename already exists, an error is raised.
* Usage:
-```bash
+```zsh
sbase recorder [OPTIONS]
```
* Options:
-```bash
+```zsh
--uc / --undetected (Use undetectable mode.)
--behave (Also output Behave/Gherkin files.)
```
@@ -452,19 +452,19 @@ Launches the SeleniumBase Recorder Desktop App.
* Usage:
-```bash
+```zsh
sbase mkpres [FILE.py] [LANG]
```
* Example:
-```bash
+```zsh
sbase mkpres new_presentation.py --en
```
* Language Options:
-```bash
+```zsh
--en / --English | --zh / --Chinese
--nl / --Dutch | --fr / --French
--it / --Italian | --ja / --Japanese
@@ -484,19 +484,19 @@ The slides can be used as a basic boilerplate.
* Usage:
-```bash
+```zsh
sbase mkchart [FILE.py] [LANG]
```
* Example:
-```bash
+```zsh
sbase mkchart new_chart.py --en
```
* Language Options:
-```bash
+```zsh
--en / --English | --zh / --Chinese
--nl / --Dutch | --fr / --French
--it / --Italian | --ja / --Japanese
@@ -516,13 +516,13 @@ The chart can be used as a basic boilerplate.
* Usage:
-```bash
+```zsh
sbase print [FILE] [OPTIONS]
```
* Options:
-```bash
+```zsh
-n (Add line Numbers to the rows)
```
@@ -535,13 +535,13 @@ with syntax-highlighting.
* Usage:
-```bash
+```zsh
sbase translate [SB_FILE.py] [LANGUAGE] [ACTION]
```
* Languages:
-```bash
+```zsh
--en / --English | --zh / --Chinese
--nl / --Dutch | --fr / --French
--it / --Italian | --ja / --Japanese
@@ -551,7 +551,7 @@ sbase translate [SB_FILE.py] [LANGUAGE] [ACTION]
* Actions:
-```bash
+```zsh
-p / --print (Print translation output to the screen)
-o / --overwrite (Overwrite the file being translated)
-c / --copy (Copy the translation to a new ``.py`` file)
@@ -559,7 +559,7 @@ sbase translate [SB_FILE.py] [LANGUAGE] [ACTION]
* Options:
-```bash
+```zsh
-n (include line Numbers when using the Print action)
```
@@ -579,7 +579,7 @@ plus the 2-letter language code of the new language.
* Usage:
-```bash
+```zsh
sbase extract-objects [SB_FILE.py]
```
@@ -593,13 +593,13 @@ seleniumbase Python file and saves those objects to the
* Usage:
-```bash
+```zsh
sbase inject-objects [SB_FILE.py] [OPTIONS]
```
* Options:
-```bash
+```zsh
-c / --comments (Add object selectors to the comments.)
```
@@ -613,13 +613,13 @@ the selected seleniumbase Python file.
* Usage:
-```bash
+```zsh
sbase objectify [SB_FILE.py] [OPTIONS]
```
* Options:
-```bash
+```zsh
-c / --comments (Add object selectors to the comments.)
```
@@ -635,13 +635,13 @@ have been replaced with variable names defined in
* Usage:
-```bash
+```zsh
sbase revert-objects [SB_FILE.py] [OPTIONS]
```
* Options:
-```bash
+```zsh
-c / --comments (Keep existing comments for the lines.)
```
@@ -656,7 +656,7 @@ selectors stored in the "page_objects.py" file.
* Usage:
-```bash
+```zsh
sbase convert [WEBDRIVER_UNITTEST_FILE.py]
```
@@ -693,13 +693,13 @@ Runs the password decryption/unobfuscation tool.
* Usage:
-```bash
+```zsh
sbase proxy [OPTIONS]
```
* Options:
-```bash
+```zsh
--hostname=HOSTNAME (Set `hostname`) (Default: `127.0.0.1`)
--port=PORT (Set `port`) (Default: `8899`)
--help / -h (Display available `proxy` options.)
@@ -714,7 +714,7 @@ Launch a basic proxy server on the current machine.
* Usage:
-```bash
+```zsh
sbase download server
```
@@ -727,13 +727,13 @@ Downloads the Selenium Server JAR file for Grid usage.
* Usage:
-```bash
-sbase grid-hub {start|stop|restart} [OPTIONS]
+```zsh
+sbase grid-hub [start|stop|restart] [OPTIONS]
```
* Options:
-```bash
+```zsh
-v / --verbose (Increases verbosity of logging output.)
--timeout=TIMEOUT (Close idle browser windows after TIMEOUT seconds.)
```
@@ -750,13 +750,13 @@ You can start, restart, or stop the Grid Hub server.
* Usage:
-```bash
-sbase grid-node {start|stop|restart} [OPTIONS]
+```zsh
+sbase grid-node [start|stop|restart] [OPTIONS]
```
* Options:
-```bash
+```zsh
--hub=HUB_IP (Grid Hub IP Address. Default: `127.0.0.1`)
-v / --verbose (Increases verbosity of logging output.)
```
From 09faaf724cc4b0dd4394e8cae987342f979f04af Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 3 Jul 2025 19:47:41 -0400
Subject: [PATCH 7/8] Refresh Python dependencies
---
requirements.txt | 4 ++--
setup.py | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 7643e24e8fa..e328b5d25f1 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -46,7 +46,7 @@ wsproto==1.2.0
websocket-client==1.8.0
selenium==4.27.1;python_version<"3.9"
selenium==4.32.0;python_version>="3.9" and python_version<"3.10"
-selenium==4.33.0;python_version>="3.10"
+selenium==4.34.0;python_version>="3.10"
cssselect==1.2.0;python_version<"3.9"
cssselect==1.3.0;python_version>="3.9"
sortedcontainers==2.4.0
@@ -77,7 +77,7 @@ rich>=14.0.0,<15
# ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.)
coverage>=7.6.1;python_version<"3.9"
-coverage>=7.9.1;python_version>="3.9"
+coverage>=7.9.2;python_version>="3.9"
pytest-cov>=5.0.0;python_version<"3.9"
pytest-cov>=6.2.1;python_version>="3.9"
flake8==5.0.4;python_version<"3.9"
diff --git a/setup.py b/setup.py
index 27acd4132eb..a588dc91ef9 100755
--- a/setup.py
+++ b/setup.py
@@ -194,7 +194,7 @@
'websocket-client==1.8.0',
'selenium==4.27.1;python_version<"3.9"',
'selenium==4.32.0;python_version>="3.9" and python_version<"3.10"',
- 'selenium==4.33.0;python_version>="3.10"',
+ 'selenium==4.34.0;python_version>="3.10"',
'cssselect==1.2.0;python_version<"3.9"',
'cssselect==1.3.0;python_version>="3.9"',
"sortedcontainers==2.4.0",
@@ -234,7 +234,7 @@
# Usage: coverage run -m pytest; coverage html; coverage report
"coverage": [
'coverage>=7.6.1;python_version<"3.9"',
- 'coverage>=7.9.1;python_version>="3.9"',
+ 'coverage>=7.9.2;python_version>="3.9"',
'pytest-cov>=5.0.0;python_version<"3.9"',
'pytest-cov>=6.2.1;python_version>="3.9"',
],
@@ -267,7 +267,7 @@
'pdfminer.six==20250324;python_version<"3.9"',
'pdfminer.six==20250506;python_version>="3.9"',
'cryptography==39.0.2;python_version<"3.9"',
- 'cryptography==45.0.4;python_version>="3.9"',
+ 'cryptography==45.0.5;python_version>="3.9"',
'cffi==1.17.1',
"pycparser==2.22",
],
From f80aed563e921d642d6c9786bb5474dfd672cb1b Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 3 Jul 2025 19:48:01 -0400
Subject: [PATCH 8/8] Version 4.40.0
---
seleniumbase/__version__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py
index 058370c0b67..4038bc4992f 100755
--- a/seleniumbase/__version__.py
+++ b/seleniumbase/__version__.py
@@ -1,2 +1,2 @@
# seleniumbase package
-__version__ = "4.39.6"
+__version__ = "4.40.0"