From 8261b7c605b08c8b431c1c4ad243a76df27d848f Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 19:30:32 -0400 Subject: [PATCH 01/11] Update CDP Mode --- seleniumbase/core/browser_launcher.py | 9 +++ seleniumbase/core/sb_cdp.py | 32 +++++----- seleniumbase/core/sb_driver.py | 18 ++++++ seleniumbase/fixtures/base_case.py | 61 ++++++++++++++++++- seleniumbase/undetected/cdp_driver/browser.py | 2 + seleniumbase/undetected/cdp_driver/tab.py | 7 +++ 6 files changed, 112 insertions(+), 17 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index e4b6d58d56b..fb7bb4c9c0c 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -176,8 +176,11 @@ def extend_driver( driver.forward = DM.forward driver.refresh = DM.refresh driver.locator = DM.locator + driver.select = DM.select + driver.select_all = DM.select_all page = types.SimpleNamespace() page.open = DM.open_url + page.goto = DM.open_url page.click = DM.click page.click_link = DM.click_link page.click_if_visible = DM.click_if_visible @@ -210,6 +213,8 @@ def extend_driver( page.find_element = DM.find_element page.find_elements = DM.find_elements page.locator = DM.locator + page.select = DM.select + page.select_all = DM.select_all page.get_current_url = DM.get_current_url page.get_page_source = DM.get_page_source page.get_title = DM.get_title @@ -231,6 +236,7 @@ def extend_driver( js.highlight = DM.highlight driver.js = js driver.open = DM.open_url + driver.goto = DM.open_url driver.click = DM.click driver.click_link = DM.click_link driver.click_if_visible = DM.click_if_visible @@ -291,6 +297,7 @@ def extend_driver( driver.switch_to_default_window = DM.switch_to_default_window driver.switch_to_newest_window = DM.switch_to_newest_window driver.open_new_window = DM.open_new_window + driver.switch_to_newest_tab = DM.switch_to_newest_tab driver.open_new_tab = DM.open_new_tab driver.switch_to_window = DM.switch_to_window driver.switch_to_tab = DM.switch_to_tab @@ -759,6 +766,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): CDPM = sb_cdp.CDPMethods(loop, page, driver) cdp.get = CDPM.get cdp.open = CDPM.open + cdp.goto = CDPM.goto cdp.reload = CDPM.reload cdp.refresh = CDPM.refresh cdp.add_handler = CDPM.add_handler @@ -972,6 +980,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): driver.solve_captcha = CDPM.solve_captcha driver.click_captcha = CDPM.click_captcha driver.find_element_by_text = CDPM.find_element_by_text + driver.flash = CDPM.flash driver._is_using_cdp = True if ( getattr(sb_config, "_cdp_proxy", None) diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 3a03c1afc7f..a3d547426b4 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -155,6 +155,9 @@ def get(self, url, **kwargs): def open(self, url, **kwargs): self.get(url, **kwargs) + def goto(self, url, **kwargs): + self.get(url, **kwargs) + def reload(self, ignore_cache=True, script_to_evaluate_on_load=None): self.loop.run_until_complete( self.page.reload( @@ -2033,6 +2036,13 @@ def click_with_offset(self, selector, x, y, center=False, scroll=True): self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.wait(0.2)) + def quit(self): + """Quit the browser in the Pure CDP Mode Sync format.""" + driver = self.driver + if hasattr(driver, "cdp_base"): + driver = driver.cdp_base + driver.quit() + def _on_a_cf_turnstile_page(self, source=None): if not source or len(source) < 400: time.sleep(0.2) @@ -2325,28 +2335,22 @@ def __click_captcha(self, use_cdp=False): 'form div:not([class]):has(input[name*="cf-turn"])' ): selector = 'form div:not([class]):has(input[name*="cf-turn"])' - elif self.is_element_present("form div:not(:has(*))"): - selector = "form div:not(:has(*))" elif self.is_element_present("body > div#check > div:not([class])"): selector = "body > div#check > div:not([class])" elif self.is_element_present(".cf-turnstile-wrapper"): selector = ".cf-turnstile-wrapper" - elif self.is_element_present( - '[id*="turnstile"] div:not([class])' - ): + elif self.is_element_present('[id*="turnstile"] div:not([class])'): selector = '[id*="turnstile"] div:not([class])' - elif self.is_element_present( - '[class*="turnstile"] div:not([class])' - ): + elif self.is_element_present('[class*="turnstile"] div:not([class])'): selector = '[class*="turnstile"] div:not([class])' - elif self.is_element_present( - "iframe[data-hcaptcha-widget-id]" - ): + elif self.is_element_present("iframe[data-hcaptcha-widget-id]"): selector = "iframe[data-hcaptcha-widget-id]" - elif self.is_element_present( - '[data-callback="onCaptchaSuccess"]' - ): + elif self.is_element_present('[data-callback="onCaptchaSuccess"]'): selector = '[data-callback="onCaptchaSuccess"]' + elif self.is_element_present('[class*="captcha"] div:not([class])'): + selector = '[class*="captcha"] div:not([class])' + elif self.is_element_present("form div:not(:has(*))"): + selector = "form div:not(:has(*))" elif self.is_element_present( "div:not([class]):not([id]):not([aria-label]) > " "div:not([class]):not([id]):not([aria-label])" diff --git a/seleniumbase/core/sb_driver.py b/seleniumbase/core/sb_driver.py index edf34713656..383b9b5aef2 100644 --- a/seleniumbase/core/sb_driver.py +++ b/seleniumbase/core/sb_driver.py @@ -41,6 +41,18 @@ def find_elements(self, by=None, value=None): value, by = page_utils.swap_selector_and_by_if_reversed(value, by) return self.driver.default_find_elements(by=by, value=value) + def select(self, *args, **kwargs): + if self.__is_cdp_swap_needed(): + return self.driver.cdp.select(*args, **kwargs) + else: + return self.find_element(*args, **kwargs) + + def select_all(self, *args, **kwargs): + if self.__is_cdp_swap_needed(): + return self.driver.cdp.select_all(*args, **kwargs) + else: + return self.find_elements(*args, **kwargs) + def add_cookie(self, *args, **kwargs): page_actions._reconnect_if_disconnected(self.driver) self.driver.default_add_cookie(*args, **kwargs) @@ -372,6 +384,12 @@ def open_new_tab(self, switch_to=True): def switch_to_window(self, *args, **kwargs): page_actions.switch_to_window(self.driver, *args, **kwargs) + def switch_to_newest_tab(self): + if self.__is_cdp_swap_needed(): + self.driver.cdp.switch_to_newest_tab() + return + self.switch_to_newest_window() + def switch_to_tab(self, *args, **kwargs): self.switch_to_window(*args, **kwargs) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 044986ead7d..2e9519d9516 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -5096,8 +5096,32 @@ def activate_cdp_mode(self, url=None, **kwargs): self.solve_captcha = self.cdp.solve_captcha if hasattr(self.cdp, "click_captcha"): self.click_captcha = self.cdp.click_captcha + if hasattr(self.cdp, "add_handler"): + self.add_handler = self.cdp.add_handler + if hasattr(self.cdp, "close_active_tab"): + self.close_active_tab = self.cdp.close_active_tab if hasattr(self.cdp, "find_element_by_text"): self.find_element_by_text = self.cdp.find_element_by_text + if hasattr(self.cdp, "flash"): + self.flash = self.cdp.flash + if hasattr(self.cdp, "get_active_tab"): + self.get_active_tab = self.cdp.get_active_tab + if hasattr(self.cdp, "get_endpoint_url"): + self.get_endpoint_url = self.cdp.get_endpoint_url + if hasattr(self.cdp, "get_event_loop"): + self.get_event_loop = self.cdp.get_event_loop + if hasattr(self.cdp, "get_tabs"): + self.get_tabs = self.cdp.get_tabs + if hasattr(self.cdp, "gui_click_and_hold"): + self.gui_click_and_hold = self.cdp.gui_click_and_hold + if hasattr(self.cdp, "gui_click_element"): + self.gui_click_element = self.cdp.gui_click_element + if hasattr(self.cdp, "gui_drag_and_drop"): + self.gui_drag_and_drop = self.cdp.gui_drag_and_drop + if hasattr(self.cdp, "gui_drag_drop_points"): + self.gui_drag_drop_points = self.cdp.gui_drag_drop_points + if hasattr(self.cdp, "highlight_overlay"): + self.highlight_overlay = self.cdp.highlight_overlay if getattr(self.driver, "_is_using_auth", None): with suppress(Exception): self.cdp.loop.run_until_complete(self.cdp.page.wait(0.25)) @@ -8861,9 +8885,22 @@ def set_text_content( element = page_actions.wait_for_element_present( self.driver, selector, by, timeout ) - if element.tag_name.lower() in ["input", "textarea"]: - self.js_update_text(selector, text, by=by, timeout=timeout) - return + try: + if element.tag_name.lower() in ["input", "textarea"]: + self.js_update_text(selector, text, by=by, timeout=timeout) + return + except (Stale_Exception, ENI_Exception): + time.sleep(0.16) + if self.__is_cdp_swap_needed(): + element = self.cdp.select(selector, timeout=timeout) + else: + self.wait_for_ready_state_complete() + element = page_actions.wait_for_element_present( + self.driver, selector, by, timeout + ) + if element.tag_name.lower() in ["input", "textarea"]: + self.js_update_text(selector, text, by=by, timeout=timeout) + return original_selector = selector css_selector = self.convert_to_css_selector(selector, by=by) if scroll: @@ -9340,6 +9377,9 @@ def switch_to_default_tab(self): def switch_to_newest_tab(self): """Same as self.switch_to_newest_window()""" + if self.__is_cdp_swap_needed(): + self.cdp.switch_to_newest_tab() + return self.switch_to_newest_window() def save_as_html(self, name, folder=None): @@ -9976,6 +10016,8 @@ def wait_for_element_present( self, selector, by="css selector", timeout=None ): """Waits for an element to appear in the HTML of a page. + Returns the element once it exists in the HTML. + Raises an exception if the element doesn't come in time. The element does not need be visible (it may be hidden).""" self.__check_scope() if not timeout: @@ -10022,6 +10064,19 @@ def wait_for_element(self, selector, by="css selector", timeout=None): self.driver, selector, by, timeout ) + def select(self, selector, by="css selector", timeout=None): + """Returns the element once it appears in the HTML. + Raises an exception if the element doesn't come in time. + The element does not need be visible (it may be hidden). + If CDP Mode has been activated: Calls self.cdp.select(). + Otherwise: Same as self.wait_for_element_present().""" + if self.__is_cdp_swap_needed(): + return self.cdp.select(selector, timeout=timeout) + else: + return self.wait_for_element_present( + selector, by=by, timeout=timeout + ) + def get_element(self, selector, by="css selector", timeout=None): """Same as wait_for_element_present() - returns the element. The element does not need be visible (it may be hidden).""" diff --git a/seleniumbase/undetected/cdp_driver/browser.py b/seleniumbase/undetected/cdp_driver/browser.py index 2f25edb65be..a11900ccc15 100644 --- a/seleniumbase/undetected/cdp_driver/browser.py +++ b/seleniumbase/undetected/cdp_driver/browser.py @@ -455,11 +455,13 @@ async def get( "*.google-analytics.com*", "*.amazon-adsystem.com*", "*.adsafeprotected.com*", + "*.m.media-amazon.com*", "*.ads.linkedin.com*", "*.casalemedia.com*", "*.doubleclick.net*", "*.admanmedia.com*", "*.quantserve.com*", + "*.ads.simpli.fi*", "*.fastclick.net*", "*.snigelweb.com*", "*.bidswitch.net*", diff --git a/seleniumbase/undetected/cdp_driver/tab.py b/seleniumbase/undetected/cdp_driver/tab.py index 788b8ac4495..64c47128d28 100644 --- a/seleniumbase/undetected/cdp_driver/tab.py +++ b/seleniumbase/undetected/cdp_driver/tab.py @@ -378,6 +378,9 @@ async def get( async def open(self, url="about:blank"): return await self.get(url=url) + async def goto(self, url="about:blank"): + return await self.get(url=url) + async def query_selector_all( self, selector: str, @@ -1745,6 +1748,10 @@ async def solve_captcha(self): '[data-callback="onCaptchaSuccess"]' ): selector = '[data-callback="onCaptchaSuccess"]' + elif await self.is_element_present( + '[class*="captcha"] div:not([class])' + ): + selector = '[class*="captcha"] div:not([class])' elif await self.is_element_present( "div:not([class]):not([id]):not([aria-label]) > " "div:not([class]):not([id]):not([aria-label])" From c80c2fb2ce97d6c83acd8a580cdc57c1f0c58225 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 19:31:42 -0400 Subject: [PATCH 02/11] Refresh Python dependencies --- requirements.txt | 2 +- setup.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index b31aab1a32d..78b4ca4e175 100755 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ setuptools~=70.2;python_version<"3.10" setuptools>=82.0.1;python_version>="3.10" wheel>=0.47.0 attrs>=26.1.0 -certifi>=2026.4.22 +certifi>=2026.5.20 exceptiongroup>=1.3.1 websockets~=15.0.1;python_version<"3.10" websockets>=16.0;python_version>="3.10" diff --git a/setup.py b/setup.py index f725f42615a..8b2a812d886 100755 --- a/setup.py +++ b/setup.py @@ -168,7 +168,7 @@ 'setuptools>=82.0.1;python_version>="3.10"', 'wheel>=0.47.0', 'attrs>=26.1.0', - 'certifi>=2026.4.22', + 'certifi>=2026.5.20', 'exceptiongroup>=1.3.1', 'websockets~=15.0.1;python_version<"3.10"', 'websockets>=16.0;python_version>="3.10"', @@ -304,7 +304,9 @@ # pip install -e .[playwright] # (For the Playwright integration.) "playwright": [ - "playwright>=1.60.0", + 'playwright>=1.60.0', + 'greenlet>=3.2.5;python_version<"3.10"', + 'greenlet>=3.5.1;python_version>="3.10"', ], # pip install -e .[pyautogui] # (Already a required dependency on Linux now.) From 16ae5cb3cbaeddc6c8bcd83035364b425aa62a43 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 19:35:38 -0400 Subject: [PATCH 03/11] Update Tour examples --- examples/tour_examples/ReadMe.md | 92 +++++++++++++++++-- .../tour_examples/bootstrap_google_tour.py | 4 +- examples/tour_examples/driverjs_maps_tour.py | 4 +- examples/tour_examples/google_tour.py | 4 +- .../tour_examples/hopscotch_google_tour.py | 4 +- examples/tour_examples/introjs_google_tour.py | 4 +- examples/tour_examples/maps_introjs_tour.py | 4 +- .../tour_examples/shepherd_google_tour.py | 4 +- 8 files changed, 97 insertions(+), 23 deletions(-) diff --git a/examples/tour_examples/ReadMe.md b/examples/tour_examples/ReadMe.md index 9ab0f0e9a5d..b81f0c3b70a 100644 --- a/examples/tour_examples/ReadMe.md +++ b/examples/tour_examples/ReadMe.md @@ -1,12 +1,8 @@ -

SeleniumBase Tour

-

🌏 Interactive Product Tours 🚎

-

Increase SaaS Product Adoption by 10x or more.

- -* SeleniumBase Tours utilize 5 JavaScript libraries for creating interactive walkthroughs on **any website**: +SeleniumBase Tours utilize 5 JavaScript libraries for creating interactive walkthroughs on **any website**: > **[IntroJS](https://introjs.com/)**, **[Bootstrap Tour](http://bootstraptour.com/)**, **[DriverJS](https://kamranahmed.info/driver.js/)**, **[Shepherd](https://shepherdjs.dev/)**, and **[Hopscotch](https://linkedinattic.github.io/hopscotch/)**. @@ -105,28 +101,106 @@ All methods have the optional ``name`` argument, which is only needed if you're from seleniumbase import BaseCase BaseCase.main(__name__, __file__, "--uc") -class MyTourClass(BaseCase): +class MyTourClass(BaseCase): def test_google_tour(self): if not self.undetectable: self.get_new_driver(undetectable=True) - self.open('https://google.com/ncr') + self.open("https://google.com/ncr") self.click_if_visible('button:contains("Accept all")') - self.wait_for_element('input[title="Search"]') + self.wait_for_element('[title="Search"]') self.hide_elements("iframe") + # Create a website tour using the ShepherdJS library with "dark" theme + # Same as: self.create_shepherd_tour(theme="dark") self.create_tour(theme="dark") self.add_tour_step("Welcome to Google!", title="SeleniumBase Tours") self.add_tour_step("Type in your query here.", '[title="Search"]') self.play_tour() - self.highlight_type('input[title="Search"]', "Google") + self.highlight_type('[title="Search"]', "Google") self.wait_for_element('[role="listbox"]') # Wait for autocomplete + # Create a website tour using the ShepherdJS library with "light" theme + # Same as: self.create_shepherd_tour(theme="light") self.create_tour(theme="light") self.add_tour_step("Then click to search.", '[value="Google Search"]') self.add_tour_step("Or press [ENTER] after entry.", '[title="Search"]') self.play_tour() + + self.highlight_type('[title="Search"]', "GitHub\n") + self.ad_block() + self.wait_for_element("#search") + + # Create a website tour using the Bootstrap Tour JS library + # Same as: self.create_bootstrap_tour() + self.create_tour(theme="bootstrap") + self.add_tour_step("3-second autoplay...") + self.add_tour_step("Here's the next tour:") + self.play_tour(interval=3) # Tour automatically continues after 3 sec + + self.open("https://www.google.com/maps/@42.3591234,-71.0915634,15z") + self.wait_for_element('[name="q"]', timeout=20) + self.wait_for_element('[aria-label="Interactive map"]', timeout=20) + self.wait_for_element('[aria-label="Zoom in"]', timeout=20) + self.wait_for_element('[aria-label="Zoom out"]') + self.wait_for_element('[jsaction*="minimap.main;"]') + self.sleep(0.5) + + # Create a website tour using the IntroJS library + # Same as: self.create_introjs_tour() + self.create_tour(theme="introjs") + self.add_tour_step("Welcome to Google Maps", title="SeleniumBase Tour") + self.add_tour_step( + "The location goes here.", '[name="q"]', title="Search Box" + ) + self.add_tour_step( + "Then click here to show it on the map.", + '[aria-label="Search"]', + alignment="bottom", + ) + self.add_tour_step( + "Or click here to get driving directions.", + 'button[aria-label="Directions"]', + alignment="bottom", + ) + self.add_tour_step( + "Use this button to switch to Satellite view.", + 'button[jsaction*="minimap.main;"]', + alignment="right", + ) + self.add_tour_step( + "Click here to zoom in.", + '[aria-label="Zoom in"]', + alignment="left", + ) + self.add_tour_step( + "Or click here to zoom out.", + '[aria-label="Zoom out"]', + alignment="left", + ) + if self.is_element_visible('button[jsaction*="settings.open;"]'): + self.add_tour_step( + "Use the Menu button to see more options.", + 'button[jsaction*="settings.open;"]', + alignment="right", + ) + elif self.is_element_visible('button[jsaction="navigationrail.more"]'): + self.add_tour_step( + "Use the Menu button to see more options.", + 'button[jsaction="navigationrail.more"]', + alignment="right", + ) + self.add_tour_step( + "Or click here to see more Google apps.", + '[aria-label="Google apps"]', + alignment="left", + ) + self.add_tour_step( + "Thanks for using SeleniumBase Tours!", title="End of Guided Tour" + ) + self.export_tour() # The default name for exports is "my_tour.js" + self.play_tour() ``` #### That code is from [google_tour.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/google_tour.py), which you can run from the ``tour_examples/`` folder with the following command: diff --git a/examples/tour_examples/bootstrap_google_tour.py b/examples/tour_examples/bootstrap_google_tour.py index b1c80b07e96..888e6d6e769 100644 --- a/examples/tour_examples/bootstrap_google_tour.py +++ b/examples/tour_examples/bootstrap_google_tour.py @@ -44,11 +44,11 @@ def test_google_tour(self): self.create_bootstrap_tour() self.add_tour_step("Welcome to Google Maps", title="SeleniumBase Tour") self.add_tour_step( - "The location goes here.", "#searchboxinput", title="Search Box" + "The location goes here.", '[name="q"]', title="Search Box" ) self.add_tour_step( "Then click here to show it on the map.", - "#searchbox-searchbutton", + '[aria-label="Search"]', alignment="bottom", ) self.add_tour_step( diff --git a/examples/tour_examples/driverjs_maps_tour.py b/examples/tour_examples/driverjs_maps_tour.py index 847fc8abc17..22d13454d5a 100644 --- a/examples/tour_examples/driverjs_maps_tour.py +++ b/examples/tour_examples/driverjs_maps_tour.py @@ -17,11 +17,11 @@ def test_create_tour(self): self.create_tour(theme="driverjs") self.add_tour_step("Welcome to Google Maps", title="SeleniumBase Tour") self.add_tour_step( - "The location goes here.", "#searchboxinput", title="Search Box" + "The location goes here.", '[name="q"]', title="Search Box" ) self.add_tour_step( "Then click here to show it on the map.", - "#searchbox-searchbutton", + '[aria-label="Search"]', alignment="bottom", ) self.add_tour_step( diff --git a/examples/tour_examples/google_tour.py b/examples/tour_examples/google_tour.py index c68a1539a53..8f5b88b2a49 100644 --- a/examples/tour_examples/google_tour.py +++ b/examples/tour_examples/google_tour.py @@ -52,11 +52,11 @@ def test_google_tour(self): self.create_tour(theme="introjs") self.add_tour_step("Welcome to Google Maps", title="SeleniumBase Tour") self.add_tour_step( - "The location goes here.", "#searchboxinput", title="Search Box" + "The location goes here.", '[name="q"]', title="Search Box" ) self.add_tour_step( "Then click here to show it on the map.", - "#searchbox-searchbutton", + '[aria-label="Search"]', alignment="bottom", ) self.add_tour_step( diff --git a/examples/tour_examples/hopscotch_google_tour.py b/examples/tour_examples/hopscotch_google_tour.py index 7bbdc1747d8..24bcdd5fe0b 100644 --- a/examples/tour_examples/hopscotch_google_tour.py +++ b/examples/tour_examples/hopscotch_google_tour.py @@ -44,11 +44,11 @@ def test_google_tour(self): self.create_hopscotch_tour() self.add_tour_step("Welcome to Google Maps", title="SeleniumBase Tour") self.add_tour_step( - "The location goes here.", "#searchboxinput", title="Search Box" + "The location goes here.", '[name="q"]', title="Search Box" ) self.add_tour_step( "Then click here to show it on the map.", - "#searchbox-searchbutton", + '[aria-label="Search"]', alignment="bottom", ) self.add_tour_step( diff --git a/examples/tour_examples/introjs_google_tour.py b/examples/tour_examples/introjs_google_tour.py index a07cfc73e70..d3ee87dafbc 100644 --- a/examples/tour_examples/introjs_google_tour.py +++ b/examples/tour_examples/introjs_google_tour.py @@ -45,11 +45,11 @@ def test_google_tour(self): self.create_introjs_tour() self.add_tour_step("Welcome to Google Maps", title="SeleniumBase Tour") self.add_tour_step( - "The location goes here.", "#searchboxinput", title="Search Box" + "The location goes here.", '[name="q"]', title="Search Box" ) self.add_tour_step( "Then click here to show it on the map.", - "#searchbox-searchbutton", + '[aria-label="Search"]', alignment="bottom", ) self.add_tour_step( diff --git a/examples/tour_examples/maps_introjs_tour.py b/examples/tour_examples/maps_introjs_tour.py index e3035435949..d898dfd1b30 100644 --- a/examples/tour_examples/maps_introjs_tour.py +++ b/examples/tour_examples/maps_introjs_tour.py @@ -17,11 +17,11 @@ def test_google_maps_tour(self): "Welcome to Google Maps", title="βœ… SeleniumBase Tours 🌎" ) self.add_tour_step( - "The location goes here.", "#searchboxinput", title="Search Box" + "The location goes here.", '[name="q"]', title="Search Box" ) self.add_tour_step( "Then click here to show it on the map.", - "#searchbox-searchbutton", + '[aria-label="Search"]', alignment="bottom", ) self.add_tour_step( diff --git a/examples/tour_examples/shepherd_google_tour.py b/examples/tour_examples/shepherd_google_tour.py index e2d0eee18cb..7d82133ce9a 100644 --- a/examples/tour_examples/shepherd_google_tour.py +++ b/examples/tour_examples/shepherd_google_tour.py @@ -44,11 +44,11 @@ def test_google_tour(self): self.create_shepherd_tour(theme="dark") self.add_tour_step("Welcome to Google Maps!") self.add_tour_step( - "Type in a location here.", "#searchboxinput", title="Search Box" + "The location goes here.", '[name="q"]', title="Search Box" ) self.add_tour_step( "Then click here to show it on the map.", - "#searchbox-searchbutton", + '[aria-label="Search"]', alignment="bottom", ) self.add_tour_step( From 3ec9d4a81d0d5ce790b4167efd069f7149009115 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 20:29:24 -0400 Subject: [PATCH 04/11] Update Stealthy Playwright examples --- examples/cdp_mode/playwright/ReadMe.md | 2 +- examples/cdp_mode/playwright/raw_basic_nested.py | 2 +- examples/cdp_mode/playwright/raw_bing_cap_nested.py | 2 +- examples/cdp_mode/playwright/raw_browserscan_nested.py | 8 ++++---- examples/cdp_mode/playwright/raw_browserscan_sync.py | 6 +++--- examples/cdp_mode/playwright/raw_copilot_nested.py | 2 +- examples/cdp_mode/playwright/raw_fingerprint_nested.py | 6 +++--- examples/cdp_mode/playwright/raw_gitlab_nested.py | 2 +- examples/cdp_mode/playwright/raw_idealista_nested.py | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/cdp_mode/playwright/ReadMe.md b/examples/cdp_mode/playwright/ReadMe.md index 5ddab416360..8038a3a236f 100644 --- a/examples/cdp_mode/playwright/ReadMe.md +++ b/examples/cdp_mode/playwright/ReadMe.md @@ -55,7 +55,7 @@ from seleniumbase import SB with SB(uc=True) as sb: sb.activate_cdp_mode() - endpoint_url = sb.cdp.get_endpoint_url() + endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) diff --git a/examples/cdp_mode/playwright/raw_basic_nested.py b/examples/cdp_mode/playwright/raw_basic_nested.py index 77f1d23f94d..99ce8e96a18 100644 --- a/examples/cdp_mode/playwright/raw_basic_nested.py +++ b/examples/cdp_mode/playwright/raw_basic_nested.py @@ -3,7 +3,7 @@ with SB(uc=True) as sb: sb.activate_cdp_mode() - endpoint_url = sb.cdp.get_endpoint_url() + endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) diff --git a/examples/cdp_mode/playwright/raw_bing_cap_nested.py b/examples/cdp_mode/playwright/raw_bing_cap_nested.py index fe3264ddc98..6f752a06ccb 100644 --- a/examples/cdp_mode/playwright/raw_bing_cap_nested.py +++ b/examples/cdp_mode/playwright/raw_bing_cap_nested.py @@ -3,7 +3,7 @@ with SB(uc=True, locale="en") as sb: sb.activate_cdp_mode() - endpoint_url = sb.cdp.get_endpoint_url() + endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) diff --git a/examples/cdp_mode/playwright/raw_browserscan_nested.py b/examples/cdp_mode/playwright/raw_browserscan_nested.py index cb7b17b81d5..58ee5e53202 100644 --- a/examples/cdp_mode/playwright/raw_browserscan_nested.py +++ b/examples/cdp_mode/playwright/raw_browserscan_nested.py @@ -3,14 +3,14 @@ with SB(uc=True, locale="en", ad_block=True) as sb: sb.activate_cdp_mode() - endpoint_url = sb.cdp.get_endpoint_url() + endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) page = browser.contexts[0].pages[0] - page.goto("https://www.browserscan.net/bot-detection") + page.goto("https://browserscan.net/bot-detection") page.wait_for_timeout(500) - sb.cdp.flash("Test Results", duration=3, pause=1) + sb.flash("Test Results", duration=1.5, pause=0.5) sb.assert_element('strong:contains("Normal")') print("Bot Not Detected") - sb.cdp.flash('strong:contains("Normal")', duration=3, pause=2) + sb.flash('strong:contains("Normal")', pause=1) diff --git a/examples/cdp_mode/playwright/raw_browserscan_sync.py b/examples/cdp_mode/playwright/raw_browserscan_sync.py index 7c685bcec03..8cc6fbb5ed2 100644 --- a/examples/cdp_mode/playwright/raw_browserscan_sync.py +++ b/examples/cdp_mode/playwright/raw_browserscan_sync.py @@ -7,9 +7,9 @@ with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) page = browser.contexts[0].pages[0] - page.goto("https://www.browserscan.net/bot-detection") + page.goto("https://browserscan.net/bot-detection") page.wait_for_timeout(500) - sb.flash("Test Results", duration=3, pause=1) + sb.flash("Test Results", duration=1.5, pause=0.5) sb.assert_element('strong:contains("Normal")') print("Bot Not Detected") - sb.flash('strong:contains("Normal")', duration=3, pause=2) + sb.flash('strong:contains("Normal")', pause=1) diff --git a/examples/cdp_mode/playwright/raw_copilot_nested.py b/examples/cdp_mode/playwright/raw_copilot_nested.py index 5e3141ded52..9b3bb52f2e5 100644 --- a/examples/cdp_mode/playwright/raw_copilot_nested.py +++ b/examples/cdp_mode/playwright/raw_copilot_nested.py @@ -3,7 +3,7 @@ with SB(uc=True) as sb: sb.activate_cdp_mode() - endpoint_url = sb.cdp.get_endpoint_url() + endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) diff --git a/examples/cdp_mode/playwright/raw_fingerprint_nested.py b/examples/cdp_mode/playwright/raw_fingerprint_nested.py index 18411853d82..391ff7ed6e2 100644 --- a/examples/cdp_mode/playwright/raw_fingerprint_nested.py +++ b/examples/cdp_mode/playwright/raw_fingerprint_nested.py @@ -3,15 +3,15 @@ with SB(uc=True, locale="en") as sb: sb.activate_cdp_mode() - endpoint_url = sb.cdp.get_endpoint_url() + endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) page = browser.contexts[0].pages[0] page.goto("https://demo.fingerprint.com/playground") page.wait_for_timeout(500) - sb.cdp.flash('a[href*="browser-bot-detection"]', duration=3, pause=1) + sb.flash('a[href*="browser-bot-detection"]', duration=3, pause=1) bot_row_selector = 'table:contains("Bot") tr:nth-of-type(3)' print(sb.get_text(bot_row_selector)) sb.assert_text("Bot Not detected", bot_row_selector) - sb.cdp.flash(bot_row_selector, duration=3, pause=2) + sb.flash(bot_row_selector, duration=3, pause=2) diff --git a/examples/cdp_mode/playwright/raw_gitlab_nested.py b/examples/cdp_mode/playwright/raw_gitlab_nested.py index 7d83b26c63c..03ebe32b3e4 100644 --- a/examples/cdp_mode/playwright/raw_gitlab_nested.py +++ b/examples/cdp_mode/playwright/raw_gitlab_nested.py @@ -3,7 +3,7 @@ with SB(uc=True, locale="en") as sb: sb.activate_cdp_mode() - endpoint_url = sb.cdp.get_endpoint_url() + endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) diff --git a/examples/cdp_mode/playwright/raw_idealista_nested.py b/examples/cdp_mode/playwright/raw_idealista_nested.py index 0fb68dfbe5b..3ddbdcb2d57 100644 --- a/examples/cdp_mode/playwright/raw_idealista_nested.py +++ b/examples/cdp_mode/playwright/raw_idealista_nested.py @@ -8,7 +8,7 @@ sb.sleep(1) sb.solve_captcha() sb.sleep(2) - endpoint_url = sb.cdp.get_endpoint_url() + endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) From a7808d0335b6748d8aae20d84357496db0aa6595 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 20:33:11 -0400 Subject: [PATCH 05/11] Update CDP Mode examples --- examples/cdp_mode/ReadMe.md | 8 ++++---- examples/cdp_mode/raw_amazon.py | 2 +- examples/cdp_mode/raw_browserscan.py | 6 +++--- examples/cdp_mode/raw_canvas.py | 4 ++-- examples/cdp_mode/raw_cdp_browserscan.py | 8 ++++---- examples/cdp_mode/raw_cdp_fingerprint.py | 4 ++-- examples/cdp_mode/raw_cdp_hyatt.py | 6 +++++- examples/cdp_mode/raw_cdp_patent.py | 9 +++++++++ examples/cdp_mode/raw_cdp_pixelscan.py | 15 +++++++-------- examples/cdp_mode/raw_cdp_timezone.py | 16 ++++++++++++++++ examples/cdp_mode/raw_cdp_with_sb.py | 6 ++++-- examples/cdp_mode/raw_demo_site.py | 2 +- examples/cdp_mode/raw_drag_and_drop.py | 8 ++++---- examples/cdp_mode/raw_facebook.py | 2 +- examples/cdp_mode/raw_fingerprint.py | 4 ++-- examples/cdp_mode/raw_gitlab.py | 1 - examples/cdp_mode/raw_hyatt.py | 8 ++++---- examples/cdp_mode/raw_linkedin.py | 2 +- examples/cdp_mode/raw_mobile_gitlab.py | 4 ++-- examples/cdp_mode/raw_mobile_roblox.py | 4 ++-- examples/cdp_mode/raw_mycdp_cookies.py | 4 ++-- examples/cdp_mode/raw_patent.py | 10 ++++++++++ examples/cdp_mode/raw_pixelscan.py | 11 +++++------ examples/cdp_mode/raw_pokemon.py | 8 ++++---- examples/cdp_mode/raw_priceline.py | 6 +++--- examples/cdp_mode/raw_publication.py | 2 +- examples/cdp_mode/raw_req_sb.py | 4 ++-- examples/cdp_mode/raw_res_nike.py | 5 ++--- examples/cdp_mode/raw_seatgeek.py | 4 ++-- examples/cdp_mode/raw_southwest.py | 24 ++++++++++++------------ examples/cdp_mode/raw_timezone_sb.py | 2 +- examples/cdp_mode/raw_trails.py | 8 ++++---- examples/cdp_mode/raw_walmart.py | 6 +++--- examples/cdp_mode/raw_xhr_sb.py | 6 +++--- 34 files changed, 128 insertions(+), 91 deletions(-) create mode 100644 examples/cdp_mode/raw_cdp_patent.py create mode 100644 examples/cdp_mode/raw_cdp_timezone.py create mode 100644 examples/cdp_mode/raw_patent.py diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index e7da2947e17..d92d723b8bf 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -154,14 +154,14 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb: sb.assert_element('img[alt="Pikachu"]') sb.scroll_into_view("div.pokemon-ability-info") sb.sleep(1.2) - sb.cdp.flash('div[class*="title"]') - sb.cdp.flash('img[alt="Pikachu"]') - sb.cdp.flash("div.pokemon-ability-info") + sb.flash('div[class*="title"]') + sb.flash('img[alt="Pikachu"]') + sb.flash("div.pokemon-ability-info") name = sb.get_text("label.styled-select") info = sb.get_text("div.version-descriptions p.active") print("*** %s: ***\n* %s" % (name, info)) sb.sleep(2) - sb.cdp.highlight_overlay("div.pokemon-ability-info") + sb.highlight_overlay("div.pokemon-ability-info") sb.sleep(2) sb.open("https://events.pokemon.com/EventLocator/") sb.sleep(2) diff --git a/examples/cdp_mode/raw_amazon.py b/examples/cdp_mode/raw_amazon.py index 8b8f226a691..4ededdff735 100644 --- a/examples/cdp_mode/raw_amazon.py +++ b/examples/cdp_mode/raw_amazon.py @@ -9,7 +9,7 @@ sb.press_keys('input[role="searchbox"]', "TI-89\n") sb.sleep(3) for i in range(16): - sb.cdp.scroll_down(16) + sb.scroll_down(16) print(sb.get_page_title()) sb.save_as_pdf_to_logs() sb.save_page_source_to_logs() diff --git a/examples/cdp_mode/raw_browserscan.py b/examples/cdp_mode/raw_browserscan.py index b653635669c..037dd8f5264 100644 --- a/examples/cdp_mode/raw_browserscan.py +++ b/examples/cdp_mode/raw_browserscan.py @@ -1,9 +1,9 @@ from seleniumbase import SB with SB(uc=True, test=True, locale="en", ad_block=True) as sb: - url = "https://www.browserscan.net/bot-detection" + url = "https://browserscan.net/bot-detection" sb.activate_cdp_mode(url) - sb.cdp.flash("Test Results", duration=3, pause=1) + sb.flash("Test Results", duration=1.5, pause=0.5) sb.assert_element('strong:contains("Normal")') print("Bot Not Detected") - sb.cdp.flash('strong:contains("Normal")', duration=3, pause=2) + sb.flash('strong:contains("Normal")', pause=1) diff --git a/examples/cdp_mode/raw_canvas.py b/examples/cdp_mode/raw_canvas.py index 7ec4380de6e..9cd23f7854e 100644 --- a/examples/cdp_mode/raw_canvas.py +++ b/examples/cdp_mode/raw_canvas.py @@ -12,7 +12,7 @@ def get_canvas_pixel_colors_at_top_left(sb): with SB(uc=True, test=True) as sb: - # Testing sb.cdp.click_with_offset() + # Testing click_with_offset() url = "https://seleniumbase.io/canvas/" sb.activate_cdp_mode(url) sb.assert_title_contains("Canvas") @@ -25,7 +25,7 @@ def 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() + # Testing gui_click_with_offset() url = "https://seleniumbase.io/other/canvas" sb.activate_cdp_mode(url) sb.assert_title_contains("Canvas") diff --git a/examples/cdp_mode/raw_cdp_browserscan.py b/examples/cdp_mode/raw_cdp_browserscan.py index 30ece9c0785..223db2ccfb2 100644 --- a/examples/cdp_mode/raw_cdp_browserscan.py +++ b/examples/cdp_mode/raw_cdp_browserscan.py @@ -1,8 +1,8 @@ from seleniumbase import sb_cdp -url = "https://www.browserscan.net/bot-detection" -sb = sb_cdp.Chrome(url, locale="en", ad_block=True) -sb.flash("Test Results", duration=3, pause=1) +sb = sb_cdp.Chrome(locale="en", ad_block=True) +sb.open("https://browserscan.net/bot-detection") +sb.flash("Test Results", duration=1.5, pause=0.5) sb.assert_element('strong:contains("Normal")') print("Bot Not Detected") -sb.flash('strong:contains("Normal")', duration=3, pause=2) +sb.flash('strong:contains("Normal")', pause=1) diff --git a/examples/cdp_mode/raw_cdp_fingerprint.py b/examples/cdp_mode/raw_cdp_fingerprint.py index 96a56739ae6..8e36d99e38f 100644 --- a/examples/cdp_mode/raw_cdp_fingerprint.py +++ b/examples/cdp_mode/raw_cdp_fingerprint.py @@ -1,7 +1,7 @@ from seleniumbase import sb_cdp -url = "https://demo.fingerprint.com/playground" -sb = sb_cdp.Chrome(url) +sb = sb_cdp.Chrome() +sb.open("https://demo.fingerprint.com/playground") sb.wait_for_element('a[href*="browser-bot-detection"]') sb.flash('a[href*="browser-bot-detection"]', duration=3, pause=1) bot_row_selector = 'table:contains("Bot") tr:nth-of-type(3)' diff --git a/examples/cdp_mode/raw_cdp_hyatt.py b/examples/cdp_mode/raw_cdp_hyatt.py index 6bf9fd27128..0d719d4feea 100644 --- a/examples/cdp_mode/raw_cdp_hyatt.py +++ b/examples/cdp_mode/raw_cdp_hyatt.py @@ -11,8 +11,12 @@ sb.type('input[id="search-term"]', location) sb.sleep(1.2) sb.click('li[data-js="suggestion"]') -sb.sleep(1.2) +sb.sleep(0.6) +sb.click_if_visible('button[aria-label="Close"]') +sb.sleep(0.8) sb.click("button.be-button-shop") +sb.sleep(1) +sb.click_if_visible('[label="Find Hotels"]') sb.sleep(6) card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' hotels = sb.select_all(card_info) diff --git a/examples/cdp_mode/raw_cdp_patent.py b/examples/cdp_mode/raw_cdp_patent.py new file mode 100644 index 00000000000..ee5f18a1f52 --- /dev/null +++ b/examples/cdp_mode/raw_cdp_patent.py @@ -0,0 +1,9 @@ +from seleniumbase import sb_cdp + +url = "https://www.lens.org/lens/patent/135-034-272-112-366/frontpage" +sb = sb_cdp.Chrome(url) +sb.sleep(3.5) +sb.solve_captcha() +sb.flash('[ng-if*="patent.title"]', duration=3, pause=2) +print("* " + sb.get_text('[ng-if*="patent.title"]') + " *") +print(sb.get_text("ol.claims")) diff --git a/examples/cdp_mode/raw_cdp_pixelscan.py b/examples/cdp_mode/raw_cdp_pixelscan.py index 5cd21eab576..0a73349c161 100644 --- a/examples/cdp_mode/raw_cdp_pixelscan.py +++ b/examples/cdp_mode/raw_cdp_pixelscan.py @@ -1,16 +1,15 @@ from seleniumbase import sb_cdp -url = "https://pixelscan.net/fingerprint-check" -sb = sb_cdp.Chrome(url, incognito=True) +sb = sb_cdp.Chrome(incognito=True) +sb.open("https://pixelscan.net/fingerprint-check") +sb.sleep(1) sb.wait_for_element("pxlscn-dynamic-ad") sb.sleep(0.5) sb.remove_elements("pxlscn-dynamic-ad") -sb.sleep(2) -sb.assert_text("No masking detected", "pxlscn-fingerprint-masking") -sb.assert_text("No automated behavior", "pxlscn-bot-detection") -sb.highlight('span.status-success') sb.sleep(1) +sb.assert_text("No automated behavior", "pxlscn-bot-detection") +sb.wait_for_element("span.status-success") +sb.assert_text("No masking detected", "pxlscn-fingerprint-masking") +sb.highlight("span.status-success") sb.highlight("pxlscn-fingerprint-masking p") -sb.sleep(1) sb.highlight("pxlscn-bot-detection p") -sb.sleep(2) diff --git a/examples/cdp_mode/raw_cdp_timezone.py b/examples/cdp_mode/raw_cdp_timezone.py new file mode 100644 index 00000000000..ed02ee80c92 --- /dev/null +++ b/examples/cdp_mode/raw_cdp_timezone.py @@ -0,0 +1,16 @@ +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome(ad_block=True, incognito=True) +url = "https://www.randymajors.org/what-time-zone-am-i-in" +sb.open(url, tzone="Asia/Kolkata", geoloc=(26.863, 80.94)) +sb.remove_elements("#right-sidebar") +sb.sleep(2.5) +sb.remove_elements('[data-google-query-id]') +sb.remove_elements("iframe:not(#embedMapFrame)") +sb.sleep(2.5) +sb.open(url, tzone="Asia/Tokyo", geoloc=(35.050681, 136.844728)) +sb.remove_elements("#right-sidebar") +sb.sleep(2.5) +sb.remove_elements('[data-google-query-id]') +sb.remove_elements("iframe:not(#embedMapFrame)") +sb.sleep(2.5) diff --git a/examples/cdp_mode/raw_cdp_with_sb.py b/examples/cdp_mode/raw_cdp_with_sb.py index 621308afb63..a9b3c782af6 100644 --- a/examples/cdp_mode/raw_cdp_with_sb.py +++ b/examples/cdp_mode/raw_cdp_with_sb.py @@ -7,6 +7,8 @@ sb.activate_cdp_mode(url) sb.sleep(2.5) sb.internalize_links() # Don't open links in a new tab + sb.click_if_visible('[aria-label="Close Modal"]') + sb.sleep(0.2) sb.click("#link_header_nav_experiences") sb.sleep(3.5) sb.remove_elements("msm-cookie-banner") @@ -15,10 +17,10 @@ where_to = 'div[data-automation*="experiences"] input' button = 'button[data-automation*="experiences-search"]' sb.wait_for_text("Where to?") - sb.cdp.gui_click_element(where_to) + sb.gui_click_element(where_to) sb.press_keys(where_to, location) sb.sleep(1) - sb.cdp.gui_click_element(button) + sb.gui_click_element(button) sb.sleep(3) print(sb.get_title()) print("************") diff --git a/examples/cdp_mode/raw_demo_site.py b/examples/cdp_mode/raw_demo_site.py index 5864d66540a..d2ad2329a36 100644 --- a/examples/cdp_mode/raw_demo_site.py +++ b/examples/cdp_mode/raw_demo_site.py @@ -1,4 +1,4 @@ -"""Example of using various CDP Mode commands""" +"""Example of using various CDP Mode commands via sb.cdp""" from seleniumbase import SB with SB(uc=True, test=True) as sb: diff --git a/examples/cdp_mode/raw_drag_and_drop.py b/examples/cdp_mode/raw_drag_and_drop.py index 993793653c2..e5057f32da7 100644 --- a/examples/cdp_mode/raw_drag_and_drop.py +++ b/examples/cdp_mode/raw_drag_and_drop.py @@ -4,7 +4,7 @@ url = "https://seleniumbase.io/other/drag_and_drop" sb.activate_cdp_mode(url) sb.assert_element_not_visible("#div1 img#drag1") - sb.cdp.gui_drag_and_drop("#drag1", "#div1") + sb.gui_drag_and_drop("#drag1", "#div1") sb.assert_element("#div1 img#drag1") sb.sleep(1) @@ -15,7 +15,7 @@ x, y = sb.get_gui_element_center("#draggable") sb.switch_to_default_content() sb.scroll_to_top() - sb.cdp.gui_drag_drop_points(x, y, x + 180, y + 90) - sb.cdp.gui_drag_drop_points(x + 180, y + 90, x + 60, y + 120) - sb.cdp.gui_drag_drop_points(x + 60, y + 120, x + 40, y + 40) + sb.gui_drag_drop_points(x, y, x + 180, y + 90) + sb.gui_drag_drop_points(x + 180, y + 90, x + 60, y + 120) + sb.gui_drag_drop_points(x + 60, y + 120, x + 40, y + 40) sb.sleep(1) diff --git a/examples/cdp_mode/raw_facebook.py b/examples/cdp_mode/raw_facebook.py index d6766c60b26..3abb6588f17 100644 --- a/examples/cdp_mode/raw_facebook.py +++ b/examples/cdp_mode/raw_facebook.py @@ -7,7 +7,7 @@ sb.click_if_visible('[aria-label="Close"] i') sb.sleep(1) for i in range(16): - sb.cdp.scroll_down(16) + sb.scroll_down(16) print(sb.get_page_title()) sb.save_as_pdf_to_logs() sb.save_page_source_to_logs() diff --git a/examples/cdp_mode/raw_fingerprint.py b/examples/cdp_mode/raw_fingerprint.py index 2092432d4e5..016c459f536 100644 --- a/examples/cdp_mode/raw_fingerprint.py +++ b/examples/cdp_mode/raw_fingerprint.py @@ -3,8 +3,8 @@ with SB(uc=True, test=True) as sb: url = "https://demo.fingerprint.com/playground" sb.activate_cdp_mode(url) - sb.cdp.flash('a[href*="browser-bot-detection"]', duration=3, pause=1) + sb.flash('a[href*="browser-bot-detection"]', duration=3, pause=1) bot_row_selector = 'table:contains("Bot") tr:nth-of-type(3)' print(sb.get_text(bot_row_selector)) sb.assert_text("Bot Not detected", bot_row_selector) - sb.cdp.flash(bot_row_selector, duration=3, pause=2) + sb.flash(bot_row_selector, duration=3, pause=2) diff --git a/examples/cdp_mode/raw_gitlab.py b/examples/cdp_mode/raw_gitlab.py index 0d9b6a1018d..ddc8cd8f92d 100644 --- a/examples/cdp_mode/raw_gitlab.py +++ b/examples/cdp_mode/raw_gitlab.py @@ -10,4 +10,3 @@ sb.assert_element('label[for="user_login"]') sb.highlight('button:contains("Sign in")') sb.highlight('h1:contains("GitLab")') - sb.post_message("SeleniumBase wasn't detected", duration=4) diff --git a/examples/cdp_mode/raw_hyatt.py b/examples/cdp_mode/raw_hyatt.py index 54d6095e312..72990eb8687 100644 --- a/examples/cdp_mode/raw_hyatt.py +++ b/examples/cdp_mode/raw_hyatt.py @@ -1,9 +1,9 @@ from seleniumbase import SB -with SB(uc=True, test=True, locale="en") as sb: +with SB(uc=True, test=True, locale="en", guest=True) as sb: url = "https://www.hyatt.com/" sb.activate_cdp_mode(url) - sb.sleep(3.2) + sb.sleep(3.4) sb.click_if_visible('button[aria-label="Close"]') sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") @@ -14,11 +14,11 @@ sb.click('li[data-js="suggestion"]') sb.sleep(0.6) sb.click_if_visible('button[aria-label="Close"]') - sb.sleep(0.6) + sb.sleep(0.8) sb.click("button.be-button-shop") sb.sleep(1) sb.click_if_visible('[label="Find Hotels"]') - sb.sleep(5) + sb.sleep(5.5) card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' hotels = sb.select_all(card_info) print("Hyatt Hotels in %s:" % location) diff --git a/examples/cdp_mode/raw_linkedin.py b/examples/cdp_mode/raw_linkedin.py index 83ec20e34e4..a76adb08314 100644 --- a/examples/cdp_mode/raw_linkedin.py +++ b/examples/cdp_mode/raw_linkedin.py @@ -7,7 +7,7 @@ sb.click_if_visible('button[aria-label="Dismiss"]') sb.sleep(1) for i in range(42): - sb.cdp.scroll_down(16) + sb.scroll_down(16) print(sb.get_page_title()) sb.save_as_pdf_to_logs() sb.save_page_source_to_logs() diff --git a/examples/cdp_mode/raw_mobile_gitlab.py b/examples/cdp_mode/raw_mobile_gitlab.py index cccf9917ed0..b9853c39161 100644 --- a/examples/cdp_mode/raw_mobile_gitlab.py +++ b/examples/cdp_mode/raw_mobile_gitlab.py @@ -4,8 +4,8 @@ with SB(uc=True, test=True) as sb: url = "https://gitlab.com/users/sign_in" sb.activate_cdp_mode() - tab = sb.cdp.get_active_tab() - loop = sb.cdp.get_event_loop() + tab = sb.get_active_tab() + loop = sb.get_event_loop() loop.run_until_complete( tab.send( mycdp.emulation.set_device_metrics_override( diff --git a/examples/cdp_mode/raw_mobile_roblox.py b/examples/cdp_mode/raw_mobile_roblox.py index 02c2d8aef6b..eee8c146a2e 100644 --- a/examples/cdp_mode/raw_mobile_roblox.py +++ b/examples/cdp_mode/raw_mobile_roblox.py @@ -8,8 +8,8 @@ "(KHTML, like Gecko) Mobile Safari/537.36" ) sb.activate_cdp_mode(agent=agent) - tab = sb.cdp.get_active_tab() - loop = sb.cdp.get_event_loop() + tab = sb.get_active_tab() + loop = sb.get_event_loop() loop.run_until_complete( tab.send( mycdp.emulation.set_device_metrics_override( diff --git a/examples/cdp_mode/raw_mycdp_cookies.py b/examples/cdp_mode/raw_mycdp_cookies.py index fe8983c7168..1625a7a0d97 100644 --- a/examples/cdp_mode/raw_mycdp_cookies.py +++ b/examples/cdp_mode/raw_mycdp_cookies.py @@ -3,6 +3,6 @@ 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() + tab = sb.get_active_tab() + loop = sb.get_event_loop() print(loop.run_until_complete(tab.send(mycdp.storage.get_cookies()))) diff --git a/examples/cdp_mode/raw_patent.py b/examples/cdp_mode/raw_patent.py new file mode 100644 index 00000000000..faa62807b72 --- /dev/null +++ b/examples/cdp_mode/raw_patent.py @@ -0,0 +1,10 @@ +from seleniumbase import SB + +with SB(uc=True, test=True) as sb: + url = "https://www.lens.org/lens/patent/135-034-272-112-366/frontpage" + sb.activate_cdp_mode(url) + sb.sleep(3) + sb.solve_captcha() + sb.flash('[ng-if*="patent.title"]', duration=3, pause=2) + print("* " + sb.get_text('[ng-if*="patent.title"]') + " *") + print(sb.get_text("ol.claims")) diff --git a/examples/cdp_mode/raw_pixelscan.py b/examples/cdp_mode/raw_pixelscan.py index cc3658161b8..8976f222fc7 100644 --- a/examples/cdp_mode/raw_pixelscan.py +++ b/examples/cdp_mode/raw_pixelscan.py @@ -3,15 +3,14 @@ with SB(uc=True, test=True, incognito=True, ad_block=True) as sb: url = "https://pixelscan.net/fingerprint-check" sb.activate_cdp_mode(url) + sb.sleep(1) sb.wait_for_element("pxlscn-dynamic-ad") sb.sleep(0.5) sb.remove_elements("pxlscn-dynamic-ad") - sb.sleep(2) - sb.assert_text("No masking detected", "pxlscn-fingerprint-masking") - sb.assert_text("No automated behavior", "pxlscn-bot-detection") - sb.cdp.highlight('span.status-success') sb.sleep(1) + sb.assert_text("No automated behavior", "pxlscn-bot-detection") + sb.wait_for_element("span.status-success") + sb.assert_text("No masking detected", "pxlscn-fingerprint-masking") + sb.cdp.highlight("span.status-success") sb.cdp.highlight("pxlscn-fingerprint-masking p") - sb.sleep(1) sb.cdp.highlight("pxlscn-bot-detection p") - sb.sleep(2) diff --git a/examples/cdp_mode/raw_pokemon.py b/examples/cdp_mode/raw_pokemon.py index 38227c06153..5a1c6ba1315 100644 --- a/examples/cdp_mode/raw_pokemon.py +++ b/examples/cdp_mode/raw_pokemon.py @@ -21,14 +21,14 @@ sb.assert_element('img[alt="Pikachu"]') sb.scroll_into_view("div.pokemon-ability-info") sb.sleep(1.2) - sb.cdp.flash('div[class*="title"]') - sb.cdp.flash('img[alt="Pikachu"]') - sb.cdp.flash("div.pokemon-ability-info") + sb.flash('div[class*="title"]') + sb.flash('img[alt="Pikachu"]') + sb.flash("div.pokemon-ability-info") name = sb.get_text("label.styled-select") info = sb.get_text("div.version-descriptions p.active") print("*** %s: ***\n* %s" % (name, info)) sb.sleep(2) - sb.cdp.highlight_overlay("div.pokemon-ability-info") + sb.highlight_overlay("div.pokemon-ability-info") sb.sleep(2) sb.open("https://events.pokemon.com/EventLocator/") sb.sleep(2) diff --git a/examples/cdp_mode/raw_priceline.py b/examples/cdp_mode/raw_priceline.py index 063c41a4a86..fe85ddfca93 100644 --- a/examples/cdp_mode/raw_priceline.py +++ b/examples/cdp_mode/raw_priceline.py @@ -25,9 +25,9 @@ sb.sleep(0.6) sb.click('form button[type="submit"]') sb.sleep(4.8) - if len(sb.cdp.get_tabs()) > 1: - sb.cdp.close_active_tab() - sb.cdp.switch_to_newest_tab() + if len(sb.get_tabs()) > 1: + sb.close_active_tab() + sb.switch_to_newest_tab() sb.sleep(0.6) sb.sleep(0.8) for y in range(1, 9): diff --git a/examples/cdp_mode/raw_publication.py b/examples/cdp_mode/raw_publication.py index 882ffb9683a..088798f3fc1 100644 --- a/examples/cdp_mode/raw_publication.py +++ b/examples/cdp_mode/raw_publication.py @@ -6,7 +6,7 @@ sb.sleep(2.2) shadow_head = "div.main-content div" if sb.is_element_present(shadow_head): - sb.cdp.gui_click_element(shadow_head) + sb.gui_click_element(shadow_head) sb.sleep(1.5) sb.assert_text("Discover the world's scientific knowledge") sb.click_if_visible('button[aria-label="Close"]') diff --git a/examples/cdp_mode/raw_req_sb.py b/examples/cdp_mode/raw_req_sb.py index aab553f6bd2..a8e2b30f1cd 100644 --- a/examples/cdp_mode/raw_req_sb.py +++ b/examples/cdp_mode/raw_req_sb.py @@ -25,7 +25,7 @@ async def request_paused_handler(event, tab): with SB(uc=True, test=True, locale="en") as sb: sb.activate_cdp_mode("about:blank") - sb.cdp.add_handler(mycdp.fetch.RequestPaused, request_paused_handler) + sb.add_handler(mycdp.fetch.RequestPaused, request_paused_handler) url = "https://gettyimages.com/photos/firefly-2003-nathan" sb.open(url) - sb.sleep(5) + sb.sleep(4) diff --git a/examples/cdp_mode/raw_res_nike.py b/examples/cdp_mode/raw_res_nike.py index a984d05f20d..0aaa8edc700 100644 --- a/examples/cdp_mode/raw_res_nike.py +++ b/examples/cdp_mode/raw_res_nike.py @@ -28,8 +28,8 @@ async def receive_handler(event: mycdp.network.ResponseReceived): with SB(uc=True, test=True, locale="en", pls="none") as sb: url = "https://www.nike.com/" sb.activate_cdp_mode(url) - sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler) - sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler) + sb.add_handler(mycdp.network.RequestWillBeSent, send_handler) + sb.add_handler(mycdp.network.ResponseReceived, receive_handler) sb.sleep(2.5) sb.click('[data-testid="user-tools-container"] search') sb.sleep(1.5) @@ -41,4 +41,3 @@ async def receive_handler(event: mycdp.network.ResponseReceived): print('**** Found results for "%s": ****' % search) for element in elements: print("* " + element.text) - sb.sleep(2) diff --git a/examples/cdp_mode/raw_seatgeek.py b/examples/cdp_mode/raw_seatgeek.py index 892d988c78f..5059ab1d8da 100644 --- a/examples/cdp_mode/raw_seatgeek.py +++ b/examples/cdp_mode/raw_seatgeek.py @@ -8,9 +8,9 @@ sb.sleep(1.6) query = "Jerry Seinfeld" sb.press_keys(input_field, query) - sb.sleep(1.6) + sb.sleep(1.8) sb.click("li#active-result-item") - sb.sleep(4.2) + sb.sleep(4.4) print('*** SeatGeek Search for "%s":' % query) item_selector = '[data-testid="listing-item"]' for item in sb.find_elements(item_selector): diff --git a/examples/cdp_mode/raw_southwest.py b/examples/cdp_mode/raw_southwest.py index 7a2537f6c77..ef0d99e85c2 100644 --- a/examples/cdp_mode/raw_southwest.py +++ b/examples/cdp_mode/raw_southwest.py @@ -6,35 +6,35 @@ sb.sleep(2.8) origin = "BOS" destination = "MDW" - sb.cdp.click_if_visible("button#onetrust-accept-btn-handler") + sb.click_if_visible("button#onetrust-accept-btn-handler") sb.sleep(0.5) - sb.cdp.gui_click_element("input#originationAirportCode") + sb.gui_click_element("input#originationAirportCode") sb.sleep(0.2) - sb.cdp.select("input#originationAirportCode").clear_input() + sb.select("input#originationAirportCode").clear_input() sb.sleep(0.2) sb.uc_gui_press_keys(" " + "\n") sb.sleep(0.5) - sb.cdp.gui_click_element("input#originationAirportCode") + sb.gui_click_element("input#originationAirportCode") sb.sleep(0.4) sb.uc_gui_press_keys(origin + "\n") sb.sleep(0.4) - sb.cdp.gui_click_element("h1") + sb.gui_click_element("h1") sb.sleep(0.3) - sb.cdp.gui_click_element("input#destinationAirportCode") + sb.gui_click_element("input#destinationAirportCode") sb.sleep(0.2) - sb.cdp.select("input#destinationAirportCode").clear_input() + sb.select("input#destinationAirportCode").clear_input() sb.sleep(0.2) sb.uc_gui_press_keys(destination + "\n") sb.sleep(0.4) - sb.cdp.gui_click_element("h1") + sb.gui_click_element("h1") sb.sleep(0.2) - sb.cdp.click_if_visible("button#onetrust-accept-btn-handler") + sb.click_if_visible("button#onetrust-accept-btn-handler") sb.sleep(0.1) - sb.cdp.click('form button[data-test="submitField"]') + sb.click('form button[data-test="submitField"]') sb.sleep(2.5) - sb.cdp.click('button[aria-labelledby*="nearby-airport-drawer-"]') + sb.click('button[aria-labelledby*="nearby-airport-drawer-"]') sb.sleep(4) - day = sb.cdp.get_text('[aria-current="true"] span[class*="cal"]') + day = sb.get_text('[aria-current="true"] span[class*="cal"]') print("**** Flights from %s to %s ****" % (origin, destination)) flights = sb.find_elements("li.air-booking-select-detail") for flight in flights: diff --git a/examples/cdp_mode/raw_timezone_sb.py b/examples/cdp_mode/raw_timezone_sb.py index c9d3cbb93d2..57d6d0e983d 100644 --- a/examples/cdp_mode/raw_timezone_sb.py +++ b/examples/cdp_mode/raw_timezone_sb.py @@ -6,6 +6,6 @@ sb.activate_cdp_mode(url, tzone="Asia/Kolkata", geoloc=(26.863, 80.94)) sb.remove_elements("#right-sidebar") sb.sleep(5) - sb.cdp.open(url, tzone="Asia/Tokyo", geoloc=(35.050681, 136.844728)) + sb.open(url, tzone="Asia/Tokyo", geoloc=(35.050681, 136.844728)) sb.remove_elements("#right-sidebar") sb.sleep(5) diff --git a/examples/cdp_mode/raw_trails.py b/examples/cdp_mode/raw_trails.py index b43bc3d4510..8c8f87e1fd0 100644 --- a/examples/cdp_mode/raw_trails.py +++ b/examples/cdp_mode/raw_trails.py @@ -16,15 +16,13 @@ sb.sleep(3.5) sb.click('button:contains("more")') sb.sleep(0.7) - sb.click_if_visible('button[data-testid="modal-close"]') - sb.sleep(0.7) print("Description: (%s)\n" % sb.get_text("h1")) print(sb.get_text('div[class*="Description_description"]')) sb.scroll_to_bottom() sb.sleep(1.5) - sb.click_if_visible('button[data-testid="modal-close"]') + sb.click_if_visible('[aria-label="Close"]') sb.sleep(1.2) - summary = '[class*="ReviewSummary_summary"] span' + summary = '[class*="ReviewSummary"] span' print("\nReview Summary:\n\n%s" % sb.get_text(summary)) reviews = sb.select_all('p[class*="styles_reviewText"]') print("\nReviews:") @@ -33,6 +31,8 @@ folder = "images_exported" file_name = "thundering_brook_falls.png" sb.scroll_to_top() + sb.sleep(0.3) + sb.click_if_visible('[aria-label="Close"]') sb.sleep(1.2) sb.save_screenshot(file_name, folder, selector="body") print('\n"./%s/%s" was saved!' % (folder, file_name)) diff --git a/examples/cdp_mode/raw_walmart.py b/examples/cdp_mode/raw_walmart.py index 39eeb09bdc1..1fccb4071fb 100644 --- a/examples/cdp_mode/raw_walmart.py +++ b/examples/cdp_mode/raw_walmart.py @@ -6,7 +6,7 @@ sb.sleep(1.8) continue_button = 'button:contains("Continue shopping")' if sb.is_element_visible(continue_button): - sb.cdp.gui_click_element(continue_button) + sb.gui_click_element(continue_button) sb.sleep(0.6) sb.click('input[aria-label="Search"]') sb.sleep(1.2) @@ -15,10 +15,10 @@ sb.press_keys('input[aria-label="Search"]', search + "\n") sb.sleep(3.8) if sb.is_element_visible("#px-captcha"): - sb.cdp.gui_click_and_hold("#px-captcha", 7.2) + sb.gui_click_and_hold("#px-captcha", 7.2) sb.sleep(4.2) if sb.is_element_visible("#px-captcha"): - sb.cdp.gui_click_and_hold("#px-captcha", 4.2) + sb.gui_click_and_hold("#px-captcha", 4.2) sb.sleep(3.2) sb.remove_elements('[data-testid="skyline-ad"]') sb.remove_elements('[data-testid="sba-container"]') diff --git a/examples/cdp_mode/raw_xhr_sb.py b/examples/cdp_mode/raw_xhr_sb.py index 093d188978a..229cd65fbfd 100644 --- a/examples/cdp_mode/raw_xhr_sb.py +++ b/examples/cdp_mode/raw_xhr_sb.py @@ -62,12 +62,12 @@ async def receiveXHR(page, requests): listenXHR(tab) # Change url to something that makes ajax requests - sb.cdp.open("https://learn.microsoft.com/en-us/") + sb.open("https://learn.microsoft.com/en-us/") time.sleep(1) for i in range(9): - sb.cdp.scroll_down(6) + sb.scroll_down(6) - loop = sb.cdp.get_event_loop() + loop = sb.get_event_loop() xhr_responses = loop.run_until_complete(receiveXHR(tab, xhr_requests)) for response in xhr_responses: print(c1 + "*** ==> XHR Request URL <== ***" + cr) From 986ee70062550b06ceb3e96a51dfcf06950a7460 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 20:34:24 -0400 Subject: [PATCH 06/11] Update examples --- examples/hack_the_planet.py | 24 ++------ examples/presenter/uc_presentation_4.py | 80 +++++++++++-------------- examples/presenter/uc_presentation_5.py | 2 +- examples/raw_recaptcha.py | 4 +- examples/raw_turnstile.py | 4 +- 5 files changed, 47 insertions(+), 67 deletions(-) diff --git a/examples/hack_the_planet.py b/examples/hack_the_planet.py index d33c2697a51..f9d77322094 100644 --- a/examples/hack_the_planet.py +++ b/examples/hack_the_planet.py @@ -85,7 +85,7 @@ def test_all_your_base_are_belong_to_us(self): self.highlight('a[href*="google.com/img"]', loops=3) self.highlight('form[role="search"]', loops=8) - self.open("https://github.com/features/actions") + self.open("https://github.com/solutions/use-case/devops") self.set_text_content("#hero-section-brand-heading", aybabtu) self.highlight("#hero-section-brand-heading", loops=14, scroll=False) @@ -172,23 +172,12 @@ def test_all_your_base_are_belong_to_us(self): self.highlight("div.intro-title", loops=8, scroll=False) self.highlight("h4", loops=8, scroll=False) - self.open("https://slack.com/help/articles/204714258-Giphy-for-Slack") + self.open( + "https://slack.com/help/articles/204379773-Upload-a-Slack-icon" + ) self.set_text_content("h1", aybabtu) - self.set_text_content('a[prettyslug="getting-started"]', "ALL") - self.set_text_content('a[prettyslug="using-slack"]', "YOUR") - self.set_text_content('a[prettyslug="your-profile"]', "BASE") - self.set_text_content('a[prettyslug="connect-tools"]', "ARE") - self.set_text_content('a[prettyslug="administration"]', "BELONG") - self.set_text_content('a[prettyslug*="tutorials"]', "TO US") self.set_text_content("h1.article_title", aybabtu) self.highlight("h1", loops=4, scroll=False) - self.highlight("div#global_menu", loops=2, scroll=False) - self.highlight('a[prettyslug*="g-started"]', loops=1, scroll=False) - self.highlight('a[prettyslug="using-slack"]', loops=1, scroll=False) - self.highlight('a[prettyslug="your-profile"]', loops=2, scroll=False) - self.highlight('a[prettyslug="connect-tools"]', loops=1, scroll=False) - self.highlight('a[prettyslug="administration"]', loops=1, scroll=False) - self.highlight('a[prettyslug*="tutorials"]', loops=2, scroll=False) self.highlight("h1.article_title", loops=5, scroll=False) self.open("https://kubernetes.io/") @@ -198,8 +187,7 @@ def test_all_your_base_are_belong_to_us(self): self.set_text_content('nav a[href="/careers/"]', "ARE") self.set_text_content('nav a[href="/partners/"]', "BELONG") self.set_text_content('nav a[href="/community/"]', "TO") - self.set_text_content("nav #navbarDropdown", "US") - self.set_text_content("nav #navbarDropdownMenuLink", ".") + self.set_text_content("nav a.dropdown-toggle", "US") if self.is_element_visible("h1"): self.set_text_content("h1", aybabtu) self.highlight("nav ul.navbar-nav", loops=3, scroll=False) @@ -209,7 +197,7 @@ def test_all_your_base_are_belong_to_us(self): self.highlight('nav a[href="/careers/"]', loops=1, scroll=False) self.highlight('nav a[href="/partners/"]', loops=1, scroll=False) self.highlight('nav a[href="/community/"]', loops=1, scroll=False) - self.highlight("nav #navbarDropdown", loops=2, scroll=False) + self.highlight("nav a.dropdown-toggle", loops=2, scroll=False) if self.is_element_visible("h1"): self.highlight("h1", loops=6, scroll=False) diff --git a/examples/presenter/uc_presentation_4.py b/examples/presenter/uc_presentation_4.py index 09bc30ee89a..e7ff50083ba 100644 --- a/examples/presenter/uc_presentation_4.py +++ b/examples/presenter/uc_presentation_4.py @@ -475,28 +475,26 @@ def test_presentation_4(self): sb.scroll_into_view("a#advSearch") sb.sleep(0.7) sb.click("a#advSearch") - sb.sleep(0.5) - sb.cdp.click("a#advSearch") sb.sleep(1.2) - sb.cdp.click('img[src*="img/pokedex/detail/025.png"]') - sb.cdp.assert_text("Pikachu", 'div[class*="title"]') - sb.cdp.assert_element('img[alt="Pikachu"]') - sb.cdp.scroll_into_view("div.pokemon-ability-info") + sb.click('img[src*="img/pokedex/detail/025.png"]') + sb.assert_text("Pikachu", 'div[class*="title"]') + sb.assert_element('img[alt="Pikachu"]') + sb.scroll_into_view("div.pokemon-ability-info") sb.sleep(1.2) - sb.cdp.flash('div[class*="title"]') - sb.cdp.flash('img[alt="Pikachu"]') - sb.cdp.flash("div.pokemon-ability-info") - name = sb.cdp.get_text("label.styled-select") - info = sb.cdp.get_text("div.version-descriptions p.active") + sb.flash('div[class*="title"]') + sb.flash('img[alt="Pikachu"]') + sb.flash("div.pokemon-ability-info") + name = sb.get_text("label.styled-select") + info = sb.get_text("div.version-descriptions p.active") print("*** %s: ***\n* %s" % (name, info)) sb.sleep(2) - sb.cdp.highlight_overlay("div.pokemon-ability-info") + sb.highlight_overlay("div.pokemon-ability-info") sb.sleep(2) - sb.cdp.open("https://events.pokemon.com/EventLocator/") + sb.open("https://events.pokemon.com/EventLocator/") sb.sleep(2) - sb.cdp.click('span:contains("Championship")') + sb.click('span:contains("Championship")') sb.sleep(2) - events = sb.cdp.select_all("div.event-info__title") + events = sb.select_all("div.event-info__title") print("*** PokΓ©mon Championship Events: ***") for event in events: print("* " + event.text) @@ -518,7 +516,7 @@ def test_presentation_4(self): sb.sleep(1.8) continue_button = 'button:contains("Continue shopping")' if sb.is_element_visible(continue_button): - sb.cdp.gui_click_element(continue_button) + sb.gui_click_element(continue_button) sb.sleep(0.6) sb.click('input[aria-label="Search"]') sb.sleep(1.2) @@ -527,10 +525,10 @@ def test_presentation_4(self): sb.press_keys('input[aria-label="Search"]', search + "\n") sb.sleep(3.8) if sb.is_element_visible("#px-captcha"): - sb.cdp.gui_click_and_hold("#px-captcha", 7.2) + sb.gui_click_and_hold("#px-captcha", 7.2) sb.sleep(3.2) if sb.is_element_visible("#px-captcha"): - sb.cdp.gui_click_and_hold("#px-captcha", 4.2) + sb.gui_click_and_hold("#px-captcha", 4.2) sb.sleep(3.2) sb.remove_elements('[data-testid="skyline-ad"]') sb.remove_elements('[data-testid="sba-container"]') @@ -617,37 +615,33 @@ def test_presentation_4(self): with SB(uc=True, test=True, locale="en", ad_block=True) as sb: url = "https://www.easyjet.com/en/" sb.activate_cdp_mode(url) - sb.sleep(2) - sb.click_if_visible("button#ensCloseBanner") - sb.sleep(1.2) + sb.sleep(1.5) + sb.click_if_visible("button#ensRejectAds", timeout=2) + sb.sleep(1) sb.click('input[name="from"]') - sb.sleep(1.2) + sb.sleep(1) sb.type('input[name="from"]', "London Gatwick") - sb.sleep(0.6) - sb.click_if_visible("button#ensCloseBanner") - sb.sleep(0.6) + sb.sleep(1) sb.click('span[data-testid="airport-name"]') - sb.sleep(1.2) + sb.sleep(1) sb.type('input[name="to"]', "Paris") - sb.sleep(1.2) + sb.sleep(1) sb.click('span[data-testid="airport-name"]') - sb.sleep(1.2) + sb.sleep(1) sb.click('input[name="when"]') - sb.sleep(1.2) - sb.cdp.click( - '[data-testid="month"]:last-of-type' - ' [aria-disabled="false"]' + sb.sleep(1) + sb.click( + '[data-testid="month"]:last-of-type [aria-disabled="false"]' ) - sb.sleep(1.2) + sb.sleep(1) sb.click( - '[data-testid="month"]:last-of-type' - ' [aria-disabled="false"]' + '[data-testid="month"]:last-of-type [aria-disabled="false"]' ) - sb.sleep(1.2) + sb.sleep(1) sb.click('button[data-testid="submit"]') - sb.sleep(3.5) + sb.sleep(4) sb.connect() - sb.sleep(4.2) + sb.sleep(1) for window in sb.driver.window_handles: sb.switch_to_window(window) if "/buy/flights" in sb.get_current_url(): @@ -657,9 +651,7 @@ def test_presentation_4(self): for day in days: if not day.text.strip(): continue - print( - "\n\n**** " + " ".join(day.text.split("\n")[0:2]) + " ****" - ) + print("**** " + " ".join(day.text.split("\n")[0:2]) + " ****") fares = day.find_elements( "css selector", 'button[class*="flightDet"]' ) @@ -793,9 +785,9 @@ def test_presentation_4(self): sb.sleep(0.6) sb.click('form button[type="submit"]') sb.sleep(4.8) - if len(sb.cdp.get_tabs()) > 1: - sb.cdp.close_active_tab() - sb.cdp.switch_to_newest_tab() + if len(sb.get_tabs()) > 1: + sb.close_active_tab() + sb.switch_to_newest_tab() sb.sleep(0.6) sb.sleep(0.8) for y in range(1, 9): diff --git a/examples/presenter/uc_presentation_5.py b/examples/presenter/uc_presentation_5.py index 6fd08a40d1b..e9b150876ab 100644 --- a/examples/presenter/uc_presentation_5.py +++ b/examples/presenter/uc_presentation_5.py @@ -522,7 +522,7 @@ def test_presentation_5(self): ' sb.sleep(3)' '\n' ' for i in range(16):\n' - ' sb.cdp.scroll_down(16)\n' + ' sb.scroll_down(16)\n' ' print(sb.get_page_title())\n' ' sb.save_as_pdf_to_logs()\n' ' sb.save_page_source_to_logs()\n' diff --git a/examples/raw_recaptcha.py b/examples/raw_recaptcha.py index 86cb4c1a6d1..2ae7f9939dd 100644 --- a/examples/raw_recaptcha.py +++ b/examples/raw_recaptcha.py @@ -1,9 +1,9 @@ from seleniumbase import SB -with SB(uc=True, test=True, incognito=True) as sb: +with SB(uc=True, test=True) as sb: url = "https://seleniumbase.io/apps/recaptcha" sb.activate_cdp_mode(url) - sb.uc_gui_click_captcha('iframe[src*="/recaptcha/"]') + 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/raw_turnstile.py b/examples/raw_turnstile.py index 9b907ae4397..e811c8ceb32 100644 --- a/examples/raw_turnstile.py +++ b/examples/raw_turnstile.py @@ -2,8 +2,8 @@ with SB(uc=True, test=True) as sb: url = "https://seleniumbase.io/apps/turnstile" - sb.uc_open_with_reconnect(url) - sb.uc_gui_click_captcha() + sb.activate_cdp_mode(url) + 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) From c78890a265e1e81a32c71c89c5d9f0d9de2b563e Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 20:41:03 -0400 Subject: [PATCH 07/11] 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 78b4ca4e175..95f16b14b5f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ pygments>=2.20.0 pyreadline3>=3.5.4;platform_system=="Windows" tabcompleter>=1.4.1 pdbp>=1.8.2 -idna>=3.15 +idna>=3.16 charset-normalizer>=3.4.7,<4 urllib3>=1.26.20,<2;python_version<"3.10" urllib3>=2.7.0,<3;python_version>="3.10" diff --git a/setup.py b/setup.py index 8b2a812d886..6b9fa500cec 100755 --- a/setup.py +++ b/setup.py @@ -193,7 +193,7 @@ 'pyreadline3>=3.5.4;platform_system=="Windows"', 'tabcompleter>=1.4.1', 'pdbp>=1.8.2', - 'idna>=3.15', + 'idna>=3.16', 'charset-normalizer>=3.4.7,<4', 'urllib3>=1.26.20,<2;python_version<"3.10"', 'urllib3>=2.7.0,<3;python_version>="3.10"', From 13b0121d5ef56a4f24863688d916e2e9e5c395d8 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 20:41:48 -0400 Subject: [PATCH 08/11] Update mkdocs.yml --- mkdocs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 77e7b27d757..04e5465ade6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -96,20 +96,20 @@ nav: - 🎭 Stealthy Playwright: examples/cdp_mode/playwright/ReadMe.md - πŸ“š Running Example Tests: examples/ReadMe.md - πŸŽ›οΈ Command Line Options: help_docs/customizing_test_runs.md - - πŸͺ„ Console Scripts: seleniumbase/console_scripts/ReadMe.md + - πŸ’» Console Scripts: seleniumbase/console_scripts/ReadMe.md - πŸ“Š Dashboard / Reports: examples/example_logs/ReadMe.md - πŸ”  Syntax Formats: help_docs/syntax_formats.md - πŸŽ–οΈ GUI / Commander: help_docs/commander.md - πŸ”΄ Recorder Mode: help_docs/recorder_mode.md - - πŸ“˜ API Reference: help_docs/method_summary.md - - πŸ“— CDP Mode APIs: help_docs/cdp_mode_methods.md + - πŸ“— API Reference: help_docs/method_summary.md + - πŸ“˜ CDP Mode APIs: help_docs/cdp_mode_methods.md - Python Setup / Install: - πŸ‰ Get Python, pip, & git: help_docs/install_python_pip_git.md - βš™οΈ Virtualenv Instructions: help_docs/virtualenv_instructions.md - πŸ„ Install SeleniumBase: help_docs/install.md - πŸ‘οΈ How it Works: help_docs/how_it_works.md - JS Manager / JS Tools: - - ❇️ JS Package Manager: help_docs/js_package_manager.md + - 🟨 JS Package Manager: help_docs/js_package_manager.md - 🎦 Demo Mode: help_docs/demo_mode.md - 🚎 Tour Maker: examples/tour_examples/ReadMe.md - πŸ›‚ Dialog Boxes: examples/dialog_boxes/ReadMe.md From b0c00f3ca01def3c77540a10d259730b0616b922 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 20:44:21 -0400 Subject: [PATCH 09/11] Update the docs --- README.md | 52 +++++++++++++++++++++++++++++------ help_docs/cdp_mode_methods.md | 10 +++---- help_docs/method_summary.md | 7 +++-- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a27abd4eb35..746f539c16d 100755 --- a/README.md +++ b/README.md @@ -50,9 +50,40 @@

-πŸ™ CDP Mode bypasses bot-detection and handles CAPTCHAs by driving the browser directly through the Chrome DevTools Protocol. Includes Stealthy Playwright Mode, which extends these advanced anti-detection patches to Playwright scripts. +πŸ™ CDP Mode bypasses bot-detection and handles CAPTCHAs with the Chrome DevTools Protocol. Includes Stealthy Playwright Mode, which extends CDP Mode's anti-detection to Playwright scripts. sb.solve_captcha() handles CAPTCHAs that aren't bypassed automatically. -πŸ“š The [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder includes over 100 ready-to-run examples of E2E testing. Examples that start with `test_` or end with `_test.py`/`_tests.py` are specifically designed to run with `pytest`. Other examples run directly with raw `python` (those files generally start with `raw_` to avoid confusion). +Python sync version of SeleniumBase's CDP Mode: (sb_cdp) + +```python +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome() +sb.open("https://demo.fingerprint.com/playground") +sb.sleep(3) +sb.driver.quit() +``` + +Playwright can use SeleniumBase's stealth browser: + +```python +from playwright.sync_api import sync_playwright +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome() +endpoint_url = sb.get_endpoint_url() + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(endpoint_url) + page = browser.contexts[0].pages[0] + page.goto("https://browserscan.net/bot-detection") + +sb.sleep(3) +sb.driver.quit() +``` + +-------- + +πŸ“š The [SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) folder includes over 150 ready-to-run examples of E2E testing. Examples that start with `test_` or end with `_test.py`/`_tests.py` run with `pytest`. Other examples run directly with raw `python` (those generally start with `raw_` to avoid confusion). πŸ₯· Stealthy CDP Mode examples are located in [./examples/cdp_mode/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples/cdp_mode). @@ -73,12 +104,12 @@ ```python from seleniumbase import sb_cdp -url = "https://www.browserscan.net/bot-detection" -sb = sb_cdp.Chrome(url, locale="en", ad_block=True) -sb.flash("Test Results", duration=3, pause=1) +sb = sb_cdp.Chrome(locale="en", ad_block=True) +sb.open("https://browserscan.net/bot-detection") +sb.flash("Test Results", duration=1.5, pause=0.5) sb.assert_element('strong:contains("Normal")') print("Bot Not Detected") -sb.flash('strong:contains("Normal")', duration=3, pause=2) +sb.flash('strong:contains("Normal")', pause=1) ``` Stealthy architecture flowchart @@ -95,15 +126,18 @@ endpoint_url = sb.get_endpoint_url() with sync_playwright() as p: browser = p.chromium.connect_over_cdp(endpoint_url) page = browser.contexts[0].pages[0] - page.goto("https://www.browserscan.net/bot-detection") + page.goto("https://browserscan.net/bot-detection") page.wait_for_timeout(500) - sb.flash("Test Results", duration=3, pause=1) + sb.flash("Test Results", duration=1.5, pause=0.5) sb.assert_element('strong:contains("Normal")') - sb.flash('strong:contains("Normal")', duration=3, pause=2) + print("Bot Not Detected") + sb.flash('strong:contains("Normal")', pause=1) ``` -------- +

🌐 CLI Options for Supported Chromium Browsers

+ πŸ’‘ You can set which Chromium browser to use via command-line options: ```zsh diff --git a/help_docs/cdp_mode_methods.md b/help_docs/cdp_mode_methods.md index 273e6441540..ed2a65bec96 100644 --- a/help_docs/cdp_mode_methods.md +++ b/help_docs/cdp_mode_methods.md @@ -9,6 +9,7 @@ Here's a list of SeleniumBase CDP Mode method definitions, which are defined in ```python sb.cdp.get(url, **kwargs) sb.cdp.open(url, **kwargs) # Same as sb.cdp.get(url, **kwargs) +sb.cdp.goto(url, **kwargs) # Same as sb.cdp.get(url, **kwargs) sb.cdp.reload(ignore_cache=True, script_to_evaluate_on_load=None) sb.cdp.refresh(*args, **kwargs) sb.cdp.get_event_loop() @@ -276,7 +277,8 @@ Methods: (Sometimes `tab` is named `page` in examples) ```python await tab.get(url="about:blank") -await tab.open(url="about:blank") +await tab.open(url="about:blank") # Same as tab.open() +await tab.goto(url="about:blank") # Same as tab.open() await tab.find(text, best_match=False, timeout=10) # text can be selector await tab.find_all(text, timeout=10) # text can be selector await tab.select(selector, timeout=10) @@ -300,8 +302,7 @@ await tab.set_window_size(left=0, top=0, width=1280, height=1024) await tab.set_window_rect(left=0, top=0, width=1280, height=1024) await tab.activate() await tab.bring_to_front() -await tab.set_window_state( - left=0, top=0, width=1280, height=720, state="normal") +await tab.set_window_state(left=0, top=0, width=1280, height=720, state="normal") await tab.get_navigation_history() await tab.get_user_agent() await tab.get_cookie_string() @@ -315,8 +316,7 @@ await tab.wait_for(selector="", text="", timeout=10) await tab.set_attributes(selector, attribute, value) await tab.internalize_links() await tab.download_file(url, filename=None) -await tab.save_screenshot( - filename="auto", format="png", full_page=False) +await tab.save_screenshot(filename="auto", format="png", full_page=False) await tab.print_to_pdf(filename="auto") await tab.set_download_path(path) await tab.get_all_linked_sources() diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index fc3894bda9f..39e5828074d 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -521,6 +521,7 @@ self.generate_traffic_chain(pages, loops=1) ############ +self.select(selector, by="css selector", timeout=None) self.get_element(selector, by="css selector", timeout=None) # Duplicates: # self.wait_for_selector(selector, by="css selector", timeout=None) @@ -707,6 +708,7 @@ self.uc_gui_handle_captcha(frame="iframe") driver.default_get(url) # Because driver.get(url) works differently in UC Mode driver.open(url) # Like driver.get(), but allows partial URLs without protocol +driver.goto(url) # Same as driver.open(url) driver.click(selector) driver.click_link(link_text) driver.click_if_visible(selector) @@ -722,6 +724,8 @@ driver.assert_text(text, selector) driver.assert_exact_text(text, selector) driver.find_element(selector) driver.find_elements(selector) +driver.select(selector) +driver.select_all(selector) driver.wait_for_element(selector) driver.wait_for_element_visible(selector) driver.wait_for_element_present(selector) @@ -751,12 +755,11 @@ driver.get_parent(element) driver.get_current_url() driver.get_page_source() driver.get_title() +driver.switch_to_newest_tab() driver.switch_to_frame(frame="iframe") driver.is_cdp_mode_active() driver.is_connected() # UC / CDP Mode can disconnect WebDriver -############ - # "driver"-specific methods added (or modified) by SeleniumBase for UC Mode: driver.get(url) # If UC Mode and site detects bots, then uc_open_with_tab(url) From ad78005b2a13d1dd2df9dcb7ef3201cab58c0b5c Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 20:44:50 -0400 Subject: [PATCH 10/11] Version 4.49.2 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 4c0c2d16a1f..4a61993721b 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.49.1" +__version__ = "4.49.2" From d253cc033f37c9c05eed5472ec79377ed704dc5d Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 21 May 2026 21:10:25 -0400 Subject: [PATCH 11/11] Update examples --- examples/cdp_mode/ReadMe.md | 14 +++++++------- examples/cdp_mode/raw_req_mod.py | 6 +++--- examples/cdp_mode/raw_res_sb.py | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index d92d723b8bf..711ee34d27e 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -186,10 +186,10 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb: ```python from seleniumbase import SB -with SB(uc=True, test=True, locale="en") as sb: +with SB(uc=True, test=True, locale="en", guest=True) as sb: url = "https://www.hyatt.com/" sb.activate_cdp_mode(url) - sb.sleep(3.2) + sb.sleep(3.4) sb.click_if_visible('button[aria-label="Close"]') sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") @@ -200,11 +200,11 @@ with SB(uc=True, test=True, locale="en") as sb: sb.click('li[data-js="suggestion"]') sb.sleep(0.6) sb.click_if_visible('button[aria-label="Close"]') - sb.sleep(0.6) + sb.sleep(0.8) sb.click("button.be-button-shop") sb.sleep(1) sb.click_if_visible('[label="Find Hotels"]') - sb.sleep(5) + sb.sleep(5.5) card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' hotels = sb.select_all(card_info) print("Hyatt Hotels in %s:" % location) @@ -285,7 +285,7 @@ with SB(uc=True, test=True, ad_block=True) as sb: sb.sleep(1.8) continue_button = 'button:contains("Continue shopping")' if sb.is_element_visible(continue_button): - sb.cdp.gui_click_element(continue_button) + sb.gui_click_element(continue_button) sb.sleep(0.6) sb.click('input[aria-label="Search"]') sb.sleep(1.2) @@ -294,10 +294,10 @@ with SB(uc=True, test=True, ad_block=True) as sb: sb.press_keys('input[aria-label="Search"]', search + "\n") sb.sleep(3.8) if sb.is_element_visible("#px-captcha"): - sb.cdp.gui_click_and_hold("#px-captcha", 7.2) + sb.gui_click_and_hold("#px-captcha", 7.2) sb.sleep(4.2) if sb.is_element_visible("#px-captcha"): - sb.cdp.gui_click_and_hold("#px-captcha", 4.2) + sb.gui_click_and_hold("#px-captcha", 4.2) sb.sleep(3.2) sb.remove_elements('[data-testid="skyline-ad"]') sb.remove_elements('[data-testid="sba-container"]') diff --git a/examples/cdp_mode/raw_req_mod.py b/examples/cdp_mode/raw_req_mod.py index 1609f1e0c63..cc4bb0bdf6e 100644 --- a/examples/cdp_mode/raw_req_mod.py +++ b/examples/cdp_mode/raw_req_mod.py @@ -16,8 +16,8 @@ async def request_paused_handler(event, tab): with SB(uc=True, test=True, locale="en", pls="none") as sb: sb.activate_cdp_mode("about:blank") - sb.cdp.add_handler(mycdp.fetch.RequestPaused, request_paused_handler) - sb.cdp.open("https://gettyimages.com/photos/jonathan-frakes-cast-2022") + sb.add_handler(mycdp.fetch.RequestPaused, request_paused_handler) + sb.open("https://gettyimages.com/photos/jonathan-frakes-cast-2022") new_size = "--width:100;--height:100;" - sb.cdp.set_attributes('[style*="--width:"]', "style", new_size) + sb.set_attributes('[style*="--width:"]', "style", new_size) sb.sleep(6) diff --git a/examples/cdp_mode/raw_res_sb.py b/examples/cdp_mode/raw_res_sb.py index 9bf17d1a327..da2e29f67b0 100644 --- a/examples/cdp_mode/raw_res_sb.py +++ b/examples/cdp_mode/raw_res_sb.py @@ -27,8 +27,8 @@ async def receive_handler(event: mycdp.network.ResponseReceived): with SB(uc=True, test=True, locale="en") as sb: sb.activate_cdp_mode("about:blank") - sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler) - sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler) + sb.add_handler(mycdp.network.RequestWillBeSent, send_handler) + sb.add_handler(mycdp.network.ResponseReceived, receive_handler) url = "https://seleniumbase.io/apps/calculator" sb.open(url) sb.sleep(1)