diff --git a/README.md b/README.md index ac55ae29be3..ec51798ffaa 100755 --- a/README.md +++ b/README.md @@ -62,9 +62,9 @@ -------- -

📓 Here's the stealthy architecture overview:

+

⚙️ Stealthy architecture flowchart:

-Stealthy architecture overview +Stealthy architecture flowchart (For maximum stealth, use CDP Mode, which includes Stealthy Playwright Mode) @@ -80,6 +80,7 @@ sb = sb_cdp.Chrome(url) elements = sb.find_elements("span.titleline > a") for element in elements: print("* " + element.text) +sb.driver.stop() ``` -------- @@ -491,6 +492,7 @@ class MyTestClass(BaseCase): ```python self.open(url) # Navigate the browser window to the URL. +self.activate_cdp_mode() # Activate CDP Mode from UC Mode. self.type(selector, text) # Update the field with the text. self.click(selector) # Click the element with the selector. self.click_link(link_text) # Click the link containing text. @@ -579,7 +581,7 @@ pytest [FILE_NAME.py]::[CLASS_NAME]::[METHOD_NAME] pynose [FILE_NAME.py]:[CLASS_NAME].[METHOD_NAME] ``` -

✅ No More Flaky Tests! SeleniumBase methods automatically wait for page elements to finish loading before interacting with them (up to a timeout limit). This means you no longer need random time.sleep() statements in your scripts.

+

✅ No More Flaky Tests! SeleniumBase methods automatically wait for page elements to finish loading before interacting with them (up to a timeout limit).

NO MORE FLAKY TESTS! ✅ SeleniumBase supports all major browsers and operating systems: diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 830eb00bc33..6ba8a2d2852 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -6,19 +6,17 @@ ---- -⚙️ This diagram shows the stealthy architecture with CDP Mode: +

⚙️ Stealthy architecture flowchart:

-High-Level Stealthy Architecture Overview +Stealthy architecture flowchart ---- -### 🎞️ YouTube tutorials that cover CDP Mode: +### 🎞️ YouTube videos about CDP Mode:

(Watch "Undetectable Automation 4" on YouTube! ▶️)

-(See `examples/cdp_mode/` for up-to-date examples.) - ---- @@ -27,7 +25,7 @@ ---- -

(Watch "Web-Scraping with GitHub Actions" on YouTube! ▶️)

+

("Unlimited Free Web-Scraping with GitHub Actions" ▶️)

---- @@ -403,12 +401,12 @@ sb.cdp.select(selector, timeout=None) sb.cdp.select_all(selector, timeout=None) sb.cdp.find_elements(selector, timeout=None) sb.cdp.find_visible_elements(selector, timeout=None) -sb.cdp.click(selector, timeout=None) -sb.cdp.click_if_visible(selector, timeout=0) -sb.cdp.click_visible_elements(selector, limit=0) -sb.cdp.click_nth_element(selector, number) -sb.cdp.click_nth_visible_element(selector, number) -sb.cdp.click_with_offset(selector, x, y, center=False) +sb.cdp.click(selector, timeout=None, scroll=True) +sb.cdp.click_if_visible(selector, timeout=0, scroll=True) +sb.cdp.click_visible_elements(selector, limit=0, scroll=True) +sb.cdp.click_nth_element(selector, number, scroll=True) +sb.cdp.click_nth_visible_element(selector, number, scroll=True) +sb.cdp.click_with_offset(selector, x, y, center=False, scroll=True) sb.cdp.click_link(link_text) sb.cdp.go_back() sb.cdp.go_forward() @@ -428,7 +426,7 @@ sb.cdp.bring_to_front() sb.cdp.get_active_element() sb.cdp.get_active_element_css() sb.cdp.click_active_element() -sb.cdp.mouse_click(selector, timeout=None) +sb.cdp.mouse_click(selector, timeout=None, scroll=True) sb.cdp.nested_click(parent_selector, selector) sb.cdp.get_nested_element(parent_selector, selector) sb.cdp.select_option_by_text(dropdown_selector, option) diff --git a/help_docs/ReadMe.md b/help_docs/ReadMe.md index aac2feb83f9..49839c38824 100644 --- a/help_docs/ReadMe.md +++ b/help_docs/ReadMe.md @@ -1,6 +1,6 @@ -

