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"',