diff --git a/README.md b/README.md index 3b74a309af8..eed7aa0ac39 100755 --- a/README.md +++ b/README.md @@ -564,6 +564,7 @@ pytest my_first_test.py --pdb --window-size=WIDTH,HEIGHT # (Set the browser's starting window size.) --maximize # (Start tests with the browser window maximized.) --screenshot # (Save a screenshot at the end of each test.) +--no-screenshot # (No screenshots saved unless tests directly ask it.) --visual-baseline # (Set the visual baseline for Visual/Layout tests.) --wire # (Use selenium-wire's webdriver for replacing selenium webdriver.) --external-pdf # (Set Chromium "plugins.always_open_pdf_externally":True.) diff --git a/examples/wordle_archive_test.py b/examples/old_wordle_script.py similarity index 93% rename from examples/wordle_archive_test.py rename to examples/old_wordle_script.py index c3e46365208..0a5c381c635 100644 --- a/examples/wordle_archive_test.py +++ b/examples/old_wordle_script.py @@ -1,5 +1,7 @@ -""" Solve the Wordle game using SeleniumBase. - This test runs on archived versions of Wordle, containing Shadow-DOM. """ +""" +Solve the Wordle game using SeleniumBase. +This test runs on archived versions of Wordle, containing Shadow-DOM. +""" import ast import random @@ -86,7 +88,7 @@ def test_wordle(self): keyboard_base = "game-app::shadow game-keyboard::shadow " word = random.choice(self.word_list) num_attempts = 0 - success = False + found_word = False for attempt in range(6): num_attempts += 1 word = random.choice(self.word_list) @@ -105,13 +107,13 @@ def test_wordle(self): letter_eval = self.get_attribute(tile % str(i), "evaluation") letter_status.append(letter_eval) if letter_status.count("correct") == 5: - success = True + found_word = True break self.word_list.remove(word) self.modify_word_list(word, letter_status) self.save_screenshot_to_logs() - if success: + if found_word: print('Word: "%s"\nAttempts: %s' % (word.upper(), num_attempts)) else: print('Final guess: "%s" (Not the correct word!)' % word.upper()) diff --git a/examples/raw_parameter_script.py b/examples/raw_parameter_script.py index 32288b63620..ec249ffe504 100755 --- a/examples/raw_parameter_script.py +++ b/examples/raw_parameter_script.py @@ -80,6 +80,7 @@ sb.maximize_option = False sb._disable_beforeunload = False sb.save_screenshot_after_test = False + sb.no_screenshot_after_test = False sb.page_load_strategy = None sb.timeout_multiplier = None sb.pytest_html_report = None diff --git a/examples/test_apple_site.py b/examples/test_apple_site.py index c897345ed5f..57c967c143c 100755 --- a/examples/test_apple_site.py +++ b/examples/test_apple_site.py @@ -8,10 +8,15 @@ def test_apple_developer_site_webdriver_instructions(self): self.demo_mode = True self.demo_sleep = 0.5 self.message_duration = 2.0 - if self.headless and ( - self.browser == "chrome" or self.browser == "edge" - ): - self.get_new_driver(browser=self.browser, headless2=True) + if self.headless: + if self._multithreaded: + print("Skipping test in headless multi-threaded mode.") + self.skip("Skipping test in headless multi-threaded mode.") + elif self.undetectable: + print("Skipping test in headless undetectable mode.") + self.skip("Skipping test in headless undetectable mode.") + elif self.browser == "chrome" or self.browser == "edge": + self.get_new_driver(browser=self.browser, headless2=True) self.open("https://developer.apple.com/search/") title = "Testing with WebDriver in Safari" self.type('[placeholder*="developer.apple.com"]', title + "\n") diff --git a/examples/test_get_locale_code.py b/examples/test_get_locale_code.py index 6e4e0b33d40..8caee490cbb 100755 --- a/examples/test_get_locale_code.py +++ b/examples/test_get_locale_code.py @@ -3,7 +3,7 @@ class LocaleTests(BaseCase): def test_get_locale_code(self): - self.open("data:,") + self.open("about:blank") locale_code = self.get_locale_code() message = '\nLocale Code = "%s"' % locale_code print(message) diff --git a/examples/visual_testing/test_layout_fail.py b/examples/visual_testing/test_layout_fail.py index 4f53fdbc513..17e0118746c 100755 --- a/examples/visual_testing/test_layout_fail.py +++ b/examples/visual_testing/test_layout_fail.py @@ -22,7 +22,7 @@ def test_applitools_change(self): # Click a button that changes the text of an element self.click('a[href="?diff1"]') # Click a button that makes a hidden element visible - self.click("button") + self.slow_click("button") print("(This test should fail)") # due to image now seen self.check_window(name="helloworld", level=3) diff --git a/examples/wordle_test.py b/examples/wordle_test.py index 862c6fdff85..d00f2414f8c 100644 --- a/examples/wordle_test.py +++ b/examples/wordle_test.py @@ -64,7 +64,7 @@ def test_wordle(self): self.initialize_word_list() word = random.choice(self.word_list) num_attempts = 0 - success = False + found_word = False for attempt in range(6): num_attempts += 1 word = random.choice(self.word_list) @@ -74,6 +74,7 @@ def test_wordle(self): button = 'button[data-key="%s"]' % letter self.click(button) button = 'button[class*="oneAndAHalf"]' + self.wait_for_ready_state_complete() self.click(button) row = ( 'div[class*="lbzlf"] div[class*="Row-module"]:nth-of-type(%s) ' @@ -81,18 +82,19 @@ def test_wordle(self): ) tile = row + 'div:nth-child(%s) div[class*="module_tile__3ayIZ"]' self.wait_for_element(tile % "5" + '[data-state*="e"]') + self.wait_for_ready_state_complete() letter_status = [] for i in range(1, 6): letter_eval = self.get_attribute(tile % str(i), "data-state") letter_status.append(letter_eval) if letter_status.count("correct") == 5: - success = True + found_word = True break self.word_list.remove(word) self.modify_word_list(word, letter_status) self.save_screenshot_to_logs() - if success: + if found_word: print('\nWord: "%s"\nAttempts: %s' % (word.upper(), num_attempts)) else: print('Final guess: "%s" (Not the correct word!)' % word.upper()) diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md index 6df862f4e59..b5518731c5e 100755 --- a/help_docs/customizing_test_runs.md +++ b/help_docs/customizing_test_runs.md @@ -178,6 +178,7 @@ pytest my_first_test.py --settings-file=custom_settings.py --window-size=WIDTH,HEIGHT # (Set the browser's starting window size.) --maximize # (Start tests with the browser window maximized.) --screenshot # (Save a screenshot at the end of each test.) +--no-screenshot # (No screenshots saved unless tests directly ask it.) --visual-baseline # (Set the visual baseline for Visual/Layout tests.) --wire # (Use selenium-wire's webdriver for replacing selenium webdriver.) --external-pdf # (Set Chromium "plugins.always_open_pdf_externally":True.) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index dc7db3126b5..52b70cdd019 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.7 -regex>=2022.9.13 +regex>=2022.10.31 docutils==0.19 python-dateutil==2.8.2 livereload==2.6.3 @@ -14,7 +14,7 @@ Jinja2==3.1.2 click==8.1.3 zipp==3.10.0 ghp-import==2.1.0 -readme-renderer==37.2 +readme-renderer==37.3 pymdown-extensions==9.7 importlib-metadata==5.0.0 bleach==5.0.1 @@ -28,7 +28,7 @@ cairosvg==2.5.2 cssselect2==0.7.0 tinycss2==1.2.1 defusedxml==0.7.1 -mkdocs==1.4.1 +mkdocs==1.4.2 mkdocs-material==8.5.7 mkdocs-exclude-search==0.6.4 mkdocs-simple-hooks==0.1.5 diff --git a/requirements.txt b/requirements.txt index 1f6768a3dce..65e0aaad021 100755 --- a/requirements.txt +++ b/requirements.txt @@ -80,7 +80,7 @@ pytest-html==1.22.1;python_version<"3.6" pytest-html==2.0.1;python_version>="3.6" pytest-metadata==1.8.0;python_version<"3.6" pytest-metadata==1.11.0;python_version>="3.6" and python_version<"3.7" -pytest-metadata==2.0.3;python_version>="3.7" +pytest-metadata==2.0.4;python_version>="3.7" pytest-ordering==0.6 pytest-rerunfailures==8.0;python_version<"3.6" pytest-rerunfailures==10.2;python_version>="3.6" @@ -98,7 +98,7 @@ beautifulsoup4==4.9.3;python_version<"3.6" beautifulsoup4==4.11.1;python_version>="3.6" cryptography==2.9.2;python_version<"3.6" cryptography==36.0.2;python_version>="3.6" and python_version<"3.7" -cryptography==38.0.1;python_version>="3.7" +cryptography==38.0.3;python_version>="3.7" pygments==2.5.2;python_version<"3.6" pygments==2.13.0;python_version>="3.6" prompt-toolkit==1.0.18;python_version<"3.6" diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index bd7dad87601..bbcf55520a2 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.7.0" +__version__ = "4.7.1" diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index b47d2612f06..59b54440dee 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -88,9 +88,10 @@ -D window-size=WIDTH,HEIGHT (Set the browser's starting window size.) -D maximize (Start tests with the browser window maximized.) -D screenshot (Save a screenshot at the end of each test.) +-D no-screenshot (No screenshots saved unless tests directly ask it.) -D visual-baseline (Set the visual baseline for Visual/Layout tests.) -D wire (Use selenium-wire's webdriver for replacing selenium webdriver.) --D external-pdf (Set Chromium "plugins.always_open_pdf_externally": True.) +-D external-pdf (Set Chromium "plugins.always_open_pdf_externally":True.) -D timeout-multiplier=MULTIPLIER (Multiplies the default timeout values.) """ @@ -185,6 +186,7 @@ def get_configured_sb(context): sb.maximize_option = False sb.is_context_manager = False sb.save_screenshot_after_test = False + sb.no_screenshot_after_test = False sb.timeout_multiplier = None sb.pytest_html_report = None sb.with_db_reporting = False @@ -565,6 +567,10 @@ def get_configured_sb(context): ]: sb.save_screenshot_after_test = True continue + # Handle: -D no-screenshot / no_screenshot / ns + if low_key in ["no-screenshot", "no_screenshot", "ns"]: + sb.no_screenshot_after_test = True + continue # Handle: -D timeout-multiplier=FLOAT / timeout_multiplier=FLOAT if low_key in ["timeout-multiplier", "timeout_multiplier"]: timeout_multiplier = userdata[key] @@ -848,6 +854,7 @@ def get_configured_sb(context): sb_config.maximize_option = sb.maximize_option sb_config.xvfb = sb.xvfb sb_config.save_screenshot = sb.save_screenshot_after_test + sb_config.no_screenshot = sb.no_screenshot_after_test sb_config._has_logs = False sb_config.variables = sb.variables sb_config.dashboard = sb.dashboard diff --git a/seleniumbase/config/settings.py b/seleniumbase/config/settings.py index 707a75cf5ac..0767eecc3b8 100755 --- a/seleniumbase/config/settings.py +++ b/seleniumbase/config/settings.py @@ -107,9 +107,9 @@ # (Headless resolutions take priority, and include all browsers.) # (Firefox starts maximized by default when running in GUI Mode.) CHROME_START_WIDTH = 1250 -CHROME_START_HEIGHT = 840 +CHROME_START_HEIGHT = 825 HEADLESS_START_WIDTH = 1440 -HEADLESS_START_HEIGHT = 1880 +HEADLESS_START_HEIGHT = 1875 # #####>>>>>----- MasterQA SETTINGS -----<<<<<##### # ##### (Used when importing MasterQA as the parent class) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 6dcb15f69c6..3aae9c1e730 100755 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -12,6 +12,7 @@ from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.edge.service import Service as EdgeService from selenium.webdriver.firefox.service import Service as FirefoxService +from selenium.webdriver.safari.service import Service as SafariService from seleniumbase.config import settings from seleniumbase.core import download_helper from seleniumbase.core import proxy_helper @@ -477,6 +478,18 @@ def _set_chrome_options( chrome_options.add_experimental_option( "mobileEmulation", emulator_settings ) + if headless or headless2: + chrome_options.add_argument( + "--window-size=%s,%s" % ( + settings.HEADLESS_START_WIDTH, settings.HEADLESS_START_HEIGHT + ) + ) + else: + chrome_options.add_argument( + "--window-size=%s,%s" % ( + settings.CHROME_START_WIDTH, settings.CHROME_START_HEIGHT + ) + ) if ( not proxy_auth and not disable_csp @@ -498,7 +511,7 @@ def _set_chrome_options( chrome_options.add_argument("--guest") else: pass - if user_data_dir and not undetectable: + if user_data_dir and not is_using_uc(undetectable, browser_name): abs_path = os.path.abspath(user_data_dir) chrome_options.add_argument("user-data-dir=%s" % abs_path) if extension_zip: @@ -527,13 +540,6 @@ def _set_chrome_options( ): # Only change it if not "normal", which is the default. chrome_options.page_load_strategy = settings.PAGE_LOAD_STRATEGY.lower() - if servername != "localhost": - use_auto_ext = True # Use Automation Extension - if not use_auto_ext: # Disable Automation Extension. (Default) - if browser_name != constants.Browser.OPERA: - chrome_options.add_argument( - "--disable-blink-features=AutomationControlled" - ) if headless2: chrome_options.add_argument("--headless=chrome") elif headless: @@ -591,10 +597,11 @@ def _set_chrome_options( chrome_options.add_argument("--proxy-pac-url=%s" % proxy_pac_url) if browser_name != constants.Browser.OPERA: # Opera Chromium doesn't support these switches - chrome_options.add_argument("--ignore-certificate-errors") + if not is_using_uc(undetectable, browser_name) or not enable_ws: + chrome_options.add_argument("--ignore-certificate-errors") if not enable_ws: chrome_options.add_argument("--disable-web-security") - if "linux" in PLATFORM or not undetectable: + if "linux" in PLATFORM or not is_using_uc(undetectable, browser_name): chrome_options.add_argument("--no-sandbox") else: # Opera Chromium only! @@ -940,7 +947,7 @@ def get_driver( headless = True if uc_subprocess and not undetectable: undetectable = True - if undetectable and mobile_emulator: + if is_using_uc(undetectable, browser_name) and mobile_emulator: mobile_emulator = False user_agent = None proxy_auth = False @@ -2060,7 +2067,21 @@ def get_local_driver( edge_options.add_experimental_option( "mobileEmulation", emulator_settings ) - if user_data_dir and not undetectable: + if headless or headless2: + edge_options.add_argument( + "--window-size=%s,%s" % ( + settings.HEADLESS_START_WIDTH, + settings.HEADLESS_START_HEIGHT, + ) + ) + else: + edge_options.add_argument( + "--window-size=%s,%s" % ( + settings.CHROME_START_WIDTH, + settings.CHROME_START_HEIGHT, + ) + ) + if user_data_dir and not is_using_uc(undetectable, browser_name): abs_path = os.path.abspath(user_data_dir) edge_options.add_argument("user-data-dir=%s" % abs_path) if extension_zip: @@ -2144,7 +2165,7 @@ def get_local_driver( edge_options.add_argument("--allow-running-insecure-content") if user_agent: edge_options.add_argument("--user-agent=%s" % user_agent) - if "linux" in PLATFORM or not undetectable: + if "linux" in PLATFORM or not is_using_uc(undetectable, browser_name): edge_options.add_argument("--no-sandbox") if remote_debug: # To access the Remote Debugger, go to: http://localhost:9222 @@ -2298,7 +2319,8 @@ def get_local_driver( # Skip if multithreaded raise Exception("Can't run Safari tests in multithreaded mode!") warnings.simplefilter("ignore", category=DeprecationWarning) - return webdriver.safari.webdriver.WebDriver(quiet=False) + service = SafariService(quiet=False) + return webdriver.safari.webdriver.WebDriver(service=service) elif browser_name == constants.Browser.OPERA: try: if LOCAL_OPERADRIVER and os.path.exists(LOCAL_OPERADRIVER): @@ -2635,7 +2657,7 @@ def get_local_driver( if selenium4_or_newer: if headless and "linux" not in PLATFORM: undetectable = False # No support for headless - if undetectable: + if is_using_uc(undetectable, browser_name): from seleniumbase import undetected from urllib.error import URLError diff --git a/seleniumbase/core/encoded_images.py b/seleniumbase/core/encoded_images.py index 8df2d597b0b..1e0338a9e41 100755 --- a/seleniumbase/core/encoded_images.py +++ b/seleniumbase/core/encoded_images.py @@ -174,3 +174,40 @@ def get_side_by_side_png(): "MAAAAASUVORK5CYII=" ) return SIDE_BY_SIDE_PNG + + +def get_no_screenshot_png(): + NO_SCREENSHOT = ( + "iVBORw0KGgoAAAANSUhEUgAAAIwAAAA8CAAAAACRYQ2XAAAF10lEQVRo3u3WeVATVxwH8" + "AiGIDEkEEjEoCCpXGVsFI3EE5DBitd4Y6nngK1a79pRUYeq1FIto9JWO9ra4mCZoiCKR4" + "tarMihYAEJJIpXNgmQmJBAIHe+/QORadC2M870mNn9a/f3e2/eZ/f33ttHwX/oopAYEkN" + "iSAyJITEkhsSQGBJDYkgMiSExJIbEkBgSQ2JIDIn59zA6RbP5H8Los7bkWQHAdDyt+SXt" + "avfEDA1OOiZ/nbFUn33R1TdqNZidMYrRlKDbAKCfwqzr2+PaCCpPGDqQmtDwGpjGIKGmb" + "/RO4jd2J4wyikJZ1AagPZ59r0+HZwnuaU81rdenUFJeo1SSYJG2b7TIfYPDGTN24BCP47" + "0Ys/LRk97XqOWKVABwk/OmDECHrInoBGwarV2jtALWZkJh7G5pkBNqBwCLqsOhJuSdAGB" + "rJQiVHZAEj9NqCaIdAGCUP5TpAIchx+O9FosTRhiUzhop7cHc/2AYx1d0qqfElaxxLQCg" + "O/SpCiidNciHt6gazQsTs8cKCDzYyucMXVpqA6wXZvtxBOkEUBO140sBxz+xDlCnh3K5E" + "ZntkISMOjyW4zf9hgOoT/HncGN/MJu38Vy8hNXOmGE171PWGrsx8gS3Obs3D2MesnVnm8" + "JoS660WgDYgV9DGMtS51Mny+WRnjxufOvjONr01JXM4GtAHtdvzbZJ/ZN0KGOxeYt3jKc" + "s6LCl0d/enRpFOwhJmPugWamzXSfI8GDSgPlp64d6f2fNm08dk/bYGRPYJB3BKkB7PLse" + "B11W6IGr/nxxd9aSNYjiEbZ83+VmoCOp/14zNHNpZxRCSqJEa9lJ3aiD9RA1yfRkxOCLg" + "HKm22mUe3seM6NmOL9BPT6wDqiKmN0hDaPuMUAxmXkV21w3G4BCL4ECl+mbnVeTUhh4H1" + "+7x7QY4tli/QT2bQDGpe65z9Om0r0iH9d+nhMvQcofLgVQtqeSGM2rBBRRgTWwOWTh/NY" + "c6mqz3YYztFWOcla0Bmifyi1XTxiwX2EyN9TbJSEhUsC62qNQNX5IFQBTIq0QRfSN9r4Y" + "KXQLXfa1TWWLiYDRrQAcabSs3lmvFWd/OJISIalkxeq6I0TkWy1ALY/9bkpyynIeW7zPR" + "ZicnJwyq9/0zgpmkgnomMG55cjyceUvTi8zQRIs0gC2TR4FDcOi1ACwg3LklRhU83kXEt" + "gNTbxoDQD77ucYx0+ZjwDALp5Ey7nBmGHowYxUAVW+jIkx0dExcQubUl2Gx0RHR8fG7TR" + "VMJeZgY4ZnFJYSlIFTCp3u1EaLNJ2Y2p4E9sAYDsl69UYWwZ1ushHrOSHEwCMK3rKtIly" + "FABg30Q9WsUWPQPw21f3iEiBChAHhorb9Xrt3WpLhstHOr1eryxvQnkP5paxUQV1xX4er" + "/r+C8yDEAEBwLyYdv7VGLROdXXn1pvmuZ0CIA0PqO3OnugX3wwAzQnuZ5RCn2tA10pmvi" + "JSoAI08Yx8ALWjEw1FA2I1AI7xs3ox5XWj1poA0wJWyYMeTH77NM8CADWDwwgU0Tf2mcB" + "jAqUAUOJP4dzDOa/Q7xt/eYe65fl++zTaZW5OcXH2HGqMzHGAFpdf8rGnSCaPFKgAfEuP" + "Olt3fppbJnTz3NaV3s0cHFL3AuNbphzne+ThoxOBo568+DJncZIe+WNj8UzqJzYUMybmq" + "Z0w44PvA4Bll/sQMYwZXGbQIMYSRY/59jQ6neVFZ8y9B2g3env7McZchzxKqALQmcH1Cv" + "TxXqcFGhPofgEDwwuBCt9kM2CY41+G/ADGG8M9A/IgjZisBWxbvQrQtc+HFcT1TFYDD0V" + "uAeV/xHRdKujeppW5eTrAcvPA+vSCtt61pD63d8WqzMs6AOj8+Wjm6UdA58UiIwBYbny+" + "9fDVTgBoydm562Q9AFVumR2wluSp4LhzaPWarCoH9IVXzICj6rQMMF0/sGF/kR6A4252r" + "uqvzjNWp2eH+U/+kbYXd3b7S9IWy98YgDx2/k8wvwNEPGrBGochUwAAAABJRU5ErkJggg" + "==" + ) + return NO_SCREENSHOT diff --git a/seleniumbase/core/log_helper.py b/seleniumbase/core/log_helper.py index 996f7902c2e..be2aeadcd82 100755 --- a/seleniumbase/core/log_helper.py +++ b/seleniumbase/core/log_helper.py @@ -12,7 +12,15 @@ def log_screenshot(test_logpath, driver, screenshot=None, get=False): screenshot_name = settings.SCREENSHOT_NAME screenshot_path = os.path.join(test_logpath, screenshot_name) + screenshot_skipped = constants.Warnings.SCREENSHOT_SKIPPED screenshot_warning = constants.Warnings.SCREENSHOT_UNDEFINED + if ( + (hasattr(sb_config, "no_screenshot") and sb_config.no_screenshot) + or screenshot == screenshot_skipped + ): + if get: + return screenshot + return try: if not screenshot: element = driver.find_element_by_tag_name("body") @@ -67,6 +75,8 @@ def get_master_time(): def get_browser_version(driver): + if sys.version_info >= (3, 11) and hasattr(sb_config, "_browser_version"): + return sb_config._browser_version driver_capabilities = driver.capabilities if "version" in driver_capabilities: browser_version = driver_capabilities["version"] @@ -76,6 +86,8 @@ def get_browser_version(driver): def get_driver_name_and_version(driver, browser): + if hasattr(sb_config, "_driver_name_version"): + return sb_config._driver_name_version if driver.capabilities["browserName"].lower() == "chrome": cap_dict = driver.capabilities["chrome"] return ("chromedriver", cap_dict["chromedriverVersion"].split(" ")[0]) @@ -202,6 +214,10 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None): the_stacks = traceback.format_list( traceback.extract_tb(sys.last_traceback) ) + elif hasattr(sb_config, "_excinfo_tb"): + the_stacks = traceback.format_list( + traceback.extract_tb(sb_config._excinfo_tb) + ) else: message = None if hasattr(test, "is_behave") and test.is_behave: @@ -218,7 +234,9 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None): if hasattr(sys, "last_value"): last_value = sys.last_value if last_value: - data_to_save.append("Exception: " + str(last_value)) + data_to_save.append("Exception: %s" + str(last_value)) + elif hasattr(sb_config, "_excinfo_value"): + data_to_save.append("Exception: %s" % sb_config._excinfo_value) else: data_to_save.append("Traceback: " + traceback_message) if hasattr(test, "is_nosetest") and test.is_nosetest: @@ -233,6 +251,11 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None): sb_config._report_time = the_time sb_config._report_traceback = traceback_message sb_config._report_exception = exc_message + try: + if not os.path.exists(test_logpath): + os.makedirs(test_logpath) + except Exception: + pass log_file = codecs.open(basic_file_path, "w+", "utf-8") log_file.writelines("\r\n".join(data_to_save)) log_file.close() @@ -315,6 +338,11 @@ def log_page_source(test_logpath, driver, source=None): "unresponsive, or closed prematurely!" ) ) + try: + if not os.path.exists(test_logpath): + os.makedirs(test_logpath) + except Exception: + pass html_file_path = os.path.join(test_logpath, html_file_name) html_file = codecs.open(html_file_path, "w+", "utf-8") html_file.write(page_source) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 3ba0dd7a132..d9cd53c97a1 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -475,7 +475,9 @@ def click( except Exception: pass if self.browser == "safari": - time.sleep(0.02) + time.sleep(0.01) + self.wait_for_ready_state_complete() + time.sleep(0.01) if self.demo_mode: if self.driver.current_url != pre_action_url: self.__demo_mode_pause_if_active() @@ -3120,6 +3122,12 @@ def set_content_to_parent_frame(self): def open_new_window(self, switch_to=True): """Opens a new browser tab/window and switches to it by default.""" self.wait_for_ready_state_complete() + if hasattr(self.driver, "tab_new"): + self.driver.tab_new("about:blank") + if switch_to: + self.switch_to_newest_window() + time.sleep(0.01) + return if selenium4_or_newer and switch_to: self.driver.switch_to.new_window("tab") else: @@ -3454,38 +3462,38 @@ def get_new_driver( # Make sure the invisible browser window is big enough width = settings.HEADLESS_START_WIDTH height = settings.HEADLESS_START_HEIGHT - try: - self.driver.set_window_size(width, height) - self.wait_for_ready_state_complete() - except Exception: - # This shouldn't fail, but in case it does, - # get safely through setUp() so that - # WebDrivers can get closed during tearDown(). - pass + if self.browser != "chrome" and self.browser != "edge": + try: + self.driver.set_window_size(width, height) + # self.wait_for_ready_state_complete() + except Exception: + # This shouldn't fail, but in case it does, + # get safely through setUp() so that + # WebDrivers can get closed during tearDown(). + pass else: + width = settings.CHROME_START_WIDTH + height = settings.CHROME_START_HEIGHT if self.browser == "chrome" or self.browser == "edge": - width = settings.CHROME_START_WIDTH - height = settings.CHROME_START_HEIGHT try: if self.maximize_option: self.driver.maximize_window() + self.wait_for_ready_state_complete() else: - self.driver.set_window_size(width, height) - self.wait_for_ready_state_complete() + pass # Now handled in browser_launcher.py + # self.driver.set_window_size(width, height) except Exception: pass # Keep existing browser resolution elif self.browser == "firefox": - width = settings.CHROME_START_WIDTH try: if self.maximize_option: self.driver.maximize_window() + self.wait_for_ready_state_complete() else: - self.driver.set_window_size(width, 720) - self.wait_for_ready_state_complete() + self.driver.set_window_size(width, height) except Exception: pass # Keep existing browser resolution elif self.browser == "safari": - width = settings.CHROME_START_WIDTH if self.maximize_option: try: self.driver.maximize_window() @@ -3494,11 +3502,10 @@ def get_new_driver( pass # Keep existing browser resolution else: try: - self.driver.set_window_rect(10, 30, width, 630) + self.driver.set_window_rect(10, 20, width, height) except Exception: pass elif self.browser == "opera": - width = settings.CHROME_START_WIDTH if self.maximize_option: try: self.driver.maximize_window() @@ -3507,7 +3514,7 @@ def get_new_driver( pass # Keep existing browser resolution else: try: - self.driver.set_window_rect(10, 30, width, 700) + self.driver.set_window_rect(10, 20, width, height) except Exception: pass if self.start_page and len(self.start_page) >= 4: @@ -11388,6 +11395,11 @@ def __assert_eq(self, *args, **kwargs): raise VisualException(minified_exception) + def _process_visual_baseline_logs(self): + if sys.version_info < (3, 11): + return + self.__process_visual_baseline_logs() + def __process_visual_baseline_logs(self): """Save copies of baseline PNGs in "./latest_logs" during failures. Also create a side_by_side.html file for visual comparisons.""" @@ -12925,6 +12937,7 @@ def setUp(self, masterqa_mode=False): settings.HEADLESS_START_HEIGHT = height self.maximize_option = sb_config.maximize_option self.save_screenshot_after_test = sb_config.save_screenshot + self.no_screenshot_after_test = sb_config.no_screenshot self.visual_baseline = sb_config.visual_baseline self.timeout_multiplier = sb_config.timeout_multiplier self.pytest_html_report = sb_config.pytest_html_report @@ -13032,6 +13045,31 @@ def setUp(self, masterqa_mode=False): self.data_path = os.path.join(self.log_path, self.__get_test_id()) self.data_abspath = os.path.abspath(self.data_path) + # Add _test_logpath value to sb_config + test_id = self.__get_test_id() + test_logpath = os.path.join(self.log_path, test_id) + sb_config._test_logpath = test_logpath + + # Add _process_dashboard_entry method to sb_config + sb_config._process_dashboard_entry = self._process_dashboard_entry + + # Add _add_pytest_html_extra method to sb_config + sb_config._add_pytest_html_extra = self._add_pytest_html_extra + + # Add _process_visual_baseline_logs method to sb_config + sb_config._process_v_baseline_logs = self._process_visual_baseline_logs + + # Add _log_fail_data method to sb_config + sb_config._log_fail_data = self._log_fail_data + + # Reset the last_page_screenshot variables + sb_config._last_page_screenshot = None + sb_config._last_page_screenshot_png = None + + # Indictate to pytest reports that SeleniumBase is being used + sb_config._sbase_detected = True + sb_config._only_unittest = False + # Mobile Emulator device metrics: CSS Width, CSS Height, & Pixel-Ratio if self.device_metrics: metrics_string = self.device_metrics @@ -13074,15 +13112,11 @@ def setUp(self, masterqa_mode=False): if self.dashboard: if self._multithreaded: with self.dash_lock: - sb_config._sbase_detected = True - sb_config._only_unittest = False if not self._dash_initialized: sb_config._dashboard_initialized = True self._dash_initialized = True self.__process_dashboard(False, init=True) else: - sb_config._sbase_detected = True - sb_config._only_unittest = False if not self._dash_initialized: sb_config._dashboard_initialized = True self._dash_initialized = True @@ -13226,38 +13260,73 @@ def setUp(self, masterqa_mode=False): def __set_last_page_screenshot(self): """self.__last_page_screenshot is only for pytest html report logs. self.__last_page_screenshot_png is for all screenshot log files.""" - if not self.__last_page_screenshot and ( - not self.__last_page_screenshot_png + 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 + ): + from seleniumbase.core import encoded_images + + NO_SCREENSHOT = encoded_images.get_no_screenshot_png() + self.__last_page_screenshot = NO_SCREENSHOT + self.__last_page_screenshot_png = SCREENSHOT_SKIPPED + sb_config._last_page_screenshot_png = NO_SCREENSHOT + return + element = None + if ( + not self.__last_page_screenshot + and not self.__last_page_screenshot_png ): try: element = self.driver.find_element(by="tag name", value="body") - if self.is_pytest and self.report_on: - self.__last_page_screenshot_png = ( - self.driver.get_screenshot_as_png() - ) + try: self.__last_page_screenshot = element.screenshot_as_base64 - else: - self.__last_page_screenshot_png = element.screenshot_as_png - except Exception: - if not self.__last_page_screenshot: - if self.is_pytest and self.report_on: - try: - self.__last_page_screenshot = ( - self.driver.get_screenshot_as_base64() - ) - except Exception: - self.__last_page_screenshot = ( - constants.Warnings.SCREENSHOT_UNDEFINED - ) - if not self.__last_page_screenshot_png: + except Exception: try: - self.__last_page_screenshot_png = ( - self.driver.get_screenshot_as_png() + self.__last_page_screenshot = ( + self.driver.get_screenshot_as_base64() ) except Exception: + pass + except Exception: + pass + if not self.__last_page_screenshot: + self.__last_page_screenshot = SCREENSHOT_UNDEFINED + self.__last_page_screenshot_png = SCREENSHOT_UNDEFINED + if element: + try: self.__last_page_screenshot_png = ( - constants.Warnings.SCREENSHOT_UNDEFINED + element.screenshot_as_png ) + except Exception: + try: + self.__last_page_screenshot_png = ( + self.driver.get_screenshot_as_png() + ) + except Exception: + pass + else: + import base64 + + try: + self.__last_page_screenshot_png = ( + base64.b64decode(self.__last_page_screenshot) + ) + except Exception: + if element: + try: + self.__last_page_screenshot_png = ( + element.screenshot_as_png + ) + except Exception: + try: + self.__last_page_screenshot_png = ( + self.driver.get_screenshot_as_png() + ) + except Exception: + pass + sb_config._last_page_screenshot_png = self.__last_page_screenshot_png def __set_last_page_url(self): if not self.__last_page_url: @@ -13278,6 +13347,7 @@ def __set_last_page_source(self): self.__last_page_source = ( constants.Warnings.PAGE_SOURCE_UNDEFINED ) + sb_config._last_page_source = self.__last_page_source def __get_exception_info(self): exc_message = None @@ -13332,6 +13402,11 @@ def __insert_test_result(self, state, err): data_payload.message = "Skipped: (no reason given)" self.testcase_manager.update_testcase_data(data_payload) + def _add_pytest_html_extra(self): + if sys.version_info < (3, 11): + return + self.__add_pytest_html_extra() + def __add_pytest_html_extra(self): if not self.__added_pytest_html_extra: try: @@ -13345,7 +13420,7 @@ def __add_pytest_html_extra(self): extra_url["name"] = "URL" extra_url["format"] = "url" extra_url["format_type"] = "url" - extra_url["content"] = self.get_current_url() + extra_url["content"] = self.__last_page_url extra_url["mime_type"] = None extra_url["extension"] = None extra_image = {} @@ -13429,8 +13504,12 @@ def __has_exception(self): return True else: return False - elif python3 and hasattr(self, "_outcome"): - if hasattr(self._outcome, "errors") and self._outcome.errors: + elif ( + python3 + and hasattr(self, "_outcome") + and hasattr(self._outcome, "errors") + ): + if self._outcome.errors: has_exception = True else: if python3: @@ -13556,6 +13635,18 @@ def __create_log_path_as_needed(self, test_logpath): except Exception: pass # Only reachable during multi-threaded runs + def _process_dashboard_entry(self, has_exception, init=False): + if self._multithreaded: + import fasteners + + self.dash_lock = fasteners.InterProcessLock( + constants.Dashboard.LOCKFILE + ) + with self.dash_lock: + self.__process_dashboard(has_exception, init) + else: + self.__process_dashboard(has_exception, init) + def __process_dashboard(self, has_exception, init=False): """SeleniumBase Dashboard Processing""" if self._multithreaded: @@ -13959,6 +14050,66 @@ def save_teardown_screenshot(self): if self.is_pytest: self.__add_pytest_html_extra() + def _log_fail_data(self): + if sys.version_info < (3, 11): + return + test_id = self.__get_test_id() + test_logpath = os.path.join(self.log_path, test_id) + log_helper.log_test_failure_data( + self, + test_logpath, + self.driver, + self.browser, + self.__last_page_url, + ) + + def _get_browser_version(self): + driver_capabilities = None + if hasattr(self, "driver") and hasattr(self.driver, "capabilities"): + driver_capabilities = self.driver.capabilities + elif hasattr(sb_config, "_browser_version"): + return sb_config._browser_version + else: + return "(Unknown Version)" + if "version" in driver_capabilities: + browser_version = driver_capabilities["version"] + else: + browser_version = driver_capabilities["browserVersion"] + return browser_version + + def _get_driver_name_and_version(self): + if not hasattr(self.driver, "capabilities"): + if hasattr(sb_config, "_driver_name_version"): + return sb_config._driver_name_version + else: + return None + driver = self.driver + if driver.capabilities["browserName"].lower() == "chrome": + cap_dict = driver.capabilities["chrome"] + return ( + "chromedriver", cap_dict["chromedriverVersion"].split(" ")[0] + ) + elif driver.capabilities["browserName"].lower() == "msedge": + cap_dict = driver.capabilities["msedge"] + return ( + "msedgedriver", cap_dict["msedgedriverVersion"].split(" ")[0] + ) + elif driver.capabilities["browserName"].lower() == "opera": + cap_dict = driver.capabilities["opera"] + return ( + "operadriver", cap_dict["operadriverVersion"].split(" ")[0] + ) + elif driver.capabilities["browserName"].lower() == "firefox": + return ( + "geckodriver", driver.capabilities["moz:geckodriverVersion"] + ) + elif self.browser == "safari": + return ("safaridriver", self._get_browser_version()) + elif self.browser == "ie": + return ("iedriver", self._get_browser_version()) + else: + return None + def tearDown(self): """ Be careful if a subclass of BaseCase overrides setUp() @@ -14006,6 +14157,10 @@ def tearDown(self): # *** Start tearDown() officially *** self.__slow_mode_pause_if_active() has_exception = self.__has_exception() + sb_config._has_exception = has_exception + sb_config._browser_version = self._get_browser_version() + sb_config._driver_name_version = self._get_driver_name_and_version() + if self.__overrided_default_timeouts: # Reset default timeouts in case there are more tests # These were changed in set_default_timeout() @@ -14055,6 +14210,11 @@ def tearDown(self): ) self.__add_pytest_html_extra() sb_config._has_logs = True + elif sys.version_info >= (3, 11) and not has_exception: + # Handle a bug on Python 3.11 where exceptions aren't seen + self.__set_last_page_screenshot() + self.__set_last_page_url() + self.__set_last_page_source() if self.with_testing_base and has_exception: test_logpath = os.path.join(self.log_path, test_id) self.__create_log_path_as_needed(test_logpath) @@ -14267,8 +14427,10 @@ def tearDown(self): # (Nosetests / Behave / Pure Python) Close all open browser windows self.__quit_all_drivers() # Resume tearDown() for all test runners, (Pytest / Nosetests / Behave) - if has_exception and self.__visual_baseline_copies: - self.__process_visual_baseline_logs() + if self.__visual_baseline_copies: + sb_config._visual_baseline_copies = True + if has_exception: + self.__process_visual_baseline_logs() if deferred_exception: # User forgot to call "self.process_deferred_asserts()" in test raise deferred_exception diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py index 72b9cdc6d05..e8e7324ce9c 100755 --- a/seleniumbase/fixtures/constants.py +++ b/seleniumbase/fixtures/constants.py @@ -142,6 +142,7 @@ class Values: class Warnings: + SCREENSHOT_SKIPPED = "Skipping screenshot!" SCREENSHOT_UNDEFINED = "Unable to get screenshot!" PAGE_SOURCE_UNDEFINED = "Unable to get page source!" INVALID_RUN_COMMAND = "INVALID RUN COMMAND!" diff --git a/seleniumbase/fixtures/js_utils.py b/seleniumbase/fixtures/js_utils.py index 04f416f6746..2b8f4f861b5 100755 --- a/seleniumbase/fixtures/js_utils.py +++ b/seleniumbase/fixtures/js_utils.py @@ -671,7 +671,7 @@ def activate_messenger(driver): if not is_jquery_activated(driver): add_js_link(driver, jquery_js) - wait_for_jquery_active(driver, timeout=0.9) + wait_for_jquery_active(driver, timeout=1) add_css_link(driver, messenger_css) add_css_link(driver, msgr_theme_flat_css) add_css_link(driver, msgr_theme_future_css) @@ -756,8 +756,13 @@ def set_messenger_theme( try: driver.execute_script(msg_style) except Exception: + time.sleep(0.05) activate_messenger(driver) - driver.execute_script(msg_style) + time.sleep(0.05) + try: + driver.execute_script(msg_style) + except Exception: + pass time.sleep(0.1) diff --git a/seleniumbase/plugins/base_plugin.py b/seleniumbase/plugins/base_plugin.py index d27384e7972..07934c22a0a 100755 --- a/seleniumbase/plugins/base_plugin.py +++ b/seleniumbase/plugins/base_plugin.py @@ -291,7 +291,7 @@ def addSuccess(self, test, capt): ) ) - def add_fails_or_errors(self, test): + def add_fails_or_errors(self, test, err): if self.report_on: self.duration = str( "%.2fs" % (float(time.time()) - float(self.start_time)) @@ -307,10 +307,36 @@ def add_fails_or_errors(self, test): test, self.test_count, self.duration ) ) + if sys.version_info >= (3, 11): + # Handle a bug on Python 3.11 where exceptions aren't seen + sb_config._browser_version = None + try: + test._BaseCase__set_last_page_screenshot() + test._BaseCase__set_last_page_url() + test._BaseCase__set_last_page_source() + sb_config._browser_version = test._get_browser_version() + test._log_fail_data() + except Exception: + pass + sb_config._excinfo_tb = err + log_path = None + if hasattr(sb_config, "_test_logpath"): + log_path = sb_config._test_logpath + if hasattr(sb_config, "_last_page_source"): + source = sb_config._last_page_source + if log_path and source: + log_helper.log_page_source(log_path, None, source) + last_page_screenshot_png = None + if hasattr(sb_config, "_last_page_screenshot_png"): + last_page_screenshot_png = sb_config._last_page_screenshot_png + if log_path and last_page_screenshot_png: + log_helper.log_screenshot( + log_path, None, last_page_screenshot_png + ) def addFailure(self, test, err, capt=None, tbinfo=None): # self.__log_all_options_if_none_specified(test) - self.add_fails_or_errors(test) + self.add_fails_or_errors(test, err) def addError(self, test, err, capt=None): """ @@ -338,7 +364,7 @@ def addError(self, test, err, capt=None): else: # self.__log_all_options_if_none_specified(test) pass - self.add_fails_or_errors(test) + self.add_fails_or_errors(test, err) def handleError(self, test, err, capt=None): """ diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index f5c15e4633d..b6d2a5320c8 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -10,6 +10,7 @@ import time from seleniumbase import config as sb_config from seleniumbase.config import settings +from seleniumbase.core import log_helper from seleniumbase.fixtures import constants is_windows = False @@ -106,6 +107,7 @@ def pytest_addoption(parser): --window-size=WIDTH,HEIGHT (Set the browser's starting window size.) --maximize (Start tests with the browser window maximized.) --screenshot (Save a screenshot at the end of each test.) + --no-screenshot (No screenshots saved unless tests directly ask it.) --visual-baseline (Set the visual baseline for Visual/Layout tests.) --wire (Use selenium-wire's webdriver for replacing selenium webdriver.) --external-pdf (Set Chromium "plugins.always_open_pdf_externally":True.) @@ -897,7 +899,7 @@ def pytest_addoption(parser): action="store_true", dest="use_auto_ext", default=False, - help="""Using this enables Chrome's Automation Extension. + help="""(DEPRECATED) - Enable the automation extension. It's not required, but some commands & advanced features may need it.""", ) @@ -1083,8 +1085,20 @@ def pytest_addoption(parser): action="store_true", dest="save_screenshot", default=False, - help="""Save a screenshot at the end of the test. - (Added to the "latest_logs/" folder.)""", + help="""Save a screenshot at the end of every test. + By default, this is only done for failures. + Will be saved in the "latest_logs/" folder.""", + ) + parser.addoption( + "--no-screenshot", + "--no_screenshot", + "--ns", + action="store_true", + dest="no_screenshot", + default=False, + help="""No screenshots saved unless tests directly ask it. + This changes default behavior where screenshots are + saved for test failures and pytest-html reports.""", ) parser.addoption( "--visual_baseline", @@ -1435,6 +1449,7 @@ def pytest_configure(config): sb_config.window_size = config.getoption("window_size") sb_config.maximize_option = config.getoption("maximize_option") sb_config.save_screenshot = config.getoption("save_screenshot") + sb_config.no_screenshot = config.getoption("no_screenshot") sb_config.visual_baseline = config.getoption("visual_baseline") sb_config.use_wire = config.getoption("use_wire") sb_config.external_pdf = config.getoption("external_pdf") @@ -1559,12 +1574,14 @@ def pytest_configure(config): if sb_config.dash_title: constants.Dashboard.TITLE = sb_config.dash_title.replace("_", " ") + if sb_config.save_screenshot and sb_config.no_screenshot: + sb_config.save_screenshot = False # "no_screenshot" has priority + if ( sb_config._multithreaded and "--co" not in sys_argv and "--collect-only" not in sys_argv ): - from seleniumbase.core import log_helper from seleniumbase.core import download_helper from seleniumbase.core import proxy_helper @@ -1666,7 +1683,6 @@ def pytest_collection_finish(session): if "--co" in sys_argv or "--collect-only" in sys_argv: return if len(session.items) > 0 and not sb_config._multithreaded: - from seleniumbase.core import log_helper from seleniumbase.core import download_helper from seleniumbase.core import proxy_helper @@ -1813,7 +1829,6 @@ def pytest_terminal_summary(terminalreporter): def _perform_pytest_unconfigure_(): - from seleniumbase.core import log_helper from seleniumbase.core import proxy_helper proxy_helper.remove_proxy_zip_if_present() @@ -2090,6 +2105,41 @@ def pytest_runtest_makereport(item, call): sb_config._d_t_log_path[test_id] = "" if test_id not in sb_config._extra_dash_entries: sb_config._extra_dash_entries.append(test_id) + elif ( + sb_config._sbase_detected + and sys.version_info >= (3, 11) + and (report.outcome == "failed" or "AssertionError" in str(call)) + and not sb_config._has_exception + ): + # Handle a bug on Python 3.11 where exceptions aren't seen + log_path = "" + if hasattr(sb_config, "_test_logpath"): + log_path = sb_config._test_logpath + if sb_config.dashboard: + sb_config._process_dashboard_entry(True) + if hasattr(sb_config, "_add_pytest_html_extra"): + sb_config._add_pytest_html_extra() + if hasattr(sb_config, "_visual_baseline_copies"): + sb_config._process_v_baseline_logs() + sb_config._excinfo_value = call.excinfo.value + sb_config._excinfo_tb = call.excinfo.tb + if "pytest_plugin.BaseClass.base_method" not in log_path: + source = None + if hasattr(sb_config, "_last_page_source"): + source = sb_config._last_page_source + if log_path and source: + log_helper.log_page_source(log_path, None, source) + last_page_screenshot_png = None + if hasattr(sb_config, "_last_page_screenshot_png"): + last_page_screenshot_png = ( + sb_config._last_page_screenshot_png + ) + if log_path and last_page_screenshot_png: + log_helper.log_screenshot( + log_path, None, last_page_screenshot_png + ) + if log_path: + sb_config._log_fail_data() try: extra_report = None if hasattr(item, "_testcase"): diff --git a/seleniumbase/plugins/sb_manager.py b/seleniumbase/plugins/sb_manager.py index 9578716d5aa..f88f21fcee3 100644 --- a/seleniumbase/plugins/sb_manager.py +++ b/seleniumbase/plugins/sb_manager.py @@ -92,6 +92,7 @@ def SB( pls=None, # Shortcut / Duplicate of "page_load_strategy". sjw=None, # Shortcut / Duplicate of "skip_js_waits". save_screenshot=None, # Save a screenshot at the end of each test. + no_screenshot=None, # No screenshots saved unless tests directly ask it. timeout_multiplier=None, # Multiplies the default timeout values. js_checking_on=None, # Check for JavaScript errors after page loads. slow=None, # Slow down the automation. Faster than using Demo Mode. @@ -136,7 +137,7 @@ def SB( '\n (Prevent that by using: `if __name__ == "__main__":`)' ) elif hasattr(sb_config, "is_nosetest") and sb_config.is_nosetest: - print( + raise Exception( "\n SB Manager script was triggered by nosetest collection!" '\n (Prevent that by using: ``if __name__ == "__main__":``)' ) @@ -470,10 +471,21 @@ def SB( elif skip_js_waits: settings.SKIP_JS_WAITS = skip_js_waits if save_screenshot is None: - if "--screenshot" in sys_argv or "--save-screenshot" in sys_argv: + if ( + "--screenshot" in sys_argv + or "--save-screenshot" in sys_argv + or "--ss" in sys_argv + ): save_screenshot = True else: save_screenshot = False + if no_screenshot is None: + if "--no-screenshot" in sys_argv or "--ns" in sys_argv: + no_screenshot = True + else: + no_screenshot = False + if save_screenshot and no_screenshot: + save_screenshot = False # "no_screenshot" has priority if js_checking_on is None: if "--check-js" in sys_argv: js_checking_on = True @@ -605,6 +617,7 @@ def SB( sb_config.maximize_option = maximize_option sb_config._disable_beforeunload = _disable_beforeunload sb_config.save_screenshot = save_screenshot + sb_config.no_screenshot = no_screenshot sb_config.page_load_strategy = page_load_strategy sb_config.timeout_multiplier = timeout_multiplier sb_config.pytest_html_report = None @@ -699,6 +712,7 @@ def SB( sb.maximize_option = sb_config.maximize_option sb._disable_beforeunload = sb_config._disable_beforeunload sb.save_screenshot_after_test = sb_config.save_screenshot + sb.no_screenshot_after_test = sb_config.no_screenshot sb.page_load_strategy = sb_config.page_load_strategy sb.timeout_multiplier = sb_config.timeout_multiplier sb.pytest_html_report = sb_config.pytest_html_report diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index f258b3c9eaf..1a217306b03 100755 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -19,6 +19,10 @@ class SeleniumBrowser(Plugin): """ This plugin adds the following command-line options to nosetests: --browser=BROWSER (The web browser to use. Default: "chrome".) + --chrome (Shortcut for "--browser=chrome". On by default.) + --edge (Shortcut for "--browser=edge".) + --firefox (Shortcut for "--browser=firefox".) + --safari (Shortcut for "--browser=safari".) --user-data-dir=DIR (Set the Chrome user data directory to use.) --protocol=PROTOCOL (The Selenium Grid protocol: http|https.) --server=SERVER (The Selenium Grid server/IP used for tests.) @@ -103,13 +107,46 @@ def options(self, parser, env): Example: (--browser=firefox)""", ) parser.add_option( - "--browser_version", - "--browser-version", - action="store", - dest="browser_version", - default="latest", - help="""The browser version to use. Explicitly select - a version number or use "latest".""", + "--chrome", + action="store_true", + dest="use_chrome", + default=False, + help="""Shortcut for --browser=chrome. On by default.)""", + ) + parser.add_option( + "--edge", + action="store_true", + dest="use_edge", + default=False, + help="""Shortcut for --browser=edge.)""", + ) + parser.add_option( + "--firefox", + action="store_true", + dest="use_firefox", + default=False, + help="""Shortcut for --browser=firefox.)""", + ) + parser.add_option( + "--ie", + action="store_true", + dest="use_ie", + default=False, + help="""Shortcut for --browser=ie.)""", + ) + parser.add_option( + "--opera", + action="store_true", + dest="use_opera", + default=False, + help="""Shortcut for --browser=opera.)""", + ) + parser.add_option( + "--safari", + action="store_true", + dest="use_safari", + default=False, + help="""Shortcut for --browser=safari.)""", ) parser.add_option( "--cap_file", @@ -622,7 +659,7 @@ def options(self, parser, env): action="store_true", dest="use_auto_ext", default=False, - help="""Using this enables Chrome's Automation Extension. + help="""(DEPRECATED) - Enable the automation extension. It's not required, but some commands & advanced features may need it.""", ) @@ -770,8 +807,20 @@ def options(self, parser, env): action="store_true", dest="save_screenshot", default=False, - help="""Save a screenshot at the end of the test. - (Added to the "latest_logs/" folder.)""", + help="""Save a screenshot at the end of every test. + By default, this is only done for failures. + Will be saved in the "latest_logs/" folder.""", + ) + parser.add_option( + "--no-screenshot", + "--no_screenshot", + "--ns", + action="store_true", + dest="no_screenshot", + default=False, + help="""No screenshots saved unless tests directly ask it. + This changes default behavior where screenshots are + saved for test failures and pytest-html reports.""", ) parser.add_option( "--visual_baseline", @@ -823,8 +872,88 @@ def configure(self, options, conf): proxy_helper.remove_proxy_zip_if_present() def beforeTest(self, test): - sb_config._context_of_runner = False # Context Manager Compatibility browser = self.options.browser + test.test.browser = browser + test.test.headless = None + test.test.headless2 = None + # As a shortcut, you can use "--edge" instead of "--browser=edge", etc, + # but you can only specify one default browser. (Default: chrome) + sb_config._browser_shortcut = None + sys_argv = sys.argv + browser_changes = 0 + browser_set = None + browser_text = None + browser_list = [] + if "--browser=chrome" in sys_argv or "--browser chrome" in sys_argv: + browser_changes += 1 + browser_set = "chrome" + browser_list.append("--browser=chrome") + if "--browser=edge" in sys_argv or "--browser edge" in sys_argv: + browser_changes += 1 + browser_set = "edge" + browser_list.append("--browser=edge") + if "--browser=firefox" in sys_argv or "--browser firefox" in sys_argv: + browser_changes += 1 + browser_set = "firefox" + browser_list.append("--browser=firefox") + if "--browser=opera" in sys_argv or "--browser opera" in sys_argv: + browser_changes += 1 + browser_set = "opera" + browser_list.append("--browser=opera") + if "--browser=safari" in sys_argv or "--browser safari" in sys_argv: + browser_changes += 1 + browser_set = "safari" + browser_list.append("--browser=safari") + if "--browser=ie" in sys_argv or "--browser ie" in sys_argv: + browser_changes += 1 + browser_set = "ie" + browser_list.append("--browser=ie") + if "--browser=remote" in sys_argv or "--browser remote" in sys_argv: + browser_changes += 1 + browser_set = "remote" + browser_list.append("--browser=remote") + browser_text = browser_set + if "--chrome" in sys_argv and not browser_set == "chrome": + browser_changes += 1 + browser_text = "chrome" + sb_config._browser_shortcut = "chrome" + browser_list.append("--chrome") + if "--edge" in sys_argv and not browser_set == "edge": + browser_changes += 1 + browser_text = "edge" + sb_config._browser_shortcut = "edge" + browser_list.append("--edge") + if "--firefox" in sys_argv and not browser_set == "firefox": + browser_changes += 1 + browser_text = "firefox" + sb_config._browser_shortcut = "firefox" + browser_list.append("--firefox") + if "--ie" in sys_argv and not browser_set == "ie": + browser_changes += 1 + browser_text = "ie" + sb_config._browser_shortcut = "ie" + browser_list.append("--ie") + if "--opera" in sys_argv and not browser_set == "opera": + browser_changes += 1 + browser_text = "opera" + sb_config._browser_shortcut = "opera" + browser_list.append("--opera") + if "--safari" in sys_argv and not browser_set == "safari": + browser_changes += 1 + browser_text = "safari" + sb_config._browser_shortcut = "safari" + browser_list.append("--safari") + if browser_changes > 1: + message = "\n\n TOO MANY browser types were entered!" + message += "\n There were %s found:\n > %s" % ( + browser_changes, + ", ".join(browser_list), + ) + message += "\n ONLY ONE default browser is allowed!" + message += "\n Select a single browser & try again!\n" + raise Exception(message) + if browser_text: + browser = browser_text if self.options.recorder_mode and browser not in ["chrome", "edge"]: message = ( "\n\n Recorder Mode ONLY supports Chrome and Edge!" @@ -864,6 +993,9 @@ def beforeTest(self, test): sb_config.is_pytest = False sb_config.is_context_manager = False test.test.browser = self.options.browser + if sb_config._browser_shortcut: + self.options.browser = sb_config._browser_shortcut + test.test.browser = sb_config._browser_shortcut test.test.cap_file = self.options.cap_file test.test.cap_string = self.options.cap_string test.test.headless = self.options.headless @@ -948,7 +1080,10 @@ def beforeTest(self, test): test.test._disable_beforeunload = self.options._disable_beforeunload test.test.window_size = self.options.window_size test.test.maximize_option = self.options.maximize_option + if self.options.save_screenshot and self.options.no_screenshot: + self.options.save_screenshot = False # no_screenshot has priority test.test.save_screenshot_after_test = self.options.save_screenshot + test.test.no_screenshot_after_test = self.options.no_screenshot test.test.visual_baseline = self.options.visual_baseline test.test.use_wire = self.options.use_wire test.test.external_pdf = self.options.external_pdf @@ -956,6 +1091,7 @@ def beforeTest(self, test): test.test.dashboard = False test.test._multithreaded = False test.test._reuse_session = False + sb_config.no_screenshot = test.test.no_screenshot_after_test if test.test.servername != "localhost": # Using Selenium Grid # (Set --server="127.0.0.1" for localhost Grid) @@ -1029,6 +1165,7 @@ def beforeTest(self, test): sb_config._is_timeout_changed = False sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT + sb_config._context_of_runner = False # Context Manager Compatibility # The driver will be received later self.driver = None test.test.driver = self.driver diff --git a/seleniumbase/undetected/__init__.py b/seleniumbase/undetected/__init__.py index 10203d93c6d..4509ff3edfb 100644 --- a/seleniumbase/undetected/__init__.py +++ b/seleniumbase/undetected/__init__.py @@ -198,7 +198,7 @@ def __init__( try: import locale - language = locale.getdefaultlocale()[0].replace("_", "-") + language = locale.getlocale()[0].replace("_", "-") except Exception: pass if not language: diff --git a/setup.py b/setup.py index 38024f4234c..7373306856f 100755 --- a/setup.py +++ b/setup.py @@ -205,7 +205,7 @@ 'pytest-html==2.0.1;python_version>="3.6"', # Newer ones had issues 'pytest-metadata==1.8.0;python_version<"3.6"', 'pytest-metadata==1.11.0;python_version>="3.6" and python_version<"3.7"', # noqa: E501 - 'pytest-metadata==2.0.3;python_version>="3.7"', + 'pytest-metadata==2.0.4;python_version>="3.7"', "pytest-ordering==0.6", 'pytest-rerunfailures==8.0;python_version<"3.6"', 'pytest-rerunfailures==10.2;python_version>="3.6"', @@ -223,7 +223,7 @@ 'beautifulsoup4==4.11.1;python_version>="3.6"', 'cryptography==2.9.2;python_version<"3.6"', 'cryptography==36.0.2;python_version>="3.6" and python_version<"3.7"', - 'cryptography==38.0.1;python_version>="3.7"', + 'cryptography==38.0.3;python_version>="3.7"', 'pygments==2.5.2;python_version<"3.6"', 'pygments==2.13.0;python_version>="3.6"', 'prompt-toolkit==1.0.18;python_version<"3.6"',