From 5d8be67547a98d8eb111d1e37e690efa42000bf2 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 20:56:30 -0400 Subject: [PATCH 1/9] More Chromium updates --- seleniumbase/core/browser_launcher.py | 19 +++++++++ seleniumbase/core/detect_b_ver.py | 3 ++ seleniumbase/fixtures/base_case.py | 16 +++++--- seleniumbase/plugins/driver_manager.py | 6 ++- seleniumbase/plugins/pytest_plugin.py | 52 ++++++++++++++++++++++++- seleniumbase/plugins/sb_manager.py | 6 ++- seleniumbase/plugins/selenium_plugin.py | 6 ++- 7 files changed, 95 insertions(+), 13 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index c8336f73b1f..a31bfeff0c3 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -551,6 +551,19 @@ def uc_open_with_tab(driver, url): def uc_open_with_reconnect(driver, url, reconnect_time=None): """Open a url, disconnect chromedriver, wait, and reconnect.""" + if ( + hasattr(sb_config, "_cdp_browser") + and sb_config._cdp_browser in ["comet", "opera", "atlas"] + ): + if not __is_cdp_swap_needed(driver): + if not driver.current_url.startswith( + ("about", "data", "chrome") + ): + driver.get("about:blank") + uc_activate_cdp_mode(driver, url) + else: + driver.cdp.open(url) + return url = shared_utils.fix_url_as_needed(url) if __is_cdp_swap_needed(driver): driver.cdp.get(url) @@ -2979,12 +2992,16 @@ def get_driver( driver_dir = DRIVER_DIR_CHS if _special_binary_exists(binary_location, "opera"): driver_dir = DRIVER_DIR_OPERA + sb_config._cdp_browser = "opera" if _special_binary_exists(binary_location, "brave"): driver_dir = DRIVER_DIR_BRAVE + sb_config._cdp_browser = "brave" if _special_binary_exists(binary_location, "comet"): driver_dir = DRIVER_DIR_COMET + sb_config._cdp_browser = "comet" if _special_binary_exists(binary_location, "atlas"): driver_dir = DRIVER_DIR_ATLAS + sb_config._cdp_browser = "atlas" if ( hasattr(sb_config, "settings") and hasattr(sb_config.settings, "NEW_DRIVER_DIR") @@ -2997,6 +3014,8 @@ def get_driver( browser_name = browser else: browser_name = "chrome" # The default if not specified + if browser_name in constants.ChromiumSubs.chromium_subs: + browser_name = "chrome" browser_name = browser_name.lower() if headless2 and browser_name == constants.Browser.FIREFOX: headless2 = False # Only for Chromium diff --git a/seleniumbase/core/detect_b_ver.py b/seleniumbase/core/detect_b_ver.py index 1006c810b9a..58963886e38 100644 --- a/seleniumbase/core/detect_b_ver.py +++ b/seleniumbase/core/detect_b_ver.py @@ -278,6 +278,7 @@ def opera_on_windows_path(browser_type=None): ), ): for subitem in ( + "Programs/Opera", "Opera", "Opera/Application", ): @@ -336,6 +337,7 @@ def comet_on_windows_path(browser_type=None): ): for subitem in ( "Comet/Application", + "Programs/Comet", ): try: candidates.append(os.sep.join((item, subitem, "Comet.exe"))) @@ -364,6 +366,7 @@ def atlas_on_windows_path(browser_type=None): ): for subitem in ( "Atlas/Application", + "Programs/Atlas", ): try: candidates.append(os.sep.join((item, subitem, "Atlas.exe"))) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index fac0ff6cf4d..8665a2771ad 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -4354,7 +4354,7 @@ def get_new_driver( elif self.browser == "firefox": try: if self.maximize_option: - self.driver.maximize_window() + self.maximize_window() self.wait_for_ready_state_complete() else: with suppress(Exception): @@ -4364,7 +4364,7 @@ def get_new_driver( elif self.browser == "safari": if self.maximize_option: try: - self.driver.maximize_window() + self.maximize_window() self.wait_for_ready_state_complete() except Exception: pass # Keep existing browser resolution @@ -11408,7 +11408,13 @@ def __get_new_timeout(self, timeout): def __is_cdp_swap_needed(self): """If the driver is disconnected, use a CDP method when available.""" - return shared_utils.is_cdp_swap_needed(self.driver) + cdp_swap_needed = shared_utils.is_cdp_swap_needed(self.driver) + if cdp_swap_needed: + if not self.cdp: + self.cdp = self.driver.cdp + return True + else: + return False ############ @@ -14001,7 +14007,7 @@ def __click_with_offset( ) raise Exception(message) except InvalidArgumentException: - if not self.browser == "chrome": + if not self.is_chromium(): raise chrome_version = self.driver.capabilities["browserVersion"] major_chrome_version = chrome_version.split(".")[0] @@ -14616,7 +14622,7 @@ def __get_shadow_element( try: shadow_root = element.shadow_root except Exception: - if self.browser == "chrome": + if self.is_chromium(): chrome_dict = self.driver.capabilities["chrome"] chrome_dr_version = chrome_dict["chromedriverVersion"] chromedriver_version = chrome_dr_version.split(" ")[0] diff --git a/seleniumbase/plugins/driver_manager.py b/seleniumbase/plugins/driver_manager.py index 6eeee58ef77..9624766c4d9 100644 --- a/seleniumbase/plugins/driver_manager.py +++ b/seleniumbase/plugins/driver_manager.py @@ -455,8 +455,6 @@ def Driver( ) if sb_config._browser_shortcut: browser = sb_config._browser_shortcut - if browser in constants.ChromiumSubs.chromium_subs: - browser = "chrome" # Still uses chromedriver if headless is None: if "--headless" in sys_argv: headless = True @@ -953,6 +951,10 @@ def Driver( driver_version = None break count += 1 + if browser in constants.ChromiumSubs.chromium_subs: + if not binary_location: + browser = "chrome" # Still uses chromedriver + sb_config._browser_shortcut = browser browser_name = browser # Launch a web browser diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index 0a34b304a68..6ca77ccd923 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -1676,8 +1676,23 @@ def pytest_configure(config): sb_config.browser = config.getoption("browser") if sb_config._browser_shortcut: sb_config.browser = sb_config._browser_shortcut - if sb_config.browser in constants.ChromiumSubs.chromium_subs: - sb_config.browser = "chrome" # Still uses chromedriver + elif sys_argv == ["-c"]: # Multithreading messes with args + if config.getoption("use_opera"): + bin_loc = detect_b_ver.get_binary_location("opera") + if bin_loc and os.path.exists(bin_loc): + sb_config.browser = "opera" + elif config.getoption("use_brave"): + bin_loc = detect_b_ver.get_binary_location("brave") + if bin_loc and os.path.exists(bin_loc): + sb_config.browser = "brave" + elif config.getoption("use_comet"): + bin_loc = detect_b_ver.get_binary_location("comet") + if bin_loc and os.path.exists(bin_loc): + sb_config.browser = "comet" + elif config.getoption("use_atlas"): + bin_loc = detect_b_ver.get_binary_location("atlas") + if bin_loc and os.path.exists(bin_loc): + sb_config.browser = "atlas" sb_config.account = config.getoption("account") sb_config.data = config.getoption("data") sb_config.var1 = config.getoption("var1") @@ -1714,6 +1729,35 @@ def pytest_configure(config): sb_config.binary_location = config.getoption("binary_location") if hasattr(sb_config, "_cdp_bin_loc") and sb_config._cdp_bin_loc: sb_config.binary_location = sb_config._cdp_bin_loc + elif not sb_config.binary_location: + if ( + config.getoption("use_opera") + or sb_config._browser_shortcut == "opera" + ): + bin_loc = detect_b_ver.get_binary_location("opera") + if bin_loc and os.path.exists(bin_loc): + sb_config.binary_location = bin_loc + elif ( + config.getoption("use_brave") + or sb_config._browser_shortcut == "brave" + ): + bin_loc = detect_b_ver.get_binary_location("brave") + if bin_loc and os.path.exists(bin_loc): + sb_config.binary_location = bin_loc + elif ( + config.getoption("use_comet") + or sb_config._browser_shortcut == "comet" + ): + bin_loc = detect_b_ver.get_binary_location("comet") + if bin_loc and os.path.exists(bin_loc): + sb_config.binary_location = bin_loc + elif ( + config.getoption("use_atlas") + or sb_config._browser_shortcut == "atlas" + ): + bin_loc = detect_b_ver.get_binary_location("atlas") + if bin_loc and os.path.exists(bin_loc): + sb_config.binary_location = bin_loc if config.getoption("use_cft") and not sb_config.binary_location: sb_config.binary_location = "cft" elif config.getoption("use_chs") and not sb_config.binary_location: @@ -1726,6 +1770,10 @@ def pytest_configure(config): sb_config.headless = True sb_config.headless1 = False sb_config.headless2 = False + if sb_config.browser in constants.ChromiumSubs.chromium_subs: + if not sb_config.binary_location: + sb_config.browser = "chrome" # Still uses chromedriver + sb_config._browser_shortcut = sb_config.browser sb_config.driver_version = config.getoption("driver_version") sb_config.page_load_strategy = config.getoption("page_load_strategy") sb_config.with_testing_base = config.getoption("with_testing_base") diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index ad58dbbeb21..e276fe6a907 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -1132,8 +1132,6 @@ def SB( sb_config.browser = browser if sb_config._browser_shortcut: sb_config.browser = sb_config._browser_shortcut - if sb_config.browser in constants.ChromiumSubs.chromium_subs: - sb_config.browser = "chrome" # Still uses chromedriver if not hasattr(sb_config, "is_behave"): sb_config.is_behave = False if not hasattr(sb_config, "is_pytest"): @@ -1242,6 +1240,10 @@ def SB( sb_config.interval = interval sb_config.cap_file = cap_file sb_config.cap_string = cap_string + if sb_config.browser in constants.ChromiumSubs.chromium_subs: + if not sb_config.binary_location: + sb_config.browser = "chrome" # Still uses chromedriver + sb_config._browser_shortcut = sb_config.browser sb = BaseCase() sb.with_testing_base = sb_config.with_testing_base diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index 5df85ab9f8d..0c6a48b4825 100644 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -1302,8 +1302,6 @@ def beforeTest(self, test): if sb_config._browser_shortcut: self.options.browser = sb_config._browser_shortcut test.test.browser = sb_config._browser_shortcut - if test.test.browser in constants.ChromiumSubs.chromium_subs: - test.test.browser = "chrome" # Still uses chromedriver test.test.cap_file = self.options.cap_file test.test.cap_string = self.options.cap_string test.test.headless = self.options.headless @@ -1355,6 +1353,10 @@ def beforeTest(self, test): test.test.headless = True test.test.headless1 = False test.test.headless2 = False + if test.test.browser in constants.ChromiumSubs.chromium_subs: + if not sb_config.binary_location: + test.test.browser = "chrome" # Still uses chromedriver + sb_config._browser_shortcut = test.test.browser test.test.driver_version = self.options.driver_version test.test.page_load_strategy = self.options.page_load_strategy test.test.chromium_arg = self.options.chromium_arg From 42d5e11fd019f04b47877a80fa55e9dfa56f251a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 20:58:06 -0400 Subject: [PATCH 2/9] Update code for resizing windows --- seleniumbase/core/sb_cdp.py | 54 +++++++++++++++++------------- seleniumbase/fixtures/base_case.py | 16 +++++++-- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 9d364aab64d..40997aff38b 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1029,12 +1029,19 @@ def js_dumps(self, obj_name): return self.loop.run_until_complete(self.page.js_dumps(obj_name)) def maximize(self): - if self.get_window()[1].window_state.value == "maximized": - return - elif self.get_window()[1].window_state.value == "minimized": - self.loop.run_until_complete(self.page.maximize()) - time.sleep(0.044) - return self.loop.run_until_complete(self.page.maximize()) + try: + if self.get_window()[1].window_state.value == "maximized": + return + elif self.get_window()[1].window_state.value == "minimized": + self.loop.run_until_complete(self.page.maximize()) + time.sleep(0.044) + return self.loop.run_until_complete(self.page.maximize()) + except Exception: + with suppress(Exception): + width = self.evaluate("screen.availWidth;") + height = self.evaluate("screen.availHeight;") + self.__set_window_rect(0, 0, width, height) + return def minimize(self): if self.get_window()[1].window_state.value != "minimized": @@ -1725,18 +1732,18 @@ def gui_click_x_y(self, x, y, timeframe=0.25): win_x = window_rect["x"] win_y = window_rect["y"] scr_width = pyautogui.size().width - self.maximize() - self.__add_light_pause() - win_width = self.get_window_size()["width"] + win_width = self.evaluate("screen.availWidth;") width_ratio = round(float(scr_width) / float(win_width), 2) width_ratio += 0.01 if width_ratio < 0.45 or width_ratio > 2.55: width_ratio = 1.01 sb_config._saved_width_ratio = width_ratio - self.minimize() - self.__add_light_pause() - self.__set_window_rect(win_x, win_y, width, height) - self.__add_light_pause() + with suppress(Exception): + self.get_window() # If this fails, skip the rest + self.minimize() + self.__add_light_pause() + self.__set_window_rect(win_x, win_y, width, height) + self.__add_light_pause() x = x * (width_ratio + 0.03) y = y * (width_ratio - 0.03) self.bring_active_window_to_front() @@ -2021,18 +2028,18 @@ def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35): win_x = window_rect["x"] win_y = window_rect["y"] scr_width = pyautogui.size().width - self.maximize() - self.__add_light_pause() - win_width = self.get_window_size()["width"] + win_width = self.evaluate("screen.availWidth;") width_ratio = round(float(scr_width) / float(win_width), 2) width_ratio += 0.01 if width_ratio < 0.45 or width_ratio > 2.55: width_ratio = 1.01 sb_config._saved_width_ratio = width_ratio - self.minimize() - self.__add_light_pause() - self.__set_window_rect(win_x, win_y, width, height) - self.__add_light_pause() + with suppress(Exception): + self.get_window() # If this fails, skip the rest + self.minimize() + self.__add_light_pause() + self.__set_window_rect(win_x, win_y, width, height) + self.__add_light_pause() x1 = x1 * width_ratio y1 = y1 * (width_ratio - 0.02) x2 = x2 * width_ratio @@ -2114,15 +2121,14 @@ def gui_hover_x_y(self, x, y, timeframe=0.25): width_ratio = sb_config._saved_width_ratio else: scr_width = pyautogui.size().width - self.maximize() - self.__add_light_pause() - win_width = self.get_window_size()["width"] + win_width = self.evaluate("screen.availWidth;") width_ratio = round(float(scr_width) / float(win_width), 2) width_ratio += 0.01 if width_ratio < 0.45 or width_ratio > 2.55: width_ratio = 1.01 sb_config._saved_width_ratio = width_ratio - self.__set_window_rect(win_x, win_y, width, height) + with suppress(Exception): + self.__set_window_rect(win_x, win_y, width, height) self.__add_light_pause() self.bring_active_window_to_front() elif ( diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 8665a2771ad..c46a769c717 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -3577,9 +3577,18 @@ def maximize_window(self): self.cdp.maximize() return self._check_browser() - self.driver.maximize_window() + try: + self.driver.maximize_window() + except Exception: + with suppress(Exception): + width = self.execute_script("return screen.availWidth;") + height = self.execute_script("return screen.availHeight;") + self.set_window_rect(0, 0, width, height) self.__demo_mode_pause_if_active(tiny=True) + def maximize(self): + self.maximize_window() + def minimize_window(self): self.__check_scope() if self.__is_cdp_swap_needed(): @@ -3589,6 +3598,9 @@ def minimize_window(self): self.driver.minimize_window() self.__demo_mode_pause_if_active(tiny=True) + def minimize(self): + self.minimize_window() + def reset_window_size(self): self.__check_scope() if self.__is_cdp_swap_needed(): @@ -4344,7 +4356,7 @@ def get_new_driver( if self.is_chromium(): try: if self.maximize_option: - self.driver.maximize_window() + self.maximize_window() self.wait_for_ready_state_complete() else: pass # Now handled in browser_launcher.py From aa259bbc47b346c8e44561fa7a647b28b31eedad Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 20:59:22 -0400 Subject: [PATCH 3/9] Update scrolling methods --- seleniumbase/core/browser_launcher.py | 1 + seleniumbase/core/sb_cdp.py | 21 ++++++++++++++++++-- seleniumbase/fixtures/base_case.py | 28 +++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index a31bfeff0c3..9ff1e9c5370 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -898,6 +898,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): cdp.assert_not_in = CDPM.assert_not_in cdp.scroll_into_view = CDPM.scroll_into_view cdp.scroll_to_y = CDPM.scroll_to_y + cdp.scroll_by_y = CDPM.scroll_by_y cdp.scroll_to_top = CDPM.scroll_to_top cdp.scroll_to_bottom = CDPM.scroll_to_bottom cdp.scroll_up = CDPM.scroll_up diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 40997aff38b..e9fdfc33f96 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -2700,6 +2700,13 @@ def scroll_to_y(self, y): self.loop.run_until_complete(self.page.evaluate(js_code)) self.loop.run_until_complete(self.page.wait()) + def scroll_by_y(self, y): + y = int(y) + js_code = "window.scrollBy(0, %s);" % y + with suppress(Exception): + self.loop.run_until_complete(self.page.evaluate(js_code)) + self.loop.run_until_complete(self.page.wait()) + def scroll_to_top(self): js_code = "window.scrollTo(0, 0);" with suppress(Exception): @@ -2713,11 +2720,21 @@ def scroll_to_bottom(self): self.loop.run_until_complete(self.page.wait()) def scroll_up(self, amount=25): - self.loop.run_until_complete(self.page.scroll_up(amount)) + """Scrolls up as a percentage of the page.""" + try: + self.loop.run_until_complete(self.page.scroll_up(amount)) + except Exception: + amount = self.get_window_size()["height"] * amount / 100 + self.execute_script("window.scrollBy(0, -%s);" % amount) self.loop.run_until_complete(self.page.wait()) def scroll_down(self, amount=25): - self.loop.run_until_complete(self.page.scroll_down(amount)) + """Scrolls down as a percentage of the page.""" + try: + self.loop.run_until_complete(self.page.scroll_down(amount)) + except Exception: + amount = self.get_window_size()["height"] * amount / 100 + self.execute_script("window.scrollBy(0, %s);" % amount) self.loop.run_until_complete(self.page.wait()) def save_page_source(self, name, folder=None): diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index c46a769c717..938be66719e 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -6523,6 +6523,34 @@ def scroll_to_y(self, y): self.execute_script(scroll_script) time.sleep(0.012) + def scroll_by_y(self, y): + """Scrolls page by y pixels.""" + self.__check_scope() + y = int(y) + if self.__is_cdp_swap_needed(): + self.cdp.scroll_by_y(y) + return + scroll_script = "window.scrollBy(0, %s);" % y + with suppress(Exception): + self.execute_script(scroll_script) + time.sleep(0.012) + + def scroll_up(self, amount=25): + """Scrolls up as a percentage of the page.""" + if self.__is_cdp_swap_needed(): + self.cdp.scroll_up(amount) + return + amount = self.get_window_size()["height"] * amount / 100 + self.execute_script("window.scrollBy(0, -%s);" % amount) + + def scroll_down(self, amount=25): + """Scrolls down as a percentage of the page.""" + if self.__is_cdp_swap_needed(): + self.cdp.scroll_down(amount) + return + amount = self.get_window_size()["height"] * amount / 100 + self.execute_script("window.scrollBy(0, %s);" % amount) + def click_xpath(self, xpath): """Technically, self.click() automatically detects xpath selectors, so self.click_xpath() is just a longer name for the same action.""" From 2843434891987c66947b8d300c1a90358dee8b62 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 21:00:57 -0400 Subject: [PATCH 4/9] Fix the `save_page_source()` method --- seleniumbase/fixtures/page_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index 373d5d6571b..ac8a358c277 100644 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -1530,7 +1530,7 @@ def save_page_source(driver, name, folder=None): rendered_source = log_helper.get_html_source_with_base_href( driver, page_source ) - html_file = open(html_file_path, "w+", "utf-8") + html_file = open(html_file_path, mode="w+", encoding="utf-8") html_file.write(rendered_source) html_file.close() From 9db95268e7d4bcb90d8a697f24ba877f26c0a889 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 21:02:05 -0400 Subject: [PATCH 5/9] Update CDN for Highcharts --- seleniumbase/fixtures/constants.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py index 58f754e9658..75ad0ed9eff 100644 --- a/seleniumbase/fixtures/constants.py +++ b/seleniumbase/fixtures/constants.py @@ -264,16 +264,13 @@ class Reveal: class HighCharts: + LIB = "https://cdn.jsdelivr.net/npm/highcharts" VER = "10.3.3" - HC_CSS = "https://code.highcharts.com/%s/css/highcharts.css" % VER - HC_JS = "https://code.highcharts.com/%s/highcharts.js" % VER - EXPORTING_JS = "https://code.highcharts.com/%s/modules/exporting.js" % VER - EXPORT_DATA_JS = ( - "https://code.highcharts.com/%s/modules/export-data.js" % VER - ) - ACCESSIBILITY_JS = ( - "https://code.highcharts.com/%s/modules/accessibility.js" % VER - ) + HC_CSS = "%s@%s/css/highcharts.css" % (LIB, VER) + HC_JS = "%s@%s/highcharts.js" % (LIB, VER) + EXPORTING_JS = "%s@%s/modules/exporting.js" % (LIB, VER) + EXPORT_DATA_JS = "%s@%s/modules/export-data.js" % (LIB, VER) + ACCESSIBILITY_JS = "%s@%s/modules/accessibility.js" % (LIB, VER) class BootstrapTour: From b3047e6c34ff55a4602e2f231145251dffc79b5a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 21:03:25 -0400 Subject: [PATCH 6/9] Refresh Python dependencies --- requirements.txt | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9ea30632e58..80b4a0d2cdc 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ pip>=25.0.1;python_version<"3.9" -pip>=25.2;python_version>="3.9" +pip>=25.3;python_version>="3.9" packaging>=25.0 setuptools~=70.2;python_version<"3.10" setuptools>=80.9.0;python_version>="3.10" @@ -52,7 +52,7 @@ websocket-client~=1.8.0;python_version<"3.9" websocket-client~=1.9.0;python_version>="3.9" selenium==4.27.1;python_version<"3.9" selenium==4.32.0;python_version>="3.9" and python_version<"3.10" -selenium==4.37.0;python_version>="3.10" +selenium==4.38.0;python_version>="3.10" cssselect==1.2.0;python_version<"3.9" cssselect==1.3.0;python_version>="3.9" sortedcontainers==2.4.0 diff --git a/setup.py b/setup.py index 3cb0a08561d..93ef5b3c836 100755 --- a/setup.py +++ b/setup.py @@ -148,7 +148,7 @@ python_requires=">=3.8", install_requires=[ 'pip>=25.0.1;python_version<"3.9"', - 'pip>=25.2;python_version>="3.9"', + 'pip>=25.3;python_version>="3.9"', 'packaging>=25.0', 'setuptools~=70.2;python_version<"3.10"', # Newer ones had issues 'setuptools>=80.9.0;python_version>="3.10"', @@ -201,7 +201,7 @@ 'websocket-client~=1.9.0;python_version>="3.9"', 'selenium==4.27.1;python_version<"3.9"', 'selenium==4.32.0;python_version>="3.9" and python_version<"3.10"', - 'selenium==4.37.0;python_version>="3.10"', + 'selenium==4.38.0;python_version>="3.10"', 'cssselect==1.2.0;python_version<"3.9"', 'cssselect==1.3.0;python_version>="3.9"', "sortedcontainers==2.4.0", From dfb814c94d9f3e20d91eab04f8b17b1c88cc7e65 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 21:04:12 -0400 Subject: [PATCH 7/9] Update the docs --- examples/cdp_mode/ReadMe.md | 10 +++--- help_docs/method_summary.md | 72 +++++++++++++++---------------------- 2 files changed, 34 insertions(+), 48 deletions(-) diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 6b079c80a40..041446d8335 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -203,7 +203,7 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb: card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' hotels = sb.cdp.select_all(card_info) print("Hyatt Hotels in %s:" % location) - print("(" + sb.cdp.get_text("ul.b-color_text-white") + ")") + print("(" + sb.cdp.get_text('span[class*="summary_destination"]') + ")") if len(hotels) == 0: print("No availability over the selected dates!") for hotel in hotels: @@ -331,12 +331,13 @@ with SB(uc=True, test=True, locale="en", pls="none") as sb: url = "https://www.nike.com/" sb.activate_cdp_mode(url) sb.sleep(2.5) - sb.cdp.click('div[data-testid="user-tools-container"]') + sb.click('[data-testid="user-tools-container"] search') sb.sleep(1.5) search = "Nike Air Force 1" - sb.cdp.press_keys('input[type="search"]', search) + sb.press_keys('input[type="search"]', search) sb.sleep(4) - elements = sb.cdp.select_all('ul[data-testid*="products"] figure .details') + details = 'ul[data-testid*="products"] figure .details' + elements = sb.select_all(details) if elements: print('**** Found results for "%s": ****' % search) for element in elements: @@ -528,6 +529,7 @@ sb.cdp.assert_in(first, second) sb.cdp.assert_not_in(first, second) sb.cdp.scroll_into_view(selector) sb.cdp.scroll_to_y(y) +sb.cdp.scroll_by_y(y) sb.cdp.scroll_to_top() sb.cdp.scroll_to_bottom() sb.cdp.scroll_up(amount=25) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index 8777c451d22..477c8e938b2 100644 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -37,14 +37,12 @@ self.submit(selector, by="css selector") self.clear(selector, by="css selector", timeout=None) self.focus(selector, by="css selector", timeout=None) self.refresh() -# Duplicates: -# self.refresh_page(), self.reload_page(), self.reload() +# Duplicates: self.refresh_page(), self.reload_page(), self.reload() self.get_current_url() self.get_origin() self.get_page_source() self.get_title() -# Duplicates: -# self.get_page_title() +# Duplicates: self.get_page_title() self.get_user_agent() self.get_locale_code() self.go_back() @@ -159,7 +157,9 @@ self.set_window_rect(x, y, width, height) self.set_window_size(width, height) self.set_window_position(x, y) self.maximize_window() +# Duplicates: self.maximize() self.minimize_window() +# Duplicates: self.minimize() self.reset_window_size() self.switch_to_frame(frame="iframe", timeout=None, invisible=False) self.switch_to_default_content() @@ -168,23 +168,17 @@ with self.frame_switch(frame, timeout=None): # Indented Code Block for Context Manager (Must use "with") self.set_content_to_frame(frame, timeout=None) self.set_content_to_default(nested=False) -# Duplicates: -# self.set_content_to_default_content(nested=False) +# Duplicates: self.set_content_to_default_content(nested=False) self.set_content_to_parent() -# Duplicates: -# self.set_content_to_parent_frame() +# Duplicates: self.set_content_to_parent_frame() self.open_new_window(switch_to=True) -# Duplicates: -# self.open_new_tab(switch_to=True) +# Duplicates: self.open_new_tab(switch_to=True) self.switch_to_window(window, timeout=None) -# Duplicates: -# self.switch_to_tab(tab, timeout=None) +# Duplicates: self.switch_to_tab(tab, timeout=None) self.switch_to_default_window() -# Duplicates: -# self.switch_to_default_tab() +# Duplicates: self.switch_to_default_tab() self.switch_to_newest_window() -# Duplicates: -# self.switch_to_newest_tab() +# Duplicates: self.switch_to_newest_tab() self.get_new_driver( browser=None, headless=None, @@ -252,13 +246,11 @@ self.save_data_to_logs(data, file_name=None) self.append_data_to_logs(data, file_name=None) self.save_page_source_to_logs(name=None) self.save_page_source(name, folder=None) -# Duplicates: -# self.save_as_html(name, folder=None) +# Duplicates: self.save_as_html(name, folder=None) self.save_cookies(name="cookies.txt") self.load_cookies(name="cookies.txt", expiry=False) self.delete_all_cookies() -# Duplicates: -# self.clear_all_cookies() +# Duplicates: self.clear_all_cookies() self.delete_saved_cookies(name="cookies.txt") self.get_saved_cookies(name="cookies.txt") self.get_cookie(name) @@ -269,8 +261,7 @@ self.add_cookies(cookies, expiry=False) self.wait_for_ready_state_complete(timeout=None) self.wait_for_angularjs(timeout=None) self.sleep(seconds) -# Duplicates: -# self.wait(seconds) +# Duplicates: self.wait(seconds) self.install_addon(xpi_file) self.activate_jquery() self.activate_demo_mode() @@ -302,6 +293,10 @@ self.slow_scroll_to(selector, by="css selector", timeout=None) self.scroll_into_view(selector, by="css selector", timeout=None) self.scroll_to_top() self.scroll_to_bottom() +self.scroll_to_y(y) +self.scroll_by_y(y) +self.scroll_up(amount=25) +self.scroll_down(amount=25) self.click_xpath(xpath) self.js_click(selector, by="css selector", all_matches=False, timeout=None, scroll=True) self.js_click_if_present(selector, by="css selector", timeout=0) @@ -316,8 +311,7 @@ self.show_elements(selector, by="css selector") self.remove_element(selector, by="css selector") self.remove_elements(selector, by="css selector") self.ad_block() -# Duplicates: -# self.block_ads() +# Duplicates: self.block_ads() self.show_file_choosers() self.disable_beforeunload() self.get_domain_url(url) @@ -345,8 +339,7 @@ self.save_data_as(data, file_name, destination_folder=None) self.append_data_to_file(data, file_name, destination_folder=None) self.get_file_data(file_name, folder=None) self.print_to_pdf(name, folder=None) -# Duplicates: -# self.save_as_pdf(name, folder=None) +# Duplicates: self.save_as_pdf(name, folder=None) self.get_downloads_folder() self.get_browser_downloads_folder() self.get_downloaded_files(regex=None, browser=False) @@ -355,8 +348,7 @@ self.get_data_from_downloaded_file(file, timeout=None, browser=False) self.is_downloaded_file_present(file, browser=False) self.is_downloaded_file_regex_present(regex, browser=False) self.delete_downloaded_file_if_present(file, browser=False) -# Duplicates: -# self.delete_downloaded_file(file, browser=False) +# Duplicates: self.delete_downloaded_file(file, browser=False) self.assert_downloaded_file(file, timeout=None, browser=False) self.assert_downloaded_file_regex(regex, timeout=None, browser=False) self.assert_data_in_downloaded_file(data, file, timeout=None, browser=False) @@ -423,16 +415,14 @@ self.set_local_storage_item(key, value) self.get_local_storage_item(key) self.remove_local_storage_item(key) self.clear_local_storage() -# Duplicates: -# self.delete_local_storage() +# Duplicates: self.delete_local_storage() self.get_local_storage_keys() self.get_local_storage_items() self.set_session_storage_item(key, value) self.get_session_storage_item(key) self.remove_session_storage_item(key) self.clear_session_storage() -# Duplicates: -# self.delete_session_storage() +# Duplicates: self.delete_session_storage() self.get_session_storage_keys() self.get_session_storage_items() @@ -496,8 +486,7 @@ self.create_introjs_tour(name=None) self.set_introjs_colors(theme_color=None, hover_color=None) self.add_tour_step(message, selector=None, name=None, title=None, theme=None, alignment=None) self.play_tour(name=None, interval=0) -# Duplicates: -# self.start_tour(name=None, interval=0): +# Duplicates: self.start_tour(name=None, interval=0): self.export_tour(name=None, filename="my_tour.js", url=None) ############ @@ -584,8 +573,7 @@ self.find_link_text(link_text, timeout=None) # self.wait_for_link_text(link_text, timeout=None) # self.wait_for_link_text_visible(link_text, timeout=None) self.assert_link_text(link_text, timeout=None) -# Duplicates: -# self.assert_link(link_text, timeout=None) +# Duplicates: self.assert_link(link_text, timeout=None) ############ @@ -630,14 +618,11 @@ self.assert_attribute_not_present( ############ self.accept_alert(timeout=None) -# Duplicates: -# self.wait_for_and_accept_alert(timeout=None) +# Duplicates: self.wait_for_and_accept_alert(timeout=None) self.dismiss_alert(timeout=None) -# Duplicates: -# self.wait_for_and_dismiss_alert(timeout=None) +# Duplicates: self.wait_for_and_dismiss_alert(timeout=None) self.switch_to_alert(timeout=None) -# Duplicates: -# self.wait_for_and_switch_to_alert(timeout=None) +# Duplicates: self.wait_for_and_switch_to_alert(timeout=None) ############ @@ -678,8 +663,7 @@ self.deferred_check_window( # name="default", level=0, baseline=False, # check_domain=True, full_diff=False, fs=False) self.process_deferred_asserts(print_only=False) -# Duplicates: -# self.process_delayed_asserts(print_only=False) +# Duplicates: self.process_delayed_asserts(print_only=False) ############ From 0f98dbf09af99ff8bfd12e3190b161c3cd4cc2a3 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 21:05:26 -0400 Subject: [PATCH 8/9] Update examples --- examples/cdp_mode/raw_cdp_copilot.py | 3 ++- examples/cdp_mode/raw_cdp_kohls.py | 30 +++++++++++++++------- examples/cdp_mode/raw_cdp_nike.py | 6 +++-- examples/cdp_mode/raw_copilot.py | 3 ++- examples/cdp_mode/raw_form_turnstile.py | 20 +++++++++++++++ examples/cdp_mode/raw_hyatt.py | 2 +- examples/cdp_mode/raw_kohls.py | 28 +++++++++++++++------ examples/cdp_mode/raw_nike.py | 7 +++--- examples/cdp_mode/raw_res_nike.py | 2 +- examples/locale_code_test.py | 3 ++- examples/presenter/uc_presentation_4.py | 14 +++++------ examples/raw_form_turnstile.py | 2 +- examples/raw_gitlab.py | 11 +++++++++ examples/raw_gui_click.py | 33 ++++++++++++++----------- examples/raw_pyautogui.py | 18 ++++---------- examples/test_hack_search.py | 5 ++-- examples/test_todomvc.py | 9 +++++++ examples/test_window_switching.py | 4 ++- 18 files changed, 133 insertions(+), 67 deletions(-) create mode 100644 examples/cdp_mode/raw_form_turnstile.py create mode 100644 examples/raw_gitlab.py diff --git a/examples/cdp_mode/raw_cdp_copilot.py b/examples/cdp_mode/raw_cdp_copilot.py index 2c59657ec33..555f5458faf 100644 --- a/examples/cdp_mode/raw_cdp_copilot.py +++ b/examples/cdp_mode/raw_cdp_copilot.py @@ -25,7 +25,8 @@ sb.wait_for_element_absent(stop_button, timeout=50) sb.wait_for_element(thumbs_up, timeout=20) sb.sleep(0.6) -sb.click('button[data-testid*="scroll-to-bottom"]') +scroll = 'button[data-testid*="scroll-to-bottom"]' +sb.click_if_visible(scroll) sb.sleep(2.2) folder = "downloaded_files" file_name = "copilot_results.html" diff --git a/examples/cdp_mode/raw_cdp_kohls.py b/examples/cdp_mode/raw_cdp_kohls.py index 3ffbe78d88b..290eed5054b 100644 --- a/examples/cdp_mode/raw_cdp_kohls.py +++ b/examples/cdp_mode/raw_cdp_kohls.py @@ -1,25 +1,37 @@ from seleniumbase import sb_cdp url = "https://www.kohls.com/" -sb = sb_cdp.Chrome(url, locale="en", guest=True) +sb = sb_cdp.Chrome(url, locale="en", ad_block=True) sb.sleep(2.8) -search = "Mickey Mouse 100 friends teal pillow" -required_text = "Mickey" +search = "Mickey Mouse Blanket" +req_1 = "Mickey" +req_2 = "Blanket" sb.press_keys('input[name="search"]', search + "\n") sb.sleep(5) -for item in sb.find_elements("div.products-container-right"): +item_selector = 'div[data-testid*="wallet-wrapper"]' +if not sb.is_element_present(item_selector): + item_selector = "li.products_grid" +for item in sb.find_elements(item_selector): if "Sponsored" in item.text: item.remove_from_dom() sb.remove_elements("#tce-sticky-wrapper") sb.remove_elements("li.sponsored-product") sb.remove_elements("#tce-dec-ces-3-banner") print('*** Kohls Search for "%s":' % search) -for item in sb.find_elements("ul.products a img"): +print(' (Results must contain "%s" and "%s".)' % (req_1, req_2)) +title_selector = "div.prod_nameBlock p" +if not sb.is_element_present(title_selector): + title_selector = 'a[class*="sm:text"][href*="/product/"]' +for item in sb.find_elements(title_selector): if item: item.flash(color="44CC88") - title = item.get_attribute("title") - if title and required_text in title: - print("* " + title) - sb.sleep(0.1) + title = item.text + if title: + if ( + req_1.lower() in title.lower() + and req_2.lower() in title.lower() + ): + print("* " + title) + sb.sleep(0.1) sb.sleep(1) sb.driver.stop() diff --git a/examples/cdp_mode/raw_cdp_nike.py b/examples/cdp_mode/raw_cdp_nike.py index 2f19a88f2f8..2bdb3ec9739 100644 --- a/examples/cdp_mode/raw_cdp_nike.py +++ b/examples/cdp_mode/raw_cdp_nike.py @@ -2,12 +2,14 @@ url = "https://www.nike.com/" sb = sb_cdp.Chrome(url) -sb.click('div[data-testid="user-tools-container"]') +sb.sleep(1.2) +sb.click('[data-testid="user-tools-container"] search') sb.sleep(1) search = "Pegasus" sb.press_keys('input[type="search"]', search) sb.sleep(4) -elements = sb.select_all('ul[data-testid*="products"] figure .details') +details = 'ul[data-testid*="products"] figure .details' +elements = sb.select_all(details) if elements: print('**** Found results for "%s": ****' % search) for element in elements: diff --git a/examples/cdp_mode/raw_copilot.py b/examples/cdp_mode/raw_copilot.py index 535cc07d118..ebcaffe19d5 100644 --- a/examples/cdp_mode/raw_copilot.py +++ b/examples/cdp_mode/raw_copilot.py @@ -26,7 +26,8 @@ sb.wait_for_element_absent(stop_button, timeout=50) sb.wait_for_element(thumbs_up, timeout=20) sb.sleep(0.6) - sb.click('button[data-testid*="scroll-to-bottom"]') + scroll = 'button[data-testid*="scroll-to-bottom"]' + sb.click_if_visible(scroll) sb.sleep(2.2) folder = "downloaded_files" file_name = "copilot_results.html" diff --git a/examples/cdp_mode/raw_form_turnstile.py b/examples/cdp_mode/raw_form_turnstile.py new file mode 100644 index 00000000000..8ac6120b4fe --- /dev/null +++ b/examples/cdp_mode/raw_form_turnstile.py @@ -0,0 +1,20 @@ +from seleniumbase import SB + +with SB(uc=True, test=True) as sb: + url = "seleniumbase.io/apps/form_turnstile" + sb.activate_cdp_mode(url) + sb.press_keys("#name", "SeleniumBase") + sb.press_keys("#email", "test@test.test") + sb.press_keys("#phone", "1-555-555-5555") + sb.click('[for="date"]') + sb.click("td.is-today button") + sb.click('div[class="select-wrapper"] input') + sb.click('span:contains("9:00 PM")') + sb.highlight_click('input[value="AR"] + span') + sb.click('input[value="cc"] + span') + sb.scroll_down(40) + sb.uc_gui_click_captcha() + sb.highlight("img#captcha-success", timeout=3) + sb.highlight_click('button:contains("Request & Pay")') + sb.highlight("img#submit-success") + sb.highlight('button:contains("Success!")') diff --git a/examples/cdp_mode/raw_hyatt.py b/examples/cdp_mode/raw_hyatt.py index fff0b4e8570..ce5e7e99a5e 100644 --- a/examples/cdp_mode/raw_hyatt.py +++ b/examples/cdp_mode/raw_hyatt.py @@ -17,7 +17,7 @@ card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' hotels = sb.cdp.select_all(card_info) print("Hyatt Hotels in %s:" % location) - print("(" + sb.cdp.get_text("ul.b-color_text-white") + ")") + print("(" + sb.cdp.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_kohls.py b/examples/cdp_mode/raw_kohls.py index 1b57bf427b8..d5d4b11a7c0 100644 --- a/examples/cdp_mode/raw_kohls.py +++ b/examples/cdp_mode/raw_kohls.py @@ -4,22 +4,34 @@ url = "https://www.kohls.com/" sb.activate_cdp_mode(url) sb.sleep(2.6) - search = "Mickey Mouse 100 friends teal pillow" - required_text = "Mickey" + search = "Mickey Mouse Blanket" + req_1 = "Mickey" + req_2 = "Blanket" sb.cdp.press_keys('input[name="search"]', search + "\n") sb.sleep(5) - for item in sb.cdp.find_elements("div.products-container-right"): + item_selector = 'div[data-testid*="wallet-wrapper"]' + if not sb.is_element_present(item_selector): + item_selector = "li.products_grid" + for item in sb.cdp.find_elements(item_selector): if "Sponsored" in item.text: item.remove_from_dom() sb.cdp.remove_elements("#tce-sticky-wrapper") sb.cdp.remove_elements("li.sponsored-product") sb.cdp.remove_elements("#tce-dec-ces-3-banner") print('*** Kohls Search for "%s":' % search) - for item in sb.cdp.find_elements("ul.products a img"): + print(' (Results must contain "%s" and "%s".)' % (req_1, req_2)) + title_selector = "div.prod_nameBlock p" + if not sb.is_element_present(title_selector): + title_selector = 'a[class*="sm:text"][href*="/product/"]' + for item in sb.cdp.find_elements(title_selector): if item: item.flash(color="44CC88") - title = item.get_attribute("title") - if title and required_text in title: - print("* " + title) - sb.sleep(0.1) + title = item.text + if title: + if ( + req_1.lower() in title.lower() + and req_2.lower() in title.lower() + ): + print("* " + title) + sb.sleep(0.1) sb.sleep(1) diff --git a/examples/cdp_mode/raw_nike.py b/examples/cdp_mode/raw_nike.py index 7e28e2fcede..770a13424c2 100644 --- a/examples/cdp_mode/raw_nike.py +++ b/examples/cdp_mode/raw_nike.py @@ -4,12 +4,13 @@ url = "https://www.nike.com/" sb.activate_cdp_mode(url) sb.sleep(2.5) - sb.cdp.click('div[data-testid="user-tools-container"]') + sb.click('[data-testid="user-tools-container"] search') sb.sleep(1.5) search = "Nike Air Force 1" - sb.cdp.press_keys('input[type="search"]', search) + sb.press_keys('input[type="search"]', search) sb.sleep(4) - elements = sb.cdp.select_all('ul[data-testid*="products"] figure .details') + details = 'ul[data-testid*="products"] figure .details' + elements = sb.select_all(details) if elements: print('**** Found results for "%s": ****' % search) for element in elements: diff --git a/examples/cdp_mode/raw_res_nike.py b/examples/cdp_mode/raw_res_nike.py index f4000c0e917..06dcbdaca6a 100644 --- a/examples/cdp_mode/raw_res_nike.py +++ b/examples/cdp_mode/raw_res_nike.py @@ -31,7 +31,7 @@ async def receive_handler(event: mycdp.network.ResponseReceived): sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler) sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler) sb.sleep(2.5) - sb.cdp.click('div[data-testid="user-tools-container"]') + sb.cdp.click('[data-testid="user-tools-container"] search') sb.sleep(1.5) search = "Nike Air Force 1" sb.cdp.press_keys('input[type="search"]', search) diff --git a/examples/locale_code_test.py b/examples/locale_code_test.py index 64d6fc6f615..aea662d8713 100644 --- a/examples/locale_code_test.py +++ b/examples/locale_code_test.py @@ -12,8 +12,9 @@ def test_locale_code(self): language_info = self.get_text( "settings-ui::shadow " "settings-main::shadow " - "settings-basic-page::shadow " + "settings-languages-page-index::shadow " "settings-languages-page::shadow " + "settings-section " "#languagesSection div.start div" ) print("Language info (chrome://settings/languages):") diff --git a/examples/presenter/uc_presentation_4.py b/examples/presenter/uc_presentation_4.py index e16ed4e050e..9adc9bbcf48 100644 --- a/examples/presenter/uc_presentation_4.py +++ b/examples/presenter/uc_presentation_4.py @@ -691,8 +691,9 @@ def test_presentation_4(self): 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' ) hotels = sb.cdp.select_all(card_info) + destination_selector = 'span[class*="summary_destination"]' print("Hyatt Hotels in %s:" % location) - print("(" + sb.cdp.get_text("ul.b-color_text-white") + ")") + print("(" + sb.cdp.get_text(destination_selector) + ")") if len(hotels) == 0: print("No availability over the selected dates!") for hotel in hotels: @@ -833,16 +834,15 @@ def test_presentation_4(self): url = "https://www.nike.com/" sb.activate_cdp_mode(url) sb.sleep(2.5) - sb.cdp.click('div[data-testid="user-tools-container"]') + sb.click('[data-testid="user-tools-container"] search') sb.sleep(1.5) search = "Nike Air Force 1" - sb.cdp.press_keys('input[type="search"]', search) + sb.press_keys('input[type="search"]', search) sb.sleep(4) - elements = sb.cdp.select_all( - 'ul[data-testid*="products"] figure .details' - ) + details = 'ul[data-testid*="products"] figure .details' + elements = sb.select_all(details) if elements: - print('\n\n**** Found results for "%s": ****' % search) + print('**** Found results for "%s": ****' % search) for element in elements: print("* " + element.text) sb.sleep(2) diff --git a/examples/raw_form_turnstile.py b/examples/raw_form_turnstile.py index 0ff1be0fa52..7af920545bd 100644 --- a/examples/raw_form_turnstile.py +++ b/examples/raw_form_turnstile.py @@ -2,7 +2,7 @@ with SB(uc=True, test=True) as sb: url = "seleniumbase.io/apps/form_turnstile" - sb.uc_open_with_reconnect(url, 2) + sb.uc_open_with_reconnect(url, 1.1) sb.press_keys("#name", "SeleniumBase") sb.press_keys("#email", "test@test.test") sb.press_keys("#phone", "1-555-555-5555") diff --git a/examples/raw_gitlab.py b/examples/raw_gitlab.py new file mode 100644 index 00000000000..731bf79d962 --- /dev/null +++ b/examples/raw_gitlab.py @@ -0,0 +1,11 @@ +from seleniumbase import SB + +with SB(uc=True, test=True) as sb: + url = "https://gitlab.com/users/sign_in" + sb.uc_open_with_reconnect(url) + sb.uc_gui_click_captcha() # Only if needed + sb.assert_element('label[for="user_login"]') + sb.assert_element('input[data-testid*="username"]') + sb.assert_element('input[data-testid*="password"]') + sb.set_messenger_theme(location="bottom_center") + sb.post_message("SeleniumBase wasn't detected!") diff --git a/examples/raw_gui_click.py b/examples/raw_gui_click.py index 614aada2345..a56a091c0bb 100644 --- a/examples/raw_gui_click.py +++ b/examples/raw_gui_click.py @@ -1,17 +1,20 @@ -import sys from seleniumbase import SB -# An bad UserAgent forces CAPTCHA-solving on macOS -agent = "cool" -if "linux" in sys.platform or "win32" in sys.platform: - agent = None # Use the default UserAgent - -with SB(uc=True, test=True, rtf=True, agent=agent) as sb: - url = "https://gitlab.com/users/sign_in" - sb.uc_open_with_reconnect(url) - sb.uc_gui_click_captcha() # Only if needed - sb.assert_element('label[for="user_login"]') - sb.assert_element('input[data-testid*="username"]') - sb.assert_element('input[data-testid*="password"]') - sb.set_messenger_theme(location="bottom_center") - sb.post_message("SeleniumBase wasn't detected!") +with SB(uc=True, test=True) as sb: + url = "seleniumbase.io/apps/form_turnstile" + sb.uc_open_with_reconnect(url, 1.1) + sb.press_keys("#name", "SeleniumBase") + sb.press_keys("#email", "test@test.test") + sb.press_keys("#phone", "1-555-555-5555") + sb.click('[for="date"]') + sb.click("td.is-today button") + sb.click('div[class="select-wrapper"] input') + sb.click('span:contains("9:00 PM")') + sb.highlight_click('input[value="AR"] + span') + sb.click('input[value="cc"] + span') + sb.scroll_to('div[class*="cf-turnstile"]') + sb.uc_gui_click_captcha() + sb.highlight("img#captcha-success", timeout=3) + sb.highlight_click('button:contains("Request & Pay")') + sb.highlight("img#submit-success") + sb.highlight('button:contains("Success!")') diff --git a/examples/raw_pyautogui.py b/examples/raw_pyautogui.py index 5e3bca507ae..d77aa257e7c 100644 --- a/examples/raw_pyautogui.py +++ b/examples/raw_pyautogui.py @@ -1,17 +1,9 @@ -import sys from seleniumbase import SB -# An bad UserAgent forces CAPTCHA-solving on macOS -agent = "cool" -if "linux" in sys.platform or "win32" in sys.platform: - agent = None # Use the default UserAgent - -with SB(uc=True, test=True, rtf=True, agent=agent) as sb: - url = "https://gitlab.com/users/sign_in" +with SB(uc=True, test=True) as sb: + url = "https://seleniumbase.io/apps/turnstile" sb.uc_open_with_reconnect(url) sb.uc_gui_handle_captcha() # Only if needed - sb.assert_element('label[for="user_login"]') - sb.assert_element('input[data-testid*="username"]') - sb.assert_element('input[data-testid*="password"]') - sb.set_messenger_theme(location="bottom_center") - sb.post_message("SeleniumBase wasn't detected!") + 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/test_hack_search.py b/examples/test_hack_search.py index 9f27e4c480d..5c8c5b990d8 100644 --- a/examples/test_hack_search.py +++ b/examples/test_hack_search.py @@ -8,10 +8,9 @@ class HackingTests(BaseCase): def test_hack_search(self): - if self.headless: + if self.headless or self.browser != "chrome": self.open_if_not_url("about:blank") - print("\n Skipping test in headless mode.") - self.skip('Skipping test in headless mode.') + self.skip('Skip test if headless or not chrome.') if not self.undetectable: self.get_new_driver(undetectable=True) self.open("https://google.com/ncr") diff --git a/examples/test_todomvc.py b/examples/test_todomvc.py index 1ed61ca16c3..9acb6c8f77f 100644 --- a/examples/test_todomvc.py +++ b/examples/test_todomvc.py @@ -4,6 +4,15 @@ class TodoMVC(BaseCase): + def setUp(self): + super().setUp() + self.open_new_window() + + def tearDown(self): + self.save_teardown_screenshot() + self.driver.close() + super().tearDown() + @parameterized.expand([["jquery"], ["react"], ["vue"]]) def test_todomvc(self, framework): self.open("https://todomvc.com/") diff --git a/examples/test_window_switching.py b/examples/test_window_switching.py index 9b698641dad..19fcc556bc4 100644 --- a/examples/test_window_switching.py +++ b/examples/test_window_switching.py @@ -9,6 +9,8 @@ class TabSwitchingTests(BaseCase): def test_switch_to_tabs(self): + self.open("about:blank") + self.get_new_driver() self.open("data:text/html,

Page A

") self.assert_text("Page A") self.open_new_window() @@ -17,6 +19,6 @@ def test_switch_to_tabs(self): self.switch_to_window(0) self.assert_text("Page A") self.assert_text_not_visible("Page B") - self.switch_to_window(1) + self.switch_to_window(-1) self.assert_text("Page B") self.assert_text_not_visible("Page A") From 2012b91fa6c9743228695f673aadac28b525e3ba Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Oct 2025 21:07:35 -0400 Subject: [PATCH 9/9] Version 4.44.0 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 276ca19001c..edaa71ccb98 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.43.3" +__version__ = "4.44.0"