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:

-```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/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/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/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",