diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index c130db5a434..6881180c77b 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -45,6 +45,8 @@ That disconnects WebDriver from Chrome (which prevents detection), and gives you access to `sb.cdp` methods (which don't trigger anti-bot checks). +> (**New:** Calling **`sb.open(url)`** from UC Mode also activates CDP Mode now.) + Simple example from [SeleniumBase/examples/cdp_mode/raw_gitlab.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_gitlab.py): ```python diff --git a/examples/cdp_mode/raw_cdp_tabs.py b/examples/cdp_mode/raw_cdp_tabs.py new file mode 100644 index 00000000000..695658ffce0 --- /dev/null +++ b/examples/cdp_mode/raw_cdp_tabs.py @@ -0,0 +1,15 @@ +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome() +sb.open("data:text/html,

Page A

") +sb.assert_text("Page A") +sb.open_new_tab() +sb.open("data:text/html,

Page B

") +sb.assert_text("Page B") +sb.switch_to_tab(0) +sb.assert_text("Page A") +sb.assert_text_not_visible("Page B") +sb.switch_to_tab(1) +sb.assert_text("Page B") +sb.assert_text_not_visible("Page A") +sb.driver.stop() diff --git a/examples/cdp_mode/raw_driver.py b/examples/cdp_mode/raw_driver.py index 47e39c37e3a..2faa4c862d0 100644 --- a/examples/cdp_mode/raw_driver.py +++ b/examples/cdp_mode/raw_driver.py @@ -1,10 +1,10 @@ import atexit from seleniumbase import Driver -driver = Driver(uc=True) +driver = Driver(uc=True, guest=True) atexit.register(driver.quit) url = "www.planetminecraft.com/account" -driver.uc_activate_cdp_mode(url) +driver.activate_cdp_mode(url) driver.sleep(1) driver.solve_captcha() driver.wait_for_element_absent("input[disabled]") diff --git a/examples/cdp_mode/raw_planetmc.py b/examples/cdp_mode/raw_planetmc.py index 76fdcb7d7ef..36949ceaef2 100644 --- a/examples/cdp_mode/raw_planetmc.py +++ b/examples/cdp_mode/raw_planetmc.py @@ -1,6 +1,6 @@ from seleniumbase import SB -with SB(uc=True, test=True) as sb: +with SB(uc=True, test=True, guest=True) as sb: url = "www.planetminecraft.com/account/sign_in/" sb.activate_cdp_mode(url) sb.sleep(1.2) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 28779e4d90f..5b19ca90f5e 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -2,8 +2,8 @@ # Minimum Python version: 3.10 (for generating docs only) regex>=2025.11.3 -pymdown-extensions>=10.16.1 -pipdeptree>=2.29.0 +pymdown-extensions>=10.17.1 +pipdeptree>=2.30.0 python-dateutil>=2.8.2 Markdown==3.10 click==8.3.0 diff --git a/requirements.txt b/requirements.txt index 5b69ca1b8a9..27af72fe3f5 100755 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ setuptools>=80.9.0;python_version>="3.10" wheel>=0.45.1 attrs~=25.3.0;python_version<"3.9" attrs>=25.4.0;python_version>="3.9" -certifi>=2025.10.5 +certifi>=2025.11.12 exceptiongroup>=1.3.0 websockets~=13.1;python_version<"3.9" websockets>=15.0.1;python_version>="3.9" @@ -48,7 +48,8 @@ trio==0.27.0;python_version<"3.9" trio>=0.31.0,<1;python_version>="3.9" and python_version<"3.10" trio>=0.32.0,<1;python_version>="3.10" trio-websocket~=0.12.2 -wsproto==1.2.0 +wsproto==1.2.0;python_version<"3.10" +wsproto==1.3.1;python_version>="3.10" websocket-client~=1.8.0;python_version<"3.9" websocket-client~=1.9.0;python_version>="3.9" selenium==4.27.1;python_version<"3.9" @@ -57,7 +58,8 @@ selenium==4.38.0;python_version>="3.10" cssselect==1.2.0;python_version<"3.9" cssselect==1.3.0;python_version>="3.9" sortedcontainers==2.4.0 -execnet==2.1.1 +execnet==2.1.1;python_version<"3.10" +execnet==2.1.2;python_version>="3.10" iniconfig==2.1.0;python_version<"3.10" iniconfig==2.3.0;python_version>="3.10" pluggy==1.5.0;python_version<"3.9" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index f1a80658ee7..f3297a6b988 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.44.10" +__version__ = "4.44.11" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 011e01e6443..db7da8c51af 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -1410,14 +1410,10 @@ def _uc_gui_click_captcha( and driver.is_element_present("%s div" % frame) ): frame = "%s div" % frame - elif ( - driver.is_element_present('[name*="cf-turnstile-"]') - and driver.is_element_present("#challenge-form div > div") - ): + elif driver.is_element_present("#challenge-form div > div"): frame = "#challenge-form div > div" elif ( - driver.is_element_present('[name*="cf-turnstile-"]') - and driver.is_element_present( + driver.is_element_present( '[style="display: grid;"] div div' ) ): @@ -1430,13 +1426,11 @@ def _uc_gui_click_captcha( ): frame = '.spacer + div div:not([class])' elif ( - driver.is_element_present('[name*="cf-turnstile-"]') - and driver.is_element_present(".spacer div:not([class])") + driver.is_element_present(".spacer div:not([class])") ): frame = ".spacer div:not([class])" elif ( - driver.is_element_present('script[src*="challenges.c"]') - and driver.is_element_present( + driver.is_element_present( '[data-testid*="challenge-"] div' ) ): diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 9ced97d5494..b68b22d5f02 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1,6 +1,7 @@ """Add CDP methods to extend the driver""" import asyncio import fasteners +import mycdp import os import random import re @@ -15,6 +16,7 @@ from seleniumbase.fixtures import page_utils from seleniumbase.fixtures import shared_utils from seleniumbase.undetected.cdp_driver import cdp_util +from seleniumbase.undetected.cdp_driver import tab as cdp_tab class CDPMethods(): @@ -1122,9 +1124,33 @@ def switch_to_newest_window(self): self.switch_to_tab(-1) def open_new_tab(self, url=None, switch_to=True): + driver = self.driver if not isinstance(url, str): url = "about:blank" - self.loop.run_until_complete(self.page.get(url, new_tab=True)) + if hasattr(driver, "cdp_base"): + self.loop.run_until_complete(self.page.get(url, new_tab=True)) + if switch_to: + self.switch_to_newest_tab() + return + + target_id = self.loop.run_until_complete( + self.page.send(mycdp.target.create_target(url)) + ) + found_target = None + targets = self.loop.run_until_complete( + self.page.send(mycdp.target.get_targets()) + ) + for target in targets: + if str(target_id) in str(target): + found_target = target + break + if found_target: + tab_url = driver.tabs[0].websocket_url + pre_tab_url = tab_url.split("/page/")[0] + "/page/" + new_tab_url = pre_tab_url + target_id + new_tab = cdp_tab.Tab(new_tab_url, found_target, driver) + driver.targets.append(new_tab) + driver.tabs.append(new_tab) if switch_to: self.switch_to_newest_tab() diff --git a/seleniumbase/core/sb_driver.py b/seleniumbase/core/sb_driver.py index 2f6a1df4423..50a07a46c21 100644 --- a/seleniumbase/core/sb_driver.py +++ b/seleniumbase/core/sb_driver.py @@ -288,11 +288,8 @@ def highlight(self, *args, **kwargs): selector = kwargs["selector"] else: selector = args[0] - if ":contains(" not in selector: - self.driver.cdp.highlight(selector) - return - else: - self.driver.connect() + self.driver.cdp.highlight(selector) + return if "scroll" in kwargs: kwargs.pop("scroll") w_args = kwargs.copy() diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 5e7dbaf8f70..8645166c463 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -230,15 +230,16 @@ def open(self, url): elif ( hasattr(self.driver, "_is_using_uc") and self.driver._is_using_uc - and hasattr(self.driver, "_is_using_auth") - and self.driver._is_using_auth + # and hasattr(self.driver, "_is_using_auth") + # and self.driver._is_using_auth and ( not hasattr(self.driver, "_is_using_cdp") or not self.driver._is_using_cdp ) ): # Auth in UC Mode requires CDP Mode - logging.info("UC Mode requires CDP Mode for auth. Activating now.") + # (and now we're always forcing it) + logging.info("open() in UC Mode now always activates CDP Mode.") self.activate_cdp_mode(url) return elif ( @@ -6313,7 +6314,12 @@ def highlight( scroll - the option to scroll to the element first (Default: True) timeout - the time to wait for the element to appear """ self.__check_scope() - if not self.__is_cdp_swap_needed(): + if self.__is_cdp_swap_needed(): + if page_utils.is_xpath_selector(selector): + if "contains(" in selector: + self.cdp.highlight(selector) + return + else: self._check_browser() self.__skip_if_esc() if isinstance(selector, WebElement): diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index 8fde34859eb..dd3606d8d50 100644 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -1759,14 +1759,15 @@ def open_url(driver, url): elif ( hasattr(driver, "_is_using_uc") and driver._is_using_uc - and hasattr(driver, "_is_using_auth") - and driver._is_using_auth + # and hasattr(driver, "_is_using_auth") + # and driver._is_using_auth and ( not hasattr(driver, "_is_using_cdp") or not driver._is_using_cdp ) ): # Auth in UC Mode requires CDP Mode + # (and now we're always forcing it) driver.uc_activate_cdp_mode(url) return elif ( diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index 6ca77ccd923..6b817f161d2 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -1918,26 +1918,16 @@ def pytest_configure(config): or " -n=" in arg_join or " -n" in arg_join or "-c" in sys_argv - or ( - "addopts" in config.inicfg.keys() - and ( - "-n=" in config.inicfg["addopts"] - or "-n " in config.inicfg["addopts"] - or "-n" in config.inicfg["addopts"] - ) - ) + or "-n=" in config.getini("addopts") + or "-n " in config.getini("addopts") + or "-n" in config.getini("addopts") ): sb_config._multithreaded = True if ( "--html" in sys_argv or " --html=" in arg_join - or ( - "addopts" in config.inicfg.keys() - and ( - "--html=" in config.inicfg["addopts"] - or "--html " in config.inicfg["addopts"] - ) - ) + or "--html=" in config.getini("addopts") + or "--html " in config.getini("addopts") ): sb_config._using_html_report = True sb_config._html_report_name = config.getoption("htmlpath") diff --git a/setup.py b/setup.py index aff87d51705..994e8ee1531 100755 --- a/setup.py +++ b/setup.py @@ -155,7 +155,7 @@ 'wheel>=0.45.1', 'attrs~=25.3.0;python_version<"3.9"', 'attrs>=25.4.0;python_version>="3.9"', - "certifi>=2025.10.5", + "certifi>=2025.11.12", "exceptiongroup>=1.3.0", 'websockets~=13.1;python_version<"3.9"', 'websockets>=15.0.1;python_version>="3.9"', @@ -197,7 +197,8 @@ 'trio>=0.31.0,<1;python_version>="3.9" and python_version<"3.10"', 'trio>=0.32.0,<1;python_version>="3.10"', 'trio-websocket~=0.12.2', - 'wsproto==1.2.0', + 'wsproto==1.2.0;python_version<"3.10"', + 'wsproto==1.3.1;python_version>="3.10"', 'websocket-client~=1.8.0;python_version<"3.9"', 'websocket-client~=1.9.0;python_version>="3.9"', 'selenium==4.27.1;python_version<"3.9"', @@ -206,7 +207,8 @@ 'cssselect==1.2.0;python_version<"3.9"', 'cssselect==1.3.0;python_version>="3.9"', "sortedcontainers==2.4.0", - 'execnet==2.1.1', + 'execnet==2.1.1;python_version<"3.10"', + 'execnet==2.1.2;python_version>="3.10"', 'iniconfig==2.1.0;python_version<"3.10"', 'iniconfig==2.3.0;python_version>="3.10"', 'pluggy==1.5.0;python_version<"3.9"',