diff --git a/README.md b/README.md index 0895fe5eb34..075a9dc32a9 100755 --- a/README.md +++ b/README.md @@ -651,6 +651,7 @@ pytest test_coffee_cart.py --trace --binary-location=PATH # (Set path of the Chromium browser binary to use.) --driver-version=VER # (Set the chromedriver or uc_driver version to use.) --sjw # (Skip JS Waits for readyState to be "complete" or Angular to load.) +--wfa # (Wait for AngularJS to be done loading after specific web actions.) --pls=PLS # (Set pageLoadStrategy on Chrome: "normal", "eager", or "none".) --headless # (Run tests in headless mode. The default arg on Linux OS.) --headless2 # (Use the new headless mode, which supports extensions.) diff --git a/examples/custom_settings.py b/examples/custom_settings.py index 2eccc6bd8eb..15d137452d1 100644 --- a/examples/custom_settings.py +++ b/examples/custom_settings.py @@ -48,7 +48,7 @@ # Called after self.click(selector), NOT element.click() WAIT_FOR_RSC_ON_CLICKS = False # Wait for AngularJS calls to complete after various browser actions. -WAIT_FOR_ANGULARJS = True +WAIT_FOR_ANGULARJS = False # Skip ALL calls to wait_for_ready_state_complete() and wait_for_angularjs(). SKIP_JS_WAITS = False diff --git a/examples/hack_the_planet.py b/examples/hack_the_planet.py index 1ae11b2655e..19466028cf9 100644 --- a/examples/hack_the_planet.py +++ b/examples/hack_the_planet.py @@ -131,10 +131,11 @@ def test_all_your_base_are_belong_to_us(self): self.highlight("section.crayons-card", loops=7, scroll=False) self.open("https://azure.microsoft.com/en-us/services/playfab/") - self.remove_elements('div[role="dialog"]') self.set_text_content("h1", aybabtu) self.set_text_content('a[aria-label*="Try Azure"]', ayb) self.set_text_content('a[aria-label*="Sign in to"]', abtu) + self.remove_elements('div[role="dialog"]') + self.remove_elements('[aria-label*="Microsoft Survey"]') self.highlight("h1", loops=6, scroll=False) self.highlight('a[aria-label*="Try Azure"]', loops=4, scroll=False) self.highlight('a[aria-label*="Sign in to"]', loops=6, scroll=False) @@ -308,15 +309,6 @@ def test_all_your_base_are_belong_to_us(self): self.highlight("h1", loops=6, scroll=False) self.highlight("input#search", loops=8, scroll=False) - self.open("https://www.atlassian.com/software/jira") - self.set_text_content('a[href*="jira/pricing"]', ayb) - self.set_text_content('a[href*="jira/enterprise"]', abtu) - self.set_text_content('a[href="/software/jira/features"]', "") - self.set_text_content("h1", aybabtu) - self.highlight('a[href*="jira/pricing"]', loops=5, scroll=False) - self.highlight('a[href*="jira/enterprise"]', loops=6, scroll=False) - self.highlight("h1", loops=8, scroll=False) - self.open("https://status.iboss.com/ibcloud/app/cloudStatus.html") self.wait_for_element_clickable('div[translate*="cloudStatus"]') self.set_text_content('div[translate*="cloudStatus"]', ayb) diff --git a/examples/raw_ahrefs.py b/examples/raw_ahrefs.py index 55898298458..f815cb03117 100644 --- a/examples/raw_ahrefs.py +++ b/examples/raw_ahrefs.py @@ -7,9 +7,9 @@ sb.uc_open_with_reconnect(url) # The bot-check is later sb.type(input_field, "github.com/seleniumbase/SeleniumBase") sb.reconnect(0.1) - sb.uc_click(submit_button, reconnect_time=4) + sb.uc_click(submit_button, reconnect_time=3.25) sb.uc_gui_click_captcha() - sb.wait_for_text_not_visible("Checking", timeout=12) + sb.wait_for_text_not_visible("Checking", timeout=11.5) sb.highlight('p:contains("github.com/seleniumbase/SeleniumBase")') sb.highlight('a:contains("Top 100 backlinks")') sb.set_messenger_theme(location="bottom_center") diff --git a/examples/raw_nopecha.py b/examples/raw_nopecha.py index 66ac9e53f4f..5f5bdc828cf 100644 --- a/examples/raw_nopecha.py +++ b/examples/raw_nopecha.py @@ -2,6 +2,7 @@ with SB(uc=True, test=True) as sb: sb.uc_open_with_reconnect("nopecha.com/demo/turnstile", 3.2) - sb.uc_gui_click_captcha("#example-container0") + if sb.is_element_visible("#example-container0"): + sb.uc_gui_click_captcha("#example-container0") sb.uc_gui_click_captcha("#example-container5") sb.sleep(3) diff --git a/examples/test_geolocation.py b/examples/test_geolocation.py index d0daf3392e9..6425e19f592 100644 --- a/examples/test_geolocation.py +++ b/examples/test_geolocation.py @@ -39,7 +39,7 @@ def test_geolocation(self): ) self.open("https://www.openstreetmap.org/") self.click("span.geolocate") - self.assert_url_contains("48.87645/2.26340") + self.assert_url_contains("48.876450/2.263400") self.save_screenshot_to_logs() if self.headed: self.sleep(4) diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md index c6ca5d9c269..64ccda7a5ae 100644 --- a/help_docs/customizing_test_runs.md +++ b/help_docs/customizing_test_runs.md @@ -142,6 +142,7 @@ pytest my_first_test.py --settings-file=custom_settings.py --binary-location=PATH # (Set path of the Chromium browser binary to use.) --driver-version=VER # (Set the chromedriver or uc_driver version to use.) --sjw # (Skip JS Waits for readyState to be "complete" or Angular to load.) +--wfa # (Wait for AngularJS to be done loading after specific web actions.) --pls=PLS # (Set pageLoadStrategy on Chrome: "normal", "eager", or "none".) --headless # (Run tests in headless mode. The default arg on Linux OS.) --headless2 # (Use the new headless mode, which supports extensions.) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index a9d21ae460e..eb8f5c71152 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -1,7 +1,7 @@ # mkdocs dependencies for generating the seleniumbase.io website # Minimum Python version: 3.8 (for generating docs only) -regex>=2024.7.24 +regex>=2024.9.11 pymdown-extensions>=10.9 pipdeptree>=2.23.3 python-dateutil>=2.8.2 diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index a408493f059..468cc6ffc49 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.30.4" +__version__ = "4.30.5" diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index 7fed5f88be6..901bf76cbc5 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -42,6 +42,7 @@ -D binary-location=PATH (Set path of the Chromium browser binary to use.) -D driver-version=VER (Set the chromedriver or uc_driver version to use.) -D sjw (Skip JS Waits for readyState to be "complete" or Angular to load.) +-D wfa (Wait for AngularJS to be done loading after specific web actions.) -D pls=PLS (Set pageLoadStrategy on Chrome: "normal", "eager", or "none".) -D headless (Run tests in headless mode. The default arg on Linux OS.) -D headless2 (Use the new headless mode, which supports extensions.) @@ -588,6 +589,10 @@ def get_configured_sb(context): if low_key in ["sjw", "skip-js-waits", "skip_js_waits"]: settings.SKIP_JS_WAITS = True continue + # Handle: -D wfa / wait-for-angularjs / wait_for_angularjs + if low_key in ["wfa", "wait-for-angularjs", "wait_for_angularjs"]: + settings.WAIT_FOR_ANGULARJS = True + continue # Handle: -D visual-baseline / visual_baseline if low_key in ["visual-baseline", "visual_baseline"]: sb.visual_baseline = True @@ -889,6 +894,16 @@ def get_configured_sb(context): # If the port is "443", the protocol is "https" if str(sb.port) == "443": sb.protocol = "https" + if ( + (sb.enable_ws is None and sb.disable_ws is None) + or (sb.disable_ws is not None and not sb.disable_ws) + or (sb.enable_ws is not None and sb.enable_ws) + ): + sb.enable_ws = True + sb.disable_ws = False + else: + sb.enable_ws = False + sb.disable_ws = True if sb.window_size: window_size = sb.window_size if window_size.count(",") != 1: diff --git a/seleniumbase/config/settings.py b/seleniumbase/config/settings.py index 0b2822100f3..1a9d88f04b3 100644 --- a/seleniumbase/config/settings.py +++ b/seleniumbase/config/settings.py @@ -75,7 +75,7 @@ # Called after self.click(selector), NOT element.click() WAIT_FOR_RSC_ON_CLICKS = False # Wait for AngularJS calls to complete after various browser actions. -WAIT_FOR_ANGULARJS = True +WAIT_FOR_ANGULARJS = False # Skip all calls to wait_for_ready_state_complete() and wait_for_angularjs(). SKIP_JS_WAITS = False diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 6559cbc186e..d1d023e18af 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -982,16 +982,19 @@ def _uc_gui_click_captcha( _uc_gui_click_x_y(driver, x, y, timeframe=0.95) except Exception: pass - reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.5 + reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.6 if IS_LINUX: reconnect_time = constants.UC.RECONNECT_TIME + 0.2 if not x or not y: reconnect_time = 1 # Make it quick (it already failed) driver.reconnect(reconnect_time) - if blind or (IS_LINUX and "Just a moment" in driver.title): - retry = True + caught = False + if driver.is_element_present(".footer .clearfix .ray-id"): blind = True - if retry and x and y and _on_a_captcha_page(driver): + caught = True + if blind: + retry = True + if retry and x and y and (caught or _on_a_captcha_page(driver)): with gui_lock: # Prevent issues with multiple processes # Make sure the window is on top page_actions.switch_to_window( @@ -1056,11 +1059,7 @@ def uc_gui_click_cf(driver, frame="iframe", retry=False, blind=False): ) -def _uc_gui_handle_captcha( - driver, - frame="iframe", - ctype=None, -): +def _uc_gui_handle_captcha_(driver, frame="iframe", ctype=None): if ctype == "cf_t": if not _on_a_cf_turnstile_page(driver): return @@ -1202,12 +1201,19 @@ def _uc_gui_handle_captcha( pyautogui.press(" ") except Exception: pass - reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.5 + reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.6 if IS_LINUX: reconnect_time = constants.UC.RECONNECT_TIME + 0.2 driver.reconnect(reconnect_time) +def _uc_gui_handle_captcha(driver, frame="iframe", ctype=None): + _uc_gui_handle_captcha_(driver, frame=frame, ctype=ctype) + if driver.is_element_present(".footer .clearfix .ray-id"): + driver.uc_open_with_reconnect(driver.current_url, 3.8) + _uc_gui_handle_captcha_(driver, frame=frame, ctype=ctype) + + def uc_gui_handle_captcha(driver, frame="iframe"): _uc_gui_handle_captcha(driver, frame=frame, ctype=None) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 0d02e781585..241d4f37300 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -16071,7 +16071,10 @@ def tearDown(self): try: self.driver.window_handles except Exception: - self.driver.connect() + try: + self.driver.connect() + except Exception: + pass self.__slow_mode_pause_if_active() has_exception = self.__has_exception() sb_config._has_exception = has_exception diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py index 5ad4aaf8438..7294063e0b7 100644 --- a/seleniumbase/fixtures/js_utils.py +++ b/seleniumbase/fixtures/js_utils.py @@ -48,7 +48,7 @@ def wait_for_ready_state_complete(driver, timeout=settings.LARGE_TIMEOUT): return False # readyState stayed "interactive" (Not "complete") -def execute_async_script(driver, script, timeout=settings.EXTREME_TIMEOUT): +def execute_async_script(driver, script, timeout=settings.LARGE_TIMEOUT): driver.set_script_timeout(timeout) return driver.execute_async_script(script) @@ -56,11 +56,20 @@ def execute_async_script(driver, script, timeout=settings.EXTREME_TIMEOUT): def wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs): if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS: return + try: + # This closes pop-up alerts + driver.execute_script("") + except Exception: + pass + if hasattr(driver, "_is_using_uc") and driver._is_using_uc: + # Calling AngularJS waits may make UC Mode detectable. + # Instead, pause for a brief moment, and then return. + time.sleep(0.007) + return if not settings.WAIT_FOR_ANGULARJS: return if timeout == settings.MINI_TIMEOUT: - timeout = settings.MINI_TIMEOUT / 2.0 - + timeout = settings.MINI_TIMEOUT / 4.0 NG_WRAPPER = ( "%(prefix)s" "var $elm=document.querySelector(" @@ -84,15 +93,10 @@ def wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs): "handler": handler, "suffix": suffix, } - try: - # This closes any pop-up alerts (otherwise the next part fails) - driver.execute_script("") - except Exception: - pass try: execute_async_script(driver, script, timeout=timeout) except Exception: - time.sleep(0.05) + time.sleep(0.0456) def convert_to_css_selector(selector, by=By.CSS_SELECTOR): diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index 6e9e803790c..9a5efe76d86 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -59,6 +59,7 @@ def pytest_addoption(parser): --binary-location=PATH (Set path of the Chromium browser binary to use.) --driver-version=VER (Set the chromedriver or uc_driver version to use.) --sjw (Skip JS Waits for readyState to be "complete" or Angular to load.) + --wfa (Wait for AngularJS to be done loading after specific web actions.) --pls=PLS (Set pageLoadStrategy on Chrome: "normal", "eager", or "none".) --headless (Run tests in headless mode. The default arg on Linux OS.) --headless2 (Use the new headless mode, which supports extensions.) @@ -358,6 +359,17 @@ def pytest_addoption(parser): and wait_for_angularjs(), which are part of many SeleniumBase methods for improving reliability.""", ) + parser.addoption( + "--wfa", + "--wait_for_angularjs", + "--wait-for-angularjs", + action="store_true", + dest="wait_for_angularjs", + default=False, + help="""Add waiting for AngularJS. (The default setting + was changed to no longer wait for AngularJS to + finish loading as an extra JavaScript call.)""", + ) parser.addoption( "--with-db_reporting", "--with-db-reporting", @@ -1546,6 +1558,8 @@ def pytest_configure(config): settings.ARCHIVE_EXISTING_DOWNLOADS = True if config.getoption("skip_js_waits"): settings.SKIP_JS_WAITS = True + if config.getoption("wait_for_angularjs"): + settings.WAIT_FOR_ANGULARJS = True sb_config.all_scripts = config.getoption("all_scripts") sb_config._time_limit = config.getoption("time_limit") sb_config.time_limit = config.getoption("time_limit") diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index 787430e11a3..63355b1ce2a 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -76,6 +76,7 @@ def SB( binary_location=None, # Set path of the Chromium browser binary to use. driver_version=None, # Set the chromedriver or uc_driver version to use. skip_js_waits=None, # Skip JS Waits (readyState=="complete" and Angular). + wait_for_angularjs=None, # Wait for AngularJS to load after some actions. use_wire=None, # Use selenium-wire's webdriver over selenium webdriver. external_pdf=None, # Set Chrome "plugins.always_open_pdf_externally":True. is_mobile=None, # Use the mobile device emulator while running tests. @@ -109,6 +110,7 @@ def SB( wire=None, # Shortcut / Duplicate of "use_wire". pls=None, # Shortcut / Duplicate of "page_load_strategy". sjw=None, # Shortcut / Duplicate of "skip_js_waits". + wfa=None, # Shortcut / Duplicate of "wait_for_angularjs". save_screenshot=None, # Save a screenshot at the end of each test. no_screenshot=None, # No screenshots saved unless tests directly ask it. page_load_strategy=None, # Set Chrome PLS to "normal", "eager", or "none". @@ -607,6 +609,17 @@ def SB( settings.SKIP_JS_WAITS = True elif skip_js_waits: settings.SKIP_JS_WAITS = skip_js_waits + if wfa is not None and wait_for_angularjs is None: + wait_for_angularjs = wfa + if wait_for_angularjs is None: + if ( + "--wfa" in sys_argv + or "--wait_for_angularjs" in sys_argv + or "--wait-for-angularjs" in sys_argv + ): + settings.WAIT_FOR_ANGULARJS = True + elif wait_for_angularjs: + settings.WAIT_FOR_ANGULARJS = wait_for_angularjs if save_screenshot is None: if ( "--screenshot" in sys_argv diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index b169316c616..0dd373f9d21 100644 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -40,6 +40,7 @@ class SeleniumBrowser(Plugin): --binary-location=PATH (Set path of the Chromium browser binary to use.) --driver-version=VER (Set the chromedriver or uc_driver version to use.) --sjw (Skip JS Waits for readyState to be "complete" or Angular to load.) + --wfa (Wait for AngularJS to be done loading after specific web actions.) --pls=PLS (Set pageLoadStrategy on Chrome: "normal", "eager", or "none".) --headless (Run tests in headless mode. The default arg on Linux OS.) --headless2 (Use the new headless mode, which supports extensions.) @@ -178,6 +179,17 @@ def options(self, parser, env): and wait_for_angularjs(), which are part of many SeleniumBase methods for improving reliability.""", ) + parser.addoption( + "--wfa", + "--wait_for_angularjs", + "--wait-for-angularjs", + action="store_true", + dest="wait_for_angularjs", + default=False, + help="""Add waiting for AngularJS. (The default setting + was changed to no longer wait for AngularJS to + finish loading as an extra JavaScript call.)""", + ) parser.addoption( "--protocol", action="store", @@ -1105,6 +1117,8 @@ def beforeTest(self, test): test.test.start_page = self.options.start_page if self.options.skip_js_waits: settings.SKIP_JS_WAITS = True + if self.options.wait_for_angularjs: + settings.WAIT_FOR_ANGULARJS = True test.test.protocol = self.options.protocol test.test.servername = self.options.servername test.test.port = self.options.port