From c09c39f8bfb1d95548bc88a46df0882263f61428 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Tue, 25 Nov 2025 17:28:04 -0500 Subject: [PATCH 1/4] Update CDP Mode --- seleniumbase/core/browser_launcher.py | 21 ++++---- seleniumbase/core/sb_cdp.py | 50 ++++++++++++++++--- seleniumbase/fixtures/base_case.py | 25 +++------- seleniumbase/fixtures/page_actions.py | 26 +++------- seleniumbase/fixtures/shared_utils.py | 3 +- seleniumbase/undetected/__init__.py | 21 ++++---- seleniumbase/undetected/cdp_driver/browser.py | 38 +++++--------- .../undetected/cdp_driver/cdp_util.py | 2 + seleniumbase/undetected/cdp_driver/tab.py | 10 +++- 9 files changed, 105 insertions(+), 91 deletions(-) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 978f21fe45d..432b224fd3a 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -443,6 +443,7 @@ def has_captcha(text): "403 Forbidden" in text or "Permission Denied" in text or 'id="challenge-error-text"' in text + or "/challenge-platform/h/b/" in text or "Just a moment..." in text or 'action="/?__cf_chl_f_tk' in text or 'id="challenge-widget-' in text @@ -450,7 +451,6 @@ def has_captcha(text): or 'class="g-recaptcha"' in text or 'content="Pixelscan"' in text or 'id="challenge-form"' in text - or "/challenge-platform" in text or "window._cf_chl_opt" in text or "/recaptcha/api.js" in text or "/turnstile/" in text @@ -656,10 +656,8 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): safe_url = False if ( - hasattr(driver, "_is_using_cdp") - and driver._is_using_cdp - and hasattr(driver, "cdp") - and driver.cdp + getattr(driver, "_is_using_cdp", None) + and getattr(driver, "cdp", None) and hasattr(driver.cdp, "loop") ): # CDP Mode was already initialized @@ -1042,7 +1040,7 @@ def uc_click( def verify_pyautogui_has_a_headed_browser(driver): """PyAutoGUI requires a headed browser so that it can focus on the correct element when performing actions.""" - if hasattr(driver, "_is_hidden") and driver._is_hidden: + if getattr(driver, "_is_hidden", None): raise Exception( "PyAutoGUI can't be used in headless mode!" ) @@ -1142,8 +1140,7 @@ def get_configured_pyautogui(pyautogui_copy): and "DISPLAY" in os.environ.keys() ): if ( - hasattr(sb_config, "_pyautogui_x11_display") - and sb_config._pyautogui_x11_display + getattr(sb_config, "_pyautogui_x11_display", None) and hasattr(pyautogui_copy._pyautogui_x11, "_display") and ( sb_config._pyautogui_x11_display @@ -1300,8 +1297,12 @@ def uc_gui_click_x_y(driver, x, y, timeframe=0.25): def _on_a_cf_turnstile_page(driver): source = driver.get_page_source() if ( - 'data-callback="onCaptchaSuccess"' in source - or "/challenge-platform/scripts/" in source + ( + 'data-callback="onCaptchaSuccess"' in source + and 'title="reCAPTCHA"' not in source + and 'id="recaptcha-token"' not in source + ) + or "/challenge-platform/h/b/" in source or 'id="challenge-widget-' in source or "challenges.cloudf" in source or "cf-turnstile-" in source diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 4731559950d..66cedf1f29c 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1149,7 +1149,42 @@ def open_new_tab(self, url=None, switch_to=True): if not isinstance(url, str): url = "about:blank" if hasattr(driver, "cdp_base"): - self.loop.run_until_complete(self.page.get(url, new_tab=True)) + try: + self.loop.run_until_complete(self.page.get(url, new_tab=True)) + except Exception: + original_targets = self.loop.run_until_complete( + self.page.send(mycdp.target.get_targets()) + ) + tab_url = driver.cdp_base.tabs[0].websocket_url + if not self.driver.is_connected(): + self.driver.connect() + self.driver.open_new_tab() + targets = self.loop.run_until_complete( + self.page.send(mycdp.target.get_targets()) + ) + new_targets = [] + for target in targets: + if target not in original_targets: + new_targets.append(target) + if new_targets: + found_target = new_targets[0] + t_str = str(new_targets[0]) + target_id = ( + t_str.split("target_id=TargetID('")[-1].split("')")[0] + ) + pre_tab_url = tab_url.split("/page/")[0] + "/page/" + new_tab_url = pre_tab_url + target_id + new_tab = cdp_tab.Tab( + new_tab_url, found_target, driver.cdp_base + ) + driver.cdp_base.targets.append(new_tab) + driver.cdp_base.tabs.append(new_tab) + self.driver.disconnect() + self.switch_to_newest_tab() + self.open(url) + return + elif getattr(sb_config, "guest_mode", None): + print(" open_new_tab() failed! (Known Guest Mode issue)") if switch_to: self.switch_to_newest_tab() return @@ -1157,14 +1192,17 @@ def open_new_tab(self, url=None, switch_to=True): target_id = self.loop.run_until_complete( self.page.send(mycdp.target.create_target(url)) ) + if not target_id and getattr(sb_config, "guest_mode", None): + print(" open_new_tab() failed! (Known Guest Mode issue)") found_target = None targets = self.loop.run_until_complete( self.page.send(mycdp.target.get_targets()) ) - for target in targets: - if str(target_id) in str(target): - found_target = target - break + if target_id: + for target in targets: + if str(target_id) in str(target): + found_target = target + break if found_target: tab_url = driver.tabs[0].websocket_url pre_tab_url = tab_url.split("/page/")[0] + "/page/" @@ -1875,7 +1913,7 @@ def _on_a_cf_turnstile_page(self, source=None): and 'title="reCAPTCHA"' not in source and 'id="recaptcha-token"' not in source ) - or "/challenge-platform/scripts/" in source + or "/challenge-platform/h/b/" in source or 'id="challenge-widget-' in source or "challenges.cloudf" in source or "cf-turnstile-" in source diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 8e1a2852e54..579c10d2ced 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -228,14 +228,9 @@ def open(self, url): self.cdp.open(url) return elif ( - hasattr(self.driver, "_is_using_uc") - and self.driver._is_using_uc - # and hasattr(self.driver, "_is_using_auth") - # and self.driver._is_using_auth - and ( - not hasattr(self.driver, "_is_using_cdp") - or not self.driver._is_using_cdp - ) + getattr(self.driver, "_is_using_uc", None) + # and getattr(self.driver, "_is_using_auth", None) + and not getattr(self.driver, "_is_using_cdp", None) ): # Auth in UC Mode requires CDP Mode # (and now we're always forcing it) @@ -243,10 +238,8 @@ def open(self, url): self.activate_cdp_mode(url) return elif ( - hasattr(self.driver, "_is_using_uc") - and self.driver._is_using_uc - and hasattr(self.driver, "_is_using_cdp") - and self.driver._is_using_cdp + getattr(self.driver, "_is_using_uc", None) + and getattr(self.driver, "_is_using_cdp", None) ): self.disconnect() self.cdp.open(url) @@ -3979,10 +3972,8 @@ def open_new_window(self, switch_to=True): self.cdp.open_new_tab(url=url, switch_to=switch_to) return elif ( - hasattr(self.driver, "_is_using_uc") - and self.driver._is_using_uc - and hasattr(self.driver, "_is_using_cdp") - and self.driver._is_using_cdp + getattr(self.driver, "_is_using_uc", None) + and getattr(self.driver, "_is_using_cdp", None) ): self.disconnect() self.cdp.open_new_tab(url=url, switch_to=switch_to) @@ -5037,7 +5028,7 @@ def deactivate_design_mode(self, url=None): def activate_cdp_mode(self, url=None, **kwargs): """Activate CDP Mode with the URL and kwargs.""" - if hasattr(self.driver, "_is_using_uc") and self.driver._is_using_uc: + if getattr(self.driver, "_is_using_uc", None): if self.__is_cdp_swap_needed(): return # CDP Mode is already active if not self.is_connected(): diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index dd3606d8d50..25632c1732b 100644 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -1646,11 +1646,7 @@ def switch_to_frame( def __switch_to_window(driver, window_handle, uc_lock=True): - if ( - hasattr(driver, "_is_using_uc") - and driver._is_using_uc - and uc_lock - ): + if getattr(driver, "_is_using_uc", None) and uc_lock: gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: driver.switch_to.window(window_handle) @@ -1734,8 +1730,7 @@ def switch_to_window( def _reconnect_if_disconnected(driver): if ( - hasattr(driver, "_is_using_uc") - and driver._is_using_uc + getattr(driver, "_is_using_uc", None) and hasattr(driver, "is_connected") and not driver.is_connected() ): @@ -1757,24 +1752,17 @@ def open_url(driver, url): driver.cdp.open(url) return elif ( - hasattr(driver, "_is_using_uc") - and driver._is_using_uc - # and hasattr(driver, "_is_using_auth") - # and driver._is_using_auth - and ( - not hasattr(driver, "_is_using_cdp") - or not driver._is_using_cdp - ) + getattr(driver, "_is_using_uc", None) + # and getattr(driver, "_is_using_auth", None) + and not getattr(driver, "_is_using_cdp", None) ): # Auth in UC Mode requires CDP Mode # (and now we're always forcing it) driver.uc_activate_cdp_mode(url) return elif ( - hasattr(driver, "_is_using_uc") - and driver._is_using_uc - and hasattr(driver, "_is_using_cdp") - and driver._is_using_cdp + getattr(driver, "_is_using_uc", None) + and getattr(driver, "_is_using_cdp", None) ): driver.disconnect() driver.cdp.open(url) diff --git a/seleniumbase/fixtures/shared_utils.py b/seleniumbase/fixtures/shared_utils.py index 908c226688c..76142e0be33 100644 --- a/seleniumbase/fixtures/shared_utils.py +++ b/seleniumbase/fixtures/shared_utils.py @@ -147,8 +147,7 @@ def fix_url_as_needed(url): def reconnect_if_disconnected(driver): if ( - hasattr(driver, "_is_using_uc") - and driver._is_using_uc + getattr(driver, "_is_using_uc", None) and hasattr(driver, "is_connected") and not driver.is_connected() ): diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py index faaea62c5dc..6945bb846d7 100644 --- a/seleniumbase/undetected/__init__.py +++ b/seleniumbase/undetected/__init__.py @@ -163,7 +163,7 @@ def __init__( from seleniumbase import config as sb_config if ( (("-n" in sys.argv) or (" -n=" in arg_join) or ("-c" in sys.argv)) - or (hasattr(sb_config, "multi_proxy") and sb_config.multi_proxy) + or getattr(sb_config, "multi_proxy", None) or not special_port_free ): debug_port = selenium.webdriver.common.service.utils.free_port() @@ -197,9 +197,7 @@ def __init__( except IndexError: pass if not user_data_dir: - if hasattr(options, "user_data_dir") and getattr( - options, "user_data_dir", None - ): + if getattr(options, "user_data_dir", None): options.add_argument( "--user-data-dir=%s" % options.user_data_dir ) @@ -405,9 +403,7 @@ def get(self, url): def add_cdp_listener(self, event_name, callback): if ( - hasattr(self, "reactor") - and self.reactor - and self.reactor is not None + getattr(self, "reactor", None) and isinstance(self.reactor, Reactor) ): self.reactor.add_event_handler(event_name, callback) @@ -416,8 +412,7 @@ def add_cdp_listener(self, event_name, callback): def clear_cdp_listeners(self): if ( - hasattr(self, "reactor") - and self.reactor + getattr(self, "reactor", None) and isinstance(self.reactor, Reactor) ): self.reactor.handlers.clear() @@ -526,7 +521,13 @@ def connect(self): with suppress(Exception): for window_handle in self.window_handles: self.switch_to.window(window_handle) - if self.current_url.startswith("chrome-extension://"): + current_url = None + if hasattr(self, "cdp") and hasattr(self.cdp, "driver"): + with suppress(Exception): + current_url = self.cdp.get_current_url() + if not current_url: + current_url = self.current_url + if current_url.startswith("chrome-extension://"): # https://issues.chromium.org/issues/396611138 # (Remove the Linux conditional when resolved) # (So that close() is always called) diff --git a/seleniumbase/undetected/cdp_driver/browser.py b/seleniumbase/undetected/cdp_driver/browser.py index bb4d49e161f..03768228bc6 100644 --- a/seleniumbase/undetected/cdp_driver/browser.py +++ b/seleniumbase/undetected/cdp_driver/browser.py @@ -329,28 +329,17 @@ async def get( _cdp_geolocation = None _cdp_recorder = None _cdp_ad_block = None - if ( - hasattr(sb_config, "_cdp_timezone") and sb_config._cdp_timezone - ): + if getattr(sb_config, "_cdp_timezone", None): _cdp_timezone = sb_config._cdp_timezone - if ( - hasattr(sb_config, "_cdp_user_agent") - and sb_config._cdp_user_agent - ): + if getattr(sb_config, "_cdp_user_agent", None): _cdp_user_agent = sb_config._cdp_user_agent - if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale: + if getattr(sb_config, "_cdp_locale", None): _cdp_locale = sb_config._cdp_locale - if hasattr(sb_config, "_cdp_platform") and sb_config._cdp_platform: + if getattr(sb_config, "_cdp_platform", None): _cdp_platform = sb_config._cdp_platform - if ( - hasattr(sb_config, "_cdp_geolocation") - and sb_config._cdp_geolocation - ): + if getattr(sb_config, "_cdp_geolocation", None): _cdp_geolocation = sb_config._cdp_geolocation - if ( - hasattr(sb_config, "ad_block_on") - and sb_config.ad_block_on - ): + if getattr(sb_config, "ad_block_on", None): _cdp_ad_block = sb_config.ad_block_on if "timezone" in kwargs: _cdp_timezone = kwargs["timezone"] @@ -435,8 +424,7 @@ async def get( await connection.send(cdp.page.set_bypass_csp(enabled=True)) # (The code below is for the Chrome 142 extension fix) if ( - hasattr(sb_config, "_cdp_proxy") - and sb_config._cdp_proxy + getattr(sb_config, "_cdp_proxy", None) and "@" in sb_config._cdp_proxy and "auth" not in kwargs ): @@ -592,7 +580,7 @@ async def start(self=None) -> Browser: break if not self.info: chromium = "Chromium" - if hasattr(sb_config, "_cdp_browser") and sb_config._cdp_browser: + if getattr(sb_config, "_cdp_browser", None): chromium = sb_config._cdp_browser chromium = chromium[0].upper() + chromium[1:] message = "Failed to connect to the browser" @@ -910,7 +898,7 @@ async def get_all( """ connection = None for _tab in self._browser.tabs: - if hasattr(_tab, "closed") and _tab.closed: + if getattr(_tab, "closed", None): continue connection = _tab break @@ -940,7 +928,7 @@ async def set_all(self, cookies: List[cdp.network.CookieParam]): """ connection = None for _tab in self._browser.tabs: - if hasattr(_tab, "closed") and _tab.closed: + if getattr(_tab, "closed", None): continue connection = _tab break @@ -967,7 +955,7 @@ async def save(self, file: PathLike = ".session.dat", pattern: str = ".*"): save_path = pathlib.Path(file).resolve() connection = None for _tab in self._browser.tabs: - if hasattr(_tab, "closed") and _tab.closed: + if getattr(_tab, "closed", None): continue connection = _tab break @@ -1013,7 +1001,7 @@ async def load(self, file: PathLike = ".session.dat", pattern: str = ".*"): included_cookies = [] connection = None for _tab in self._browser.tabs: - if hasattr(_tab, "closed") and _tab.closed: + if getattr(_tab, "closed", None): continue connection = _tab break @@ -1036,7 +1024,7 @@ async def clear(self): """ connection = None for _tab in self._browser.tabs: - if hasattr(_tab, "closed") and _tab.closed: + if getattr(_tab, "closed", None): continue connection = _tab break diff --git a/seleniumbase/undetected/cdp_driver/cdp_util.py b/seleniumbase/undetected/cdp_driver/cdp_util.py index b7acafc19d6..73a0393562f 100644 --- a/seleniumbase/undetected/cdp_driver/cdp_util.py +++ b/seleniumbase/undetected/cdp_driver/cdp_util.py @@ -617,6 +617,8 @@ async def start( sb_config._cdp_browser = "atlas" else: sb_config._cdp_browser = "chrome" + sb_config.incognito = incognito + sb_config.guest_mode = guest if not config: config = Config( user_data_dir, diff --git a/seleniumbase/undetected/cdp_driver/tab.py b/seleniumbase/undetected/cdp_driver/tab.py index d53ac36ee55..b24e7dcc48c 100644 --- a/seleniumbase/undetected/cdp_driver/tab.py +++ b/seleniumbase/undetected/cdp_driver/tab.py @@ -342,13 +342,19 @@ async def get( if new_window and not new_tab: new_tab = True if new_tab: - if hasattr(sb_config, "incognito") and sb_config.incognito: + if ( + getattr(sb_config, "incognito", None) + or ( + getattr(sb_config, "_cdp_browser", None) + in ["comet", "atlas"] + ) + ): return await self.browser.get( url, new_tab=False, new_window=True, **kwargs ) else: return await self.browser.get( - url, new_tab, new_window, **kwargs + url, new_tab=True, new_window=False, **kwargs ) else: if not kwargs: From 5301d013f5726de26a4f45be53a2c3c283af3f98 Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Tue, 25 Nov 2025 17:29:05 -0500 Subject: [PATCH 2/4] Refactor the code --- seleniumbase/console_scripts/sb_install.py | 16 +-- seleniumbase/core/browser_launcher.py | 60 +++------ seleniumbase/core/log_helper.py | 23 ++-- seleniumbase/core/mysql.py | 2 +- seleniumbase/core/report_helper.py | 8 +- seleniumbase/core/session_helper.py | 6 +- seleniumbase/fixtures/base_case.py | 142 ++++++++------------- seleniumbase/fixtures/js_utils.py | 10 +- seleniumbase/fixtures/shared_utils.py | 3 +- seleniumbase/plugins/pytest_plugin.py | 50 ++------ seleniumbase/plugins/selenium_plugin.py | 13 +- 11 files changed, 110 insertions(+), 223 deletions(-) diff --git a/seleniumbase/console_scripts/sb_install.py b/seleniumbase/console_scripts/sb_install.py index 32aa536f991..4ee154e2411 100644 --- a/seleniumbase/console_scripts/sb_install.py +++ b/seleniumbase/console_scripts/sb_install.py @@ -177,7 +177,7 @@ def requests_get_with_retry(url): def get_cft_known_good_versions(): - if hasattr(sb_config, "cft_kgv_json") and sb_config.cft_kgv_json: + if getattr(sb_config, "cft_kgv_json", None): return sb_config.cft_kgv_json cft_ngv_url = ( "https://googlechromelabs.github.io/" @@ -188,7 +188,7 @@ def get_cft_known_good_versions(): def get_cft_latest_versions_per_milestone(): - if hasattr(sb_config, "cft_lvpm_json") and sb_config.cft_lvpm_json: + if getattr(sb_config, "cft_lvpm_json", None): return sb_config.cft_lvpm_json cft_lvpm_url = ( "https://googlechromelabs.github.io/" @@ -205,7 +205,7 @@ def get_cft_latest_version_from_milestone(milestone): def get_latest_chromedriver_version(channel="Stable"): try: - if hasattr(sb_config, "cft_lkgv_json") and sb_config.cft_lkgv_json: + if getattr(sb_config, "cft_lkgv_json", None): return sb_config.cft_lkgv_json["channels"][channel]["version"] req = requests_get( "https://googlechromelabs.github.io/" @@ -239,10 +239,7 @@ def get_latest_canary_chromedriver_version(): def log_d(message): """If setting sb_config.settings.HIDE_DRIVER_DOWNLOADS to True, output from driver downloads are logged instead of printed.""" - if ( - hasattr(sb_config.settings, "HIDE_DRIVER_DOWNLOADS") - and sb_config.settings.HIDE_DRIVER_DOWNLOADS - ): + if getattr(sb_config.settings, "HIDE_DRIVER_DOWNLOADS", None): logging.debug(message) else: print(message) @@ -251,7 +248,7 @@ def log_d(message): def main(override=None, intel_for_uc=None, force_uc=None): if override: found_proxy = None - if hasattr(sb_config, "proxy_driver") and sb_config.proxy_driver: + if getattr(sb_config, "proxy_driver", None): if " --proxy=" in " ".join(sys.argv): for arg in sys.argv: if arg.startswith("--proxy="): @@ -311,8 +308,7 @@ def main(override=None, intel_for_uc=None, force_uc=None): downloads_folder = DRIVER_DIR if ( hasattr(sb_config, "settings") - and hasattr(sb_config.settings, "NEW_DRIVER_DIR") - and sb_config.settings.NEW_DRIVER_DIR + and getattr(sb_config.settings, "NEW_DRIVER_DIR", None) and os.path.exists(sb_config.settings.NEW_DRIVER_DIR) ): downloads_folder = sb_config.settings.NEW_DRIVER_DIR diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 432b224fd3a..9cb5047954f 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -104,10 +104,7 @@ def log_d(message): """If setting sb_config.settings.HIDE_DRIVER_DOWNLOADS to True, output from driver downloads are logged instead of printed.""" - if ( - hasattr(settings, "HIDE_DRIVER_DOWNLOADS") - and settings.HIDE_DRIVER_DOWNLOADS - ): + if getattr(settings, "HIDE_DRIVER_DOWNLOADS", None): logging.debug(message) else: print(message) @@ -959,8 +956,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs): driver.find_element_by_text = CDPM.find_element_by_text driver._is_using_cdp = True if ( - hasattr(sb_config, "_cdp_proxy") - and sb_config._cdp_proxy + getattr(sb_config, "_cdp_proxy", None) and "@" in sb_config._cdp_proxy ): time.sleep(0.077) @@ -1076,11 +1072,9 @@ def __install_pyautogui_if_missing(): xvfb_width = 1366 xvfb_height = 768 if ( - hasattr(sb_config, "_xvfb_width") - and sb_config._xvfb_width + getattr(sb_config, "_xvfb_width", None) and isinstance(sb_config._xvfb_width, int) - and hasattr(sb_config, "_xvfb_height") - and sb_config._xvfb_height + and getattr(sb_config, "_xvfb_height", None) and isinstance(sb_config._xvfb_height, int) ): xvfb_width = sb_config._xvfb_width @@ -1107,8 +1101,7 @@ def __install_pyautogui_if_missing(): sb_config._virtual_display = _xvfb_display sb_config.headless_active = True if ( - hasattr(sb_config, "reuse_session") - and sb_config.reuse_session + getattr(sb_config, "reuse_session", None) and hasattr(sb_config, "_vd_list") and isinstance(sb_config._vd_list, list) ): @@ -1245,10 +1238,7 @@ def uc_gui_click_x_y(driver, x, y, timeframe=0.25): connected = driver.is_connected() if ( not connected - and ( - not hasattr(sb_config, "_saved_width_ratio") - or not sb_config._saved_width_ratio - ) + and not getattr(sb_config, "_saved_width_ratio", None) and not __is_cdp_swap_needed(driver) ): driver.reconnect(0.1) @@ -1898,8 +1888,7 @@ def _uc_gui_handle_captcha_(driver, frame="iframe", ctype=None): driver.is_element_present(".footer .clearfix .ray-id") or driver.is_element_present("script[data-cf-beacon]") ) - and hasattr(sb_config, "_saved_cf_tab_count") - and sb_config._saved_cf_tab_count + and getattr(sb_config, "_saved_cf_tab_count", None) and not __is_cdp_swap_needed(driver) ): driver.uc_open_with_disconnect(driver.current_url, 3.8) @@ -2535,8 +2524,7 @@ def _set_chrome_options( chrome_options.page_load_strategy = page_load_strategy.lower() elif ( not page_load_strategy - and hasattr(settings, "PAGE_LOAD_STRATEGY") - and settings.PAGE_LOAD_STRATEGY + and getattr(settings, "PAGE_LOAD_STRATEGY", None) and settings.PAGE_LOAD_STRATEGY.lower() in ["eager", "none"] ): # Only change it if not "normal", which is the default. @@ -3044,15 +3032,9 @@ def get_driver( ): sb_config._ext_dirs = [] driver_dir = DRIVER_DIR - if ( - hasattr(sb_config, "binary_location") - and sb_config.binary_location == "cft" - ): + if getattr(sb_config, "binary_location", None) == "cft": driver_dir = DRIVER_DIR_CFT - if ( - hasattr(sb_config, "binary_location") - and sb_config.binary_location == "chs" - ): + if getattr(sb_config, "binary_location", None) == "chs": driver_dir = DRIVER_DIR_CHS if _special_binary_exists(binary_location, "opera"): driver_dir = DRIVER_DIR_OPERA @@ -3068,8 +3050,7 @@ def get_driver( sb_config._cdp_browser = "atlas" if ( hasattr(sb_config, "settings") - and hasattr(sb_config.settings, "NEW_DRIVER_DIR") - and sb_config.settings.NEW_DRIVER_DIR + and getattr(sb_config.settings, "NEW_DRIVER_DIR", None) and os.path.exists(sb_config.settings.NEW_DRIVER_DIR) ): driver_dir = sb_config.settings.NEW_DRIVER_DIR @@ -4015,16 +3996,10 @@ def get_local_driver( downloads_path = DOWNLOADS_FOLDER driver_dir = DRIVER_DIR special_chrome = False - if ( - hasattr(sb_config, "binary_location") - and sb_config.binary_location == "cft" - ): + if getattr(sb_config, "binary_location", None) == "cft": special_chrome = True driver_dir = DRIVER_DIR_CFT - if ( - hasattr(sb_config, "binary_location") - and sb_config.binary_location == "chs" - ): + if getattr(sb_config, "binary_location", None) == "chs": special_chrome = True driver_dir = DRIVER_DIR_CHS if _special_binary_exists(binary_location, "opera"): @@ -4041,8 +4016,7 @@ def get_local_driver( driver_dir = DRIVER_DIR_ATLAS if ( hasattr(sb_config, "settings") - and hasattr(sb_config.settings, "NEW_DRIVER_DIR") - and sb_config.settings.NEW_DRIVER_DIR + and getattr(sb_config.settings, "NEW_DRIVER_DIR", None) and os.path.exists(sb_config.settings.NEW_DRIVER_DIR) ): driver_dir = sb_config.settings.NEW_DRIVER_DIR @@ -4652,8 +4626,7 @@ def get_local_driver( edge_options.page_load_strategy = page_load_strategy.lower() elif ( not page_load_strategy - and hasattr(settings, "PAGE_LOAD_STRATEGY") - and settings.PAGE_LOAD_STRATEGY + and getattr(settings, "PAGE_LOAD_STRATEGY", None) and settings.PAGE_LOAD_STRATEGY.lower() in ["eager", "none"] ): # Only change it if not "normal", which is the default. @@ -4865,8 +4838,7 @@ def get_local_driver( options.page_load_strategy = page_load_strategy.lower() elif ( not page_load_strategy - and hasattr(settings, "PAGE_LOAD_STRATEGY") - and settings.PAGE_LOAD_STRATEGY + and getattr(settings, "PAGE_LOAD_STRATEGY", None) and settings.PAGE_LOAD_STRATEGY.lower() in ["eager", "none"] ): # Only change it if not "normal", which is the default. diff --git a/seleniumbase/core/log_helper.py b/seleniumbase/core/log_helper.py index ed380683c4c..ce537b6790c 100644 --- a/seleniumbase/core/log_helper.py +++ b/seleniumbase/core/log_helper.py @@ -25,7 +25,7 @@ def log_screenshot(test_logpath, driver, screenshot=None, get=False): screenshot_skipped = constants.Warnings.SCREENSHOT_SKIPPED screenshot_warning = constants.Warnings.SCREENSHOT_UNDEFINED if ( - (hasattr(sb_config, "no_screenshot") and sb_config.no_screenshot) + getattr(sb_config, "no_screenshot", None) or screenshot == screenshot_skipped ): if get: @@ -186,11 +186,7 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None): data_to_save.append( "--------------------------------------------------------------------" ) - if ( - hasattr(test, "_outcome") - and hasattr(test._outcome, "errors") - and test._outcome.errors - ): + if hasattr(test, "_outcome") and getattr(test._outcome, "errors", None): try: exc_message = test._outcome.errors[-1][1][1] traceback_address = test._outcome.errors[-1][1][2] @@ -225,12 +221,11 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None): data_to_save.append("Exception: %s" % exc_message) else: traceback_message = None - if hasattr(test, "is_behave") and test.is_behave: + if getattr(test, "is_behave", None): if sb_config.behave_scenario.status.name == "failed": if ( hasattr(sb_config, "behave_step") - and hasattr(sb_config.behave_step, "error_message") - and sb_config.behave_step.error_message + and getattr(sb_config.behave_step, "error_message", None) ): traceback_message = sb_config.behave_step.error_message else: @@ -262,7 +257,7 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None): ) else: message = None - if hasattr(test, "is_behave") and test.is_behave: + if getattr(test, "is_behave", None): message = "Behave step was not implemented or skipped!" else: message = "Traceback not found!" @@ -281,7 +276,7 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None): data_to_save.append("Exception: %s" % sb_config._excinfo_value) else: data_to_save.append("Traceback:\n %s" % traceback_message) - if hasattr(test, "is_nosetest") and test.is_nosetest: + if getattr(test, "is_nosetest", None): # Also save the data for the report sb_config._report_test_id = test_id sb_config._report_fail_page = last_page @@ -394,7 +389,7 @@ def log_page_source(test_logpath, driver, source=None): def get_test_id(test): - if hasattr(test, "is_behave") and test.is_behave: + if getattr(test, "is_behave", None): file_name = sb_config.behave_scenario.filename line_num = sb_config.behave_line_num scenario_name = sb_config.behave_scenario.name @@ -402,7 +397,7 @@ def get_test_id(test): scenario_name = scenario_name.split(" -- @")[0] test_id = "%s:%s => %s" % (file_name, line_num, scenario_name) return test_id - elif hasattr(test, "is_context_manager") and test.is_context_manager: + elif getattr(test, "is_context_manager", None): filename = test.__class__.__module__.split(".")[-1] + ".py" classname = test.__class__.__name__ methodname = test._testMethodName @@ -412,7 +407,7 @@ def get_test_id(test): stack_base = traceback.format_stack()[0].split(", in ")[0] test_base = stack_base.split(", in ")[0].split(os.sep)[-1] - if hasattr(test, "cm_filename") and test.cm_filename: + if getattr(test, "cm_filename", None): filename = test.cm_filename else: filename = test_base.split('"')[0] diff --git a/seleniumbase/core/mysql.py b/seleniumbase/core/mysql.py index 88d91f65f0f..f51cf7a75fe 100644 --- a/seleniumbase/core/mysql.py +++ b/seleniumbase/core/mysql.py @@ -42,7 +42,7 @@ def __init__(self, database_env="test", conf_creds=None): db_user = settings.DB_USERNAME db_pass = settings.DB_PASSWORD db_schema = settings.DB_SCHEMA - if hasattr(sb_config, "settings_file") and sb_config.settings_file: + if getattr(sb_config, "settings_file", None): override = settings_parser.set_settings(sb_config.settings_file) if "DB_HOST" in override.keys(): db_server = override["DB_HOST"] diff --git a/seleniumbase/core/report_helper.py b/seleniumbase/core/report_helper.py index 51ddbf054b8..d408721a841 100644 --- a/seleniumbase/core/report_helper.py +++ b/seleniumbase/core/report_helper.py @@ -100,16 +100,12 @@ def process_failures(test, test_count, duration): bad_page_image = "failure_%s.png" % test_count bad_page_data = "failure_%s.txt" % test_count screenshot_path = os.path.join(LATEST_REPORT_DIR, bad_page_image) - if hasattr(test, "_last_page_screenshot") and test._last_page_screenshot: + if getattr(test, "_last_page_screenshot", None): with open(screenshot_path, mode="wb") as file: file.write(test._last_page_screenshot) save_test_failure_data(test, bad_page_data, folder=LATEST_REPORT_DIR) exc_message = None - if ( - hasattr(test, "_outcome") - and hasattr(test._outcome, "errors") - and test._outcome.errors - ): + if hasattr(test, "_outcome") and getattr(test._outcome, "errors", None): try: exc_message = test._outcome.errors[-1][1][1] except Exception: diff --git a/seleniumbase/core/session_helper.py b/seleniumbase/core/session_helper.py index 886baa8c40a..3894beda18c 100644 --- a/seleniumbase/core/session_helper.py +++ b/seleniumbase/core/session_helper.py @@ -3,10 +3,8 @@ def end_reused_class_session_as_needed(): if ( - hasattr(sb_config, "reuse_class_session") - and sb_config.reuse_class_session - and hasattr(sb_config, "shared_driver") - and sb_config.shared_driver + getattr(sb_config, "reuse_class_session", None) + and getattr(sb_config, "shared_driver", None) ): if ( hasattr(sb_config.shared_driver, "service") diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 579c10d2ced..30755c50a54 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -1353,7 +1353,7 @@ def go_back(self): if self.__is_cdp_swap_needed(): self.cdp.go_back() return - if hasattr(self, "recorder_mode") and self.recorder_mode: + if getattr(self, "recorder_mode", None): self.save_recorded_actions() pre_action_url = None with suppress(Exception): @@ -1381,7 +1381,7 @@ def go_forward(self): if self.__is_cdp_swap_needed(): self.cdp.go_forward() return - if hasattr(self, "recorder_mode") and self.recorder_mode: + if getattr(self, "recorder_mode", None): self.save_recorded_actions() self.__last_page_load_url = None self.driver.forward() @@ -4886,7 +4886,7 @@ def add_cookies(self, cookies, expiry=False): self.driver.add_cookie(cookie) def __set_esc_skip(self): - if hasattr(self, "esc_end") and self.esc_end: + if getattr(self, "esc_end", None): script = ( """document.onkeydown = function(evt) { evt = evt || window.event; @@ -4904,7 +4904,7 @@ def __set_esc_skip(self): self.execute_script(script) def __skip_if_esc(self): - if hasattr(self, "esc_end") and self.esc_end: + if getattr(self, "esc_end", None): if self.execute_script("return document.sb_esc_end;") == "yes": self.skip() @@ -4926,8 +4926,7 @@ def wait_for_ready_state_complete(self, timeout=None): self.__disable_beforeunload_as_needed() if ( self.page_load_strategy == "none" - and hasattr(settings, "SKIP_JS_WAITS") - and settings.SKIP_JS_WAITS + and getattr(settings, "SKIP_JS_WAITS", None) ): time.sleep(0.01) if self.undetectable: @@ -4948,10 +4947,7 @@ def wait_for_angularjs(self, timeout=None, **kwargs): def sleep(self, seconds): self.__check_scope() - if ( - not hasattr(sb_config, "time_limit") - or (hasattr(sb_config, "time_limit") and not sb_config.time_limit) - ): + if not getattr(sb_config, "time_limit", None): time.sleep(seconds) elif seconds < 0.4: shared_utils.check_if_time_limit_exceeded() @@ -4966,11 +4962,7 @@ def sleep(self, seconds): if now_ms >= stop_ms: break time.sleep(0.2) - if ( - self.recorder_mode - and hasattr(sb_config, "record_sleep") - and sb_config.record_sleep - ): + if self.recorder_mode and getattr(sb_config, "record_sleep", None): time_stamp = self.execute_script("return Date.now();") origin = self.get_origin() action = ["sleep", seconds, origin, time_stamp] @@ -5045,10 +5037,7 @@ def activate_cdp_mode(self, url=None, **kwargs): self.solve_captcha = self.cdp.solve_captcha if hasattr(self.cdp, "find_element_by_text"): self.find_element_by_text = self.cdp.find_element_by_text - if ( - hasattr(self.driver, "_is_using_auth") - and self.driver._is_using_auth - ): + if getattr(self.driver, "_is_using_auth", None): with suppress(Exception): self.cdp.loop.run_until_complete(self.cdp.page.wait(0.25)) self.undetectable = True @@ -5700,14 +5689,13 @@ def __process_recorded_actions(self): methodname = self._testMethodName context_filename = None if ( - hasattr(sb_config, "is_context_manager") - and sb_config.is_context_manager + getattr(sb_config, "is_context_manager", None) and (filename == "base_case.py" or methodname == "runTest") ): import traceback stack_base = traceback.format_stack()[0].split(os.sep)[-1] test_base = stack_base.split(", in ")[0] - if hasattr(self, "cm_filename") and self.cm_filename: + if getattr(self, "cm_filename", None): filename = self.cm_filename else: filename = test_base.split('"')[0] @@ -5724,7 +5712,7 @@ def __process_recorded_actions(self): classname = "MyTestClass" methodname = methodname.replace("[", "__").replace("]", "") methodname = re.sub(r"[\W]", "_", methodname) - if hasattr(self, "is_behave") and self.is_behave: + if getattr(self, "is_behave", None): classname = sb_config.behave_feature.name classname = classname.replace("/", " ").replace(" & ", " ") classname = re.sub(r"[^\w" + r"_ " + r"]", "", classname) @@ -5872,7 +5860,7 @@ def __process_recorded_actions(self): test_id = sb_config._test_id file_name = test_id.split("::")[0].split("/")[-1].split("\\")[-1] file_name = file_name.split(".py")[0] + "_rec.py" - if hasattr(self, "is_behave") and self.is_behave: + if getattr(self, "is_behave", None): file_name = sb_config.behave_scenario.filename.replace(".", "_") file_name = file_name.split("/")[-1].split("\\")[-1] + "_rec.py" file_name = file_name @@ -5891,7 +5879,7 @@ def __process_recorded_actions(self): if terminal_size > 30 and star_len > terminal_size: star_len = terminal_size spc = "\n\n" - if hasattr(self, "rec_print") and self.rec_print: + if getattr(self, "rec_print", None): spc = "" sys.stdout.write("\nCreated recordings%s%s" % (os.sep, file_name)) print() @@ -5912,7 +5900,7 @@ def __process_recorded_actions(self): rec_message = rec_message.replace(">>>", c2 + ">>>" + cr) print("%s%s%s%s%s\n%s" % (spc, rec_message, c1, file_path, cr, stars)) - if hasattr(self, "rec_behave") and self.rec_behave: + if getattr(self, "rec_behave", None): # Also generate necessary behave-gherkin files. self.__process_recorded_behave_actions(srt_actions, colorama) @@ -5923,7 +5911,7 @@ def __process_recorded_behave_actions(self, srt_actions, colorama): filename = self.__get_filename() feature_class = None scenario_test = None - if hasattr(self, "is_behave") and self.is_behave: + if getattr(self, "is_behave", None): feature_class = sb_config.behave_feature.name scenario_test = sb_config.behave_scenario.name else: @@ -5977,7 +5965,7 @@ def __process_recorded_behave_actions(self, srt_actions, colorama): os.makedirs(steps_folder) file_name = filename.split(".")[0] - if hasattr(self, "is_behave") and self.is_behave: + if getattr(self, "is_behave", None): file_name = sb_config.behave_scenario.filename.replace(".", "_") file_name = file_name.split("/")[-1].split("\\")[-1] + "_rec.feature" file_path = os.path.join(features_folder, file_name) @@ -5994,7 +5982,7 @@ def __process_recorded_behave_actions(self, srt_actions, colorama): if terminal_size > 30 and star_len > terminal_size: star_len = terminal_size spc = "\n" - if hasattr(self, "rec_print") and self.rec_print: + if getattr(self, "rec_print", None): spc = "" print() if " " not in file_path: @@ -9012,7 +9000,7 @@ def skip(self, reason=""): self.__passed_then_skipped = True self.__will_be_skipped = True sb_config._results[test_id] = "Skipped" - if hasattr(self, "with_db_reporting") and self.with_db_reporting: + if getattr(self, "with_db_reporting", None): if self.is_pytest: self.__skip_reason = reason else: @@ -9709,7 +9697,7 @@ def _print(self, msg): To force a print during multithreaded tests, use: "sys.stderr.write()". To print without the new-line character end, use: "sys.stdout.write()". """ - if hasattr(sb_config, "_multithreaded") and sb_config._multithreaded: + if getattr(sb_config, "_multithreaded", None): if not isinstance(msg, str): with suppress(Exception): msg = str(msg) @@ -13931,7 +13919,7 @@ def __js_click(self, selector, by="css selector"): simulateClick(someLink);""" % css_selector ) - if hasattr(self, "recorder_mode") and self.recorder_mode: + if getattr(self, "recorder_mode", None): self.save_recorded_actions() try: self.execute_script(script) @@ -13979,7 +13967,7 @@ def __js_click_element(self, element): var someLink = arguments[0]; simulateClick(someLink);""" ) - if hasattr(self, "recorder_mode") and self.recorder_mode: + if getattr(self, "recorder_mode", None): self.save_recorded_actions() try: self.execute_script(script, element) @@ -14186,7 +14174,7 @@ def __jquery_click(self, selector, by="css selector"): selector = self.convert_to_css_selector(selector, by=by) selector = self.__make_css_match_first_element_only(selector) click_script = """jQuery('%s')[0].click();""" % selector - if hasattr(self, "recorder_mode") and self.recorder_mode: + if getattr(self, "recorder_mode", None): self.save_recorded_actions() self.safe_execute_script(click_script) @@ -14342,8 +14330,7 @@ def __switch_to_newest_window_if_not_blank(self): def __needs_minimum_wait(self): if ( self.page_load_strategy == "none" - and hasattr(settings, "SKIP_JS_WAITS") - and settings.SKIP_JS_WAITS + and getattr(settings, "SKIP_JS_WAITS", None) ): return True else: @@ -14675,10 +14662,7 @@ def __ad_block_as_needed(self): def __disable_beforeunload_as_needed(self): """Disables beforeunload as needed. Also resets frame_switch state.""" - if ( - hasattr(self, "_disable_beforeunload") - and self._disable_beforeunload - ): + if getattr(self, "_disable_beforeunload", None): self.disable_beforeunload() if self.recorder_mode: try: @@ -15479,9 +15463,9 @@ def setUp(self, masterqa_mode=False): self.__skip_reason = None self.testcase_manager.insert_testcase_data(data_payload) self.case_start_time = int(time.time() * 1000.0) - elif hasattr(self, "is_behave") and self.is_behave: + elif getattr(self, "is_behave", None): self.__initialize_variables() - elif hasattr(self, "is_nosetest") and self.is_nosetest: + elif getattr(self, "is_nosetest", None): pass # Setup performed in plugins for pynose else: # Pure Python run. (Eg. SB() and Driver() Managers) @@ -15673,7 +15657,7 @@ def setUp(self, masterqa_mode=False): ) raise Exception(message) - if not hasattr(self, "is_nosetest") or not self.is_nosetest: + if not getattr(self, "is_nosetest", None): # Xvfb Virtual Display activation for Linux self.__activate_virtual_display_as_needed() @@ -15849,10 +15833,7 @@ def __set_last_page_screenshot(self): self.__last_page_screenshot_png is for all screenshot log files.""" SCREENSHOT_SKIPPED = constants.Warnings.SCREENSHOT_SKIPPED SCREENSHOT_UNDEFINED = constants.Warnings.SCREENSHOT_UNDEFINED - if ( - hasattr(self, "no_screenshot_after_test") - and self.no_screenshot_after_test - ): + if getattr(self, "no_screenshot_after_test", None): from seleniumbase.core import encoded_images NO_SCREENSHOT = encoded_images.get_no_screenshot_png() @@ -15883,10 +15864,7 @@ def __set_last_page_screenshot(self): ignore_test_time_limit=True, ) try: - if ( - hasattr(settings, "SCREENSHOT_WITH_BACKGROUND") - and settings.SCREENSHOT_WITH_BACKGROUND - ): + if getattr(settings, "SCREENSHOT_WITH_BACKGROUND", None): self.__last_page_screenshot = ( self.driver.get_screenshot_as_base64() ) @@ -15957,8 +15935,7 @@ def __get_exception_info(self): exc_message = None if ( hasattr(self, "_outcome") - and hasattr(self._outcome, "errors") - and self._outcome.errors + and getattr(self._outcome, "errors", None) ): try: exc_message = self._outcome.errors[-1][1][1] @@ -16046,8 +16023,7 @@ def __add_pytest_html_extra(self): def __delay_driver_quit(self): delay_driver_quit = False if ( - hasattr(self, "_using_sb_fixture") - and self._using_sb_fixture + getattr(self, "_using_sb_fixture", None) and "--pdb" in sys.argv and self.__has_exception() and len(self._drivers_list) == 1 @@ -16103,7 +16079,7 @@ def __has_exception(self): has_exception = False if hasattr(sys, "last_traceback") and sys.last_traceback is not None: has_exception = True - elif hasattr(self, "is_context_manager") and self.is_context_manager: + elif getattr(self, "is_context_manager", None): if self.with_testing_base and self._has_failure: return True else: @@ -16127,7 +16103,7 @@ def __has_exception(self): def __get_test_id(self): """The id used in various places such as the test log path.""" - if hasattr(self, "is_behave") and self.is_behave: + if getattr(self, "is_behave", None): file_name = sb_config.behave_scenario.filename file_name = file_name.replace("/", ".").replace("\\", ".") scenario_name = sb_config.behave_scenario.name @@ -16137,7 +16113,7 @@ def __get_test_id(self): scenario_name = scenario_name.replace(" ", "_") test_id = "%s.%s" % (file_name, scenario_name) return test_id - elif hasattr(self, "is_context_manager") and self.is_context_manager: + elif getattr(self, "is_context_manager", None): if hasattr(self, "_manager_saved_id"): self.__saved_id = self._manager_saved_id if self.__saved_id: @@ -16149,7 +16125,7 @@ def __get_test_id(self): import traceback stack_base = traceback.format_stack()[0].split(os.sep)[-1] test_base = stack_base.split(", in ")[0] - if hasattr(self, "cm_filename") and self.cm_filename: + if getattr(self, "cm_filename", None): filename = self.cm_filename else: filename = test_base.split('"')[0] @@ -16164,7 +16140,7 @@ def __get_test_id(self): ) if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6: test_id = self._sb_test_identifier - elif hasattr(self, "_using_sb_fixture") and self._using_sb_fixture: + elif getattr(self, "_using_sb_fixture", None): test_id = sb_config._latest_display_id test_id = test_id.replace(".py::", ".").replace("::", ".") test_id = test_id.replace("/", ".").replace("\\", ".") @@ -16186,7 +16162,7 @@ def __get_test_id_2(self): return full_name.split("] ")[0] + "]" else: return full_name.split(" ")[0] - if hasattr(self, "is_behave") and self.is_behave: + if getattr(self, "is_behave", None): return self.__get_test_id() test_id = "%s.%s.%s" % ( self.__class__.__module__.split(".")[-1], @@ -16207,7 +16183,7 @@ def __get_display_id(self): return full_name.split("] ")[0] + "]" else: return full_name.split(" ")[0] - if hasattr(self, "is_behave") and self.is_behave: + if getattr(self, "is_behave", None): file_name = sb_config.behave_scenario.filename line_num = sb_config.behave_line_num scenario_name = sb_config.behave_scenario.name @@ -16240,7 +16216,7 @@ def __get_filename(self): if "PYTEST_CURRENT_TEST" in os.environ: test_id = os.environ["PYTEST_CURRENT_TEST"].split(" ")[0] filename = test_id.split("::")[0].split("/")[-1] - elif hasattr(self, "is_behave") and self.is_behave: + elif getattr(self, "is_behave", None): filename = sb_config.behave_scenario.filename filename = filename.split("/")[-1].split("\\")[-1] else: @@ -16276,8 +16252,7 @@ def __process_dashboard(self, has_exception, init=False): sb_config._pdb_failure = True elif ( self.is_pytest - and hasattr(sb_config, "_pdb_failure") - and sb_config._pdb_failure + and getattr(sb_config, "_pdb_failure", None) and not has_exception ): return # Handle case where "pytest --pdb" marks failures as Passed @@ -16671,7 +16646,7 @@ def save_teardown_screenshot(self): self.__check_scope() except Exception: return - if hasattr(self, "recorder_mode") and self.recorder_mode: + if getattr(self, "recorder_mode", None): # In case tearDown() leaves the origin, save actions first. self.save_recorded_actions() if ( @@ -16866,7 +16841,7 @@ def tearDown(self): if not hasattr(self, "_using_sb_fixture") and self.__called_teardown: # This test already called tearDown() return - if hasattr(self, "recorder_mode") and self.recorder_mode: + if getattr(self, "recorder_mode", None): page_actions._reconnect_if_disconnected(self.driver) try: self.__process_recorded_actions() @@ -17101,7 +17076,7 @@ def tearDown(self): self.testcase_manager.update_testcase_log_url(data_payload) else: # (Pynose / Behave / Pure Python) - if hasattr(self, "is_behave") and self.is_behave: + if getattr(self, "is_behave", None): if sb_config.behave_scenario.status.name == "failed": has_exception = True sb_config._has_exception = True @@ -17161,8 +17136,8 @@ def tearDown(self): self._last_page_url = self.get_current_url() except Exception: self._last_page_url = "(Error: Unknown URL)" - if hasattr(self, "is_behave") and self.is_behave and has_exception: - if hasattr(sb_config, "pdb_option") and sb_config.pdb_option: + if getattr(self, "is_behave", None) and has_exception: + if getattr(sb_config, "pdb_option", None): if ( hasattr(sb_config, "behave_step") and hasattr(sb_config.behave_step, "exc_traceback") @@ -17170,24 +17145,14 @@ def tearDown(self): self.__activate_behave_post_mortem_debug_mode() if self._final_debug: self.__activate_debug_mode_in_teardown() - elif ( - hasattr(sb_config, "_do_sb_post_mortem") - and sb_config._do_sb_post_mortem - ): + elif getattr(sb_config, "_do_sb_post_mortem", None): self.__activate_sb_mgr_post_mortem_debug_mode() - elif ( - hasattr(sb_config, "_do_sb_final_trace") - and sb_config._do_sb_final_trace - ): + elif getattr(sb_config, "_do_sb_final_trace", None): self.__activate_debug_mode_in_teardown() # (Pynose / Behave / Pure Python) Close all open browser windows self.__quit_all_drivers() # Resume tearDown() for all test runners, (Pytest / Pynose / Behave) - if ( - hasattr(self, "_xvfb_display") - and self._xvfb_display - and not self._reuse_session - ): + if getattr(self, "_xvfb_display", None) and not self._reuse_session: # Stop the Xvfb virtual display launched from BaseCase try: if hasattr(self._xvfb_display, "stop"): @@ -17199,16 +17164,9 @@ def tearDown(self): except Exception: pass if ( - hasattr(sb_config, "_virtual_display") - and sb_config._virtual_display + getattr(sb_config, "_virtual_display", None) and hasattr(sb_config._virtual_display, "stop") - and ( - not hasattr(sb_config, "reuse_session") - or ( - hasattr(sb_config, "reuse_session") - and not sb_config.reuse_session - ) - ) + and not getattr(sb_config, "reuse_session", None) ): # CDP Mode may launch a 2nd Xvfb virtual display try: diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py index aa11c40a2c3..0728f2dbd91 100644 --- a/seleniumbase/fixtures/js_utils.py +++ b/seleniumbase/fixtures/js_utils.py @@ -31,7 +31,7 @@ def wait_for_ready_state_complete(driver, timeout=settings.LARGE_TIMEOUT): (Previously, tests would fail immediately if exceeding the timeout.)""" if hasattr(driver, "_swap_driver"): return - if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS: + if getattr(settings, "SKIP_JS_WAITS", None): return start_ms = time.time() * 1000.0 stop_ms = start_ms + (timeout * 1000.0) @@ -65,13 +65,13 @@ def execute_async_script(driver, script, timeout=settings.LARGE_TIMEOUT): def wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs): - if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS: + if getattr(settings, "SKIP_JS_WAITS", None): return with suppress(Exception): # This closes pop-up alerts execute_script(driver, "") if ( - (hasattr(driver, "_is_using_uc") and driver._is_using_uc) + getattr(driver, "_is_using_uc", None) or not settings.WAIT_FOR_ANGULARJS ): wait_for_ready_state_complete(driver) @@ -874,7 +874,7 @@ def set_messenger_theme( theme = "future" if location == "default": location = "bottom_right" - if hasattr(sb_config, "mobile_emulator") and sb_config.mobile_emulator: + if getattr(sb_config, "mobile_emulator", None): location = "top_center" if max_messages == "default": max_messages = "8" @@ -978,7 +978,7 @@ def post_messenger_success_message(driver, message, msg_dur=None): with suppress(Exception): theme = "future" location = "bottom_right" - if hasattr(sb_config, "mobile_emulator") and sb_config.mobile_emulator: + if getattr(sb_config, "mobile_emulator", None): location = "top_right" set_messenger_theme(driver, theme=theme, location=location) post_message(driver, message, msg_dur, style="success") diff --git a/seleniumbase/fixtures/shared_utils.py b/seleniumbase/fixtures/shared_utils.py index 76142e0be33..9cf42887338 100644 --- a/seleniumbase/fixtures/shared_utils.py +++ b/seleniumbase/fixtures/shared_utils.py @@ -297,8 +297,7 @@ def __time_limit_exceeded(message): def check_if_time_limit_exceeded(): if ( - hasattr(sb_config, "time_limit") - and sb_config.time_limit + getattr(sb_config, "time_limit", None) and not sb_config.recorder_mode ): time_limit = sb_config.time_limit diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index e120ea951a4..e3721b7f511 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -1729,7 +1729,7 @@ def pytest_configure(config): sb_config.extension_dir = config.getoption("extension_dir") sb_config.disable_features = config.getoption("disable_features") sb_config.binary_location = config.getoption("binary_location") - if hasattr(sb_config, "_cdp_bin_loc") and sb_config._cdp_bin_loc: + if getattr(sb_config, "_cdp_bin_loc", None): sb_config.binary_location = sb_config._cdp_bin_loc elif not sb_config.binary_location: if ( @@ -2187,16 +2187,12 @@ def pytest_runtest_teardown(item): self = item._testcase with suppress(Exception): if ( - hasattr(self, "driver") - and self.driver + getattr(self, "driver", None) and "--pdb" not in sys_argv ): if not (is_windows or self.driver.service.process): self.driver.quit() - elif ( - hasattr(sb_config, "_sb_pdb_driver") - and sb_config._sb_pdb_driver - ): + elif getattr(sb_config, "_sb_pdb_driver", None): with suppress(Exception): if ( not is_windows @@ -2206,32 +2202,18 @@ def pytest_runtest_teardown(item): sb_config._sb_pdb_driver = None with suppress(Exception): if ( - hasattr(self, "_xvfb_display") - and self._xvfb_display + getattr(self, "_xvfb_display", None) and hasattr(self._xvfb_display, "stop") - and ( - not hasattr(sb_config, "reuse_session") - or ( - hasattr(sb_config, "reuse_session") - and not sb_config.reuse_session - ) - ) + and not getattr(sb_config, "reuse_session", None) ): self.headless_active = False sb_config.headless_active = False self._xvfb_display.stop() self._xvfb_display = None if ( - hasattr(sb_config, "_virtual_display") - and sb_config._virtual_display + getattr(sb_config, "_virtual_display", None) and hasattr(sb_config._virtual_display, "stop") - and ( - not hasattr(sb_config, "reuse_session") - or ( - hasattr(sb_config, "reuse_session") - and not sb_config.reuse_session - ) - ) + and not getattr(sb_config, "reuse_session", None) ): sb_config._virtual_display.stop() sb_config._virtual_display = None @@ -2330,12 +2312,9 @@ def _perform_pytest_unconfigure_(config): else: start_time = reporter._session_start.time # (pytest >= 8.4.0) duration = time.time() - start_time - if ( - (hasattr(sb_config, "multi_proxy") and not sb_config.multi_proxy) - or not hasattr(sb_config, "multi_proxy") - ): + if not getattr(sb_config, "multi_proxy", None): proxy_helper.remove_proxy_zip_if_present() - if hasattr(sb_config, "reuse_session") and sb_config.reuse_session: + if getattr(sb_config, "reuse_session", None): # Close the shared browser session if sb_config.shared_driver: try: @@ -2352,14 +2331,13 @@ def _perform_pytest_unconfigure_(config): sb_config.shared_driver = None with suppress(Exception): if ( - hasattr(sb_config, "_virtual_display") - and sb_config._virtual_display + getattr(sb_config, "_virtual_display", None) and hasattr(sb_config._virtual_display, "stop") ): sb_config._virtual_display.stop() sb_config._virtual_display = None sb_config.headless_active = False - if hasattr(sb_config, "_vd_list") and sb_config._vd_list: + if getattr(sb_config, "_vd_list", None): if isinstance(sb_config._vd_list, list): for display in sb_config._vd_list: if display: @@ -2374,7 +2352,7 @@ def _perform_pytest_unconfigure_(config): shared_utils.make_dir_files_writable("./assets/") log_helper.clear_empty_logs() # Dashboard post-processing: Disable time-based refresh and stamp complete - if not hasattr(sb_config, "dashboard") or not sb_config.dashboard: + if not getattr(sb_config, "dashboard", None): html_report_path = None the_html_r = None abs_path = os.path.abspath(".") @@ -2679,11 +2657,11 @@ def pytest_unconfigure(config): ) ): return - if hasattr(sb_config, "_multithreaded") and sb_config._multithreaded: + if getattr(sb_config, "_multithreaded", None): import fasteners dash_lock = fasteners.InterProcessLock(constants.Dashboard.LOCKFILE) - if hasattr(sb_config, "dashboard") and sb_config.dashboard: + if getattr(sb_config, "dashboard", None): # Multi-threaded tests with the Dashboard abs_path = os.path.abspath(".") dash_lock_file = constants.Dashboard.LOCKFILE diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index 0c6a48b4825..42733e1aa56 100644 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -1338,7 +1338,7 @@ def beforeTest(self, test): test.test.extension_dir = self.options.extension_dir test.test.disable_features = self.options.disable_features test.test.binary_location = self.options.binary_location - if hasattr(sb_config, "_cdp_bin_loc") and sb_config._cdp_bin_loc: + if getattr(sb_config, "_cdp_bin_loc", None): test.test.binary_location = sb_config._cdp_bin_loc if self.options.use_cft and not test.test.binary_location: test.test.binary_location = "cft" @@ -1515,10 +1515,7 @@ def beforeTest(self, test): def finalize(self, result): """This runs after all tests have completed with nosetests.""" - if ( - (hasattr(sb_config, "multi_proxy") and not sb_config.multi_proxy) - or not hasattr(sb_config, "multi_proxy") - ): + if not getattr(sb_config, "multi_proxy", None): proxy_helper.remove_proxy_zip_if_present() def afterTest(self, test): @@ -1536,8 +1533,7 @@ def afterTest(self, test): pass with suppress(Exception): if ( - hasattr(self, "_xvfb_display") - and self._xvfb_display + getattr(self, "_xvfb_display", None) and hasattr(self._xvfb_display, "stop") ): self.headless_active = False @@ -1545,8 +1541,7 @@ def afterTest(self, test): self._xvfb_display.stop() self._xvfb_display = None if ( - hasattr(sb_config, "_virtual_display") - and sb_config._virtual_display + getattr(sb_config, "_virtual_display", None) and hasattr(sb_config._virtual_display, "stop") ): sb_config._virtual_display.stop() From ea1c39e21b45d6f8430ac4445340a57e9222c8e8 Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Tue, 25 Nov 2025 17:32:24 -0500 Subject: [PATCH 3/4] Update examples --- examples/cdp_mode/ReadMe.md | 10 +++++++--- examples/cdp_mode/raw_cdp_hyatt.py | 2 +- examples/cdp_mode/raw_hyatt.py | 2 +- examples/cdp_mode/raw_pokemon.py | 2 +- examples/cdp_mode/raw_walmart.py | 6 +++++- examples/cdp_mode/raw_xhr_async.py | 6 +++--- examples/cdp_mode/raw_xhr_sb.py | 6 +++--- examples/presenter/uc_presentation_4.py | 10 +++++++--- examples/raw_multi_sb.py | 24 ++++++++++++++++++++++++ 9 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 examples/raw_multi_sb.py diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 323c6ceda45..26aac55a0f4 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -140,7 +140,7 @@ from seleniumbase import SB with SB(uc=True, test=True, locale="en", ad_block=True) as sb: url = "https://www.pokemon.com/us" sb.activate_cdp_mode(url) - sb.sleep(3.5) + sb.sleep(1.5) sb.click_if_visible("button#onetrust-accept-btn-handler") sb.sleep(1.2) sb.click("a span.icon_pokeball") @@ -193,7 +193,7 @@ from seleniumbase import SB with SB(uc=True, test=True, locale="en") as sb: url = "https://www.hyatt.com/" sb.activate_cdp_mode(url) - sb.sleep(3.8) + sb.sleep(3.2) sb.click_if_visible('button[aria-label="Close"]') sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") @@ -282,7 +282,11 @@ from seleniumbase import SB with SB(uc=True, test=True, ad_block=True) as sb: url = "https://www.walmart.com/" sb.activate_cdp_mode(url) - sb.sleep(2.8) + 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.sleep(0.6) sb.click('input[aria-label="Search"]') sb.sleep(1.2) search = "Settlers of Catan Board Game" diff --git a/examples/cdp_mode/raw_cdp_hyatt.py b/examples/cdp_mode/raw_cdp_hyatt.py index 072bf9e5452..6bf9fd27128 100644 --- a/examples/cdp_mode/raw_cdp_hyatt.py +++ b/examples/cdp_mode/raw_cdp_hyatt.py @@ -2,7 +2,7 @@ url = "https://www.hyatt.com/" sb = sb_cdp.Chrome(url, locale="en", guest=True) -sb.sleep(4.2) +sb.sleep(3.6) sb.click_if_visible('button[aria-label="Close"]') sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") diff --git a/examples/cdp_mode/raw_hyatt.py b/examples/cdp_mode/raw_hyatt.py index eca5cdfd970..9876c283c94 100644 --- a/examples/cdp_mode/raw_hyatt.py +++ b/examples/cdp_mode/raw_hyatt.py @@ -3,7 +3,7 @@ with SB(uc=True, test=True, locale="en") as sb: url = "https://www.hyatt.com/" sb.activate_cdp_mode(url) - sb.sleep(3.8) + sb.sleep(3.2) sb.click_if_visible('button[aria-label="Close"]') sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") diff --git a/examples/cdp_mode/raw_pokemon.py b/examples/cdp_mode/raw_pokemon.py index d5d0163c986..7b5f2f1badc 100644 --- a/examples/cdp_mode/raw_pokemon.py +++ b/examples/cdp_mode/raw_pokemon.py @@ -3,7 +3,7 @@ with SB(uc=True, test=True, locale="en", guest=True) as sb: url = "https://www.pokemon.com/us" sb.activate_cdp_mode(url) - sb.sleep(3.5) + sb.sleep(1.5) sb.click_if_visible("button#onetrust-accept-btn-handler") sb.sleep(1.2) sb.click("a span.icon_pokeball") diff --git a/examples/cdp_mode/raw_walmart.py b/examples/cdp_mode/raw_walmart.py index e5cfb029abe..daa6b0c317a 100644 --- a/examples/cdp_mode/raw_walmart.py +++ b/examples/cdp_mode/raw_walmart.py @@ -3,7 +3,11 @@ with SB(uc=True, test=True, ad_block=True) as sb: url = "https://www.walmart.com/" sb.activate_cdp_mode(url) - sb.sleep(2.8) + 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.sleep(0.6) sb.click('input[aria-label="Search"]') sb.sleep(1.2) search = "Settlers of Catan Board Game" diff --git a/examples/cdp_mode/raw_xhr_async.py b/examples/cdp_mode/raw_xhr_async.py index ee548503449..2ee00bb054e 100644 --- a/examples/cdp_mode/raw_xhr_async.py +++ b/examples/cdp_mode/raw_xhr_async.py @@ -64,9 +64,9 @@ async def crawl(): # Change url to something that makes ajax requests tab = await driver.get("https://learn.microsoft.com/en-us/") - time.sleep(2) - for i in range(20): - await tab.scroll_down(4) + time.sleep(1.5) + for i in range(18): + await tab.scroll_down(3) time.sleep(0.02) xhr_responses = await receiveXHR(tab, xhr_requests) diff --git a/examples/cdp_mode/raw_xhr_sb.py b/examples/cdp_mode/raw_xhr_sb.py index 81ff278235c..093d188978a 100644 --- a/examples/cdp_mode/raw_xhr_sb.py +++ b/examples/cdp_mode/raw_xhr_sb.py @@ -63,9 +63,9 @@ async def receiveXHR(page, requests): # Change url to something that makes ajax requests sb.cdp.open("https://learn.microsoft.com/en-us/") - time.sleep(2) - for i in range(10): - sb.cdp.scroll_down(8) + time.sleep(1) + for i in range(9): + sb.cdp.scroll_down(6) loop = sb.cdp.get_event_loop() xhr_responses = loop.run_until_complete(receiveXHR(tab, xhr_requests)) diff --git a/examples/presenter/uc_presentation_4.py b/examples/presenter/uc_presentation_4.py index 8b2103301f7..59b26be4e5b 100644 --- a/examples/presenter/uc_presentation_4.py +++ b/examples/presenter/uc_presentation_4.py @@ -463,7 +463,7 @@ def test_presentation_4(self): with SB(uc=True, test=True, locale="en", ad_block=True) as sb: url = "https://www.pokemon.com/us" sb.activate_cdp_mode(url) - sb.sleep(3.5) + sb.sleep(1.5) sb.click_if_visible("button#onetrust-accept-btn-handler") sb.sleep(1.2) sb.click("a span.icon_pokeball") @@ -515,7 +515,11 @@ def test_presentation_4(self): with SB(uc=True, test=True, ad_block=True) as sb: url = "https://www.walmart.com/" sb.activate_cdp_mode(url) - sb.sleep(2.8) + 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.sleep(0.6) sb.click('input[aria-label="Search"]') sb.sleep(1.2) search = "Settlers of Catan Board Game" @@ -679,7 +683,7 @@ def test_presentation_4(self): with SB(uc=True, test=True, locale="en", ad_block=True) as sb: url = "https://www.hyatt.com/" sb.activate_cdp_mode(url) - sb.sleep(3.5) + sb.sleep(3.2) sb.click_if_visible('button[aria-label="Close"]') sb.sleep(0.1) sb.click_if_visible("#onetrust-reject-all-handler") diff --git a/examples/raw_multi_sb.py b/examples/raw_multi_sb.py new file mode 100644 index 00000000000..b32fbe04ba7 --- /dev/null +++ b/examples/raw_multi_sb.py @@ -0,0 +1,24 @@ +import sys +import threading +from concurrent.futures import ThreadPoolExecutor +from random import randint, seed +from seleniumbase import SB +sys.argv.append("-n") # Tell SeleniumBase to do thread-locking as needed + + +def launch_driver(url): + seed(len(threading.enumerate())) # Random seed for browser placement + with SB() as sb: + sb.set_window_rect(randint(4, 720), randint(8, 410), 700, 500) + sb.open(url=url) + if sb.is_element_visible("h1"): + sb.highlight("h1", loops=9) + else: + sb.sleep(2.2) + + +if __name__ == "__main__": + urls = ['https://seleniumbase.io/demo_page' for i in range(4)] + with ThreadPoolExecutor(max_workers=len(urls)) as executor: + for url in urls: + executor.submit(launch_driver, url) From 1f1bf5de591cdec4972a1cadf58df52a1b23cf50 Mon Sep 17 00:00:00 2001 From: Michael Mintz <mdmintz@gmail.com> Date: Tue, 25 Nov 2025 17:32:35 -0500 Subject: [PATCH 4/4] Version 4.44.18 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index f25699b531a..0287f511513 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.44.17" +__version__ = "4.44.18"