From 7d79aa82eb7575706799716403088b7db2604100 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 22 Jul 2026 14:59:43 -0400 Subject: [PATCH 1/5] Make CDP Mode stealthier --- seleniumbase/undetected/cdp_driver/element.py | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/seleniumbase/undetected/cdp_driver/element.py b/seleniumbase/undetected/cdp_driver/element.py index 2c0b8a2bc19..e549b006890 100644 --- a/seleniumbase/undetected/cdp_driver/element.py +++ b/seleniumbase/undetected/cdp_driver/element.py @@ -2,6 +2,7 @@ import asyncio import logging import pathlib +import random import secrets import typing from contextlib import suppress @@ -522,29 +523,37 @@ async def mouse_click_async( using_pim = await self.tab.evaluate(script2) if not using_px and not using_pim: asyncio.create_task(self.flash_async(0.25))''' + with suppress(Exception): + await self.mouse_move_async() + await asyncio.sleep(random.uniform(0.003, 0.005)) + x = center[0] + random.uniform(-0.8, 0.8) + y = center[1] + random.uniform(-0.8, 0.8) asyncio.create_task( self._tab.send( cdp.input_.dispatch_mouse_event( "mousePressed", - x=center[0], - y=center[1], + x=x, + y=y, modifiers=modifiers, button=cdp.input_.MouseButton(button), buttons=buttons, click_count=1, + force=0.5, ) ), ) + await asyncio.sleep(random.uniform(0.011, 0.015)) asyncio.create_task( self._tab.send( cdp.input_.dispatch_mouse_event( "mouseReleased", - x=center[0], - y=center[1], + x=x + random.uniform(-0.12, 0.12), + y=y + random.uniform(-0.12, 0.12), modifiers=modifiers, button=cdp.input_.MouseButton(button), buttons=buttons, click_count=1, + force=0.0, ) ), ) @@ -580,6 +589,9 @@ async def mouse_click_with_offset_async( logger.debug("Clicking on location: %.2f, %.2f" % center_pos) else: logger.debug("Clicking on location: %.2f, %.2f" % (x_pos, y_pos)) + with suppress(Exception): + await self.mouse_move_async() + await asyncio.sleep(random.uniform(0.003, 0.005)) script1 = 'sessionStorage.getItem("pxsid") !== null;' script2 = 'sessionStorage.getItem("PIM-SESSION-ID") !== null;' using_px = True @@ -605,6 +617,7 @@ async def mouse_click_with_offset_async( button=cdp.input_.MouseButton(button), buttons=buttons, click_count=1, + force=0.5, ) ) ) @@ -618,6 +631,7 @@ async def mouse_click_with_offset_async( button=cdp.input_.MouseButton(button), buttons=buttons, click_count=1, + force=0.0, ) ), ) @@ -884,6 +898,7 @@ async def send_keys_async(self, text: str): ) ) # 4. Trigger keyup DOM event + await asyncio.sleep(random.uniform(0.011, 0.015)) await self._tab.send( cdp.input_.dispatch_key_event( type_="keyUp", From f98dd28d35df3e8e25f98fa2f1fc2c7a420d7287 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 22 Jul 2026 15:04:59 -0400 Subject: [PATCH 2/5] Sync up BaseCase and sb_cdp methods --- seleniumbase/core/sb_cdp.py | 4 ++++ seleniumbase/fixtures/base_case.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 94c41cee093..34ea18c506d 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1170,6 +1170,10 @@ def clear_input(self, selector, timeout=None): self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.sleep(0.025)) + def clear(self, selector, timeout=None): + """Same as clear_input(), which clears the text field.""" + self.clear_input(selector=selector, timeout=timeout) + def set_value(self, selector, text, timeout=None): """Similar to send_keys(), but clears the text field first.""" if not timeout: diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 7b7325c4bc2..b41b94d1319 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -5109,6 +5109,8 @@ def activate_cdp_mode(self, url=None, **kwargs): self.click_captcha = self.cdp.click_captcha if hasattr(self.cdp, "add_handler"): self.add_handler = self.cdp.add_handler + if hasattr(self.cdp, "clear_input"): + self.clear_input = self.cdp.clear_input if hasattr(self.cdp, "close_active_tab"): self.close_active_tab = self.cdp.close_active_tab if hasattr(self.cdp, "find_element_by_text"): From ea68a2eca2b3142f941d9da8537c2cc0e917afaf Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 22 Jul 2026 15:17:03 -0400 Subject: [PATCH 3/5] Refresh Python dependencies --- requirements.txt | 2 +- setup.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2f3472c7856..3d14832c0a7 100755 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ setuptools~=70.2;python_version<"3.10" setuptools>=83.0.0;python_version>="3.10" wheel>=0.47.0 attrs>=26.1.0 -certifi>=2026.6.17 +certifi>=2026.7.22 exceptiongroup>=1.3.1 websockets~=15.0.1;python_version<"3.10" websockets>=16.1.1;python_version>="3.10" diff --git a/setup.py b/setup.py index 40b697d209f..10873e9cc2d 100755 --- a/setup.py +++ b/setup.py @@ -169,7 +169,7 @@ 'setuptools>=83.0.0;python_version>="3.10"', 'wheel>=0.47.0', 'attrs>=26.1.0', - 'certifi>=2026.6.17', + 'certifi>=2026.7.22', 'exceptiongroup>=1.3.1', 'websockets~=15.0.1;python_version<"3.10"', 'websockets>=16.1.1;python_version>="3.10"', @@ -302,9 +302,10 @@ # pip install -e .[playwright] # (For the Playwright integration.) "playwright": [ - 'playwright>=1.60.0', + 'playwright>=1.60.0;python_version<"3.10"', + 'playwright>=1.61.0;python_version>="3.10"', 'greenlet>=3.2.5;python_version<"3.10"', - 'greenlet>=3.5.3;python_version>="3.10"', + 'greenlet>=3.5.4;python_version>="3.10"', ], # pip install -e .[pyautogui] # (Already a required dependency on Linux now.) From e5bbe21beb6817e9bb43ce23ba899e62dc6b0723 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 22 Jul 2026 15:18:51 -0400 Subject: [PATCH 4/5] Update CDP Mode examples --- examples/cdp_mode/raw_cdp_clearcote.py | 9 +++++---- examples/cdp_mode/raw_cdp_immoscout.py | 2 +- examples/cdp_mode/raw_easyjet.py | 6 ++++-- examples/cdp_mode/raw_hilton.py | 6 +++--- examples/cdp_mode/raw_southwest.py | 8 ++------ 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/examples/cdp_mode/raw_cdp_clearcote.py b/examples/cdp_mode/raw_cdp_clearcote.py index 26fa26ca44a..e12d1bced31 100644 --- a/examples/cdp_mode/raw_cdp_clearcote.py +++ b/examples/cdp_mode/raw_cdp_clearcote.py @@ -6,16 +6,17 @@ sb.click('button[aria-label="Marker 2"]') sb.click('button[aria-label="Marker 3"]') sb.press_keys("input", "audit") +sb.set_value('input[type="range"]', "100") sb.select_option_by_text("select", "Mouse") sb.click('button:contains("Run the audit")') sb.sleep(6) -sb.assert_element("div.text-emerald-700", timeout=8) +sb.assert_element("div.text-successText", timeout=8) try: - sb.assert_text("100", "div.text-emerald-700", timeout=5) - sb.highlight('div.text-emerald-700:contains("100")') + sb.assert_text("100", "div.text-successText", timeout=5) + sb.highlight('div.text-successText:contains("100")') print(" ✅ The browser fingerprint is clean! Score: 100") except Exception: - score = sb.get_text("div.text-emerald-700") + score = sb.get_text("div.text-successText") print(f" ❌ Fingerprint tampering detected! Score: {score}") sb.sleep(1) folder = "downloaded_files" diff --git a/examples/cdp_mode/raw_cdp_immoscout.py b/examples/cdp_mode/raw_cdp_immoscout.py index 8be1af245a2..8c68e68a6ba 100644 --- a/examples/cdp_mode/raw_cdp_immoscout.py +++ b/examples/cdp_mode/raw_cdp_immoscout.py @@ -2,7 +2,7 @@ (PyAutoGUI is installed at runtime if not yet installed.)""" from seleniumbase import sb_cdp -sb = sb_cdp.Chrome(use_chromium=True, locale="en") +sb = sb_cdp.Chrome(locale="en") sb.goto("https://www.immoscout24.ch/en/real-estate/rent") sb.sleep(1.6) sb.solve_captcha() diff --git a/examples/cdp_mode/raw_easyjet.py b/examples/cdp_mode/raw_easyjet.py index 848d6dccac0..d7c2ff517d3 100644 --- a/examples/cdp_mode/raw_easyjet.py +++ b/examples/cdp_mode/raw_easyjet.py @@ -6,9 +6,11 @@ sb.sleep(1.8) sb.click_if_visible("button#ensRejectAds", timeout=2) sb.sleep(1) - sb.click('input[name="from"]') + input_from = 'input[name="from"]' + sb.click(input_from) sb.sleep(1) - sb.press_keys('input[name="from"]', "London Gatwick") + sb.clear(input_from) + sb.press_keys(input_from, "London Gatwick") sb.sleep(1) sb.click('span[data-testid="airport-name"]') sb.sleep(1) diff --git a/examples/cdp_mode/raw_hilton.py b/examples/cdp_mode/raw_hilton.py index d6ab75784ea..a65730314e0 100644 --- a/examples/cdp_mode/raw_hilton.py +++ b/examples/cdp_mode/raw_hilton.py @@ -3,7 +3,7 @@ with SB(uc=True, test=True, locale="en", use_chromium=True) as sb: sb.activate_cdp_mode() sb.goto("https://www.hilton.com/en/") - sb.sleep(4.5) + sb.sleep(4.2) location = "Sunnyvale, CA, USA" location_input = "input#location-input" sb.wait_for_element(location_input) @@ -17,9 +17,9 @@ sb.click('button[aria-current="date"]') sb.sleep(1.2) sb.click_if_visible('[role="dialog"] button.btn--solid') - sb.sleep(1.5) + sb.sleep(1.4) sb.click('button[data-testid="search-submit-button"]') - sb.sleep(6.5) + sb.sleep(6.4) sb.reconnect() for window in sb.driver.window_handles: sb.switch_to_window(window) diff --git a/examples/cdp_mode/raw_southwest.py b/examples/cdp_mode/raw_southwest.py index df64c01bd38..529df5c50b2 100644 --- a/examples/cdp_mode/raw_southwest.py +++ b/examples/cdp_mode/raw_southwest.py @@ -12,8 +12,7 @@ sb.sleep(0.5) sb.gui_click_element("input#originationAirportCode") sb.sleep(0.2) - sb.select("input#originationAirportCode").clear_input() - sb.sleep(0.2) + sb.clear("input#originationAirportCode") sb.uc_gui_press_keys(" " + "\n") sb.sleep(0.5) sb.gui_click_element("input#originationAirportCode") @@ -21,15 +20,12 @@ sb.uc_gui_press_keys(origin + "\n") sb.sleep(0.4) sb.gui_click_element("h1") - sb.sleep(0.3) sb.gui_click_element("input#destinationAirportCode") sb.sleep(0.2) - sb.select("input#destinationAirportCode").clear_input() - sb.sleep(0.2) + sb.clear("input#destinationAirportCode") sb.uc_gui_press_keys(destination + "\n") sb.sleep(0.4) sb.gui_click_element("h1") - sb.sleep(0.2) sb.click_if_visible("button#onetrust-accept-btn-handler") sb.sleep(0.1) sb.click('form button[data-test="submitField"]') From cee06e0934db22fb144019e18b8f3e933c431eb8 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 22 Jul 2026 15:19:44 -0400 Subject: [PATCH 5/5] Version 4.51.6 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 522bac39614..a3a1f6b520a 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.51.5" +__version__ = "4.51.6"