SeleniumBase

+

SeleniumBase

Help Docs

@@ -157,6 +157,6 @@ --------

-SeleniumBase +SeleniumBase

diff --git a/help_docs/cdp_mode_methods.md b/help_docs/cdp_mode_methods.md index 0a9dc7f2ff7..67f86baf2d9 100644 --- a/help_docs/cdp_mode_methods.md +++ b/help_docs/cdp_mode_methods.md @@ -29,12 +29,12 @@ sb.cdp.select(selector, timeout=None) sb.cdp.select_all(selector, timeout=None) sb.cdp.find_elements(selector, timeout=None) sb.cdp.find_visible_elements(selector, timeout=None) -sb.cdp.click(selector, timeout=None) -sb.cdp.click_if_visible(selector) -sb.cdp.click_visible_elements(selector, limit=0) -sb.cdp.click_nth_element(selector, number) -sb.cdp.click_nth_visible_element(selector, number) -sb.cdp.click_with_offset(selector, x, y, center=False) +sb.cdp.click(selector, timeout=None, scroll=True) +sb.cdp.click_if_visible(selector, timeout=0, scroll=True) +sb.cdp.click_visible_elements(selector, limit=0, scroll=True) +sb.cdp.click_nth_element(selector, number, scroll=True) +sb.cdp.click_nth_visible_element(selector, number, scroll=True) +sb.cdp.click_with_offset(selector, x, y, center=False, scroll=True) sb.cdp.click_link(link_text) sb.cdp.go_back() sb.cdp.go_forward() @@ -54,7 +54,7 @@ sb.cdp.bring_to_front() sb.cdp.get_active_element() sb.cdp.get_active_element_css() sb.cdp.click_active_element() -sb.cdp.mouse_click(selector, timeout=None) +sb.cdp.mouse_click(selector, timeout=None, scroll=True) sb.cdp.nested_click(parent_selector, selector) sb.cdp.get_nested_element(parent_selector, selector) sb.cdp.select_option_by_text(dropdown_selector, option) diff --git a/help_docs/features_list.md b/help_docs/features_list.md index cdf2fdb9a86..dd8106376a3 100644 --- a/help_docs/features_list.md +++ b/help_docs/features_list.md @@ -1,36 +1,29 @@ - -

(Watch the SeleniumBase tutorial from Selenium-Conf-2023 on YouTube)

-

SeleniumBase Features: 🏰

