From 1c503bded47513f01634722e4ea10e957eaa3b20 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 11 Mar 2026 14:24:00 -0400 Subject: [PATCH 1/6] Update CDP Mode --- seleniumbase/core/browser_launcher.py | 1 + seleniumbase/core/sb_cdp.py | 5 +++++ seleniumbase/undetected/__init__.py | 1 + seleniumbase/undetected/cdp_driver/cdp_util.py | 4 ++++ 4 files changed, 11 insertions(+) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 546f54e1dc5..c5028c838f9 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -766,6 +766,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): cdp.get_rd_port = CDPM.get_rd_port cdp.get_rd_url = CDPM.get_rd_url cdp.get_endpoint_url = CDPM.get_endpoint_url + cdp.get_websocket_url = CDPM.get_websocket_url cdp.get_port = CDPM.get_port cdp.find_element = CDPM.find_element cdp.find = CDPM.find_element diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index ee0d8a3bf44..c771a0a6fa4 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -206,6 +206,11 @@ def get_port(self): """Same as get_rd_port(), which returns the remote-debugging port.""" return self.get_rd_port() + def get_websocket_url(self): + """Returns the websocket URL of the active tab. + The websocket URL starts with `ws://`.""" + return self.get_active_tab().websocket_url + def add_handler(self, event, handler): self.page.add_handler(event, handler) diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py index 6945bb846d7..889309e9a65 100644 --- a/seleniumbase/undetected/__init__.py +++ b/seleniumbase/undetected/__init__.py @@ -179,6 +179,7 @@ def __init__( options.add_argument("--remote-debugging-host=%s" % debug_host) options.add_argument("--remote-debugging-port=%s" % debug_port) if user_data_dir: + user_data_dir = os.path.abspath(user_data_dir) options.add_argument("--user-data-dir=%s" % user_data_dir) language, keep_user_data_dir = None, bool(user_data_dir) # See if a custom user profile is specified in options diff --git a/seleniumbase/undetected/cdp_driver/cdp_util.py b/seleniumbase/undetected/cdp_driver/cdp_util.py index 572554021f6..52b140ed64f 100644 --- a/seleniumbase/undetected/cdp_driver/cdp_util.py +++ b/seleniumbase/undetected/cdp_driver/cdp_util.py @@ -566,6 +566,8 @@ async def start( platform_var = platform_var[1:-1] if IS_LINUX and not headless and not headed and not xvfb: xvfb = True # The default setting on Linux + if port and not host: + host = "127.0.0.1" # Assume localhost if not host or not port: # The browser hasn't been launched yet. (May need a virtual display) __activate_virtual_display_as_needed( @@ -611,6 +613,8 @@ async def start( elif udd_string.startswith("'") and udd_string.endswith("'"): udd_string = udd_string[1:-1] user_data_dir = udd_string + if user_data_dir: + user_data_dir = os.path.abspath(user_data_dir) if not browser_executable_path: browser = None if "browser" in kwargs: From a8c01815af06bad8768a8a8de8adc40f69960a61 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 11 Mar 2026 14:24:56 -0400 Subject: [PATCH 2/6] Do some refactoring --- seleniumbase/core/mysql.py | 13 ------- seleniumbase/fixtures/base_case.py | 35 ++----------------- .../undetected/cdp_driver/connection.py | 30 ---------------- 3 files changed, 2 insertions(+), 76 deletions(-) diff --git a/seleniumbase/core/mysql.py b/seleniumbase/core/mysql.py index f51cf7a75fe..328a927c32f 100644 --- a/seleniumbase/core/mysql.py +++ b/seleniumbase/core/mysql.py @@ -7,7 +7,6 @@ class DatabaseManager: def __init__(self, database_env="test", conf_creds=None): """Create a connection to the MySQL DB.""" import fasteners - import sys import time from seleniumbase import config as sb_config from seleniumbase.config import settings @@ -19,18 +18,6 @@ def __init__(self, database_env="test", conf_creds=None): constants.PipInstall.FINDLOCK ) with pip_find_lock: - if sys.version_info < (3, 9): - # Fix bug with newer cryptography on Python 3.8: - # "pyo3_runtime.PanicException: Python API call failed" - # (Match the version needed for pdfminer.six functions) - try: - import cryptography - if cryptography.__version__ != "39.0.2": - shared_utils.pip_install( - "cryptography", version="39.0.2" - ) - except Exception: - shared_utils.pip_install("cryptography", version="39.0.2") try: import cryptography # noqa: F401 import pymysql diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index e60b3ae665e..2a563b57881 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -14732,36 +14732,10 @@ def __get_shadow_element( is_present = False for selector_part in selectors[1:]: shadow_root = None - if ( - (self.is_chromium() or self.browser == "firefox") - and int(self.__get_major_browser_version()) >= 96 - ): + if self.is_chromium() or self.browser == "firefox": try: shadow_root = element.shadow_root except Exception: - if self.is_chromium(): - chrome_dict = self.driver.capabilities["chrome"] - chrome_dr_version = chrome_dict["chromedriverVersion"] - chromedriver_version = chrome_dr_version.split(" ")[0] - major_c_dr_version = chromedriver_version.split(".")[0] - if int(major_c_dr_version) < 96: - upgrade_to = "latest" - major_browser_version = ( - self.__get_major_browser_version() - ) - if int(major_browser_version) >= 96: - upgrade_to = str(major_browser_version) - message = ( - "You need to upgrade to a newer\n" - "version of chromedriver to interact\n" - "with Shadow root elements!\n" - "(Current driver version is: %s)" - "\n(Minimum driver version is: 96.*)" - "\nTo upgrade, run this:" - '\n"seleniumbase get chromedriver %s"' - % (chromedriver_version, upgrade_to) - ) - raise Exception(message) if timeout != 0.1: # Skip wait for special 0.1 (See above) time.sleep(2) try: @@ -14770,12 +14744,7 @@ def __get_shadow_element( raise Exception( "Element {%s} has no shadow root!" % selector_chain ) - else: # This part won't work on Chrome 96 or newer. - # If using Chrome 96 or newer (and on an old Python version), - # you'll need to upgrade in order to access Shadow roots. - # Firefox users will likely hit: - # https://github.com/mozilla/geckodriver/issues/1711 - # When Firefox adds support, switch to element.shadow_root + else: try: shadow_root = self.execute_script( "return arguments[0].shadowRoot;", element diff --git a/seleniumbase/undetected/cdp_driver/connection.py b/seleniumbase/undetected/cdp_driver/connection.py index 2236ce9048a..196b03e9aa8 100644 --- a/seleniumbase/undetected/cdp_driver/connection.py +++ b/seleniumbase/undetected/cdp_driver/connection.py @@ -133,31 +133,6 @@ def __repr__(self): return fmt -class EventTransaction(Transaction): - event = None - value = None - - def __init__(self, event_object): - try: - super().__init__(None) - except BaseException: - pass - self.set_result(event_object) - self.event = self.value = self.result() - - def __repr__(self): - status = "finished" - success = False if self.exception() else True - event_object = self.result() - fmt = ( - f"{self.__class__.__name__}\n\t" - f"event: {event_object.__class__.__module__}.{event_object.__class__.__name__}\n\t" # noqa - f"status: {status}\n\t" - f"success: {success}>" - ) - return fmt - - class CantTouchThis(type): def __setattr__(cls, attr, value): """:meta private:""" @@ -619,11 +594,6 @@ async def listener_loop(self): # Probably an event try: event = cdp.util.parse_json_event(message) - # event_tx = EventTransaction(event) - # if not self.connection.mapper: - # self.connection.__count__ = itertools.count(0) - # event_tx.id = next(self.connection.__count__) - # self.connection.mapper[event_tx.id] = event_tx except Exception as e: logger.info( "%s: %s during parsing of json from event : %s" From eb83c130875e6ed201d32bf84a4ab2b317b06c1a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 11 Mar 2026 14:26:33 -0400 Subject: [PATCH 3/6] Update the docs --- examples/cdp_mode/ReadMe.md | 1 + help_docs/cdp_mode_methods.md | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 40890d32910..78469c311bc 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -390,6 +390,7 @@ sb.cdp.get_rd_port() # Returns the remote-debugging port sb.cdp.get_rd_url() # Returns the remote-debugging URL sb.cdp.get_endpoint_url() # Same as sb.cdp.get_rd_url() sb.cdp.get_port() # Same as sb.cdp.get_rd_port() +sb.cdp.get_websocket_url() # Returns the websocket URL sb.cdp.add_handler(event, handler) sb.cdp.find_element(selector, best_match=False, timeout=None) sb.cdp.find(selector, best_match=False, timeout=None) diff --git a/help_docs/cdp_mode_methods.md b/help_docs/cdp_mode_methods.md index 780c410ccf6..f5a503ce578 100644 --- a/help_docs/cdp_mode_methods.md +++ b/help_docs/cdp_mode_methods.md @@ -17,6 +17,7 @@ sb.cdp.get_rd_port() # Returns the remote-debugging port sb.cdp.get_rd_url() # Returns the remote-debugging URL sb.cdp.get_endpoint_url() # Same as sb.cdp.get_rd_url() sb.cdp.get_port() # Same as sb.cdp.get_rd_port() +sb.cdp.get_websocket_url() # Returns the websocket URL sb.cdp.add_handler(event, handler) sb.cdp.find_element(selector, best_match=False, timeout=None) sb.cdp.find(selector, best_match=False, timeout=None) From 3772ff01f47adffdb2c88b09726116fa6ea55089 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 11 Mar 2026 14:30:29 -0400 Subject: [PATCH 4/6] Refresh Python dependencies --- mkdocs_build/requirements.txt | 3 ++- requirements.txt | 10 +++++----- setup.py | 10 +++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index a4a46d48dc9..7494b46ea18 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.2.19 +regex>=2026.2.28 pymdown-extensions>=10.21 pipdeptree>=2.31.0 python-dateutil>=2.8.2 @@ -17,4 +17,5 @@ mkdocs==1.6.1 mkdocs-material==9.6.23 mkdocs-exclude-search==0.6.6 mkdocs-simple-hooks==0.1.5 +mkdocs-get-deps==0.2.0 mkdocs-material-extensions==1.3.1 diff --git a/requirements.txt b/requirements.txt index 539b7274f2b..45ba2874714 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ pip>=26.0.1 packaging>=26.0 setuptools~=70.2;python_version<"3.10" -setuptools>=82.0.0;python_version>="3.10" +setuptools>=82.0.1;python_version>="3.10" wheel>=0.46.3 attrs>=25.4.0 certifi>=2026.2.25 @@ -9,12 +9,12 @@ exceptiongroup>=1.3.1 websockets~=15.0.1;python_version<"3.10" websockets>=16.0;python_version>="3.10" filelock~=3.19.1;python_version<"3.10" -filelock>=3.24.3;python_version>="3.10" +filelock>=3.25.1;python_version>="3.10" fasteners>=0.20 -mycdp>=1.3.2 +mycdp>=1.3.3 pynose>=1.5.5 platformdirs~=4.4.0;python_version<"3.10" -platformdirs>=4.9.2;python_version>="3.10" +platformdirs>=4.9.4;python_version>="3.10" typing-extensions>=4.15.0 sbvirtualdisplay>=1.4.0 MarkupSafe>=3.0.3 @@ -29,7 +29,7 @@ pyreadline3>=3.5.4;platform_system=="Windows" tabcompleter>=1.4.0 pdbp>=1.8.2 idna>=3.11 -charset-normalizer>=3.4.4,<4 +charset-normalizer>=3.4.5,<4 urllib3>=1.26.20,<2;python_version<"3.10" urllib3>=1.26.20,<3;python_version>="3.10" requests~=2.32.5 diff --git a/setup.py b/setup.py index 096adac4d6e..a58a86e11a5 100755 --- a/setup.py +++ b/setup.py @@ -149,7 +149,7 @@ 'pip>=26.0.1', 'packaging>=26.0', 'setuptools~=70.2;python_version<"3.10"', # Newer ones had issues - 'setuptools>=82.0.0;python_version>="3.10"', + 'setuptools>=82.0.1;python_version>="3.10"', 'wheel>=0.46.3', 'attrs>=25.4.0', 'certifi>=2026.2.25', @@ -157,12 +157,12 @@ 'websockets~=15.0.1;python_version<"3.10"', 'websockets>=16.0;python_version>="3.10"', 'filelock~=3.19.1;python_version<"3.10"', - 'filelock>=3.24.3;python_version>="3.10"', + 'filelock>=3.25.1;python_version>="3.10"', 'fasteners>=0.20', - 'mycdp>=1.3.2', + 'mycdp>=1.3.3', 'pynose>=1.5.5', 'platformdirs~=4.4.0;python_version<"3.10"', - 'platformdirs>=4.9.2;python_version>="3.10"', + 'platformdirs>=4.9.4;python_version>="3.10"', 'typing-extensions>=4.15.0', 'sbvirtualdisplay>=1.4.0', 'MarkupSafe>=3.0.3', @@ -177,7 +177,7 @@ 'tabcompleter>=1.4.0', 'pdbp>=1.8.2', 'idna>=3.11', - 'charset-normalizer>=3.4.4,<4', + 'charset-normalizer>=3.4.5,<4', 'urllib3>=1.26.20,<2;python_version<"3.10"', 'urllib3>=1.26.20,<3;python_version>="3.10"', 'requests~=2.32.5', From d32e5f27c9fac636384273cea8dc9a1f76a01f69 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 11 Mar 2026 14:31:43 -0400 Subject: [PATCH 5/6] Update CDP Mode examples --- examples/cdp_mode/raw_amazon.py | 15 +++++++++++++++ examples/cdp_mode/raw_facebook.py | 15 +++++++++++++++ examples/cdp_mode/raw_mycdp_cookies.py | 8 ++++++++ examples/cdp_mode/raw_reuse_browser.py | 9 +++++++++ 4 files changed, 47 insertions(+) create mode 100644 examples/cdp_mode/raw_amazon.py create mode 100644 examples/cdp_mode/raw_facebook.py create mode 100644 examples/cdp_mode/raw_mycdp_cookies.py create mode 100644 examples/cdp_mode/raw_reuse_browser.py diff --git a/examples/cdp_mode/raw_amazon.py b/examples/cdp_mode/raw_amazon.py new file mode 100644 index 00000000000..a16685d56d4 --- /dev/null +++ b/examples/cdp_mode/raw_amazon.py @@ -0,0 +1,15 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, ad_block=True) as sb: + url = "https://www.amazon.com" + sb.activate_cdp_mode(url) + sb.sleep(2) + sb.click_if_visible('button[alt="Continue shopping"]') + sb.sleep(2) + sb.press_keys('input[role="searchbox"]', "TI-89\n") + sb.sleep(3) + print(sb.get_page_title()) + sb.save_as_pdf_to_logs() + sb.save_page_source_to_logs() + sb.save_screenshot_to_logs() + print("Logs have been saved to: ./latest_logs/") diff --git a/examples/cdp_mode/raw_facebook.py b/examples/cdp_mode/raw_facebook.py new file mode 100644 index 00000000000..762e6e9eaaa --- /dev/null +++ b/examples/cdp_mode/raw_facebook.py @@ -0,0 +1,15 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, ad_block=True) as sb: + url = "https://www.facebook.com/SeleniumBase" + sb.activate_cdp_mode(url) + sb.sleep(1) + sb.click_if_visible('[aria-label="Close"] i') + sb.sleep(1) + for i in range(14): + sb.cdp.scroll_down(15) + print(sb.get_page_title()) + sb.save_as_pdf_to_logs() + sb.save_page_source_to_logs() + sb.save_screenshot_to_logs() + print("Logs have been saved to: ./latest_logs/") diff --git a/examples/cdp_mode/raw_mycdp_cookies.py b/examples/cdp_mode/raw_mycdp_cookies.py new file mode 100644 index 00000000000..fe8983c7168 --- /dev/null +++ b/examples/cdp_mode/raw_mycdp_cookies.py @@ -0,0 +1,8 @@ +import mycdp +from seleniumbase import SB + +with SB(uc=True, test=True) as sb: + sb.activate_cdp_mode("https://learn.microsoft.com/en-us/") + tab = sb.cdp.get_active_tab() + loop = sb.cdp.get_event_loop() + print(loop.run_until_complete(tab.send(mycdp.storage.get_cookies()))) diff --git a/examples/cdp_mode/raw_reuse_browser.py b/examples/cdp_mode/raw_reuse_browser.py new file mode 100644 index 00000000000..6706af0fdfc --- /dev/null +++ b/examples/cdp_mode/raw_reuse_browser.py @@ -0,0 +1,9 @@ +"""Test connecting to an existing browser.""" +from seleniumbase import sb_cdp + +sb1 = sb_cdp.Chrome("https://example.com") +port = sb1.get_rd_port() +sb2 = sb_cdp.Chrome(host="127.0.0.1", port=port) +print("The remote-debugging port: %s" % port) +assert sb1.get_rd_port() == sb2.get_rd_port() +assert sb1.get_current_url() == sb2.get_current_url() From 44edd28cd693d404f440d3a9c3757d5eb713ba4d Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 11 Mar 2026 14:32:11 -0400 Subject: [PATCH 6/6] Version 4.47.2 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 14717ee0290..d3618996606 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.47.1" +__version__ = "4.47.2"