From 58e5ba8336ba6fb52e0f4ac9ae0d029e129668c8 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 30 Oct 2025 02:06:52 -0400 Subject: [PATCH 1/3] Update CDP Mode --- seleniumbase/core/browser_launcher.py | 3 + seleniumbase/core/sb_cdp.py | 126 ++++++++++++++---- seleniumbase/fixtures/base_case.py | 7 + .../undetected/cdp_driver/connection.py | 5 +- seleniumbase/undetected/cdp_driver/element.py | 93 ++++++++++--- 5 files changed, 186 insertions(+), 48 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 07cc8b12a50..a1ce6e213e3 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -764,6 +764,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): cdp.click_active_element = CDPM.click_active_element cdp.click_if_visible = CDPM.click_if_visible cdp.click_visible_elements = CDPM.click_visible_elements + cdp.click_with_offset = CDPM.click_with_offset cdp.mouse_click = CDPM.mouse_click cdp.get_parent = CDPM.get_parent cdp.remove_element = CDPM.remove_element @@ -793,11 +794,13 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): cdp.set_attributes = CDPM.set_attributes cdp.is_attribute_present = CDPM.is_attribute_present cdp.is_online = CDPM.is_online + cdp.solve_captcha = CDPM.solve_captcha cdp.gui_press_key = CDPM.gui_press_key cdp.gui_press_keys = CDPM.gui_press_keys cdp.gui_write = CDPM.gui_write cdp.gui_click_x_y = CDPM.gui_click_x_y cdp.gui_click_element = CDPM.gui_click_element + cdp.gui_click_with_offset = CDPM.gui_click_with_offset cdp.gui_click_captcha = CDPM.gui_click_captcha cdp.gui_drag_drop_points = CDPM.gui_drag_drop_points cdp.gui_drag_and_drop = CDPM.gui_drag_and_drop diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index e9fdfc33f96..d7f6b3e779a 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -64,6 +64,11 @@ def __add_sync_methods(self, element): ) element.highlight_overlay = lambda: self.__highlight_overlay(element) element.mouse_click = lambda: self.__mouse_click(element) + element.click_with_offset = ( + lambda *args, **kwargs: self.__mouse_click_with_offset_async( + element, *args, **kwargs + ) + ) element.mouse_drag = ( lambda destination: self.__mouse_drag(element, destination) ) @@ -447,6 +452,15 @@ def __mouse_click(self, element): self.loop.run_until_complete(self.page.wait()) return result + def __mouse_click_with_offset_async(self, element, *args, **kwargs): + result = ( + self.loop.run_until_complete( + element.mouse_click_with_offset_async(*args, **kwargs) + ) + ) + self.loop.run_until_complete(self.page.wait()) + return result + def __mouse_drag(self, element, destination): return ( self.loop.run_until_complete(element.mouse_drag_async(destination)) @@ -689,10 +703,16 @@ def click(self, selector, timeout=None): self.__slow_mode_pause_if_set() element = self.find_element(selector, timeout=timeout) element.scroll_into_view() - if element.tag_name == "div" or element.tag_name == "input": - element.mouse_click() # Simulated click (not PyAutoGUI) + tag_name = element.tag_name + if tag_name: + tag_name = tag_name.lower().strip() + if ( + tag_name in ["a", "button", "canvas", "div", "input", "li", "span"] + and "contains(" not in selector + ): + element.mouse_click() # Simulated click (NOT PyAutoGUI) else: - element.click() + element.click() # Standard CDP click self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.wait()) @@ -738,7 +758,7 @@ def click_visible_elements(self, selector, limit=0): element.scroll_into_view() element.click() click_count += 1 - time.sleep(0.042) + time.sleep(0.044) self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.wait()) except Exception: @@ -1757,11 +1777,32 @@ def gui_click_element(self, selector, timeframe=0.25): self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.wait()) - def _on_a_cf_turnstile_page(self): - time.sleep(0.042) - source = self.get_page_source() + def gui_click_with_offset( + self, selector, x, y, timeframe=0.25, center=False + ): + """Click an element at an {X,Y}-offset location. + {0,0} is the top-left corner of the element. + If center==True, {0,0} becomes the center of the element. + The timeframe is the time spent moving the mouse.""" + if center: + px, py = self.get_gui_element_center(selector) + self.gui_click_x_y(px + x, py + y, timeframe=timeframe) + else: + element_rect = self.get_gui_element_rect(selector) + px = element_rect["x"] + py = element_rect["y"] + self.gui_click_x_y(px + x, py + y, timeframe=timeframe) + + def click_with_offset(self, selector, x, y, center=False): + element = self.find_element(selector) + element.scroll_into_view() + element.click_with_offset(x=x, y=y, center=center) + self.__slow_mode_pause_if_set() + self.loop.run_until_complete(self.page.wait()) + + def _on_a_cf_turnstile_page(self, source=None): if not source or len(source) < 400: - time.sleep(0.22) + time.sleep(0.2) source = self.get_page_source() if ( 'data-callback="onCaptchaSuccess"' in source @@ -1773,20 +1814,21 @@ def _on_a_cf_turnstile_page(self): return True return False - def _on_a_g_recaptcha_page(self): - time.sleep(0.042) - source = self.get_page_source() + def _on_a_g_recaptcha_page(self, source=None): if not source or len(source) < 400: - time.sleep(0.22) + time.sleep(0.2) source = self.get_page_source() if ( 'id="recaptcha-token"' in source or 'title="reCAPTCHA"' in source ): return True + elif "/recaptcha/api.js" in source: + time.sleep(1.6) # Still loading + return True return False - def __gui_click_recaptcha(self): + def __gui_click_recaptcha(self, use_cdp=False): selector = None if self.is_element_visible('iframe[title="reCAPTCHA"]'): selector = 'iframe[title="reCAPTCHA"]' @@ -1797,19 +1839,39 @@ def __gui_click_recaptcha(self): element_rect = self.get_gui_element_rect(selector, timeout=1) e_x = element_rect["x"] e_y = element_rect["y"] - x = e_x + 29 - y = e_y + 35 + x_offset = 26 + y_offset = 35 + if shared_utils.is_windows(): + x_offset = 29 + x = e_x + x_offset + y = e_y + y_offset sb_config._saved_cf_x_y = (x, y) time.sleep(0.08) - self.gui_click_x_y(x, y) + if use_cdp: + self.sleep(0.03) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) + with gui_lock: # Prevent issues with multiple processes + self.bring_active_window_to_front() + time.sleep(0.056) + self.click_with_offset(selector, x_offset, y_offset) + time.sleep(0.056) + else: + self.gui_click_x_y(x, y) + + def solve_captcha(self): + self.__click_captcha(use_cdp=True) def gui_click_captcha(self): - if self._on_a_cf_turnstile_page(): - pass - elif self._on_a_g_recaptcha_page(): - self.__gui_click_recaptcha() + self.__click_captcha(use_cdp=False) + + def __click_captcha(self, use_cdp=False): + """Uses PyAutoGUI unless use_cdp == True""" + self.sleep(0.056) + source = self.get_page_source() + if self._on_a_g_recaptcha_page(source): + self.__gui_click_recaptcha(use_cdp) return - else: + elif not self._on_a_cf_turnstile_page(source): return selector = None if ( @@ -1970,14 +2032,24 @@ def gui_click_captcha(self): element_rect = self.get_gui_element_rect(selector, timeout=1) e_x = element_rect["x"] e_y = element_rect["y"] - x = e_x + 32 - if not shared_utils.is_windows(): - y = e_y + 32 - else: - y = e_y + 28 + x_offset = 32 + y_offset = 32 + if shared_utils.is_windows(): + y_offset = 28 + x = e_x + x_offset + y = e_y + y_offset sb_config._saved_cf_x_y = (x, y) time.sleep(0.08) - self.gui_click_x_y(x, y) + if use_cdp: + self.sleep(0.03) + gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) + with gui_lock: # Prevent issues with multiple processes + self.bring_active_window_to_front() + time.sleep(0.056) + self.click_with_offset(selector, x_offset, y_offset) + time.sleep(0.056) + else: + self.gui_click_x_y(x, y) def __gui_drag_drop(self, x1, y1, x2, y2, timeframe=0.25, uc_lock=False): self.__install_pyautogui_if_missing() diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 938be66719e..9b9642c5c6c 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -5008,6 +5008,9 @@ def activate_cdp_mode(self, url=None, **kwargs): self.get_new_driver(undetectable=True) self.driver.uc_open_with_cdp_mode(url, **kwargs) self.cdp = self.driver.cdp + if hasattr(self.cdp, "solve_captcha"): + self.solve_captcha = self.cdp.solve_captcha + self.undetectable = True def activate_recorder(self): """Activate Recorder Mode on the current tab/window. @@ -11452,6 +11455,7 @@ def __is_cdp_swap_needed(self): if cdp_swap_needed: if not self.cdp: self.cdp = self.driver.cdp + self.undetectable = True return True else: return False @@ -13970,6 +13974,9 @@ def __click_with_offset( timeout=None, center=None, ): + if self.__is_cdp_swap_needed(): + self.cdp.click_with_offset(selector, x, y, center=center) + return self.wait_for_ready_state_complete() if self.__needs_minimum_wait(): time.sleep(0.14) diff --git a/seleniumbase/undetected/cdp_driver/connection.py b/seleniumbase/undetected/cdp_driver/connection.py index fc0aa1493d3..75c53adf969 100644 --- a/seleniumbase/undetected/cdp_driver/connection.py +++ b/seleniumbase/undetected/cdp_driver/connection.py @@ -7,7 +7,6 @@ import logging import sys import types -from asyncio import iscoroutine, iscoroutinefunction from typing import ( Optional, Generator, @@ -666,8 +665,8 @@ async def listener_loop(self): for callback in callbacks: try: if ( - iscoroutinefunction(callback) - or iscoroutine(callback) + inspect.iscoroutinefunction(callback) + or inspect.iscoroutine(callback) ): try: await callback(event, self.connection) diff --git a/seleniumbase/undetected/cdp_driver/element.py b/seleniumbase/undetected/cdp_driver/element.py index 491d86ef3df..d3829b18d75 100644 --- a/seleniumbase/undetected/cdp_driver/element.py +++ b/seleniumbase/undetected/cdp_driver/element.py @@ -484,17 +484,14 @@ async def mouse_click_async( buttons: typing.Optional[int] = 1, modifiers: typing.Optional[int] = 0, hold: bool = False, - _until_event: typing.Optional[type] = None, ): """ Native click (on element). - Note: This likely does not work at the moment. Use click() instead. :param button: str (default = "left") :param buttons: which button (default 1 = left) :param modifiers: *(Optional)* Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0). - :param _until_event: Internal. Event to wait for before returning. """ try: center = (await self.get_position_async()).center @@ -505,6 +502,7 @@ async def mouse_click_async( return logger.debug("Clicking on location: %.2f, %.2f" % center) await asyncio.gather( + self.flash_async(0.25), self._tab.send( cdp.input_.dispatch_mouse_event( "mousePressed", @@ -528,10 +526,66 @@ async def mouse_click_async( ) ), ) + + async def mouse_click_with_offset_async( + self, + x: typing.Union[float, int], + y: typing.Union[float, int], + center: bool = False, + button: str = "left", + buttons: typing.Optional[int] = 1, + modifiers: typing.Optional[int] = 0, + ): + x_offset = int(x) + y_offset = int(y) try: - await self.flash_async() - except BaseException: - pass + position = (await self.get_position_async()) + width = position.width + height = position.height + x_pos = position.left + y_pos = position.top + center_pos = (await self.get_position_async()).center + except AttributeError: + return + if not center_pos: + logger.warning("Could not calculate box model for %s", self) + return + if center: + x_pos = center_pos[0] + y_pos = center_pos[1] + width = 0 + height = 0 + logger.debug("Clicking on location: %.2f, %.2f" % center_pos) + else: + logger.debug("Clicking on location: %.2f, %.2f" % (x_pos, y_pos)) + await asyncio.gather( + self.flash_async( + x_offset=x_offset - int(width / 2), + y_offset=y_offset - int(height / 2), + ), + self._tab.send( + cdp.input_.dispatch_mouse_event( + "mousePressed", + x=x_pos + x_offset, + y=y_pos + y_offset, + modifiers=modifiers, + button=cdp.input_.MouseButton(button), + buttons=buttons, + click_count=1, + ) + ), + self._tab.send( + cdp.input_.dispatch_mouse_event( + "mouseReleased", + x=x_pos + x_offset, + y=y_pos + y_offset, + modifiers=modifiers, + button=cdp.input_.MouseButton(button), + buttons=buttons, + click_count=1, + ) + ), + ) async def mouse_move_async(self): """ @@ -664,7 +718,7 @@ async def scroll_into_view_async(self): return # await self.apply("""(el) => el.scrollIntoView(false)""") - async def clear_input_async(self, _until_event: type = None): + async def clear_input_async(self): """Clears an input field.""" try: await self.apply('function (element) { element.value = "" } ') @@ -952,18 +1006,21 @@ async def flash_async( .replace(" ", "") .replace("\n", "") ) - arguments = [cdp.runtime.CallArgument( - object_id=self._remote_object.object_id - )] - await self._tab.send( - cdp.runtime.call_function_on( - script, - object_id=self._remote_object.object_id, - arguments=arguments, - await_promise=True, - user_gesture=True, + try: + arguments = [cdp.runtime.CallArgument( + object_id=self._remote_object.object_id + )] + await self._tab.send( + cdp.runtime.call_function_on( + script, + object_id=self._remote_object.object_id, + arguments=arguments, + await_promise=True, + user_gesture=True, + ) ) - ) + except Exception: + pass async def highlight_overlay_async(self): """ From 35c3e388b1cc8de1b595355c912d37093c3d64f4 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 30 Oct 2025 02:08:12 -0400 Subject: [PATCH 2/3] Update examples --- examples/cdp_mode/ReadMe.md | 10 ++++--- examples/cdp_mode/raw_antibot.py | 6 ++--- examples/cdp_mode/raw_async.py | 4 +-- examples/cdp_mode/raw_canvas.py | 35 +++++++++++++++++++++++++ examples/cdp_mode/raw_cdp_drivers.py | 6 ++--- examples/cdp_mode/raw_cdp_gitlab.py | 2 +- examples/cdp_mode/raw_cdp_hyatt.py | 9 ++++--- examples/cdp_mode/raw_cdp_recaptcha.py | 2 +- examples/cdp_mode/raw_cdp_turnstile.py | 2 +- examples/cdp_mode/raw_driver.py | 2 +- examples/cdp_mode/raw_footlocker.py | 10 +++---- examples/cdp_mode/raw_form_turnstile.py | 2 +- examples/cdp_mode/raw_gitlab.py | 2 +- examples/cdp_mode/raw_hyatt.py | 11 ++++---- examples/cdp_mode/raw_indeed_login.py | 5 +--- examples/cdp_mode/raw_nevada_search.py | 20 ++++++++++++++ examples/cdp_mode/raw_planetmc.py | 2 +- examples/cdp_mode/raw_priceline.py | 18 +++++++------ examples/cdp_mode/raw_radwell.py | 8 +++--- examples/cdp_mode/raw_seatgeek.py | 17 ++++++++++++ examples/cdp_mode/raw_turnstile.py | 2 +- examples/presenter/uc_presentation_4.py | 32 ++++++++++++---------- examples/test_canvas.py | 8 +++--- 23 files changed, 147 insertions(+), 68 deletions(-) create mode 100644 examples/cdp_mode/raw_canvas.py create mode 100644 examples/cdp_mode/raw_nevada_search.py create mode 100644 examples/cdp_mode/raw_seatgeek.py diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 041446d8335..cedc13dd069 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -191,13 +191,14 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb: sb.activate_cdp_mode(url) sb.sleep(3.5) sb.click_if_visible('button[aria-label="Close"]') + sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") - sb.sleep(1) + sb.sleep(1.2) location = "Anaheim, CA, USA" sb.type('input[id="search-term"]', location) - sb.sleep(1) + sb.sleep(1.2) sb.click('li[data-js="suggestion"]') - sb.sleep(1) + sb.sleep(1.2) sb.click("button.be-button-shop") sb.sleep(6) card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' @@ -381,6 +382,7 @@ sb.cdp.click_if_visible(selector) sb.cdp.click_visible_elements(selector, limit=0) sb.cdp.click_nth_element(selector, number) sb.cdp.click_nth_visible_element(selector, number) +sb.cdp.click_with_offset(selector, x, y, center=False) sb.cdp.click_link(link_text) sb.cdp.go_back() sb.cdp.go_forward() @@ -475,11 +477,13 @@ sb.cdp.set_session_storage_item(key, value) sb.cdp.set_attributes(selector, attribute, value) sb.cdp.is_attribute_present(selector, attribute, value=None) sb.cdp.is_online() +sb.cdp.solve_captcha() sb.cdp.gui_press_key(key) sb.cdp.gui_press_keys(keys) sb.cdp.gui_write(text) sb.cdp.gui_click_x_y(x, y, timeframe=0.25) sb.cdp.gui_click_element(selector, timeframe=0.25) +sb.cdp.gui_click_with_offset(selector, x, y, timeframe=0.25, center=False) sb.cdp.gui_click_captcha() sb.cdp.gui_drag_drop_points(x1, y1, x2, y2, timeframe=0.35) sb.cdp.gui_drag_and_drop(drag_selector, drop_selector, timeframe=0.35) diff --git a/examples/cdp_mode/raw_antibot.py b/examples/cdp_mode/raw_antibot.py index 9239e862fa5..58036f418e6 100644 --- a/examples/cdp_mode/raw_antibot.py +++ b/examples/cdp_mode/raw_antibot.py @@ -5,11 +5,9 @@ sb.activate_cdp_mode(url) sb.press_keys("input#username", "demo_user") sb.press_keys("input#password", "secret_pass") - x, y = sb.cdp.get_gui_element_center("button#myButton") - sb.uc_gui_click_x_y(x, y) + sb.click("button#myButton") sb.sleep(1.5) - x, y = sb.cdp.get_gui_element_center("a#log-in") - sb.uc_gui_click_x_y(x, y) + sb.click("a#log-in") sb.assert_text("Welcome!", "h1") sb.set_messenger_theme(location="bottom_center") sb.post_message("SeleniumBase wasn't detected!") diff --git a/examples/cdp_mode/raw_async.py b/examples/cdp_mode/raw_async.py index 8e598e66364..fc193aadb58 100644 --- a/examples/cdp_mode/raw_async.py +++ b/examples/cdp_mode/raw_async.py @@ -57,10 +57,10 @@ async def main(): where_to = 'div[data-automation*="experiences"] input' button = 'button[data-automation*="experiences-search"]' sb.wait_for_text("Where to?") - sb.gui_click_element(where_to) + sb.click(where_to) sb.press_keys(where_to, location) sb.sleep(1) - sb.gui_click_element(button) + sb.click(button) sb.sleep(3) print(sb.get_title()) print("************") diff --git a/examples/cdp_mode/raw_canvas.py b/examples/cdp_mode/raw_canvas.py new file mode 100644 index 00000000000..32c9f98f1e9 --- /dev/null +++ b/examples/cdp_mode/raw_canvas.py @@ -0,0 +1,35 @@ +"""Use SeleniumBase to interact with "canvas" elements.""" +from seleniumbase import SB + + +def get_canvas_pixel_colors_at_top_left(sb): + # Return the RGB colors of the canvas's top left pixel + color = sb.cdp.evaluate( + "document.querySelector('canvas').getContext('2d')" + ".getImageData(%s,%s,1,1).data;" % (0, 0) + ) + return [color["0"], color["1"], color["2"]] + + +with SB(uc=True, test=True) as sb: + # Testing sb.cdp.click_with_offset() + url = "https://seleniumbase.io/canvas/" + sb.activate_cdp_mode(url) + sb.assert_title_contains("Canvas") + sb.highlight("canvas") + rgb = get_canvas_pixel_colors_at_top_left(sb) + sb.assert_equal(rgb, [221, 242, 231]) # Looks greenish + sb.cdp.click_with_offset("canvas", 500, 350) + sb.highlight("canvas", loops=5) + rgb = get_canvas_pixel_colors_at_top_left(sb) + sb.assert_equal(rgb, [39, 43, 56]) # Blue by hamburger + +with SB(uc=True, test=True) as sb: + # Testing sb.cdp.gui_click_with_offset() + url = "https://seleniumbase.io/other/canvas" + sb.activate_cdp_mode(url) + sb.assert_title_contains("Canvas") + sb.cdp.gui_click_with_offset("canvas", 0, 0, center=True) + sb.sleep(1) + sb.uc_gui_press_key("ENTER") + sb.sleep(0.5) diff --git a/examples/cdp_mode/raw_cdp_drivers.py b/examples/cdp_mode/raw_cdp_drivers.py index fb7e0b992cf..5f826c4d24f 100644 --- a/examples/cdp_mode/raw_cdp_drivers.py +++ b/examples/cdp_mode/raw_cdp_drivers.py @@ -13,15 +13,15 @@ print(sb.get_current_url()) sb.type("input#username", "demo_user") sb.type("input#password", "secret_pass") - sb.cdp.gui_click_element("button") + sb.click("button") sb.sleep(1) - sb.cdp.gui_click_element("a#log-in") + sb.click("a#log-in") sb.assert_text("Welcome!", "h1") sb.sleep(2) sb.switch_to_driver(driver2) sb.assert_url_contains("hobbit") print(sb.get_current_url()) - sb.cdp.gui_click_element("button") + sb.click("button") sb.assert_text("Welcome to Middle Earth!") sb.click("img") sb.sleep(3) diff --git a/examples/cdp_mode/raw_cdp_gitlab.py b/examples/cdp_mode/raw_cdp_gitlab.py index 893753aefd5..d983b081253 100644 --- a/examples/cdp_mode/raw_cdp_gitlab.py +++ b/examples/cdp_mode/raw_cdp_gitlab.py @@ -3,7 +3,7 @@ url = "https://gitlab.com/users/sign_in" sb = sb_cdp.Chrome(url, incognito=True) sb.sleep(2.2) -sb.gui_click_captcha() +sb.solve_captcha() sb.highlight('h1:contains("GitLab")') sb.highlight('button:contains("Sign in")') sb.driver.stop() diff --git a/examples/cdp_mode/raw_cdp_hyatt.py b/examples/cdp_mode/raw_cdp_hyatt.py index 43c1be21c53..072bf9e5452 100644 --- a/examples/cdp_mode/raw_cdp_hyatt.py +++ b/examples/cdp_mode/raw_cdp_hyatt.py @@ -4,19 +4,20 @@ sb = sb_cdp.Chrome(url, locale="en", guest=True) sb.sleep(4.2) sb.click_if_visible('button[aria-label="Close"]') +sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") -sb.sleep(1) +sb.sleep(1.2) location = "Anaheim, CA, USA" sb.type('input[id="search-term"]', location) -sb.sleep(1) +sb.sleep(1.2) sb.click('li[data-js="suggestion"]') -sb.sleep(1) +sb.sleep(1.2) sb.click("button.be-button-shop") sb.sleep(6) card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' hotels = sb.select_all(card_info) print("Hyatt Hotels in %s:" % location) -print("(" + sb.get_text("ul.b-color_text-white") + ")") +print("(" + sb.get_text('span[class*="summary_destination"]') + ")") if len(hotels) == 0: print("No availability over the selected dates!") for hotel in hotels: diff --git a/examples/cdp_mode/raw_cdp_recaptcha.py b/examples/cdp_mode/raw_cdp_recaptcha.py index 707800ed967..82c75835a02 100644 --- a/examples/cdp_mode/raw_cdp_recaptcha.py +++ b/examples/cdp_mode/raw_cdp_recaptcha.py @@ -2,7 +2,7 @@ url = "https://seleniumbase.io/apps/recaptcha" sb = sb_cdp.Chrome(url) -sb.gui_click_captcha() +sb.solve_captcha() sb.assert_element("img#captcha-success") sb.set_messenger_theme(location="top_left") sb.post_message("SeleniumBase wasn't detected", duration=3) diff --git a/examples/cdp_mode/raw_cdp_turnstile.py b/examples/cdp_mode/raw_cdp_turnstile.py index 34bab0f3888..aa8a1cdb53e 100644 --- a/examples/cdp_mode/raw_cdp_turnstile.py +++ b/examples/cdp_mode/raw_cdp_turnstile.py @@ -2,7 +2,7 @@ url = "https://seleniumbase.io/apps/turnstile" sb = sb_cdp.Chrome(url) -sb.gui_click_captcha() +sb.solve_captcha() sb.assert_element("img#captcha-success") sb.set_messenger_theme(location="top_left") sb.post_message("SeleniumBase wasn't detected", duration=3) diff --git a/examples/cdp_mode/raw_driver.py b/examples/cdp_mode/raw_driver.py index 4fe1c17f481..d9ff6a20648 100644 --- a/examples/cdp_mode/raw_driver.py +++ b/examples/cdp_mode/raw_driver.py @@ -6,6 +6,6 @@ url = "www.planetminecraft.com/account" driver.uc_activate_cdp_mode(url) driver.sleep(1) -driver.uc_gui_click_captcha() +driver.cdp.solve_captcha() driver.wait_for_element_absent("input[disabled]") driver.sleep(2) diff --git a/examples/cdp_mode/raw_footlocker.py b/examples/cdp_mode/raw_footlocker.py index 8db0e64a5d8..110b22e917d 100644 --- a/examples/cdp_mode/raw_footlocker.py +++ b/examples/cdp_mode/raw_footlocker.py @@ -4,16 +4,16 @@ url = "https://www.footlocker.com/" sb.activate_cdp_mode(url) sb.sleep(2.5) - sb.cdp.click_if_visible('button[id*="Agree"]') + sb.click_if_visible('button[id*="Agree"]') sb.sleep(1.5) - sb.cdp.mouse_click('input[name="query"]') + sb.click('input[name="query"]') sb.sleep(1.5) search = "Nike Shoes" - sb.cdp.press_keys('input[name="query"]', search) + sb.press_keys('input[name="query"]', search) sb.sleep(2.5) - sb.cdp.mouse_click('ul[id*="typeahead"] li div') + sb.click('ul[id*="typeahead"] li div') sb.sleep(3.5) - elements = sb.cdp.select_all("a.ProductCard-link") + elements = sb.select_all("a.ProductCard-link") if elements: print('**** Found results for "%s": ****' % search) for element in elements: diff --git a/examples/cdp_mode/raw_form_turnstile.py b/examples/cdp_mode/raw_form_turnstile.py index 8ac6120b4fe..f5eaa3ff1fb 100644 --- a/examples/cdp_mode/raw_form_turnstile.py +++ b/examples/cdp_mode/raw_form_turnstile.py @@ -13,7 +13,7 @@ sb.highlight_click('input[value="AR"] + span') sb.click('input[value="cc"] + span') sb.scroll_down(40) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.highlight("img#captcha-success", timeout=3) sb.highlight_click('button:contains("Request & Pay")') sb.highlight("img#submit-success") diff --git a/examples/cdp_mode/raw_gitlab.py b/examples/cdp_mode/raw_gitlab.py index e5aa6293af7..5d36c7ed91c 100644 --- a/examples/cdp_mode/raw_gitlab.py +++ b/examples/cdp_mode/raw_gitlab.py @@ -4,7 +4,7 @@ url = "https://gitlab.com/users/sign_in" sb.activate_cdp_mode(url) sb.sleep(2.2) - sb.uc_gui_click_captcha() + sb.solve_captcha() # (The rest is for testing and demo purposes) sb.assert_text("Username", '[for="user_login"]', timeout=3) sb.assert_element('label[for="user_login"]') diff --git a/examples/cdp_mode/raw_hyatt.py b/examples/cdp_mode/raw_hyatt.py index ce5e7e99a5e..9311f12bd05 100644 --- a/examples/cdp_mode/raw_hyatt.py +++ b/examples/cdp_mode/raw_hyatt.py @@ -5,19 +5,20 @@ sb.activate_cdp_mode(url) sb.sleep(3.5) sb.click_if_visible('button[aria-label="Close"]') + sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") - sb.sleep(1) + sb.sleep(1.2) location = "Anaheim, CA, USA" sb.type('input[id="search-term"]', location) - sb.sleep(1) + sb.sleep(1.2) sb.click('li[data-js="suggestion"]') - sb.sleep(1) + sb.sleep(1.2) sb.click("button.be-button-shop") sb.sleep(6) card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' - hotels = sb.cdp.select_all(card_info) + hotels = sb.select_all(card_info) print("Hyatt Hotels in %s:" % location) - print("(" + sb.cdp.get_text('span[class*="summary_destination"]') + ")") + print("(" + sb.get_text('span[class*="summary_destination"]') + ")") if len(hotels) == 0: print("No availability over the selected dates!") for hotel in hotels: diff --git a/examples/cdp_mode/raw_indeed_login.py b/examples/cdp_mode/raw_indeed_login.py index 8cdcc36b890..e21efe474b0 100644 --- a/examples/cdp_mode/raw_indeed_login.py +++ b/examples/cdp_mode/raw_indeed_login.py @@ -10,8 +10,5 @@ sb.click('button[type="submit"]') sb.sleep(3.5) selector = 'div[class*="pass-Captcha"]' - element_rect = sb.cdp.get_gui_element_rect(selector, timeout=1) - x = element_rect["x"] + 32 - y = element_rect["y"] + 42 - sb.cdp.gui_click_x_y(x, y) + sb.click_with_offset(selector, 32, 42) sb.sleep(4.5) diff --git a/examples/cdp_mode/raw_nevada_search.py b/examples/cdp_mode/raw_nevada_search.py new file mode 100644 index 00000000000..eb5c063789f --- /dev/null +++ b/examples/cdp_mode/raw_nevada_search.py @@ -0,0 +1,20 @@ +"""Business Entity Search / Bypasses hCaptcha.""" +from seleniumbase import SB + +with SB(uc=True, test=True, guest=True) as sb: + url = "https://www.nvsilverflume.gov/home" + sb.activate_cdp_mode(url) + sb.sleep(3) + sb.click('a[href="/redirectToCenuity/be"]') + sb.sleep(3.6) + sb.assert_element('label:contains("Business Search")') + sb.click('input#BusinessSearch_Index_rdContains') + sb.sleep(0.6) + name_field = 'input[data-automation-id*="EntityName"]' + search = "Laser Tag" + sb.press_keys(name_field, search + "\n") + sb.sleep(6.5) + print('*** Business Search for "%s":' % search) + businesses = sb.select_all("td a[onclick]") + for business in businesses: + print(business.text) diff --git a/examples/cdp_mode/raw_planetmc.py b/examples/cdp_mode/raw_planetmc.py index 53ea4020dc0..5b76720f840 100644 --- a/examples/cdp_mode/raw_planetmc.py +++ b/examples/cdp_mode/raw_planetmc.py @@ -4,6 +4,6 @@ url = "www.planetminecraft.com/account/sign_in/" sb.activate_cdp_mode(url) sb.sleep(2) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.wait_for_element_absent("input[disabled]") sb.sleep(2) diff --git a/examples/cdp_mode/raw_priceline.py b/examples/cdp_mode/raw_priceline.py index 4b2e7b273e4..1ee38865fc9 100644 --- a/examples/cdp_mode/raw_priceline.py +++ b/examples/cdp_mode/raw_priceline.py @@ -4,17 +4,19 @@ url = "https://www.priceline.com" sb.activate_cdp_mode(url) sb.sleep(2.5) - sb.cdp.click('input[name="endLocation"]') - sb.sleep(1) - location = "Portland, OR, USA" + sb.click('input[name="endLocation"]') + sb.sleep(1.2) + location = "Portland, Oregon, US" selection = "Oregon, United States" # (Dropdown option) - sb.cdp.press_keys('input[name="endLocation"]', location) - sb.sleep(1) + sb.press_keys('input[name="endLocation"]', location) + sb.sleep(1.5) sb.click_if_visible('input[name="endLocation"]') - sb.sleep(0.5) - sb.cdp.click(selection) + sb.sleep(0.6) + sb.click(selection) sb.sleep(1.5) - sb.cdp.click('button[aria-label="Dismiss calendar"]') + sb.click('button[aria-label="Dismiss calendar"]') + sb.sleep(0.5) + sb.click('button[data-testid="HOTELS_SUBMIT_BUTTON"]') sb.sleep(5.5) if len(sb.cdp.get_tabs()) > 1: sb.cdp.close_active_tab() diff --git a/examples/cdp_mode/raw_radwell.py b/examples/cdp_mode/raw_radwell.py index 305b21cfc2e..7ba7666f3b8 100644 --- a/examples/cdp_mode/raw_radwell.py +++ b/examples/cdp_mode/raw_radwell.py @@ -4,10 +4,10 @@ url = "https://www.radwell.com/en-US/Search/Advanced/" sb.activate_cdp_mode(url) sb.sleep(3) - sb.cdp.press_keys("form#basicsearch input", "821C-PM-111DA-142") + sb.press_keys("form#basicsearch input", "821C-PM-111DA-142") sb.sleep(1) - sb.cdp.click('[value="Search Icon"]') + sb.click('[value="Search Icon"]') sb.sleep(3) - sb.cdp.assert_text("MAC VALVES INC", "a.manufacturer-link") - sb.cdp.highlight("a.manufacturer-link") + sb.assert_text("MAC VALVES INC", "a.manufacturer-link") + sb.highlight("a.manufacturer-link") sb.sleep(1) diff --git a/examples/cdp_mode/raw_seatgeek.py b/examples/cdp_mode/raw_seatgeek.py new file mode 100644 index 00000000000..be2ad34ce6c --- /dev/null +++ b/examples/cdp_mode/raw_seatgeek.py @@ -0,0 +1,17 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, guest=True) as sb: + url = "https://seatgeek.com/" + sb.activate_cdp_mode(url) + input_field = 'input[name="search"]' + sb.wait_for_element(input_field) + sb.sleep(1.6) + query = "Jerry Seinfeld" + sb.press_keys(input_field, query) + sb.sleep(1.6) + sb.click("li#active-result-item") + sb.sleep(4.2) + print('*** SeatGeek Search for "%s":' % query) + item_selector = '[data-testid="listing-item"]' + for item in sb.find_elements(item_selector): + print(item.text) diff --git a/examples/cdp_mode/raw_turnstile.py b/examples/cdp_mode/raw_turnstile.py index 2065ca68b26..e811c8ceb32 100644 --- a/examples/cdp_mode/raw_turnstile.py +++ b/examples/cdp_mode/raw_turnstile.py @@ -3,7 +3,7 @@ with SB(uc=True, test=True) as sb: url = "https://seleniumbase.io/apps/turnstile" sb.activate_cdp_mode(url) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.assert_element("img#captcha-success", timeout=3) sb.set_messenger_theme(location="top_left") sb.post_message("SeleniumBase wasn't detected", duration=3) diff --git a/examples/presenter/uc_presentation_4.py b/examples/presenter/uc_presentation_4.py index 9adc9bbcf48..b54176c624f 100644 --- a/examples/presenter/uc_presentation_4.py +++ b/examples/presenter/uc_presentation_4.py @@ -356,7 +356,8 @@ def test_presentation_4(self): url = "www.planetminecraft.com/account/sign_in/" sb.activate_cdp_mode(url) sb.sleep(2) - sb.cdp.gui_click_element("#turnstile-widget div") + sb.uc_gui_click_captcha() + sb.wait_for_element_absent("input[disabled]") sb.sleep(2) self.create_presentation(theme="serif", transition="none") @@ -678,22 +679,23 @@ def test_presentation_4(self): sb.activate_cdp_mode(url) sb.sleep(3.5) sb.click_if_visible('button[aria-label="Close"]') + sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") - sb.sleep(1) + sb.sleep(1.2) location = "Anaheim, CA, USA" sb.type('input[id="search-term"]', location) - sb.sleep(1) + sb.sleep(1.2) sb.click('li[data-js="suggestion"]') - sb.sleep(1) + sb.sleep(1.2) sb.click("button.be-button-shop") sb.sleep(6) card_info = ( 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' ) - hotels = sb.cdp.select_all(card_info) + hotels = sb.select_all(card_info) destination_selector = 'span[class*="summary_destination"]' print("Hyatt Hotels in %s:" % location) - print("(" + sb.cdp.get_text(destination_selector) + ")") + print("(" + sb.get_text(destination_selector) + ")") if len(hotels) == 0: print("No availability over the selected dates!") for hotel in hotels: @@ -764,17 +766,19 @@ def test_presentation_4(self): url = "https://www.priceline.com" sb.activate_cdp_mode(url) sb.sleep(2.5) - sb.cdp.click('input[name="endLocation"]') - sb.sleep(1) - location = "Portland, OR, USA" + sb.click('input[name="endLocation"]') + sb.sleep(1.2) + location = "Portland, Oregon, US" selection = "Oregon, United States" # (Dropdown option) - sb.cdp.press_keys('input[name="endLocation"]', location) - sb.sleep(1) + sb.press_keys('input[name="endLocation"]', location) + sb.sleep(1.5) sb.click_if_visible('input[name="endLocation"]') - sb.sleep(0.5) - sb.cdp.click(selection) + sb.sleep(0.6) + sb.click(selection) sb.sleep(1.5) - sb.cdp.click('button[aria-label="Dismiss calendar"]') + sb.click('button[aria-label="Dismiss calendar"]') + sb.sleep(0.5) + sb.click('button[data-testid="HOTELS_SUBMIT_BUTTON"]') sb.sleep(5.5) if len(sb.cdp.get_tabs()) > 1: sb.cdp.close_active_tab() diff --git a/examples/test_canvas.py b/examples/test_canvas.py index 76f457d4596..dfc107e02a9 100644 --- a/examples/test_canvas.py +++ b/examples/test_canvas.py @@ -4,8 +4,8 @@ class CanvasTests(BaseCase): - def get_pixel_colors(self): - # Return the RGB colors of the canvas element's top left pixel + def get_canvas_pixel_colors_at_top_left(self): + # Return the RGB colors of the canvas's top left pixel x = 0 y = 0 if self.browser == "safari": @@ -41,9 +41,9 @@ def test_click_with_offset(self): self.skip("Skip this test in undetectable mode.") self.assert_title_contains("Canvas") self.highlight("canvas") - rgb = self.get_pixel_colors() + rgb = self.get_canvas_pixel_colors_at_top_left() self.assert_equal(rgb, [221, 242, 231]) # Looks greenish self.click_with_offset("canvas", 500, 350) self.highlight("canvas", loops=5) - rgb = self.get_pixel_colors() + rgb = self.get_canvas_pixel_colors_at_top_left() self.assert_equal(rgb, [39, 42, 56]) # Blue by hamburger From 4dc8732357ad44918fe6938a9b57dedc157b1840 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 30 Oct 2025 02:08:25 -0400 Subject: [PATCH 3/3] Version 4.44.2 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 7de72f77c61..be0c7543316 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.44.1" +__version__ = "4.44.2"