From 7b68a9da877bd8c581b75c4af9ffdf94f62d5290 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 25 Sep 2025 01:46:07 -0400 Subject: [PATCH 1/6] Make improvements to CDP Mode --- seleniumbase/core/browser_launcher.py | 43 +++++----- seleniumbase/core/sb_cdp.py | 85 ++++++++++---------- seleniumbase/fixtures/page_actions.py | 6 +- seleniumbase/undetected/__init__.py | 9 ++- seleniumbase/undetected/cdp_driver/config.py | 25 ++++++ 5 files changed, 96 insertions(+), 72 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 9bb3435e561..cebfb54a993 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -12,6 +12,7 @@ import urllib3 import warnings from contextlib import suppress +from filelock import FileLock from selenium import webdriver from selenium.common.exceptions import ElementClickInterceptedException from selenium.common.exceptions import InvalidSessionIdException @@ -429,6 +430,12 @@ def __is_cdp_swap_needed(driver): return shared_utils.is_cdp_swap_needed(driver) +def uc_execute_cdp_cmd(driver, *args, **kwargs): + if not driver.is_connected(): + driver.connect() + return driver.default_execute_cdp_cmd(*args, **kwargs) + + def uc_special_open_if_cf( driver, url, @@ -641,7 +648,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): ) loop.run_until_complete(driver.cdp_base.wait(0)) - gui_lock = fasteners.InterProcessLock(constants.MultiBrowser.PYAUTOGUILOCK) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) if ( "chrome-extension://" in str(driver.cdp_base.main_tab) @@ -1080,9 +1087,7 @@ def uc_gui_press_key(driver, key): install_pyautogui_if_missing(driver) import pyautogui pyautogui = get_configured_pyautogui(pyautogui) - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: pyautogui.press(key) @@ -1091,9 +1096,7 @@ def uc_gui_press_keys(driver, keys): install_pyautogui_if_missing(driver) import pyautogui pyautogui = get_configured_pyautogui(pyautogui) - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: for key in keys: pyautogui.press(key) @@ -1103,9 +1106,7 @@ def uc_gui_write(driver, text): install_pyautogui_if_missing(driver) import pyautogui pyautogui = get_configured_pyautogui(pyautogui) - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: pyautogui.write(text) @@ -1138,9 +1139,7 @@ def _uc_gui_click_x_y(driver, x, y, timeframe=0.25, uc_lock=False): % (x, y, screen_width, screen_height) ) if uc_lock: - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) if timeframe >= 0.25: @@ -1159,9 +1158,7 @@ def _uc_gui_click_x_y(driver, x, y, timeframe=0.25, uc_lock=False): def uc_gui_click_x_y(driver, x, y, timeframe=0.25): - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes install_pyautogui_if_missing(driver) import pyautogui @@ -1280,9 +1277,7 @@ def _uc_gui_click_captcha( x = None y = None visible_iframe = True - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes needs_switch = False width_ratio = 1.0 @@ -1643,9 +1638,7 @@ def _uc_gui_handle_captcha_(driver, frame="iframe", ctype=None): import pyautogui pyautogui = get_configured_pyautogui(pyautogui) visible_iframe = True - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes needs_switch = False if not __is_cdp_swap_needed(driver): @@ -5691,6 +5684,12 @@ def get_local_driver( driver, *args, **kwargs ) ) + driver.default_execute_cdp_cmd = driver.execute_cdp_cmd + driver.execute_cdp_cmd = ( + lambda *args, **kwargs: uc_execute_cdp_cmd( + driver, *args, **kwargs + ) + ) driver._is_hidden = (headless or headless2) driver._is_using_uc = True with suppress(Exception): diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 637dbe7d5af..9102569c3c1 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -6,6 +6,7 @@ import sys import time from contextlib import suppress +from filelock import FileLock from seleniumbase import config as sb_config from seleniumbase.config import settings from seleniumbase.fixtures import constants @@ -1065,24 +1066,42 @@ def medimize(self): time.sleep(0.044) return self.loop.run_until_complete(self.page.medimize()) - def set_window_rect(self, x, y, width, height): - if self.get_window()[1].window_state.value == "minimized": - self.loop.run_until_complete( + def __set_window_rect(self, x, y, width, height, uc_lock=False): + if uc_lock: + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) + with gui_lock: + self.__make_sure_pyautogui_lock_is_writable() + if self.get_window()[1].window_state.value == "minimized": + self.loop.run_until_complete( + self.page.set_window_size( + left=x, top=y, width=width, height=height) + ) + time.sleep(0.044) + return self.loop.run_until_complete( + self.page.set_window_size( + left=x, top=y, width=width, height=height) + ) + else: + if self.get_window()[1].window_state.value == "minimized": + self.loop.run_until_complete( + self.page.set_window_size( + left=x, top=y, width=width, height=height) + ) + time.sleep(0.044) + return self.loop.run_until_complete( self.page.set_window_size( left=x, top=y, width=width, height=height) ) - time.sleep(0.044) - return self.loop.run_until_complete( - self.page.set_window_size( - left=x, top=y, width=width, height=height) - ) + + def set_window_rect(self, x, y, width, height): + return self.__set_window_rect(x, y, width, height, uc_lock=True) def reset_window_size(self): x = settings.WINDOW_START_X y = settings.WINDOW_START_Y width = settings.CHROME_START_WIDTH height = settings.CHROME_START_HEIGHT - self.set_window_rect(x, y, width, height) + self.__set_window_rect(x, y, width, height, uc_lock=True) self.__add_light_pause() def open_new_window(self, url=None, switch_to=True): @@ -1548,9 +1567,7 @@ def gui_press_key(self, key): self.__install_pyautogui_if_missing() import pyautogui pyautogui = self.__get_configured_pyautogui(pyautogui) - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: self.__make_sure_pyautogui_lock_is_writable() pyautogui.press(key) @@ -1562,9 +1579,7 @@ def gui_press_keys(self, keys): self.__install_pyautogui_if_missing() import pyautogui pyautogui = self.__get_configured_pyautogui(pyautogui) - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: self.__make_sure_pyautogui_lock_is_writable() for key in keys: @@ -1577,9 +1592,7 @@ def gui_write(self, text): self.__install_pyautogui_if_missing() import pyautogui pyautogui = self.__get_configured_pyautogui(pyautogui) - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: self.__make_sure_pyautogui_lock_is_writable() pyautogui.write(text) @@ -1598,9 +1611,7 @@ def __gui_click_x_y(self, x, y, timeframe=0.25, uc_lock=False): % (x, y, screen_width, screen_height) ) if uc_lock: - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes self.__make_sure_pyautogui_lock_is_writable() pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) @@ -1619,9 +1630,7 @@ def __gui_click_x_y(self, x, y, timeframe=0.25, uc_lock=False): pyautogui.click(x=x, y=y) def gui_click_x_y(self, x, y, timeframe=0.25): - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes self.__make_sure_pyautogui_lock_is_writable() self.__install_pyautogui_if_missing() @@ -1645,7 +1654,7 @@ def gui_click_x_y(self, x, y, timeframe=0.25): sb_config._saved_width_ratio = width_ratio self.minimize() self.__add_light_pause() - self.set_window_rect(win_x, win_y, width, height) + self.__set_window_rect(win_x, win_y, width, height) self.__add_light_pause() x = x * width_ratio y = y * width_ratio @@ -1831,9 +1840,7 @@ def __gui_drag_drop(self, x1, y1, x2, y2, timeframe=0.25, uc_lock=False): % (x2, y2, screen_width, screen_height) ) if uc_lock: - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad) self.__add_light_pause() @@ -1851,9 +1858,7 @@ def __gui_drag_drop(self, x1, y1, x2, y2, timeframe=0.25, uc_lock=False): def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35): """Use PyAutoGUI to drag-and-drop from one point to another. Can simulate click-and-hold when using the same point twice.""" - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes self.__install_pyautogui_if_missing() import pyautogui @@ -1876,7 +1881,7 @@ def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35): sb_config._saved_width_ratio = width_ratio self.minimize() self.__add_light_pause() - self.set_window_rect(win_x, win_y, width, height) + self.__set_window_rect(win_x, win_y, width, height) self.__add_light_pause() x1 = x1 * width_ratio y1 = y1 * (width_ratio - 0.02) @@ -1920,9 +1925,7 @@ def __gui_hover_x_y(self, x, y, timeframe=0.25, uc_lock=False): % (x, y, screen_width, screen_height) ) if uc_lock: - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) time.sleep(0.056) @@ -1936,9 +1939,7 @@ def __gui_hover_x_y(self, x, y, timeframe=0.25, uc_lock=False): print(" pyautogui.moveTo(%s, %s)" % (x, y)) def gui_hover_x_y(self, x, y, timeframe=0.25): - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes self.__install_pyautogui_if_missing() import pyautogui @@ -1971,7 +1972,7 @@ def gui_hover_x_y(self, x, y, timeframe=0.25): if width_ratio < 0.45 or width_ratio > 2.55: width_ratio = 1.01 sb_config._saved_width_ratio = width_ratio - self.set_window_rect(win_x, win_y, width, height) + self.__set_window_rect(win_x, win_y, width, height) self.__add_light_pause() self.bring_active_window_to_front() elif ( @@ -2002,9 +2003,7 @@ def gui_hover_element(self, selector, timeframe=0.25): self.loop.run_until_complete(self.page.wait()) def gui_hover_and_click(self, hover_selector, click_selector): - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: self.__make_sure_pyautogui_lock_is_writable() self.bring_active_window_to_front() @@ -2586,7 +2585,7 @@ class Chrome(CDPMethods): def __init__(self, url=None, **kwargs): if not url: url = "about:blank" - loop = asyncio.new_event_loop() driver = cdp_util.start_sync(**kwargs) + loop = asyncio.new_event_loop() page = loop.run_until_complete(driver.get(url)) super().__init__(loop, page, driver) diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index 66b0bf554dc..c0febe70a70 100644 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -18,10 +18,10 @@ By.PARTIAL_LINK_TEXT # "partial link text" """ import codecs -import fasteners import os import time from contextlib import suppress +from filelock import FileLock from selenium.common.exceptions import ElementNotInteractableException from selenium.common.exceptions import ElementNotVisibleException from selenium.common.exceptions import NoAlertPresentException @@ -1632,9 +1632,7 @@ def __switch_to_window(driver, window_handle, uc_lock=True): and driver._is_using_uc and uc_lock ): - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: driver.switch_to.window(window_handle) else: diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py index 8d4128cb1e3..faaea62c5dc 100644 --- a/seleniumbase/undetected/__init__.py +++ b/seleniumbase/undetected/__init__.py @@ -5,6 +5,7 @@ import subprocess import sys import time +from filelock import FileLock import selenium.webdriver.chrome.service import selenium.webdriver.chrome.webdriver import selenium.webdriver.common.service @@ -117,6 +118,7 @@ def __init__( self.patcher = None import fasteners from seleniumbase.fixtures import constants + from seleniumbase.fixtures import shared_utils if patch_driver: uc_lock = fasteners.InterProcessLock( constants.MultiBrowser.DRIVER_FIXING_LOCK @@ -284,10 +286,11 @@ def __init__( options.binary_location, *options.arguments ) else: - gui_lock = fasteners.InterProcessLock( - constants.MultiBrowser.PYAUTOGUILOCK - ) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: + shared_utils.make_writable( + constants.MultiBrowser.PYAUTOGUILOCK + ) browser = subprocess.Popen( [options.binary_location, *options.arguments], stdin=subprocess.PIPE, diff --git a/seleniumbase/undetected/cdp_driver/config.py b/seleniumbase/undetected/cdp_driver/config.py index e701bb4a801..2315a311aac 100644 --- a/seleniumbase/undetected/cdp_driver/config.py +++ b/seleniumbase/undetected/cdp_driver/config.py @@ -5,6 +5,7 @@ import sys import tempfile import zipfile +from contextlib import suppress from seleniumbase.config import settings from typing import Union, List, Optional @@ -82,6 +83,14 @@ def __init__( self._custom_data_dir = False else: self.user_data_dir = user_data_dir + profile = os.path.join(self.user_data_dir, "Default") + preferences_file = os.path.join(profile, "Preferences") + preferences = get_default_preferences() + if not os.path.exists(profile): + with suppress(Exception): + os.makedirs(profile) + with open(preferences_file, "w") as f: + f.write(preferences) if not browser_executable_path: browser_executable_path = find_chrome_executable() self._browser_args = browser_args @@ -270,9 +279,25 @@ def is_root(): return ctypes.windll.shell32.IsUserAnAdmin() != 0 +def get_default_preferences(): + return ( + """{"credentials_enable_service": false, + "password_manager_enabled": false, + "password_manager_leak_detection": false}""" + ) + + def temp_profile_dir(): """Generate a temp dir (path)""" path = os.path.normpath(tempfile.mkdtemp(prefix="uc_")) + profile = os.path.join(path, "Default") + preferences_file = os.path.join(profile, "Preferences") + preferences = get_default_preferences() + if not os.path.exists(profile): + with suppress(Exception): + os.makedirs(profile) + with open(preferences_file, "w") as f: + f.write(preferences) return path From ad9db00bec56d2175beb9397b209f087cfb33f4d Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 25 Sep 2025 01:46:34 -0400 Subject: [PATCH 2/6] Refresh Python dependencies --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d22444aecfb..69d1995b7ef 100755 --- a/requirements.txt +++ b/requirements.txt @@ -80,7 +80,7 @@ rich>=14.1.0,<15 # ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.) coverage>=7.6.1;python_version<"3.9" -coverage>=7.10.6;python_version>="3.9" +coverage>=7.10.7;python_version>="3.9" pytest-cov>=5.0.0;python_version<"3.9" pytest-cov>=7.0.0;python_version>="3.9" flake8==5.0.4;python_version<"3.9" diff --git a/setup.py b/setup.py index bc266315a34..84ab945e6e8 100755 --- a/setup.py +++ b/setup.py @@ -237,7 +237,7 @@ # Usage: coverage run -m pytest; coverage html; coverage report "coverage": [ 'coverage>=7.6.1;python_version<"3.9"', - 'coverage>=7.10.6;python_version>="3.9"', + 'coverage>=7.10.7;python_version>="3.9"', 'pytest-cov>=5.0.0;python_version<"3.9"', 'pytest-cov>=7.0.0;python_version>="3.9"', ], From 73cc04f5d4df300796bf0d6518ed95616052e981 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 25 Sep 2025 01:49:57 -0400 Subject: [PATCH 3/6] Update CDP Mode examples --- examples/cdp_mode/raw_basic_async.py | 8 +++++--- examples/cdp_mode/raw_cdp.py | 4 +++- examples/cdp_mode/raw_multi_async.py | 10 ++++++---- examples/cdp_mode/raw_multi_captcha.py | 22 ++++++++++++++++++++++ examples/cdp_mode/raw_multi_cdp.py | 15 +++++++++------ 5 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 examples/cdp_mode/raw_multi_captcha.py diff --git a/examples/cdp_mode/raw_basic_async.py b/examples/cdp_mode/raw_basic_async.py index 5e1f400d906..8547cc2b365 100644 --- a/examples/cdp_mode/raw_basic_async.py +++ b/examples/cdp_mode/raw_basic_async.py @@ -1,11 +1,12 @@ import asyncio from seleniumbase import cdp_driver +from seleniumbase import decorators async def main(): url = "seleniumbase.io/simple/login" - driver = await cdp_driver.start_async(incognito=True) - page = await driver.get(url) + driver = await cdp_driver.start_async() + page = await driver.get(url, lang="en") print(await page.evaluate("document.title")) element = await page.select("#username") await element.send_keys_async("demo_user") @@ -24,4 +25,5 @@ async def main(): if __name__ == "__main__": # Call an async function with awaited methods loop = asyncio.new_event_loop() - loop.run_until_complete(main()) + with decorators.print_runtime("raw_basic_async.py"): + loop.run_until_complete(main()) diff --git a/examples/cdp_mode/raw_cdp.py b/examples/cdp_mode/raw_cdp.py index b3e18b98a45..d9f4b686f7b 100644 --- a/examples/cdp_mode/raw_cdp.py +++ b/examples/cdp_mode/raw_cdp.py @@ -21,7 +21,9 @@ def main(): sb.press_keys(where_to, location) sb.sleep(1) sb.gui_click_element(button) - sb.sleep(3) + sb.sleep(2) + sb.click_if_visible('button[aria-label="Close"]') + sb.sleep(1) print(sb.get_title()) print("************") for i in range(8): diff --git a/examples/cdp_mode/raw_multi_async.py b/examples/cdp_mode/raw_multi_async.py index e0a8817b091..638af36a066 100644 --- a/examples/cdp_mode/raw_multi_async.py +++ b/examples/cdp_mode/raw_multi_async.py @@ -3,6 +3,7 @@ from concurrent.futures import ThreadPoolExecutor from random import randint from seleniumbase import cdp_driver +from seleniumbase import decorators async def main(url): @@ -24,7 +25,8 @@ def set_up_loop(url): if __name__ == "__main__": - urls = ["https://seleniumbase.io/demo_page" for i in range(4)] - with ThreadPoolExecutor(max_workers=len(urls)) as executor: - for url in urls: - executor.submit(set_up_loop, url) + urls = ["https://seleniumbase.io/demo_page" for i in range(5)] + with decorators.print_runtime("raw_multi_async.py"): + with ThreadPoolExecutor(max_workers=len(urls)) as executor: + for url in urls: + executor.submit(set_up_loop, url) diff --git a/examples/cdp_mode/raw_multi_captcha.py b/examples/cdp_mode/raw_multi_captcha.py new file mode 100644 index 00000000000..f865667b934 --- /dev/null +++ b/examples/cdp_mode/raw_multi_captcha.py @@ -0,0 +1,22 @@ +# Testing multiple CDP drivers using the sync API +from concurrent.futures import ThreadPoolExecutor +from random import randint +from seleniumbase import decorators +from seleniumbase import sb_cdp + + +def main(url): + sb = sb_cdp.Chrome(url, lang="en") + sb.set_window_rect(randint(4, 680), randint(8, 380), 840, 520) + sb.sleep(2.2) + sb.gui_click_captcha() + sb.sleep(2) + sb.driver.quit() + + +if __name__ == "__main__": + urls = ["https://seleniumbase.io/apps/turnstile" for i in range(5)] + with decorators.print_runtime("raw_multi_captcha.py"): + with ThreadPoolExecutor(max_workers=len(urls)) as executor: + for url in urls: + executor.submit(main, url) diff --git a/examples/cdp_mode/raw_multi_cdp.py b/examples/cdp_mode/raw_multi_cdp.py index b814b44c325..dbec0400be3 100644 --- a/examples/cdp_mode/raw_multi_cdp.py +++ b/examples/cdp_mode/raw_multi_cdp.py @@ -1,20 +1,23 @@ # Testing multiple CDP drivers using the sync API from concurrent.futures import ThreadPoolExecutor from random import randint +from seleniumbase import decorators from seleniumbase import sb_cdp def main(url): - sb = sb_cdp.Chrome(url) - sb.set_window_rect(randint(4, 720), randint(8, 410), 800, 500) + sb = sb_cdp.Chrome(url, lang="en") + sb.set_window_rect(randint(4, 680), randint(8, 380), 840, 520) sb.press_keys("input", "Text") sb.highlight("button") sb.click("button") sb.sleep(2) + sb.driver.quit() if __name__ == "__main__": - urls = ["https://seleniumbase.io/demo_page" for i in range(4)] - with ThreadPoolExecutor(max_workers=len(urls)) as executor: - for url in urls: - executor.submit(main, url) + urls = ["https://seleniumbase.io/demo_page" for i in range(5)] + with decorators.print_runtime("raw_multi_cdp.py"): + with ThreadPoolExecutor(max_workers=len(urls)) as executor: + for url in urls: + executor.submit(main, url) From 3ed8401f5a16296e44995868403e63aeb5110138 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 25 Sep 2025 02:36:15 -0400 Subject: [PATCH 4/6] Update visual testing examples --- examples/visual_testing/layout_test.py | 29 +++++++-------------- examples/visual_testing/test_layout_fail.py | 11 -------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/examples/visual_testing/layout_test.py b/examples/visual_testing/layout_test.py index c5fe036db27..1a6f1273af2 100644 --- a/examples/visual_testing/layout_test.py +++ b/examples/visual_testing/layout_test.py @@ -3,31 +3,22 @@ class VisualLayoutTests(BaseCase): - def test_applitools_layout_change(self): + def test_xkcd_layout_change(self): self.demo_mode = False # (It would interfere with html comparisons) - self.open("https://applitools.com/helloworld/?diff1") - self.wait_for_element('a[href="?diff1"]') + self.open("https://xkcd.com/1424/") print('\nCreating baseline in "visual_baseline" folder.') self.sleep(0.08) - self.check_window(name="helloworld", baseline=True) - # Click a button that changes the text of an element - # (Text changes do not impact visual comparisons) - self.sleep(0.06) - self.click('a[href="?diff1"]') - self.sleep(0.14) + self.check_window(name="xkcd", baseline=True) + # Go to a different comic + self.open("https://xkcd.com/1425/") # Verify html tags match the baseline - self.check_window(name="helloworld", level=1) + self.check_window(name="xkcd", level=1) # Verify html tags and attribute names match the baseline - self.check_window(name="helloworld", level=2) - # Verify html tags and attribute values match the baseline - self.check_window(name="helloworld", level=3) - # Click a button that makes a hidden element visible - self.click("button") - self.check_window(name="helloworld", level=1) - self.check_window(name="helloworld", level=2) + self.check_window(name="xkcd", level=2) + # Verify html tags and attribute values don't match the baseline with self.assert_raises(Exception): - self.check_window(name="helloworld", level=3) + self.check_window(name="xkcd", level=3) # Now that we know the Exception was raised as expected, # let's print out the comparison results by running a Level-0 check. # (NOTE: Running with level-0 will print but NOT raise an Exception.) - self.check_window(name="helloworld", level=0) + self.check_window(name="xkcd", level=0) diff --git a/examples/visual_testing/test_layout_fail.py b/examples/visual_testing/test_layout_fail.py index 354d5d2ecd5..284a4e45676 100644 --- a/examples/visual_testing/test_layout_fail.py +++ b/examples/visual_testing/test_layout_fail.py @@ -15,17 +15,6 @@ def test_python_home_change(self, sb): class VisualLayoutFailureTests(BaseCase): - def test_applitools_change(self): - self.open("https://applitools.com/helloworld/?diff1") - print('\nCreating baseline in "visual_baseline" folder.') - self.check_window(name="helloworld", baseline=True) - # Click a button that changes the text of an element - self.click('a[href="?diff1"]') - # Click a button that makes a hidden element visible - self.slow_click("button") - print("(This test should fail)") # due to image now seen - self.check_window(name="helloworld", level=3) - def test_xkcd_logo_change(self): self.open("https://xkcd.com/554/") print('\nCreating baseline in "visual_baseline" folder.') From f4b95bd5293fb77711c534541d8cc1216e836b68 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 25 Sep 2025 02:54:39 -0400 Subject: [PATCH 5/6] Update an example --- examples/hack_the_planet.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/examples/hack_the_planet.py b/examples/hack_the_planet.py index c4ec8f22382..20fefe56f46 100644 --- a/examples/hack_the_planet.py +++ b/examples/hack_the_planet.py @@ -130,25 +130,12 @@ def test_all_your_base_are_belong_to_us(self): self.highlight("section.crayons-card", loops=7, scroll=False) self.open("https://store.steampowered.com/") + self.set_text_content('a[href*="steamcommunity.com/"]', " ") self.set_text_content('div.content a[href*="/about/"]', " ") self.set_text_content('div.content a[href*="help.steam"]', aybabtu) - self.set_text_content("#foryou_tab a", "ALL") - self.set_text_content("#noteworthy_tab a", "YOUR BASE") - self.set_text_content("#genre_tab a", "ARE") - self.set_text_content('span:contains("Points Shop")', "BELONG") - self.set_text_content('span:contains("News")', "TO") - self.set_text_content('span:contains("Labs")', "US") - self.set_value("input#store_nav_search_term", ayb + " . . . .") - self.highlight('div.content a[href*="help.steam"]', loops=6) - self.highlight("#store_nav_area", loops=2, scroll=False) - self.highlight("#foryou_tab a", loops=1, scroll=False) - self.highlight("#noteworthy_tab a", loops=3, scroll=False) - self.highlight("#genre_tab a", loops=1, scroll=False) - self.highlight('span:contains("BELONG")', loops=1, scroll=False) - self.highlight('span:contains("TO")', loops=1, scroll=False) - self.highlight('span:contains("US")', loops=2, scroll=False) - self.js_click('input[id*="nav_search"]') - self.highlight('input[id*="nav_search"]', loops=6, scroll=False) + zoom_in = '[href*="help.steam"]{zoom: 1.5;-moz-transform: scale(1.5);}' + self.add_css_style(zoom_in) + self.highlight('div.content a[href*="help.steam"]', loops=12) self.open("https://xkcd.com/286/") self.set_text_content('a[href="/archive"]', "ALL") From 0857c3b954585313cb33fc7a56d41b1eb34589cc Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 25 Sep 2025 02:55:11 -0400 Subject: [PATCH 6/6] Version 4.41.9 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 432f6578e95..249d4805d86 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.41.8" +__version__ = "4.41.9"