* A powerful Python framework for browser automation and E2E UI testing. -* Includes [Recorder Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md) for instantly generating browser tests in Python. -* Supports multiple browsers, tabs, iframes, and proxies in the same test. -* Includes [Test Case Management Software](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/case_plans.md) with Markdown technology. +* Has [CDP Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md) for bypassing bot-detection. (`activate_cdp_mode(url)`) +* [Stealthy Playwright Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/playwright/ReadMe.md) lets you bypass bot-detection with Playwright. +* There's [Recorder Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md) for instantly generating browser tests in Python. +* Supports [pytest](https://docs.pytest.org/en/latest/), [unittest](https://docs.python.org/3/library/unittest.html), [nose](http://nose.readthedocs.io/en/latest/), and [behave](https://behave.readthedocs.io/en/stable/index.html) for finding & running tests. * Automatic smart-waiting improves reliability and prevents flaky tests. -* Supports [pytest](https://docs.pytest.org/en/latest/), [unittest](https://docs.python.org/3/library/unittest.html), [nose](http://nose.readthedocs.io/en/latest/), and [behave](https://behave.readthedocs.io/en/stable/index.html) for finding/running tests. * All the code is open source. Look inside to learn about any feature. * Powerful logging tools for [dashboards, reports, and screenshots](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md). -* Can run tests in Headless Mode to hide the browser. (``--headless``) -* Can run tests multithreaded from parallel browsers. (``-n NUM_THREADS``) -* Can run tests from a shared browser session. (``--reuse-session``/``--rs``) -* Can run tests using [Chromium's mobile device emulator](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/mobile_testing.md). (``--mobile``) -* Can run tests through a proxy server. (``--proxy=IP_ADDRESS:PORT``) -* Can run tests with proxy settings via PAC URL. (``--proxy-pac-url=URL.pac``) -* Can run tests through an authenticated proxy server. (``--proxy=USER:PASS@HOST:PORT``) -* Can run tests with proxy+auth via PAC URL. (``--proxy-pac-url=USER:PASS@URL.pac``) -* Can run tests with a customized browser user agent. (``--agent=USER_AGENT_STRING``) -* Can set a Chromium User Data Directory/Profile to load. (``--user-data-dir=DIR``) -* Can [avoid detection](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md) by sites that try to block Selenium. (``--undetected``/``--uc``) -* Can [use CDP Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md) for advanced stealth abilities. (``activate_cdp_mode(url)``) -* Can integrate with [selenium-wire](https://github.com/wkeeling/selenium-wire) for inspecting browser requests. (``--wire``) -* Can load Chrome Extension ZIP files. (``--extension-zip=ZIP``) -* Can load Chrome Extension folders. (``--extension-dir=DIR``) -* Powerful [console scripts](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/console_scripts/ReadMe.md). (Type **``seleniumbase``** or **``sbase``** to use.) +* Can run tests in Headless Mode to hide the browser. (`--headless`) +* Can run tests multithreaded from parallel browsers. (`-n NUM_THREADS`) +* Can run tests from a shared browser session. (`--reuse-session`/`--rs`) +* Can run tests using [Chromium's mobile device emulator](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/mobile_testing.md). (`--mobile`) +* Can run tests through a proxy server. (`--proxy=IP_ADDRESS:PORT`) +* Can run tests with proxy settings via PAC URL. (`--proxy-pac-url=URL.pac`) +* Can run tests through an authenticated proxy server. (`--proxy=USER:PASS@HOST:PORT`) +* Can run tests with proxy+auth via PAC URL. (`--proxy-pac-url=USER:PASS@URL.pac`) +* Can run tests with a customized browser user agent. (`--agent=USER_AGENT_STRING`) +* Can set a Chromium User Data Directory to load. (`--user-data-dir=DIR`) +* Can load Chromium Extension folders. (`--extension-dir=DIR`) +* Powerful [console scripts](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/console_scripts/ReadMe.md). (Type **`seleniumbase`** or **`sbase`** to use.) * Has the ability to translate tests into [multiple spoken languages](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/translations). * Has a flexible [command-line interface](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md) for customizing test runs. * Has a [global config file](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) for configuring settings as needed. @@ -43,19 +36,13 @@ * Can handle Google Authenticator logins with [Python's one-time password library](https://pyotp.readthedocs.io/en/latest/). * Can load and make assertions on PDF files from websites or the local file system. * Can inspect HTML to find issues and points of interest with the [HTML Inspector](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/html_inspector.md). -* Is backwards-compatible with Python [WebDriver](https://www.selenium.dev/projects/) methods. (Use: ``self.driver``) -* Can execute JavaScript code from Python calls. (Use: ``self.execute_script()``) -* Can pierce through [Shadow DOM selectors](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/shadow_dom.md). (Add ``::shadow`` to CSS fragments.) +* Is backwards-compatible with Python [WebDriver](https://www.selenium.dev/projects/) methods. (Use: `self.driver`) +* Can execute JavaScript code from Python calls. (Use: `self.execute_script()`) +* Can pierce through [Shadow DOM selectors](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/shadow_dom.md). (Add `::shadow` to CSS fragments.) * Includes a hybrid-automation solution, [MasterQA](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/masterqa/ReadMe.md), to speed up manual testing. +* Includes [Test Case Management Software](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/case_plans.md) with Markdown technology. * Includes useful [Python decorators and password obfuscation methods](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/common/ReadMe.md). -------- - -

(Have fun with test automation!)

- -(Watch the original tutorial on YouTube) - -

SeleniumBase

- - + diff --git a/help_docs/how_it_works.md b/help_docs/how_it_works.md index ff5f6765f70..6a68889afc3 100644 --- a/help_docs/how_it_works.md +++ b/help_docs/how_it_works.md @@ -6,27 +6,27 @@ 👁️🔎 The primary [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) works by extending [pytest](https://docs.pytest.org/en/latest/) as a direct plugin. SeleniumBase automatically spins up web browsers for tests, and then gives those tests access to the SeleniumBase libraries through the [BaseCase class](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py). Tests are also given access to [SeleniumBase command-line options](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md) and [SeleniumBase methods](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md). -👁️🔎 ``pytest`` uses a feature called test discovery to automatically find and run Python methods that start with ``test_`` when those methods are located in Python files that start with ``test_`` or end with ``_test.py``. +👁️🔎 `pytest` uses a feature called test discovery to automatically find and run Python methods that start with `test_` when those methods are located in Python files that start with `test_` or end with `_test.py`/`_tests.py`. -👁️🔎 The primary [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) starts by importing ``BaseCase``: +👁️🔎 The primary [SeleniumBase syntax format](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md) starts by importing `BaseCase`: ```python from seleniumbase import BaseCase ``` -👁️🔎 This next line activates ``pytest`` when a file is called directly with ``python`` by accident: +👁️🔎 This next line activates `pytest` when a file is called directly with `python` by accident: ```python BaseCase.main(__name__, __file__) ``` -👁️🔎 Classes can inherit ``BaseCase`` to gain SeleniumBase functionality: +👁️🔎 Classes can inherit `BaseCase` to gain SeleniumBase functionality: ```python class MyTestClass(BaseCase): ``` -👁️🔎 Test methods inside ``BaseCase`` classes become SeleniumBase tests: (These tests automatically launch a web browser before starting, and quit the web browser after ending. Default settings can be changed via command-line options.) +👁️🔎 Test methods inside `BaseCase` classes become SeleniumBase tests: (These tests automatically launch a web browser before starting, and quit the web browser after ending. Default settings can be changed via command-line options.) ```python class MyTestClass(BaseCase): @@ -34,7 +34,7 @@ class MyTestClass(BaseCase): # ... ``` -👁️🔎 [SeleniumBase APIs](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md) can be called from tests via ``self``: +👁️🔎 [SeleniumBase APIs](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md) can be called from tests via `self`: ```python class MyTestClass(BaseCase): @@ -63,7 +63,7 @@ class TestSimpleLogin(BaseCase): (See the example, [test_simple_login.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/test_simple_login.py), for reference.) -👁️🔎 Here are some examples of running tests with ``pytest``: +👁️🔎 Here are some examples of running tests with `pytest`: ```zsh pytest test_mfa_login.py @@ -101,22 +101,22 @@ finally: ### ✅ No More Flaky Tests! -

SeleniumBase methods automatically wait for page elements to finish loading before interacting with them (up to a timeout limit). This means you no longer need random time.sleep() statements in your scripts.

+

SeleniumBase methods automatically wait for page elements to finish loading before interacting with them (up to a timeout limit).

NO MORE FLAKY TESTS! **There are three layers of protection that provide reliability for tests using SeleniumBase:** -* **(1)**: Selenium's default ``pageLoadStrategy`` is ``normal``: This strategy causes Selenium to wait for the full page to load, with HTML content and sub-resources downloaded and parsed. +* **(1)**: Selenium's default `pageLoadStrategy` is `normal`: This strategy causes Selenium to wait for the full page to load, with HTML content and sub-resources downloaded and parsed. -* **(2)**: SeleniumBase includes methods such as ``wait_for_ready_state_complete()``, which run inside other SeleniumBase methods to ensure that it's safe to proceed with the next command. +* **(2)**: SeleniumBase includes methods such as `wait_for_ready_state_complete()`, which run inside other SeleniumBase methods to ensure that it's safe to proceed with the next command. * **(3)**: SeleniumBase methods automatically wait for elements to be visible and interactable before interacting with those elements. **If you want to speed up your tests and you think the third level of protection is enough by itself, you can use command-line options to remove the first, the second, or both of those first two levels of protection:** -* ``--pls=none`` --> Set ``pageLoadStrategy`` to ``"none"``: This strategy causes Selenium to return immediately after the initial HTML content is fully received by the browser. +* `--pls=none` --> Set `pageLoadStrategy` to "none": This strategy causes Selenium to return immediately after the initial HTML content is fully received by the browser. -* ``--sjw`` --> Skip JS Waits, such as ``wait_for_ready_state_complete()``. +* `--sjw` --> Skip JS Waits, such as `wait_for_ready_state_complete()`. -------- diff --git a/help_docs/js_package_manager.md b/help_docs/js_package_manager.md index e7c942a453b..26390f356ec 100644 --- a/help_docs/js_package_manager.md +++ b/help_docs/js_package_manager.md @@ -8,7 +8,7 @@ 🎦 (Demo Mode) -🚎 (Website Tours) +🚎 (Tour Maker) 🎞️ (Presentation Maker) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index 77a70edf85d..fc3894bda9f 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -90,14 +90,16 @@ self.find_elements(selector, by="css selector", limit=0) # Duplicates: # self.select_all(selector, by="css selector", limit=0) self.find_visible_elements(selector, by="css selector", limit=0) -self.click_visible_elements(selector, by="css selector", limit=0, timeout=None) -self.click_nth_visible_element(selector, number, by="css selector", timeout=None) -self.click_if_visible(selector, by="css selector", timeout=0) +self.click_visible_elements( + selector, by="css selector", limit=0, timeout=None, scroll=True) +self.click_nth_visible_element( + selector, number, by="css selector", timeout=None, scroll=True) +self.click_if_visible(selector, by="css selector", timeout=0, scroll=True) self.click_active_element() self.click_with_offset( - selector, x, y, by="css selector", mark=None, timeout=None, center=None) + selector, x, y, by="css selector", mark=None, timeout=None, center=None, scroll=True) self.double_click_with_offset( - selector, x, y, by="css selector", mark=None, timeout=None, center=None) + selector, x, y, by="css selector", mark=None, timeout=None, center=None, scroll=True) self.is_checked(selector, by="css selector", timeout=None) # Duplicates: # self.is_selected(selector, by="css selector", timeout=None) diff --git a/mkdocs.yml b/mkdocs.yml index 2a3810420ab..77e7b27d757 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -92,11 +92,13 @@ plugins: nav: - ✅ SeleniumBase README: README.md - 🏰 List of Features: help_docs/features_list.md + - 🐙 CDP Mode: examples/cdp_mode/ReadMe.md + - 🎭 Stealthy Playwright: examples/cdp_mode/playwright/ReadMe.md - 📚 Running Example Tests: examples/ReadMe.md - 🎛️ Command Line Options: help_docs/customizing_test_runs.md - 🪄 Console Scripts: seleniumbase/console_scripts/ReadMe.md - 📊 Dashboard / Reports: examples/example_logs/ReadMe.md - - 🔡 Syntax Formats: help_docs/syntax_formats.md + - 🔠 Syntax Formats: help_docs/syntax_formats.md - 🎖️ GUI / Commander: help_docs/commander.md - 🔴 Recorder Mode: help_docs/recorder_mode.md - 📘 API Reference: help_docs/method_summary.md @@ -115,20 +117,19 @@ nav: - 📶 Chart Maker: examples/chart_maker/ReadMe.md - 🎞️ Presentation Maker: examples/presenter/ReadMe.md - Integrations: + - 🐳 Docker: integrations/docker/ReadMe.md - 👤 UC Mode: help_docs/uc_mode.md - - 🐙 CDP Mode: examples/cdp_mode/ReadMe.md - - 🎭 Stealthy Playwright: examples/cdp_mode/playwright/ReadMe.md - 🤖 GitHub CI: integrations/github/workflows/ReadMe.md - 🛂 MasterQA: seleniumbase/masterqa/ReadMe.md - 🗂️ Case Plans: help_docs/case_plans.md - 📱 Mobile Mode: help_docs/mobile_testing.md - 🌐 Selenium Grid: seleniumbase/utilities/selenium_grid/ReadMe.md - 🖼️ Visual Testing: examples/visual_testing/ReadMe.md - - 🕵️ The HTML Inspector: help_docs/html_inspector.md - 🤖 Azure Pipelines: integrations/azure/azure_pipelines/ReadMe.md - 🤖 Jenkins on Azure: integrations/azure/jenkins/ReadMe.md - 🤖 Jenkins on Google Cloud: integrations/google_cloud/ReadMe.md - - 🤖 NodeJS Test Runner: https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/node_js + - 🕵️ The HTML Inspector: help_docs/html_inspector.md + - 🤖 NodeJS Test Runners: https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/node_js - Presentations: - ✅ Core Presentation: https://seleniumbase.io/other/core_presentation.html - 🎞️ Presenter Demo: https://seleniumbase.io/other/presenter.html @@ -169,7 +170,6 @@ nav: - 🔐 Decorators / Security: seleniumbase/common/ReadMe.md - 🗂️ Case Plans (examples): examples/case_summary.md - 🧭 Using Safari Driver: help_docs/using_safari_driver.md - - 🐳 Docker Start Guide: integrations/docker/ReadMe.md - 👤 Shadow DOM Support: help_docs/shadow_dom.md - 👥 macOS Hidden Files: help_docs/hidden_files_info.md - 🗄️ MySQL Instructions: help_docs/mysql_installation.md diff --git a/mkdocs_build/prepare.py b/mkdocs_build/prepare.py index 81d33b1d4a5..9600d50f192 100644 --- a/mkdocs_build/prepare.py +++ b/mkdocs_build/prepare.py @@ -92,20 +92,16 @@ def main(*args, **kwargs): files_to_process.append(os.path.join(scanned_dir, dir_)) video_embed = ( - '
' - '
" + 'origin=https://seleniumbase.io&autoplay=0&' + 'cc_load_policy=0&cc_lang_pref=&iv_load_policy=1&' + 'loop=0&modestbranding=1&rel=0&fs=1&' + 'playsinline=0&autohide=2&theme=dark&color=red&' + 'controls=1&" class="__youtube_prefs__ no-lazyload" ' + 'title="YouTube player" allow="autoplay; encrypted-media" ' + 'allowfullscreen="" data-no-lazy="1">' ) updated_files_to_process = [] diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index c15bb5df5b2..c3076d8cb10 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -3,7 +3,7 @@ regex>=2026.2.28 pymdown-extensions>=10.21 -pipdeptree>=2.33.0 +pipdeptree>=2.34.0 python-dateutil>=2.8.2 Markdown==3.10.2 click==8.3.1 diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index dc6ed23571e..9dec5ae7a68 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.47.5" +__version__ = "4.47.6" diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index d74a03bcd6f..7722c0c6552 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -429,7 +429,7 @@ def find_visible_elements(self, selector, timeout=None): visible_elements.append(element) return visible_elements - def click_nth_element(self, selector, number): + def click_nth_element(self, selector, number, scroll=True): elements = self.select_all(selector) if len(elements) < number: raise Exception( @@ -440,10 +440,11 @@ def click_nth_element(self, selector, number): if number < 0: number = 0 element = elements[number] - element.scroll_into_view() + if scroll: + element.scroll_into_view() element.click() - def click_nth_visible_element(self, selector, number): + def click_nth_visible_element(self, selector, number, scroll=True): """Finds all matching page elements and clicks the nth visible one. Example: self.click_nth_visible_element('[type="checkbox"]', 5) (Clicks the 5th visible checkbox on the page.)""" @@ -457,7 +458,8 @@ def click_nth_visible_element(self, selector, number): if number < 0: number = 0 element = elements[number] - element.scroll_into_view() + if scroll: + element.scroll_into_view() element.click() def click_link(self, link_text): @@ -793,12 +795,13 @@ def get_active_element_css(self): js_code = js_code.replace("return getBestSelector", "getBestSelector") return self.loop.run_until_complete(self.page.evaluate(js_code)) - def click(self, selector, timeout=None): + def click(self, selector, timeout=None, scroll=True): if not timeout: timeout = settings.SMALL_TIMEOUT self.__slow_mode_pause_if_set() element = self.find_element(selector, timeout=timeout) - element.scroll_into_view() + if scroll: + element.scroll_into_view() tag_name = element.tag_name if tag_name: tag_name = tag_name.lower().strip() @@ -824,10 +827,10 @@ def click_active_element(self): self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.wait(0.2)) - def click_if_visible(self, selector, timeout=0): + def click_if_visible(self, selector, timeout=0, scroll=True): if self.is_element_visible(selector): with suppress(Exception): - self.click(selector, timeout=1) + self.click(selector, timeout=1, scroll=scroll) elif timeout == 0: return else: @@ -836,7 +839,7 @@ def click_if_visible(self, selector, timeout=0): if self.is_element_visible(selector): self.click(selector, timeout=1) - def click_visible_elements(self, selector, limit=0): + def click_visible_elements(self, selector, limit=0, scroll=True): """Finds all matching page elements and clicks visible ones in order. If a click reloads or opens a new page, the clicking will stop. If no matching elements appear, an Exception will be raised. @@ -859,7 +862,8 @@ def click_visible_elements(self, selector, limit=0): except Exception: continue if (width != 0 or height != 0): - element.scroll_into_view() + if scroll: + element.scroll_into_view() element.click() click_count += 1 time.sleep(0.044) @@ -868,13 +872,14 @@ def click_visible_elements(self, selector, limit=0): except Exception: break - def mouse_click(self, selector, timeout=None): + def mouse_click(self, selector, timeout=None, scroll=True): """(Attempt simulating a mouse click)""" if not timeout: timeout = settings.SMALL_TIMEOUT self.__slow_mode_pause_if_set() element = self.find_element(selector, timeout=timeout) - element.scroll_into_view() + if scroll: + element.scroll_into_view() element.mouse_click() self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.wait(0.2)) @@ -1970,9 +1975,10 @@ def gui_click_with_offset( py = element_rect["y"] self.gui_click_x_y(px + x, py + y, timeframe=timeframe) - def click_with_offset(self, selector, x, y, center=False): + def click_with_offset(self, selector, x, y, center=False, scroll=True): element = self.find_element(selector) - element.scroll_into_view() + if scroll: + element.scroll_into_view() if "--debug" in sys.argv: displayed_selector = "`%s`" % selector if '"' not in selector: diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 2a563b57881..ea2fab9129b 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -411,7 +411,7 @@ def click( original_by = by selector, by = self.__recalculate_selector(selector, by) if self.__is_cdp_swap_needed(): - self.cdp.click(selector, timeout=timeout) + self.cdp.click(selector, timeout=timeout, scroll=scroll) return if delay and (type(delay) in [int, float]) and delay > 0: time.sleep(delay) @@ -2254,7 +2254,7 @@ def find_visible_elements(self, selector, by="css selector", limit=0): ) def click_visible_elements( - self, selector, by="css selector", limit=0, timeout=None + self, selector, by="css selector", limit=0, timeout=None, scroll=True ): """Finds all matching page elements and clicks visible ones in order. If a click reloads or opens a new page, the clicking will stop. @@ -2270,7 +2270,7 @@ def click_visible_elements( timeout = self.__get_new_timeout(timeout) selector, by = self.__recalculate_selector(selector, by) if self.__is_cdp_swap_needed(): - self.cdp.click_visible_elements(selector, limit) + self.cdp.click_visible_elements(selector, limit, scroll=scroll) return self.wait_for_ready_state_complete() if self.__needs_minimum_wait(): @@ -2297,7 +2297,8 @@ def click_visible_elements( return try: if element.is_displayed(): - self.__scroll_to_element(element) + if scroll: + self.__scroll_to_element(element) if self.browser == "safari": self.execute_script("arguments[0].click();", element) else: @@ -2311,7 +2312,8 @@ def click_visible_elements( time.sleep(0.12) try: if element.is_displayed(): - self.__scroll_to_element(element) + if scroll: + self.__scroll_to_element(element) if self.browser == "safari": self.execute_script( "arguments[0].click();", element @@ -2348,7 +2350,7 @@ def click_visible_elements( self.__switch_to_newest_window_if_not_blank() def click_nth_visible_element( - self, selector, number, by="css selector", timeout=None + self, selector, number, by="css selector", timeout=None, scroll=True ): """Finds all matching page elements and clicks the nth visible one. Example: self.click_nth_visible_element('[type="checkbox"]', 5) @@ -2360,7 +2362,7 @@ def click_nth_visible_element( timeout = self.__get_new_timeout(timeout) selector, by = self.__recalculate_selector(selector, by) if self.__is_cdp_swap_needed(): - self.cdp.click_nth_visible_element(selector, number) + self.cdp.click_nth_visible_element(selector, number, scroll=scroll) return self.wait_for_ready_state_complete() self.wait_for_element_present(selector, by=by, timeout=timeout) @@ -2379,7 +2381,8 @@ def click_nth_visible_element( pre_action_url = self.driver.current_url pre_window_count = len(self.driver.window_handles) try: - self.__scroll_to_element(element) + if scroll: + self.__scroll_to_element(element) self.__element_click(element) except (Stale_Exception, ENI_Exception, ECI_Exception): time.sleep(0.12) @@ -2409,18 +2412,20 @@ def click_nth_visible_element( ): self.__switch_to_newest_window_if_not_blank() - def click_if_visible(self, selector, by="css selector", timeout=0): + def click_if_visible( + self, selector, by="css selector", timeout=0, scroll=True + ): """If the page selector exists and is visible, clicks on the element. This method only clicks on the first matching element found. Use click_visible_elements() to click all matching elements. If a "timeout" is provided, waits that long for the element to appear before giving up and returning without a click().""" if self.__is_cdp_swap_needed(): - self.cdp.click_if_visible(selector, timeout=timeout) + self.cdp.click_if_visible(selector, timeout=timeout, scroll=scroll) return self.wait_for_ready_state_complete() if self.is_element_visible(selector, by=by): - self.click(selector, by=by) + self.click(selector, by=by, scroll=scroll) elif timeout > 0: with suppress(Exception): self.wait_for_element_visible( @@ -2428,7 +2433,7 @@ def click_if_visible(self, selector, by="css selector", timeout=0): ) self.sleep(0.2) if self.is_element_visible(selector, by=by): - self.click(selector, by=by) + self.click(selector, by=by, scroll=scroll) def click_active_element(self): if self.__is_cdp_swap_needed(): @@ -2484,6 +2489,7 @@ def click_with_offset( mark=None, timeout=None, center=None, + scroll=True, ): """Click an element at an {X,Y}-offset location. {0,0} is the top-left corner of the element. @@ -2500,6 +2506,7 @@ def click_with_offset( mark=mark, timeout=timeout, center=center, + scroll=scroll, ) def double_click_with_offset( @@ -2511,6 +2518,7 @@ def double_click_with_offset( mark=None, timeout=None, center=None, + scroll=True, ): """Double click an element at an {X,Y}-offset location. {0,0} is the top-left corner of the element. @@ -2527,6 +2535,7 @@ def double_click_with_offset( mark=mark, timeout=timeout, center=center, + scroll=scroll, ) def is_checked(self, selector, by="css selector", timeout=None): @@ -14043,9 +14052,12 @@ def __click_with_offset( mark=None, timeout=None, center=None, + scroll=True, ): if self.__is_cdp_swap_needed(): - self.cdp.click_with_offset(selector, x, y, center=center) + self.cdp.click_with_offset( + selector, x, y, center=center, scroll=scroll + ) return self.wait_for_ready_state_complete() if self.__needs_minimum_wait(): @@ -14061,9 +14073,13 @@ def __click_with_offset( if self.demo_mode: self.__highlight(selector, by=by, loops=1) elif self.slow_mode: - self.__slow_scroll_to_element(element) + if scroll: + self.__slow_scroll_to_element(element) + else: + self.sleep(0.2) else: - self.__scroll_to_element(element, selector, by) + if scroll: + self.__scroll_to_element(element, selector, by) self.wait_for_ready_state_complete() if self.__needs_minimum_wait(): time.sleep(0.03) diff --git a/seleniumbase/undetected/cdp_driver/tab.py b/seleniumbase/undetected/cdp_driver/tab.py index 28b577cace9..0adfbc0d5c8 100644 --- a/seleniumbase/undetected/cdp_driver/tab.py +++ b/seleniumbase/undetected/cdp_driver/tab.py @@ -1629,11 +1629,17 @@ async def click_if_visible(self, selector, timeout=0): await element.click_async() async def click_with_offset(self, selector, x, y, center=False, timeout=5): + """Click an element at an {X,Y}-offset location. + {0,0} is the top-left corner of the element. + This method is used to click on CAPTCHAs.""" element = await self.find(selector, timeout=timeout) await element.scroll_into_view_async() await element.mouse_click_with_offset_async(x=x, y=y, center=center) async def solve_captcha(self): + """This method does a few things to click a CAPTCHA: + 1. Checks to see if a CAPTCHA is on the current page. + 2. If found, calls `click_with_offset(*)` to click it.""" await self.sleep(0.11) source = await self.get_html() if await self.__on_a_cf_turnstile_page(source):