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/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/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:
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 @@seleniumbase
directly from PyPI, (the Python Package Index), use:seleniumbase
install from PyPI:seleniumbase
from a Git clone, use:seleniumbase
install from GitHub:seleniumbase
from a GitHub branch, use:๐บ๏ธ This example is from maps_introjs_tour.py. (The --interval=1
makes the tour go automatically to the next step after 1 second.)
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/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/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/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"
diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py
index 71931e81701..8d6a42d3f3a 100644
--- a/seleniumbase/behave/behave_sb.py
+++ b/seleniumbase/behave/behave_sb.py
@@ -11,6 +11,8 @@
-D edge (Shortcut for "-D browser=edge".)
-D firefox (Shortcut for "-D browser=firefox".)
-D safari (Shortcut for "-D browser=safari".)
+-D cft (Shortcut for using `Chrome for Testing`)
+-D chs (Shortcut for using `Chrome-Headless-Shell`)
-D settings-file=FILE (Override default SeleniumBase settings.)
-D env=ENV (Set the test env. Access with "self.env" in tests.)
-D account=STR (Set account. Access with "self.account" in tests.)
@@ -176,6 +178,7 @@ def get_configured_sb(context):
sb.extension_zip = None
sb.extension_dir = None
sb.binary_location = None
+ sb_config.binary_location = None
sb.driver_version = None
sb.page_load_strategy = None
sb.database_env = "test"
@@ -488,6 +491,19 @@ def get_configured_sb(context):
if binary_location == "true":
binary_location = sb.binary_location # revert to default
sb.binary_location = binary_location
+ sb_config.binary_location = binary_location
+ continue
+ # Handle: -D cft
+ if low_key in ["cft"] and not sb_config.binary_location:
+ binary_location = "cft"
+ sb.binary_location = binary_location
+ sb_config.binary_location = binary_location
+ continue
+ # Handle: -D chs
+ if low_key in ["chs"] and not sb_config.binary_location:
+ binary_location = "chs"
+ sb.binary_location = binary_location
+ sb_config.binary_location = binary_location
continue
# Handle: -D driver-version=VER / driver_version=VER
if low_key in ["driver-version", "driver_version"]:
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.)
```
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 d8872a8cfc7..8ec35e70717 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):
@@ -100,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:]
@@ -301,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")
@@ -543,11 +581,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()
@@ -792,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
@@ -2838,6 +2879,24 @@ def get_driver(
device_pixel_ratio=None,
browser=None, # A duplicate of browser_name to avoid confusion
):
+ driver_dir = DRIVER_DIR
+ if (
+ hasattr(sb_config, "binary_location")
+ and sb_config.binary_location == "cft"
+ ):
+ driver_dir = DRIVER_DIR_CFT
+ if (
+ hasattr(sb_config, "binary_location")
+ and 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
@@ -2882,7 +2941,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)
@@ -2936,7 +2995,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)
@@ -3772,6 +3831,41 @@ 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 (
+ hasattr(sb_config, "binary_location")
+ and sb_config.binary_location == "cft"
+ ):
+ special_chrome = True
+ driver_dir = DRIVER_DIR_CFT
+ if (
+ hasattr(sb_config, "binary_location")
+ and 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:
@@ -3819,9 +3913,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"
@@ -3857,9 +3951,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:
@@ -3960,9 +4054,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
@@ -3976,9 +4070,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"
@@ -4004,7 +4098,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,
)
@@ -4087,10 +4181,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")
@@ -4118,7 +4212,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"
@@ -4128,7 +4222,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"
@@ -4166,9 +4260,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"
@@ -4499,7 +4593,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"],
)
@@ -4711,10 +4805,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")
@@ -4752,10 +4846,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":
@@ -4803,9 +4901,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"
@@ -4813,9 +4911,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.*"""
@@ -4843,20 +4941,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
@@ -4893,9 +4991,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
@@ -4944,9 +5042,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
@@ -4954,10 +5052,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:
@@ -4971,9 +5069,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 (
@@ -4985,7 +5083,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
+ )
)
)
):
@@ -5037,20 +5137,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"
@@ -5059,7 +5159,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)
@@ -5204,9 +5304,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],
)
@@ -5247,8 +5347,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(
@@ -5326,7 +5426,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,
)
@@ -5461,9 +5561,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"],
)
@@ -5740,9 +5840,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"
diff --git a/seleniumbase/core/log_helper.py b/seleniumbase/core/log_helper.py
index bd1ebe25be9..2ba1c3d47ef 100644
--- a/seleniumbase/core/log_helper.py
+++ b/seleniumbase/core/log_helper.py
@@ -228,7 +228,11 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None):
traceback_message = None
if hasattr(test, "is_behave") and test.is_behave:
if sb_config.behave_scenario.status.name == "failed":
- if sb_config.behave_step.error_message:
+ if (
+ hasattr(sb_config, "behave_step")
+ and hasattr(sb_config.behave_step, "error_message")
+ and sb_config.behave_step.error_message
+ ):
traceback_message = sb_config.behave_step.error_message
else:
format_exception = traceback.format_exception(
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/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
diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py
index 8714785b2e6..d5da1cf966e 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)
@@ -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)
@@ -16747,7 +16905,11 @@ def tearDown(self):
self._last_page_url = "(Error: Unknown URL)"
if hasattr(self, "is_behave") and self.is_behave and has_exception:
if hasattr(sb_config, "pdb_option") and sb_config.pdb_option:
- self.__activate_behave_post_mortem_debug_mode()
+ if (
+ hasattr(sb_config, "behave_step")
+ and hasattr(sb_config.behave_step, "exc_traceback")
+ ):
+ self.__activate_behave_post_mortem_debug_mode()
if self._final_debug:
self.__activate_debug_mode_in_teardown()
elif (
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,
diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py
index 130de345af9..3a227f50a86 100644
--- a/seleniumbase/plugins/pytest_plugin.py
+++ b/seleniumbase/plugins/pytest_plugin.py
@@ -28,6 +28,8 @@ def pytest_addoption(parser):
--edge (Shortcut for "--browser=edge".)
--firefox (Shortcut for "--browser=firefox".)
--safari (Shortcut for "--browser=safari".)
+ --cft (Shortcut for using `Chrome for Testing`)
+ --chs (Shortcut for using `Chrome-Headless-Shell`)
--settings-file=FILE (Override default SeleniumBase settings.)
--env=ENV (Set the test env. Access with "self.env" in tests.)
--account=STR (Set account. Access with "self.account" in tests.)
diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py
index 20d48b2eeb9..c64f503431a 100644
--- a/seleniumbase/plugins/selenium_plugin.py
+++ b/seleniumbase/plugins/selenium_plugin.py
@@ -16,6 +16,8 @@ class SeleniumBrowser(Plugin):
--edge (Shortcut for "--browser=edge".)
--firefox (Shortcut for "--browser=firefox".)
--safari (Shortcut for "--browser=safari".)
+ --cft (Shortcut for using `Chrome for Testing`)
+ --chs (Shortcut for using `Chrome-Headless-Shell`)
--user-data-dir=DIR (Set the Chrome user data directory to use.)
--protocol=PROTOCOL (The Selenium Grid protocol: http|https.)
--server=SERVER (The Selenium Grid server/IP used for tests.)
@@ -1221,6 +1223,7 @@ def beforeTest(self, test):
test.test.binary_location = "cft"
elif self.options.use_chs and not test.test.binary_location:
test.test.binary_location = "chs"
+ sb_config.binary_location = test.test.binary_location
if (
test.test.binary_location
and test.test.binary_location.lower() == "chs"
diff --git a/setup.py b/setup.py
index 1931a3333d2..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",
],
@@ -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",