From 0125a8dfa4cf8187377a68cb094bd1aa400244c0 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 30 Jun 2026 21:46:47 -0400 Subject: [PATCH 1/7] Add support for "socks5h" proxies without auth --- seleniumbase/console_scripts/sb_install.py | 2 ++ seleniumbase/core/browser_launcher.py | 6 +++++- seleniumbase/core/proxy_helper.py | 18 +++++++++--------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/seleniumbase/console_scripts/sb_install.py b/seleniumbase/console_scripts/sb_install.py index b753cf631de..9019abb4cfc 100644 --- a/seleniumbase/console_scripts/sb_install.py +++ b/seleniumbase/console_scripts/sb_install.py @@ -139,6 +139,8 @@ def get_proxy_info(): protocol = "https" elif "socks4" in proxy_string: protocol = "socks4" + elif "socks5h" in proxy_string: + protocol = "socks5h" elif "socks5" in proxy_string: protocol = "socks5" proxy_string = proxy_helper.validate_proxy_string(proxy_string) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 7d727eb6ade..b1250bbbe71 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -340,6 +340,8 @@ def requests_get(url, proxy_string=None): protocol = "https" elif "socks4" in proxy_string: protocol = "socks4" + elif "socks5h" in proxy_string: + protocol = "socks5h" elif "socks5" in proxy_string: protocol = "socks5" proxies = {protocol: proxy_string} @@ -2901,7 +2903,9 @@ def _set_firefox_options( socks_ver = 0 chunks = proxy_string.split(":") if len(chunks) == 3 and ( - chunks[0] == "socks4" or chunks[0] == "socks5" + chunks[0] == "socks4" + or chunks[0] == "socks5" + or chunks[0] == "socks5h" ): socks_proxy = True socks_ver = int(chunks[0][5]) diff --git a/seleniumbase/core/proxy_helper.py b/seleniumbase/core/proxy_helper.py index f7fa78e8b87..7c3c06ecc99 100644 --- a/seleniumbase/core/proxy_helper.py +++ b/seleniumbase/core/proxy_helper.py @@ -177,6 +177,8 @@ def validate_proxy_string(proxy_string, keep_scheme=False): proxy_scheme = "https" elif proxy_string.startswith("socks4://"): proxy_scheme = "socks4" + elif proxy_string.startswith("socks5h://"): + proxy_scheme = "socks5h" elif proxy_string.startswith("socks5://"): proxy_scheme = "socks5" valid = False @@ -189,8 +191,10 @@ def validate_proxy_string(proxy_string, keep_scheme=False): elif proxy_string.startswith("https://"): proxy_string = proxy_string.split("https://")[1] elif "://" in proxy_string: - if not proxy_string.startswith("socks4://") and not ( - proxy_string.startswith("socks5://") + if ( + not proxy_string.startswith("socks4://") + and not proxy_string.startswith("socks5://") + and not proxy_string.startswith("socks5h://") ): proxy_string = proxy_string.split("://")[1] chunks = proxy_string.split(":") @@ -201,13 +205,9 @@ def validate_proxy_string(proxy_string, keep_scheme=False): elif len(chunks) == 3: if re.match(r"^\d+$", chunks[2]): if page_utils.is_valid_url("http:" + ":".join(chunks[1:])): - if chunks[0] == "http": - valid = True - elif chunks[0] == "https": - valid = True - elif chunks[0] == "socks4": - valid = True - elif chunks[0] == "socks5": + if chunks[0] in [ + "http", "https", "socks4", "socks5", "socks5h" + ]: valid = True else: proxy_string = val_ip.group() From 7c937ed4a760522ca0d546ae859c016f1e098343 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 30 Jun 2026 21:48:24 -0400 Subject: [PATCH 2/7] Update how PDF downloads are handled --- seleniumbase/fixtures/base_case.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index eb2b10ea858..cb1e258028e 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -7776,15 +7776,30 @@ def get_pdf_text( file_path = None if page_utils.is_valid_url(pdf): downloads_folder = download_helper.get_downloads_folder() - if nav: - if self.get_current_url() != pdf: - self.open(pdf) file_name = pdf.split("/")[-1] file_path = os.path.join(downloads_folder, file_name) - if not os.path.exists(file_path): - self.download_file(pdf) - elif override: - self.download_file(pdf) + if nav and not self.external_pdf: + self.open(pdf) + if (self.undetectable and self.external_pdf): + just_opened = False + if ( + self.get_current_url() != pdf + and not os.path.exists( + os.path.join(downloads_folder, file_name) + ) + ): + just_opened = True + self.open(pdf) + if self.external_pdf: + self.assert_downloaded_file(file_name, timeout=10) + elif self.undetectable and just_opened: + self.sleep(3) + self.save_as_pdf(file_name, folder=downloads_folder) + else: + self.save_as_pdf(file_name, folder=downloads_folder) + else: + if not os.path.exists(file_path) or override: + self.download_file(pdf) else: if not os.path.exists(pdf): raise Exception("%s is not a valid URL or file path!" % pdf) From 9ac19fbb520957429e062d952b8863210469dd3a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 30 Jun 2026 21:54:19 -0400 Subject: [PATCH 3/7] Update console scripts --- seleniumbase/console_scripts/run.py | 78 +++++++++++----------- seleniumbase/console_scripts/sb_install.py | 66 +++++++++--------- 2 files changed, 73 insertions(+), 71 deletions(-) diff --git a/seleniumbase/console_scripts/run.py b/seleniumbase/console_scripts/run.py index 6cf3fa357c7..4eb97262a61 100644 --- a/seleniumbase/console_scripts/run.py +++ b/seleniumbase/console_scripts/run.py @@ -52,15 +52,15 @@ def show_basic_usage(): seleniumbase_logo = logo_helper.get_seleniumbase_logo() print(seleniumbase_logo) - time.sleep(0.035) + time.sleep(0.044) print("") - time.sleep(0.031) + time.sleep(0.034) show_package_location() - time.sleep(0.031) + time.sleep(0.034) show_version_info() - time.sleep(0.031) + time.sleep(0.034) print("") - time.sleep(0.555) # Enough time to see the logo & version + time.sleep(0.468) # Enough time to see the logo & version sc = "" sc += "╭──────────────────────────────────────────────────╮\n" sc += '│ * USAGE: "seleniumbase [COMMAND] [PARAMETERS]" │\n' @@ -134,40 +134,40 @@ def show_install_usage(): sc = " " + c2 + "** " + c3 + "get / install" + c2 + " **" + cr print(sc) print("") - print(" Usage:") - print(" seleniumbase install [DRIVER_NAME] [OPTIONS]") - print(" OR: seleniumbase get [DRIVER_NAME] [OPTIONS]") - print(" OR: sbase install [DRIVER_NAME] [OPTIONS]") - print(" OR: sbase get [DRIVER_NAME] [OPTIONS]") - print(" (Drivers: chromedriver, cft, uc_driver,") - print(" edgedriver, chs, geckodriver)") - print(" Options:") - print(" VERSION Specify the version to download.") - print(" Tries to detect the needed version.") - print(" If using chromedriver or edgedriver,") - print(" you can use the major version integer.") - print() - print(" -p / --path Also copy driver to /usr/local/bin") - print(" Examples:") - print(" sbase get chromedriver") - print(" sbase get geckodriver") - print(" sbase get edgedriver") - print(" sbase get chromedriver 149") - print(" sbase get chromedriver 149.0.7827.115") - print(" sbase get chromedriver stable") - print(" sbase get chromedriver beta") - print(" sbase get chromedriver -p") - print(" sbase get chromium") - print(" sbase get chromium --revision=1639046") - print(" sbase get cft 149") - print(" sbase get chs") - print(" Output:") - print(" Downloads the webdriver to seleniumbase/drivers/") - print(" (chromedriver is required for Chrome automation)") - print(" (geckodriver is required for Firefox automation)") - print(" (edgedriver is required for MS__Edge automation)") - print(" (cft is for the `Chrome for Testing` binary exe)") - print(" (chs is for the `Chrome-Headless-Shell` binary.)") + print("Usage:") + print(" seleniumbase install [DRIVER/BINARY] [OPTIONS]") + print(" OR: seleniumbase get [DRIVER/BINARY] [OPTIONS]") + print(" OR: sbase install [DRIVER/BINARY] [OPTIONS]") + print(" OR: sbase get [DRIVER/BINARY] [OPTIONS]") + print(" (Drivers: chromedriver, uc_driver,") + print(" edgedriver, geckodriver)") + print(" (Binaries: cft, chs, chromium)") + print("Options:") + print(" VERSION Driver/binary version to download.") + print(" (Otherwise detects the version)") + print(" --revision The Chromium revision to download.") + print(" (Otherwise downloads the latest)") + print(" -p OR --path Also copy driver to /usr/local/bin") + print("Examples:") + print(" sbase get chromedriver") + print(" sbase get geckodriver") + print(" sbase get edgedriver") + print(" sbase get chromedriver 149") + print(" sbase get chromedriver 149.0.7827.155") + print(" sbase get chromedriver stable") + print(" sbase get chromedriver beta") + print(" sbase get chromedriver -p") + print(" sbase get chromium") + print(" sbase get chromium --revision=1639046") + print(" sbase get cft 149") + print(" sbase get chs") + print("Output:") + print(" Downloads driver/binary to seleniumbase/drivers/") + print(" (chromedriver is for Selenium Chrome automation)") + print(" (geckodriver is for Selenium Firefox automation)") + print(" (edgedriver is for Selenium MS__Edge automation)") + print(" (cft is for the `Chrome for Testing` binary exe)") + print(" (chs is for the `Chrome-Headless-Shell` binary.)") print("") diff --git a/seleniumbase/console_scripts/sb_install.py b/seleniumbase/console_scripts/sb_install.py index 9019abb4cfc..bfd9bbcd0f6 100644 --- a/seleniumbase/console_scripts/sb_install.py +++ b/seleniumbase/console_scripts/sb_install.py @@ -71,38 +71,40 @@ def invalid_run_command(): exp = " ** get / install **\n\n" - exp += " Usage:\n" - exp += " seleniumbase install [DRIVER_NAME] [OPTIONS]\n" - exp += " OR sbase install [DRIVER_NAME] [OPTIONS]\n" - exp += " OR seleniumbase get [DRIVER_NAME] [OPTIONS]\n" - exp += " OR sbase get [DRIVER_NAME] [OPTIONS]\n" - exp += " (Drivers: chromedriver, cft, uc_driver,\n" - exp += " edgedriver, chs, geckodriver)\n" - exp += " Options:\n" - exp += " VERSION Specify the version.\n" - exp += " Tries to detect the needed version.\n" - exp += " If using chromedriver or edgedriver,\n" - exp += " you can use the major version integer.\n" - exp += "\n" - exp += " -p OR --path Also copy the driver to /usr/local/bin\n" - exp += " Examples:\n" - exp += " sbase get chromedriver\n" - exp += " sbase get geckodriver\n" - exp += " sbase get edgedriver\n" - exp += " sbase get chromedriver 114\n" - exp += " sbase get chromedriver 114.0.5735.90\n" - exp += " sbase get chromedriver stable\n" - exp += " sbase get chromedriver beta\n" - exp += " sbase get chromedriver -p\n" - exp += " sbase get cft 131\n" - exp += " sbase get chs\n" - exp += " Output:\n" - exp += " Downloads the webdriver to seleniumbase/drivers/\n" - exp += " (chromedriver is required for Chrome automation)\n" - exp += " (geckodriver is required for Firefox automation)\n" - exp += " (edgedriver is required for MS__Edge automation)\n" - exp += " (cft is for the `Chrome for Testing` binary exe)\n" - exp += " (chs is for the `Chrome-Headless-Shell` binary.)\n" + exp += "Usage:\n" + exp += " seleniumbase install [DRIVER/BINARY] [OPTIONS]\n" + exp += " OR sbase install [DRIVER/BINARY] [OPTIONS]\n" + exp += " OR seleniumbase get [DRIVER/BINARY] [OPTIONS]\n" + exp += " OR sbase get [DRIVER/BINARY] [OPTIONS]\n" + exp += " (Drivers: chromedriver, uc_driver,\n" + exp += " edgedriver, geckodriver)\n" + exp += " (Binaries: cft, chs, chromium)\n" + exp += "Options:\n" + exp += " VERSION Driver/binary version to download.\n" + exp += " (Otherwise detects the version)\n" + exp += " --revision The Chromium revision to download.\n" + exp += " (Otherwise downloads the latest)\n" + exp += " -p OR --path Also copy driver to /usr/local/bin\n" + exp += "Examples:\n" + exp += " sbase get chromedriver\n" + exp += " sbase get geckodriver\n" + exp += " sbase get edgedriver\n" + exp += " sbase get chromedriver 149\n" + exp += " sbase get chromedriver 149.0.7827.155\n" + exp += " sbase get chromedriver stable\n" + exp += " sbase get chromedriver beta\n" + exp += " sbase get chromedriver -p\n" + exp += " sbase get chromium\n" + exp += " sbase get chromium --revision=1639046\n" + exp += " sbase get cft 149\n" + exp += " sbase get chs\n" + exp += "Output:\n" + exp += " Downloads driver/binary to seleniumbase/drivers/\n" + exp += " (chromedriver is for Selenium Chrome automation)\n" + exp += " (geckodriver is for Selenium Firefox automation)\n" + exp += " (edgedriver is for Selenium MS__Edge automation)\n" + exp += " (cft is for the `Chrome for Testing` binary exe)\n" + exp += " (chs is for the `Chrome-Headless-Shell` binary.)\n" print("") raise Exception("%s\n\n%s" % (constants.Warnings.INVALID_RUN_COMMAND, exp)) From 0a5c44d1b6026b115c2ef257004465a21a9d6ea7 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 30 Jun 2026 21:55:39 -0400 Subject: [PATCH 4/7] Refresh optional Python dependencies --- mkdocs_build/requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 034462cbd96..07207d3571b 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -1,7 +1,7 @@ # mkdocs dependencies for generating the seleniumbase.io website # Minimum Python version: 3.10 (for generating docs only) -regex>=2026.5.9 +regex>=2026.6.28 pymdown-extensions>=10.21.3 pipdeptree>=3.1.0 python-dateutil>=2.8.2 diff --git a/setup.py b/setup.py index 0bb09fdbd42..24cad7996c8 100755 --- a/setup.py +++ b/setup.py @@ -301,7 +301,7 @@ "playwright": [ 'playwright>=1.60.0', 'greenlet>=3.2.5;python_version<"3.10"', - 'greenlet>=3.5.1;python_version>="3.10"', + 'greenlet>=3.5.3;python_version>="3.10"', ], # pip install -e .[pyautogui] # (Already a required dependency on Linux now.) From a022ada3f823bd6058074ef99da47f12fd218202 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 30 Jun 2026 21:58:21 -0400 Subject: [PATCH 5/7] Update documentation and comments --- README.md | 1 + examples/translations/ReadMe.md | 32 ++++++++++++---------- help_docs/customizing_test_runs.md | 15 ++++++---- help_docs/hidden_files_info.md | 3 +- help_docs/mysql_installation.md | 2 +- help_docs/shadow_dom.md | 6 ++-- help_docs/syntax_formats.md | 31 +++++++++++---------- help_docs/translations.md | 32 +++++++++++----------- help_docs/using_safari_driver.md | 4 +-- help_docs/verify_webdriver.md | 6 ++-- seleniumbase/console_scripts/ReadMe.md | 2 +- seleniumbase/console_scripts/sb_install.py | 31 +++++++++++++-------- seleniumbase/undetected/cdp_driver/tab.py | 26 ++++++++---------- 13 files changed, 103 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index e1889da3e36..4255d326530 100755 --- a/README.md +++ b/README.md @@ -763,6 +763,7 @@ pytest test_coffee_cart.py --trace --brave # (Shortcut for "--browser=brave".) --comet # (Shortcut for "--browser=comet".) --atlas # (Shortcut for "--browser=atlas".) +--chromium # (Shortcut for using base `Chromium`) --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/examples/translations/ReadMe.md b/examples/translations/ReadMe.md index c01698eee1e..c47c29c9979 100644 --- a/examples/translations/ReadMe.md +++ b/examples/translations/ReadMe.md @@ -1,3 +1,5 @@ + +

🌏 Translated Tests 🈺

@@ -48,7 +50,7 @@ class 私のテストクラス(セレニウムテストケース):

Translation API 🈺

-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. +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. ```zsh seleniumbase translate @@ -59,19 +61,19 @@ seleniumbase translate seleniumbase translate [SB_FILE.py] [LANGUAGE] [ACTION] * Languages: -``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` -``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` -``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` -``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` -``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` +`--en` / `--English` | `--zh` / `--Chinese` +`--nl` / `--Dutch` | `--fr` / `--French` +`--it` / `--Italian` | `--ja` / `--Japanese` +`--ko` / `--Korean` | `--pt` / `--Portuguese` +`--ru` / `--Russian` | `--es` / `--Spanish` * Actions: -``-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) +`-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) * Options: -``-n`` (include line Numbers when using the Print action) +`-n` (include line Numbers when using the Print action) * Examples: Translate test_1.py into Chinese and only print the output: @@ -83,14 +85,14 @@ Translate test_3.py into Dutch and make a copy of the file: * Output: Translates a SeleniumBase Python file into the language -specified. Method calls and ``import`` lines get swapped. +specified. Method calls and `import` lines get swapped. Both a language and an action must be specified. -The ``-p`` action can be paired with one other action. -When running with ``-c`` (or ``--copy``) the new file name +The `-p` action can be paired with one other action. +When running with `-c` (or `--copy`) the new file name will be the original name appended with an underscore plus the 2-letter language code of the new language. -(Example: Translating ``test_1.py`` into Japanese with -``-c`` will create a new file called ``test_1_ja.py``.) +(Example: Translating `test_1.py` into Japanese with +`-c` will create a new file called `test_1_ja.py`.) ``` -------- diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md index da2aa902a91..45eb48df9e3 100644 --- a/help_docs/customizing_test_runs.md +++ b/help_docs/customizing_test_runs.md @@ -132,6 +132,11 @@ pytest my_first_test.py --settings-file=custom_settings.py --edge # (Shortcut for "--browser=edge".) --firefox # (Shortcut for "--browser=firefox".) --safari # (Shortcut for "--browser=safari".) +--opera # (Shortcut for "--browser=opera".) +--brave # (Shortcut for "--browser=brave".) +--comet # (Shortcut for "--browser=comet".) +--atlas # (Shortcut for "--browser=atlas".) +--chromium # (Shortcut for using base `Chromium`) --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.) @@ -462,7 +467,7 @@ Visit Changing the default driver version: -🔵 By default, SeleniumBase will make sure that the major driver version matches the major browser version for Chromium tests. (Eg. If Chrome `117.X` is installed and you have chromedriver `117.X`, then nothing happens, but if you had chromedriver `116.X` instead, then SeleniumBase would download chromedriver `117.X` to match the browser version.) +🔵 By default, SeleniumBase will make sure that the major driver version matches the major browser version for Chromium tests. (Eg. If Chrome `149.X` is installed and you have chromedriver `149.X`, then nothing happens, but if you had chromedriver `148.X` instead, then SeleniumBase would download chromedriver `149.X` to match the browser version.) 🎛️ To change this default behavior, you can use: @@ -471,8 +476,8 @@ pytest --driver-version=VER ``` The `VER` in `--driver-version=VER` can be: -* A major driver version. Eg. `117`. (milestone) -* An exact driver version. Eg. `117.0.5938.92`. +* A major driver version. Eg. `149`. (milestone) +* An exact driver version. Eg. `149.0.7827.155`. * `"browser"` (exact match on browser version) * `"keep"` (keep using the driver you already have) * `"latest"` / `"stable"` (latest stable version) @@ -513,10 +518,10 @@ With the `SB()` and `Driver()` formats, the binary location is set via the `bina sbase get chromium ``` -Then, run scripts with `--use-chromium` / `use_chromium=True`: +Then, run scripts with `--chromium` / `use_chromium=True`: ```zsh -pytest --use-chromium -n8 --dashboard --html=report.html -v --rs --headless +pytest --chromium -n8 --dashboard --html=report.html -v --rs --headless ``` -------- diff --git a/help_docs/hidden_files_info.md b/help_docs/hidden_files_info.md index 8d5d3fcec9f..b0b72d24c61 100644 --- a/help_docs/hidden_files_info.md +++ b/help_docs/hidden_files_info.md @@ -2,7 +2,7 @@ ## Showing hidden files on macOS -Depending on your macOS settings, some files may be hidden from view in your Finder window, such as ``.gitignore``. +Depending on your macOS settings, some files may be hidden from view in your Finder window, such as `.gitignore`. * On newer versions of macOS, use the following in a Finder window to view hidden files: @@ -18,6 +18,5 @@ defaults write com.apple.finder AppleShowAllFiles -bool true More info on that can be found here: diff --git a/help_docs/mysql_installation.md b/help_docs/mysql_installation.md index bfcda1c5a23..fc82c8314e7 100644 --- a/help_docs/mysql_installation.md +++ b/help_docs/mysql_installation.md @@ -87,7 +87,7 @@ Update your [settings.py](https://github.com/seleniumbase/SeleniumBase/blob/mast ### Have SeleniumBase tests write to your MySQL DB: -Add the ``--with-db_reporting`` argument on the command-line when you want tests to write to your MySQL database. Example: +Add the `--with-db_reporting` argument on the command-line when you want tests to write to your MySQL database. Example: ```zsh pytest --with-db_reporting diff --git a/help_docs/shadow_dom.md b/help_docs/shadow_dom.md index a0ef8bf9ae9..32f7fc80fae 100644 --- a/help_docs/shadow_dom.md +++ b/help_docs/shadow_dom.md @@ -4,7 +4,7 @@ ## Shadow DOM support / Shadow-root interaction -🔵 SeleniumBase lets you pierce through open Shadow DOM selectors (to interact with elements inside) by adding ``::shadow`` to CSS fragments that include a shadow-root element. For multi-layered shadow-roots, you must individually pierce through each shadow-root element that you want to get through. +🔵 SeleniumBase lets you pierce through open Shadow DOM selectors (to interact with elements inside) by adding `::shadow` to CSS fragments that include a shadow-root element. For multi-layered shadow-roots, you must individually pierce through each shadow-root element that you want to get through. 🔵 Here are some examples of Shadow DOM selectors: @@ -20,9 +20,9 @@ css_4 = "downloads-manager::shadow downloads-toolbar::shadow cr-toolbar::shadow css_5 = "downloads-manager::shadow downloads-toolbar::shadow cr-toolbar::shadow cr-toolbar-search-field::shadow #clearSearch" ``` -🔵 The shadow-root (``::shadow``) elements are transitional, and therefore cannot be the final part of your CSS selectors. Complete your CSS selectors by including an element that's inside a shadow-root. +🔵 The shadow-root (`::shadow`) elements are transitional, and therefore cannot be the final part of your CSS selectors. Complete your CSS selectors by including an element that's inside a shadow-root. -🔵 NOTE: ``::shadow`` selectors only exist within SeleniumBase. (They are not part of standard CSS.) +🔵 NOTE: `::shadow` selectors only exist within SeleniumBase. (They are not part of standard CSS.) 🔵 Here are some examples of tests that interact with Shadow DOM elements: * [examples/shadow_root_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/shadow_root_test.py) diff --git a/help_docs/syntax_formats.md b/help_docs/syntax_formats.md index 311233d174a..c9c48db81d8 100644 --- a/help_docs/syntax_formats.md +++ b/help_docs/syntax_formats.md @@ -44,7 +44,7 @@ In this format, (which is used by most of the tests in the SeleniumBase examples folder), BaseCase is imported at the top of a Python file, followed by a Python class inheriting BaseCase. Then, any test method defined in that class automatically gains access to SeleniumBase methods, including the setUp() and tearDown() methods that are automatically called for opening and closing web browsers at the start and end of tests. -To run a test of this format, use **``pytest``** or ``pynose``. Adding ``BaseCase.main(__name__, __file__)`` enables ``python`` to run ``pytest`` on your file indirectly. Here's an example: +To run a test of this format, use **`pytest`** or `pynose`. Adding `BaseCase.main(__name__, __file__)` enables `python` to run `pytest` on your file indirectly. Here's an example: ```python from seleniumbase import BaseCase @@ -64,7 +64,7 @@ class MyTestClass(BaseCase): (See examples/test_demo_site.py for the full test.) -Using ``BaseCase`` inheritance is a great starting point for anyone learning SeleniumBase, and it follows good object-oriented programming principles. +Using `BaseCase` inheritance is a great starting point for anyone learning SeleniumBase, and it follows good object-oriented programming principles.

2. BaseCase subclass inheritance

@@ -285,7 +285,7 @@ class OverrideDriverTest(BaseCase):

10. Overriding the driver via "sb" fixture

-When you want to use SeleniumBase methods via the ``sb`` pytest fixture, but you want total freedom to control how you spin up your web browsers, this is the format you want. Although SeleniumBase gives you plenty of command-line options to change how your browsers are launched, this format gives you more control when the existing options aren't enough. +When you want to use SeleniumBase methods via the `sb` pytest fixture, but you want total freedom to control how you spin up your web browsers, this is the format you want. Although SeleniumBase gives you plenty of command-line options to change how your browsers are launched, this format gives you more control when the existing options aren't enough. ```python """Overriding the "sb" fixture to override the driver.""" @@ -645,7 +645,7 @@ class MiClaseDePrueba(CasoDePrueba):

20. Gherkin syntax with "behave" BDD runner

-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: +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: ```zsh features/ @@ -659,7 +659,7 @@ features/ └── step_file.py ``` -A ``*.feature`` file might look like this: +A `*.feature` file might look like this: ```gherkin Feature: SeleniumBase scenarios for the RealWorld App @@ -681,7 +681,7 @@ Feature: SeleniumBase scenarios for the RealWorld App (From examples/behave_bdd/features/realworld.feature) -You'll need the ``environment.py`` file for tests to work. Here it is: +You'll need the `environment.py` file for tests to work. Here it is: ```python from seleniumbase import BaseCase @@ -699,15 +699,15 @@ from seleniumbase.behave.behave_sb import after_all # noqa (From examples/behave_bdd/features/environment.py) -Inside that file, you can use ``BaseCase`` (or a subclass) for the inherited class. +Inside that file, you can use `BaseCase` (or a subclass) for the inherited class. -For your ``behave`` tests to have access to SeleniumBase Behave steps, you can create an ``imported.py`` file with the following line: +For your `behave` tests to have access to SeleniumBase Behave steps, you can create an `imported.py` file with the following line: ```python from seleniumbase.behave import steps # noqa ``` -That will allow you to use lines like this in your ``*.feature`` files: +That will allow you to use lines like this in your `*.feature` files: ```gherkin Feature: SeleniumBase scenarios for the RealWorld App @@ -724,7 +724,7 @@ Feature: SeleniumBase scenarios for the RealWorld App And Save screenshot to logs ``` -You can also create your own step files (Eg. ``step_file.py``): +You can also create your own step files (Eg. `step_file.py`): ```python from behave import step @@ -841,7 +841,7 @@ with DriverContext() as driver:

23. The driver manager (via direct import)

-Another way of running Selenium tests with pure ``python`` (as opposed to using ``pytest`` or ``pynose``) is by using this format, which bypasses [BaseCase](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py) methods while still giving you a flexible driver with a manager. SeleniumBase includes helper files such as [page_actions.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/page_actions.py), which may help you get around some of the limitations of bypassing ``BaseCase``. Here's an example: +Another way of running Selenium tests with pure `python` (as opposed to using `pytest` or `pynose`) is by using this format, which bypasses [BaseCase](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/base_case.py) methods while still giving you a flexible driver with a manager. SeleniumBase includes helper files such as [page_actions.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/fixtures/page_actions.py), which may help you get around some of the limitations of bypassing `BaseCase`. Here's an example: ```python """Driver() example. (Runs with "python").""" @@ -870,7 +870,7 @@ finally: (From examples/raw_driver_manager.py) -Here's an example of basic login with the ``Driver()`` format: +Here's an example of basic login with the `Driver()` format: ```python from seleniumbase import Driver @@ -892,9 +892,12 @@ finally: (From examples/raw_login_driver.py) -The ``Driver()`` manager format can be used as a drop-in replacement for virtually every Python/selenium framework, as it uses the raw ``driver`` instance for handling commands. The ``Driver()`` method simplifies the work of managing drivers with optimal settings, and it can be configured with multiple args. The ``Driver()`` also accepts command-line options (such as ``python --headless``) so that you don't need to modify your tests directly to use different settings. These command-line options only take effect if the associated method args remain unset (or set to ``None``) for the specified options. +The `Driver()` manager format can be used as a drop-in replacement for virtually every Python/selenium framework, as it uses the raw `driver` instance for handling commands. The `Driver()` method simplifies the work of managing drivers with optimal settings, and it can be configured with multiple args. The `Driver()` also accepts command-line options (such as `python --headless`) so that you don't need to modify your tests directly to use different settings. These command-line options only take effect if the associated method args remain unset (or set to `None`) for the specified options. -When using the ``Driver()`` format, you may need to activate a Virtual Display on your own if you want to run headed tests in a headless Linux environment. (See https://github.com/mdmintz/sbVirtualDisplay for details.) One such example of this is using an authenticated proxy, which is configured via a Chrome extension that is generated at runtime. (Note that regular headless mode in Chrome doesn't support extensions.) +When using the `Driver()` format, you may need to activate a Virtual Display on your own if you want to run headed tests in a headless Linux environment. (See https://github.com/mdmintz/sbVirtualDisplay for details.) One such example of this is using an authenticated proxy, which is configured via a Chrome extension that is generated at runtime. (Note that regular headless mode in Chrome doesn't support extensions.) + +Set `uc=True` in order to make the `driver` stealthy: +`driver = Driver(uc=True)`

24. Pure CDP Mode (Async API. No Selenium)

diff --git a/help_docs/translations.md b/help_docs/translations.md index 8070a34bff5..c47c29c9979 100644 --- a/help_docs/translations.md +++ b/help_docs/translations.md @@ -48,9 +48,9 @@ class 私のテストクラス(セレニウムテストケース): ``` -

Translation API 🈺

+

Translation API 🈺

-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. +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. ```zsh seleniumbase translate @@ -61,19 +61,19 @@ seleniumbase translate seleniumbase translate [SB_FILE.py] [LANGUAGE] [ACTION] * Languages: -``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` -``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` -``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` -``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` -``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` +`--en` / `--English` | `--zh` / `--Chinese` +`--nl` / `--Dutch` | `--fr` / `--French` +`--it` / `--Italian` | `--ja` / `--Japanese` +`--ko` / `--Korean` | `--pt` / `--Portuguese` +`--ru` / `--Russian` | `--es` / `--Spanish` * Actions: -``-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) +`-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) * Options: -``-n`` (include line Numbers when using the Print action) +`-n` (include line Numbers when using the Print action) * Examples: Translate test_1.py into Chinese and only print the output: @@ -85,14 +85,14 @@ Translate test_3.py into Dutch and make a copy of the file: * Output: Translates a SeleniumBase Python file into the language -specified. Method calls and ``import`` lines get swapped. +specified. Method calls and `import` lines get swapped. Both a language and an action must be specified. -The ``-p`` action can be paired with one other action. -When running with ``-c`` (or ``--copy``) the new file name +The `-p` action can be paired with one other action. +When running with `-c` (or `--copy`) the new file name will be the original name appended with an underscore plus the 2-letter language code of the new language. -(Example: Translating ``test_1.py`` into Japanese with -``-c`` will create a new file called ``test_1_ja.py``.) +(Example: Translating `test_1.py` into Japanese with +`-c` will create a new file called `test_1_ja.py`.) ``` -------- diff --git a/help_docs/using_safari_driver.md b/help_docs/using_safari_driver.md index e2e77be56dd..fdd245a7e99 100644 --- a/help_docs/using_safari_driver.md +++ b/help_docs/using_safari_driver.md @@ -6,6 +6,6 @@ You can find the official Apple documentation regarding "Testing with WebDriver in Safari" on the following page: [https://developer.apple.com/documentation/webkit/testing_with_webdriver_in_safari](https://developer.apple.com/documentation/webkit/testing_with_webdriver_in_safari) -Run ``safaridriver --enable`` once in a terminal to enable Safari's WebDriver. (If you’re upgrading from a previous macOS release, you may need to prefix the command with ``sudo``.) +Run `safaridriver --enable` once in a terminal to enable Safari's WebDriver. (If you’re upgrading from a previous macOS release, you may need to prefix the command with `sudo`.) -Now you can use ``--safari`` to run your **SeleniumBase** tests on Safari. +Now you can use `--safari` to run your **SeleniumBase** tests on Safari. diff --git a/help_docs/verify_webdriver.md b/help_docs/verify_webdriver.md index 0cef3db055b..a2cefedee2a 100644 --- a/help_docs/verify_webdriver.md +++ b/help_docs/verify_webdriver.md @@ -2,9 +2,9 @@

Verifying that web drivers are installed

-On newer versions of SeleniumBase, the driver is automatically downloaded to the ``seleniumbase/drivers`` folder as needed, and does not need to be on the System Path when running tests. +When using SeleniumBase, the driver is automatically downloaded to the `seleniumbase/drivers` folder as needed, and doesn't need to be on the System Path when running tests. -Drivers can be manually downloaded to the ``seleniumbase/drivers`` folder with commands such as: +Drivers can also be manually downloaded to the `seleniumbase/drivers` folder with commands such as: ```zsh sbase get chromedriver @@ -22,7 +22,7 @@ If you want to check that you have the correct driver installed on your System P sbase get chromedriver --path ``` -(The above ``--path`` addition is for Linux/Mac only, which uses ``/usr/local/bin/``. The "Path" is different on Windows, and you'll need to manually copy the driver to your System Path, which is defined in the Control Panel's System Environment Variables.) +(The above `--path` addition is for Linux/Mac only, which uses `/usr/local/bin/`. The "Path" is different on Windows, and you'll need to manually copy the driver to your System Path, which is defined in the Control Panel's System Environment Variables.) *You can verify that the correct drivers exist on your System Path by checking inside a Python command prompt.* diff --git a/seleniumbase/console_scripts/ReadMe.md b/seleniumbase/console_scripts/ReadMe.md index c49cafc6e7f..f2c287ea977 100644 --- a/seleniumbase/console_scripts/ReadMe.md +++ b/seleniumbase/console_scripts/ReadMe.md @@ -64,7 +64,7 @@ sbase get chromedriver sbase get geckodriver sbase get edgedriver sbase get chromedriver 149 -sbase get chromedriver 149.0.7827.115 +sbase get chromedriver 149.0.7827.155 sbase get chromedriver stable sbase get chromedriver beta sbase get chromedriver -p diff --git a/seleniumbase/console_scripts/sb_install.py b/seleniumbase/console_scripts/sb_install.py index bfd9bbcd0f6..30e297340d2 100644 --- a/seleniumbase/console_scripts/sb_install.py +++ b/seleniumbase/console_scripts/sb_install.py @@ -2,20 +2,25 @@ Downloads the specified webdriver to "seleniumbase/drivers/" Usage: - sbase get {chromedriver|geckodriver|edgedriver| - iedriver|uc_driver|cft|chs} [OPTIONS] + seleniumbase install [DRIVER/BINARY] [OPTIONS] + OR: seleniumbase get [DRIVER/BINARY] [OPTIONS] + OR: sbase install [DRIVER/BINARY] [OPTIONS] + OR: sbase get [DRIVER/BINARY] [OPTIONS] + (Drivers: chromedriver, uc_driver, + edgedriver, geckodriver) + (Binaries: cft, chs, chromium) Options: - VERSION Specify the version. - Tries to detect the needed version. - If using chromedriver or edgedriver, - you can use the major version integer. - -p OR --path Also copy the driver to /usr/local/bin + VERSION Driver/binary version to download. + (Otherwise detects the version) + --revision The Chromium revision to download. + (Otherwise downloads the latest) + -p OR --path Also copy driver to /usr/local/bin Examples: sbase get chromedriver sbase get geckodriver sbase get edgedriver sbase get chromedriver 149 - sbase get chromedriver 149.0.7827.115 + sbase get chromedriver 149.0.7827.155 sbase get chromedriver stable sbase get chromedriver beta sbase get chromedriver -p @@ -24,10 +29,12 @@ sbase get cft 149 sbase get chs Output: - Downloads the webdriver to seleniumbase/drivers/ - (chromedriver is required for Chrome automation) - (geckodriver is required for Firefox automation) - (edgedriver is required for MS__Edge automation) + Downloads driver/binary to seleniumbase/drivers/ + (chromedriver is for Selenium Chrome automation) + (geckodriver is for Selenium Firefox automation) + (edgedriver is for Selenium MS__Edge automation) + (cft is for the `Chrome for Testing` binary exe) + (chs is for the `Chrome-Headless-Shell` binary.) """ import colorama import logging diff --git a/seleniumbase/undetected/cdp_driver/tab.py b/seleniumbase/undetected/cdp_driver/tab.py index 5c8a4e5fc0c..a1320072d0f 100644 --- a/seleniumbase/undetected/cdp_driver/tab.py +++ b/seleniumbase/undetected/cdp_driver/tab.py @@ -27,27 +27,25 @@ class Tab(Connection): """ - :ref:`tab` is the controlling mechanism/connection to a 'target', - for most of us 'target' can be read as 'tab'. However it could also - be an iframe, serviceworker or background script for example, - although there isn't much to control for those. - If you open a new window by using - :py:meth:`browser.get(..., new_window=True)` - Your url will open a new window. This window is a 'tab'. + :ref:`tab` is the controlling mechanism/connection to a `target`. + The `target` can be read as a `tab`, however, it could also + be an iframe, a serviceworker, or a background script (for example). + If you open a new window by using `browser.get(..., new_window=True)`, + your URL will open a new window. This window is a `tab`. When you browse to another page, the tab will be the same (browser view). - It's important to keep some reference to tab objects, in case you're + It's important to keep some reference to tab objects in case you're done interacting with elements and want to operate on the page level again. Custom CDP commands --------------------------- Tab object provide many useful and often-used methods. It is also possible - to utilize the included cdp classes to to something totally custom. + to utilize the included CDP classes to to something totally custom. - The cdp package is a set of so-called "domains" with each having methods, + The CDP package is a set of so-called "domains" with each having methods, events and types. - To send a cdp method, for example :py:obj:`cdp.page.navigate`, - you'll have to check whether the method accepts any parameters - and whether they are required or not. + To send a CDP method, for example `cdp.page.navigate`, + you'll have to check whether the method accepts any + parameters and whether they are required or not. You can use: @@ -936,7 +934,7 @@ async def minimize(self): return await self.set_window_state(state="minimize") async def fullscreen(self): - """Minimize page/tab/window""" + """Fullscreen page/tab/window""" return await self.set_window_state(state="fullscreen") async def medimize(self): From 0b076173e998697661b8d57abaa95ee52c1b6760 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 30 Jun 2026 21:59:17 -0400 Subject: [PATCH 6/7] Update examples --- examples/cdp_mode/ReadMe.md | 2 +- .../playwright/raw_pixelscan_async.py | 29 +++++++++++++++++++ .../cdp_mode/playwright/raw_pixelscan_sync.py | 21 ++++++++++++++ .../cdp_mode/playwright/raw_walmart_sync.py | 2 +- examples/cdp_mode/raw_cdp_target.py | 27 +++++++++++++++++ examples/cdp_mode/raw_cdp_tavus.py | 9 ------ examples/cdp_mode/raw_cdp_walmart.py | 2 +- examples/cdp_mode/raw_cdp_zillow.py | 24 +++++++++++++++ examples/cdp_mode/raw_indeed.py | 6 ++-- examples/cdp_mode/raw_indeed_login.py | 17 ----------- examples/cdp_mode/raw_target.py | 28 ++++++++++++++++++ examples/cdp_mode/raw_tavus.py | 10 ------- examples/cdp_mode/raw_walmart.py | 2 +- examples/presenter/uc_presentation_4.py | 9 +++--- examples/raw_uc_driver.py | 16 ++++++++++ examples/test_chinese_pdf.py | 4 +-- examples/test_get_pdf_text.py | 10 +++++-- examples/test_pdf_asserts.py | 7 +++-- 18 files changed, 170 insertions(+), 55 deletions(-) create mode 100644 examples/cdp_mode/playwright/raw_pixelscan_async.py create mode 100644 examples/cdp_mode/playwright/raw_pixelscan_sync.py create mode 100644 examples/cdp_mode/raw_cdp_target.py delete mode 100644 examples/cdp_mode/raw_cdp_tavus.py create mode 100644 examples/cdp_mode/raw_cdp_zillow.py delete mode 100644 examples/cdp_mode/raw_indeed_login.py create mode 100644 examples/cdp_mode/raw_target.py delete mode 100644 examples/cdp_mode/raw_tavus.py create mode 100644 examples/raw_uc_driver.py diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index a8a47f2651c..46f5c283467 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -297,7 +297,7 @@ with SB(uc=True, test=True, ad_block=True) as sb: sb.click_if_visible('[data-automation-id="sb-btn-close-mark"]') items = sb.find_elements('[data-item-id]') for item in items: - if required_text in item.text: + if required_text.lower() in item.text.lower(): description = item.querySelector( '[data-automation-id="product-title"]' ) diff --git a/examples/cdp_mode/playwright/raw_pixelscan_async.py b/examples/cdp_mode/playwright/raw_pixelscan_async.py new file mode 100644 index 00000000000..ed87506fec4 --- /dev/null +++ b/examples/cdp_mode/playwright/raw_pixelscan_async.py @@ -0,0 +1,29 @@ +import asyncio +import time +from playwright.async_api import async_playwright +from playwright.async_api import expect +from seleniumbase import cdp_driver + + +async def main(): + driver = await cdp_driver.start_async() + endpoint_url = driver.get_endpoint_url() + + async with async_playwright() as p: + browser = await p.chromium.connect_over_cdp(endpoint_url) + page = browser.contexts[0].pages[0] + await page.goto("https://pixelscan.net/fingerprint-check") + time.sleep(4) + await expect( + page.locator("pxlscn-bot-detection") + ).to_contain_text("No automated behavior", timeout=4000) + await page.wait_for_selector("span.status-success", timeout=4000) + await expect( + page.locator("pxlscn-fingerprint-masking") + ).to_contain_text("No masking detected", timeout=4000) + time.sleep(2) + print("Bot Not Detected") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/cdp_mode/playwright/raw_pixelscan_sync.py b/examples/cdp_mode/playwright/raw_pixelscan_sync.py new file mode 100644 index 00000000000..c4c93b83ecd --- /dev/null +++ b/examples/cdp_mode/playwright/raw_pixelscan_sync.py @@ -0,0 +1,21 @@ +from playwright.sync_api import sync_playwright +from playwright.sync_api import expect +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome() +endpoint_url = sb.get_endpoint_url() + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(endpoint_url) + page = browser.contexts[0].pages[0] + page.goto("https://pixelscan.net/fingerprint-check") + sb.sleep(4) + expect( + page.locator("pxlscn-bot-detection") + ).to_contain_text("No automated behavior", timeout=4000) + page.wait_for_selector("span.status-success", timeout=4000) + expect( + page.locator("pxlscn-fingerprint-masking") + ).to_contain_text("No masking detected", timeout=4000) + sb.sleep(2) + print("Bot Not Detected") diff --git a/examples/cdp_mode/playwright/raw_walmart_sync.py b/examples/cdp_mode/playwright/raw_walmart_sync.py index 02916fe794a..2fec140a01d 100644 --- a/examples/cdp_mode/playwright/raw_walmart_sync.py +++ b/examples/cdp_mode/playwright/raw_walmart_sync.py @@ -33,7 +33,7 @@ items = page.locator('[data-item-id]') for i in range(items.count()): item = items.nth(i) - if required_text in item.inner_text(): + if required_text.lower() in item.inner_text().lower(): description = item.locator('[data-automation-id="product-title"]') if ( description diff --git a/examples/cdp_mode/raw_cdp_target.py b/examples/cdp_mode/raw_cdp_target.py new file mode 100644 index 00000000000..84412ba1f6b --- /dev/null +++ b/examples/cdp_mode/raw_cdp_target.py @@ -0,0 +1,27 @@ +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome() +sb.goto("https://www.target.com/") +sb.sleep(1.5) +sb.click("input#search") +sb.sleep(0.5) +search = "Settlers of Catan Board Game" +required_text = "Catan" +sb.type("input#search", search) +sb.sleep(0.5) +sb.click('button[aria-label="search"]') +sb.sleep(2.5) +print('*** Target Search for "%s":' % search) +print(' (Results must contain "%s".)' % required_text) +unique_item_text = [] +items = sb.find_elements('[data-test="product-details"]') +for item in items: + if required_text.lower() in item.text.lower(): + description = item.querySelector('a[data-test*="Card/title"]') + if description and description.text not in unique_item_text: + unique_item_text.append(description.text) + print("* " + description.text) + price = item.querySelector('[data-test="current-price"]') + if price: + print(" (" + price.text + ")") + item.scroll_into_view() diff --git a/examples/cdp_mode/raw_cdp_tavus.py b/examples/cdp_mode/raw_cdp_tavus.py deleted file mode 100644 index e6f1c2c9ba7..00000000000 --- a/examples/cdp_mode/raw_cdp_tavus.py +++ /dev/null @@ -1,9 +0,0 @@ -from seleniumbase import sb_cdp - -sb = sb_cdp.Chrome() -sb.goto("platform.tavus.io/auth/sign-in?is_developer=true") -sb.sleep(3) -sb.solve_captcha() -sb.sleep(1) -sb.assert_element('input[type="email"]') -sb.assert_element('button[type="submit"]') diff --git a/examples/cdp_mode/raw_cdp_walmart.py b/examples/cdp_mode/raw_cdp_walmart.py index 7b715906da6..4e4500af62c 100644 --- a/examples/cdp_mode/raw_cdp_walmart.py +++ b/examples/cdp_mode/raw_cdp_walmart.py @@ -21,7 +21,7 @@ sb.click_if_visible('[data-automation-id="sb-btn-close-mark"]') items = sb.find_elements('[data-item-id]') for item in items: - if required_text in item.text: + if required_text.lower() in item.text.lower(): description = item.querySelector( '[data-automation-id="product-title"]' ) diff --git a/examples/cdp_mode/raw_cdp_zillow.py b/examples/cdp_mode/raw_cdp_zillow.py new file mode 100644 index 00000000000..512cae5439e --- /dev/null +++ b/examples/cdp_mode/raw_cdp_zillow.py @@ -0,0 +1,24 @@ +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome(use_chromium=True, ad_block=True) +sb.goto("https://www.zillow.com/") +sb.sleep(2) +search = "Bar Harbor ME homes on the waterfront" +sb.type('input[aria-label="Search"]', search) +sb.sleep(1) +sb.click('button[type="submit"]') +sb.sleep(2) +sb.click_if_visible('span:contains("Cancel")') +sb.sleep(0.5) +sb.click_if_visible('button[title="Close"]') +items = sb.find_visible_elements('[data-testid="property-card"]') +print('*** %s:' % search) +for i, item in enumerate(items): + print("<----- %s ----->" % (i + 1)) + print(item.query_selector('[data-testid*="price"]').text) + print(item.query_selector('[data-testid*="details"]').text) + print(item.query_selector('[data-testid*="address"]').text) + item.scroll_into_view() +print("<-------------->") +sb.sleep(2) +sb.quit() diff --git a/examples/cdp_mode/raw_indeed.py b/examples/cdp_mode/raw_indeed.py index 44c4d800553..c3e29f57305 100644 --- a/examples/cdp_mode/raw_indeed.py +++ b/examples/cdp_mode/raw_indeed.py @@ -3,13 +3,13 @@ with SB(uc=True, test=True) as sb: sb.activate_cdp_mode() sb.goto("https://www.indeed.com/companies/search") - sb.sleep(2) + sb.sleep(0.8) sb.solve_captcha() - sb.sleep(1) + sb.sleep(0.8) search_box = "input#company-search" if not sb.is_element_present(search_box): sb.solve_captcha() - sb.sleep(1) + sb.sleep(0.8) company = "NASA Jet Propulsion Laboratory" sb.click(search_box) sb.sleep(0.1) diff --git a/examples/cdp_mode/raw_indeed_login.py b/examples/cdp_mode/raw_indeed_login.py deleted file mode 100644 index 68198d55991..00000000000 --- a/examples/cdp_mode/raw_indeed_login.py +++ /dev/null @@ -1,17 +0,0 @@ -from seleniumbase import SB - -with SB(uc=True, test=True, incognito=True) as sb: - sb.activate_cdp_mode() - sb.goto("https://secure.indeed.com/auth") - sb.sleep(2.2) - if not sb.is_element_visible('input[type="email"]'): - sb.solve_captcha() - sb.sleep(2.5) - sb.type('input[type="email"]', "test@test.com") - sb.sleep(0.5) - sb.solve_captcha() - sb.sleep(0.5) - sb.click('button[type="submit"]') - sb.sleep(2.5) - sb.solve_captcha() - sb.sleep(4.5) diff --git a/examples/cdp_mode/raw_target.py b/examples/cdp_mode/raw_target.py new file mode 100644 index 00000000000..5e891846c87 --- /dev/null +++ b/examples/cdp_mode/raw_target.py @@ -0,0 +1,28 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, ad_block=True) as sb: + sb.activate_cdp_mode() + sb.goto("https://www.target.com/") + sb.sleep(1.5) + sb.click("input#search") + sb.sleep(0.5) + search = "Settlers of Catan Board Game" + required_text = "Catan" + sb.type("input#search", search) + sb.sleep(0.5) + sb.click('button[aria-label="search"]') + sb.sleep(2.5) + print('*** Target Search for "%s":' % search) + print(' (Results must contain "%s".)' % required_text) + unique_item_text = [] + items = sb.find_elements('[data-test="product-details"]') + for item in items: + if required_text.lower() in item.text.lower(): + description = item.querySelector('a[data-test*="Card/title"]') + if description and description.text not in unique_item_text: + unique_item_text.append(description.text) + print("* " + description.text) + price = item.querySelector('[data-test="current-price"]') + if price: + print(" (" + price.text + ")") + item.scroll_into_view() diff --git a/examples/cdp_mode/raw_tavus.py b/examples/cdp_mode/raw_tavus.py deleted file mode 100644 index 3c4fdfa48f0..00000000000 --- a/examples/cdp_mode/raw_tavus.py +++ /dev/null @@ -1,10 +0,0 @@ -from seleniumbase import SB - -with SB(uc=True, test=True) as sb: - sb.activate_cdp_mode() - sb.goto("platform.tavus.io/auth/sign-in?is_developer=true") - sb.sleep(3) - sb.solve_captcha() - sb.sleep(1) - sb.assert_element('input[type="email"]') - sb.assert_element('button[type="submit"]') diff --git a/examples/cdp_mode/raw_walmart.py b/examples/cdp_mode/raw_walmart.py index 53b80db0f49..6882061f611 100644 --- a/examples/cdp_mode/raw_walmart.py +++ b/examples/cdp_mode/raw_walmart.py @@ -22,7 +22,7 @@ sb.click_if_visible('[data-automation-id="sb-btn-close-mark"]') items = sb.find_elements('[data-item-id]') for item in items: - if required_text in item.text: + if required_text.lower() in item.text.lower(): description = item.querySelector( '[data-automation-id="product-title"]' ) diff --git a/examples/presenter/uc_presentation_4.py b/examples/presenter/uc_presentation_4.py index 454dd188c79..a503faae661 100644 --- a/examples/presenter/uc_presentation_4.py +++ b/examples/presenter/uc_presentation_4.py @@ -530,7 +530,7 @@ def test_presentation_4(self): sb.click_if_visible('[data-automation-id="sb-btn-close-mark"]') items = sb.find_elements('[data-item-id]') for item in items: - if required_text in item.text: + if required_text.lower() in item.text.lower(): description = item.querySelector( '[data-automation-id="product-title"]' ) @@ -763,13 +763,12 @@ def test_presentation_4(self): sb.activate_cdp_mode() sb.goto("https://www.priceline.com") sb.sleep(3) - input_selector = 'input[name="endLocation"]' - if not sb.is_element_present(input_selector): - input_selector = "div.location-input input" + input_selector = "div.location-input input" + sb.gui_hover_element(input_selector) sb.mouse_click(input_selector) - sb.sleep(0.5) location = "Portland, OR" selection = "Oregon, United States" # (Dropdown option) + sb.gui_hover_element(input_selector) sb.press_keys(input_selector, location) sb.sleep(0.5) sb.click(selection) diff --git a/examples/raw_uc_driver.py b/examples/raw_uc_driver.py new file mode 100644 index 00000000000..6d733c302e7 --- /dev/null +++ b/examples/raw_uc_driver.py @@ -0,0 +1,16 @@ +"""UC Mode Driver for evading bot-detection.""" +from seleniumbase import Driver + +driver = Driver(uc=True) +driver.get("https://browserscan.net/bot-detection") +driver.assert_element('strong:contains("Normal")') +driver.sleep(1) +driver.get("https://bot.sannysoft.com/") +driver.assert_element("#user-agent-result.passed") +driver.assert_element("#webdriver-result.passed") +driver.assert_element("#advanced-webdriver-result.passed") +driver.assert_element("#permissions-result.passed") +driver.assert_element("#plugins-length-result.passed") +driver.assert_element("#plugins-type-result.passed") +driver.sleep(1) +driver.quit() diff --git a/examples/test_chinese_pdf.py b/examples/test_chinese_pdf.py index 207bdde7cdd..c5d4d4a13db 100644 --- a/examples/test_chinese_pdf.py +++ b/examples/test_chinese_pdf.py @@ -2,13 +2,13 @@ BaseCase.main(__name__, __file__) -class ChinesePdfTests(BaseCase): +class ChinesePDFTests(BaseCase): def test_chinese_pdf(self): self.goto("data:,") pdf = "https://seleniumbase.io/cdn/pdf/unittest_zh.pdf" # Get and print PDF text - pdf_text = self.get_pdf_text(pdf, page=2) + pdf_text = self.get_pdf_text(pdf, page=2, nav=True) print("\n" + pdf_text) # Assert PDF contains the expected text on Page 2 diff --git a/examples/test_get_pdf_text.py b/examples/test_get_pdf_text.py index 3b65bb9a246..6acf0d7c093 100644 --- a/examples/test_get_pdf_text.py +++ b/examples/test_get_pdf_text.py @@ -1,13 +1,17 @@ from seleniumbase import BaseCase -BaseCase.main(__name__, __file__) +BaseCase.main(__name__, __file__, "--uc") -class PdfTests(BaseCase): +class PDFTests(BaseCase): def test_get_pdf_text(self): self.goto("data:,") + if self.headless: + self.skip("Skip this test in headless mode!") + if not self.undetectable or not self.external_pdf: + self.activate_cdp_mode(external_pdf=True) pdf = ( "https://nostarch.com/download/" "Automate_the_Boring_Stuff_sample_ch17.pdf" ) - pdf_text = self.get_pdf_text(pdf, page=1) + pdf_text = self.get_pdf_text(pdf, page=1, nav=True) print("\n" + pdf_text) diff --git a/examples/test_pdf_asserts.py b/examples/test_pdf_asserts.py index c703ececdbd..39eb614c3f2 100644 --- a/examples/test_pdf_asserts.py +++ b/examples/test_pdf_asserts.py @@ -2,16 +2,19 @@ BaseCase.main(__name__, __file__) -class PdfAssertTests(BaseCase): +class PDFAssertTests(BaseCase): def test_assert_pdf_text(self): self.goto("data:,") + if self.headless: + self.skip("Skip this test in headless mode!") + if not self.undetectable or not self.external_pdf: + self.activate_cdp_mode(external_pdf=True) # Assert PDF contains the expected text on Page 1 self.assert_pdf_text( "https://nostarch.com/download/Automate_the_Boring_Stuff_dTOC.pdf", "Programming Is a Creative Activity", page=1, ) - # Assert PDF contains the expected text on any of the pages self.assert_pdf_text( "https://nostarch.com/download/Automate_the_Boring_Stuff_dTOC.pdf", From 17d71ef57a31a4306d2e117278e8988c47a42783 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 30 Jun 2026 21:59:41 -0400 Subject: [PATCH 7/7] Version 4.50.4 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 4c5a00bb3ef..98998be124f 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.50.3" +__version__ = "4.50.4"