diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 970d453963d..88e3d6f29a1 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -6,16 +6,21 @@ -------- - +

(Watch the CDP Mode tutorial on YouTube! ▶️)

-------- - +

(Watch "Hacking websites with CDP" on YouTube! ▶️)

-------- + +

(Watch "Web-Scraping with GitHub Actions" on YouTube! ▶️)

+ +-------- + 👤 UC Mode avoids bot-detection by first disconnecting WebDriver from the browser at strategic times, calling special PyAutoGUI methods to bypass CAPTCHAs (as needed), and finally reconnecting the driver afterwards so that WebDriver actions can be performed again. Although this approach works for bypassing simple CAPTCHAs, more flexibility is needed for bypassing bot-detection on websites with advanced protection. (That's where CDP Mode comes in.) 🐙 CDP Mode is based on python-cdp, trio-cdp, and nodriver. trio-cdp is an early implementation of python-cdp, and nodriver is a modern implementation of python-cdp. (Refactored Python-CDP code is imported from MyCDP.) diff --git a/examples/cdp_mode/raw_glassdoor.py b/examples/cdp_mode/raw_glassdoor.py new file mode 100644 index 00000000000..5fc31491f47 --- /dev/null +++ b/examples/cdp_mode/raw_glassdoor.py @@ -0,0 +1,11 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, ad_block=True) as sb: + url = "https://www.glassdoor.com/Reviews/index.htm" + sb.activate_cdp_mode(url) + sb.uc_gui_click_captcha() + sb.highlight('[data-test="global-nav-glassdoor-logo"]') + sb.highlight('[data-test="site-header-companies"]') + sb.highlight('[data-test="search-button"]') + sb.highlight('[data-test="sign-in-button"]') + sb.highlight('[data-test="company-search-autocomplete"]') diff --git a/examples/raw_invisible_captcha.py b/examples/raw_invisible_captcha.py new file mode 100644 index 00000000000..54989667179 --- /dev/null +++ b/examples/raw_invisible_captcha.py @@ -0,0 +1,9 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, incognito=True) as sb: + url = "https://seleniumbase.io/apps/invisible_recaptcha" + sb.activate_cdp_mode(url) + sb.sleep(1) + sb.assert_element("img#captcha-success", timeout=3) + sb.set_messenger_theme(location="top_left") + sb.post_message("SeleniumBase wasn't detected", duration=3) diff --git a/examples/raw_recaptcha.py b/examples/raw_recaptcha.py index 51cfc1647ba..8277f5670f8 100644 --- a/examples/raw_recaptcha.py +++ b/examples/raw_recaptcha.py @@ -1,14 +1,5 @@ from seleniumbase import SB -with SB(uc=True, test=True, incognito=True) as sb: - url = "https://seleniumbase.io/apps/recaptcha" - sb.activate_cdp_mode(url) - sb.sleep(1) - sb.uc_gui_handle_captcha() # Try with TAB + SPACEBAR - sb.assert_element("img#captcha-success", timeout=3) - sb.set_messenger_theme(location="top_left") - sb.post_message("SeleniumBase wasn't detected", duration=3) - with SB(uc=True, test=True, incognito=True) as sb: url = "https://seleniumbase.io/apps/recaptcha" sb.activate_cdp_mode(url) diff --git a/examples/uc_cdp_events.py b/examples/uc_cdp_events.py index 6fcaf2c2477..1e596a1f718 100644 --- a/examples/uc_cdp_events.py +++ b/examples/uc_cdp_events.py @@ -1,6 +1,6 @@ from rich.pretty import pprint from seleniumbase import BaseCase -BaseCase.main(__name__, __file__, "--uc", "--uc-cdp", "-s") +BaseCase.main(__name__, __file__, "--uc", "--uc-cdp") class CDPTests(BaseCase): diff --git a/examples/verify_undetected.py b/examples/verify_undetected.py index e00ab35ac15..555c485df8e 100644 --- a/examples/verify_undetected.py +++ b/examples/verify_undetected.py @@ -2,7 +2,7 @@ Some sites use scripts to detect Selenium, and then block you. To evade detection, add --uc as a pytest command-line option.""" from seleniumbase import BaseCase -BaseCase.main(__name__, __file__, "--uc", "-s") +BaseCase.main(__name__, __file__, "--uc") class UndetectedTest(BaseCase): diff --git a/help_docs/uc_mode.md b/help_docs/uc_mode.md index 02921e894ea..ef9d37f7cb9 100644 --- a/help_docs/uc_mode.md +++ b/help_docs/uc_mode.md @@ -8,22 +8,22 @@ --- - +

(Watch the 1st UC Mode tutorial on YouTube! ▶️)

---- - +

(Watch the 2nd UC Mode tutorial on YouTube! ▶️)

---- - +

(Watch the 3rd UC Mode tutorial on YouTube! ▶️)

---- - +

(Watch the 4th UC Mode tutorial on YouTube! ▶️)

---- diff --git a/integrations/docker/ReadMe.md b/integrations/docker/ReadMe.md index 455be9b4b70..024b4e24600 100644 --- a/integrations/docker/ReadMe.md +++ b/integrations/docker/ReadMe.md @@ -14,11 +14,11 @@ https://docs.docker.com/engine/install/ docker build -t seleniumbase . -If running on an Apple M1/M2 Mac, use this instead: +**(NOTE) - If running on an Apple M1/M2 Mac, use this instead:** docker build --platform linux/amd64 -t seleniumbase . -M1/M2 Mac users should also see [StackOverflow.com/a/76586216/7058266](https://stackoverflow.com/a/76586216/7058266) to **Enable Rosetta in Docker Desktop**. (Otherwise **you will** encounter errors like this when Chrome tries to launch: `"Chrome failed to start: crashed."`) +**M1/M2 Mac users** should also see [StackOverflow.com/a/76586216/7058266](https://stackoverflow.com/a/76586216/7058266) to **Enable Rosetta in Docker Desktop**. (Otherwise **you will** encounter errors like this when Chrome tries to launch: `"Chrome failed to start: crashed."`) #### 4. Run [the example test](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py) with Chrome inside your Docker: (Once the test completes after a few seconds, you'll automatically exit the Docker shell) diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 4379c934aae..865cb756b92 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -18,7 +18,7 @@ lxml==5.3.0 pyquery==2.0.1 readtime==3.0.0 mkdocs==1.6.1 -mkdocs-material==9.5.49 +mkdocs-material==9.5.50 mkdocs-exclude-search==0.6.6 mkdocs-simple-hooks==0.1.5 mkdocs-material-extensions==1.3.1 diff --git a/requirements.txt b/requirements.txt index b0b6de70735..d3037254d8c 100755 --- a/requirements.txt +++ b/requirements.txt @@ -7,8 +7,9 @@ attrs>=24.3.0 certifi>=2024.12.14 exceptiongroup>=1.2.2 websockets~=13.1;python_version<"3.9" -websockets>=14.1;python_version>="3.9" -filelock>=3.16.1 +websockets>=14.2;python_version>="3.9" +filelock~=3.16.1;python_version<"3.9" +filelock>=3.17.0;python_version>="3.9" fasteners>=0.19 mycdp>=1.1.0 pynose>=1.5.3 @@ -41,7 +42,8 @@ trio==0.28.0;python_version>="3.9" trio-websocket==0.11.1 wsproto==1.2.0 websocket-client==1.8.0 -selenium==4.27.1 +selenium==4.27.1;python_version<"3.9" +selenium==4.28.0;python_version>="3.9" cssselect==1.2.0 sortedcontainers==2.4.0 execnet==2.1.1 diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 905605c9b17..d897bc15c8d 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.33.15" +__version__ = "4.34.0" diff --git a/seleniumbase/console_scripts/ReadMe.md b/seleniumbase/console_scripts/ReadMe.md index 04dcc5b5911..43faa057e6c 100644 --- a/seleniumbase/console_scripts/ReadMe.md +++ b/seleniumbase/console_scripts/ReadMe.md @@ -290,7 +290,9 @@ sbase mkdir ui_tests * Options: -``-b`` / ``--basic`` (Only config files. No tests added.) +```bash +-b / --basic (Only config files. No tests added.) +``` * Output: @@ -350,27 +352,33 @@ sbase mkfile new_test.py * Options: -``--uc`` (UC Mode boilerplate using SB context manager) -`-b` / `--basic` (Basic boilerplate / single-line test) -`-r` / `--rec` (Adds Pdb+ breakpoint for Recorder Mode) -``--url=URL`` (Makes the test start on a specific page) +```bash +--uc (UC Mode boilerplate using SB context manager) +-b / --basic (Basic boilerplate / single-line test) +-r / --rec (Adds Pdb+ breakpoint for Recorder Mode) +--url=URL (Makes the test start on a specific page) +``` * Language Options: -``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` -``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` -``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` -``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` -``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` +```bash +--en / --English | --zh / --Chinese +--nl / --Dutch | --fr / --French +--it / --Italian | --ja / --Japanese +--ko / --Korean | --pt / --Portuguese +--ru / --Russian | --es / --Spanish +``` * Syntax Formats: -``--bc`` / ``--basecase`` (BaseCase class inheritance) -``--pf`` / ``--pytest-fixture`` (sb pytest fixture) -``--cf`` / ``--class-fixture`` (class + sb pytest fixture) -``--cm`` / ``--context-manager`` (SB context manager) -``--dc`` / ``--driver-context`` (DriverContext manager) -``--dm`` / ``--driver-manager`` (Driver manager) +```bash +--bc / --basecase (BaseCase class inheritance) +--pf / --pytest-fixture (sb pytest fixture) +--cf / --class-fixture (class + sb pytest fixture) +--cm / --context-manager (SB context manager) +--dc / --driver-context (DriverContext manager) +--dm / --driver-manager (Driver manager) +``` * Output: @@ -404,13 +412,15 @@ sbase codegen new_test.py --url=wikipedia.org * Options: -``--url=URL`` (Sets the initial start page URL.) -``--edge`` (Use Edge browser instead of Chrome.) -``--gui`` / ``--headed`` (Use headed mode on Linux.) -``--uc`` / ``--undetected`` (Use undetectable mode.) -``--ee`` (Use SHIFT + ESC to end the recording.) -``--overwrite`` (Overwrite file when it exists.) -``--behave`` (Also output Behave/Gherkin files.) +```bash +--url=URL (Sets the initial start page URL.) +--edge (Use Edge browser instead of Chrome.) +--gui / --headed (Use headed mode on Linux.) +--uc / --undetected (Use undetectable mode.) +--ee (Use SHIFT + ESC to end the recording.) +--overwrite (Overwrite file when it exists.) +--behave (Also output Behave/Gherkin files.) +``` * Output: @@ -427,8 +437,10 @@ sbase recorder [OPTIONS] * Options: -``--uc`` / ``--undetected`` (Use undetectable mode.) -``--behave`` (Also output Behave/Gherkin files.) +```bash +--uc / --undetected (Use undetectable mode.) +--behave (Also output Behave/Gherkin files.) +``` * Output: @@ -450,11 +462,13 @@ sbase mkpres new_presentation.py --en * Language Options: -``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` -``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` -``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` -``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` -``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` +```bash +--en / --English | --zh / --Chinese +--nl / --Dutch | --fr / --French +--it / --Italian | --ja / --Japanese +--ko / --Korean | --pt / --Portuguese +--ru / --Russian | --es / --Spanish +``` * Output: @@ -480,11 +494,13 @@ sbase mkchart new_chart.py --en * Language Options: -``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` -``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` -``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` -``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` -``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` +```bash +--en / --English | --zh / --Chinese +--nl / --Dutch | --fr / --French +--it / --Italian | --ja / --Japanese +--ko / --Korean | --pt / --Portuguese +--ru / --Russian | --es / --Spanish +``` * Output: @@ -504,7 +520,9 @@ sbase print [FILE] [OPTIONS] * Options: -``-n`` (Add line Numbers to the rows) +```bash +-n (Add line Numbers to the rows) +``` * Output: @@ -521,21 +539,27 @@ sbase translate [SB_FILE.py] [LANGUAGE] [ACTION] * Languages: -``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` -``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` -``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` -``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` -``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` +```bash +--en / --English | --zh / --Chinese +--nl / --Dutch | --fr / --French +--it / --Italian | --ja / --Japanese +--ko / --Korean | --pt / --Portuguese +--ru / --Russian | --es / --Spanish +``` * Actions: -``-p`` / ``--print`` (Print translation output to the screen) -``-o`` / ``--overwrite`` (Overwrite the file being translated) -``-c`` / ``--copy`` (Copy the translation to a new ``.py`` file) +```bash +-p / --print (Print translation output to the screen) +-o / --overwrite (Overwrite the file being translated) +-c / --copy (Copy the translation to a new ``.py`` file) +``` * Options: -``-n`` (include line Numbers when using the Print action) +```bash +-n (include line Numbers when using the Print action) +``` * Output: @@ -573,7 +597,9 @@ sbase inject-objects [SB_FILE.py] [OPTIONS] * Options: -``-c``, ``--comments`` (Add object selectors to the comments.) +```bash +-c / --comments (Add object selectors to the comments.) +``` * Output: @@ -591,7 +617,9 @@ sbase objectify [SB_FILE.py] [OPTIONS] * Options: -``-c``, ``--comments`` (Add object selectors to the comments.) +```bash +-c / --comments (Add object selectors to the comments.) +``` * Output: @@ -611,7 +639,9 @@ sbase revert-objects [SB_FILE.py] [OPTIONS] * Options: -``-c``, ``--comments`` (Keep existing comments for the lines.) +```bash +-c / --comments (Keep existing comments for the lines.) +``` * Output: @@ -639,7 +669,7 @@ Works on both Selenium IDE & Katalon Recorder scripts. * Usage: -``sbase encrypt`` OR ``sbase obfuscate`` +``sbase encrypt`` / ``sbase obfuscate`` * Output: @@ -650,7 +680,7 @@ Runs the password encryption/obfuscation tool. * Usage: -``sbase decrypt`` OR ``sbase unobfuscate`` +``sbase decrypt`` / ``sbase unobfuscate`` * Output: @@ -667,9 +697,11 @@ sbase proxy [OPTIONS] * Options: -``--hostname=HOSTNAME`` (Set ``hostname``) (Default: ``127.0.0.1``) -``--port=PORT`` (Set ``port``) (Default: ``8899``) -``--help`` / ``-h`` (Display list of all available ``proxy`` options.) +```bash +--hostname=HOSTNAME (Set `hostname`) (Default: `127.0.0.1`) +--port=PORT (Set `port`) (Default: `8899`) +--help / -h (Display available `proxy` options.) +``` * Output: @@ -699,8 +731,10 @@ sbase grid-hub {start|stop|restart} [OPTIONS] * Options: -``-v``, ``--verbose`` (Increases verbosity of logging output.) -``--timeout=TIMEOUT`` (Close idle browser windows after TIMEOUT seconds.) +```bash +-v / --verbose (Increases verbosity of logging output.) +--timeout=TIMEOUT (Close idle browser windows after TIMEOUT seconds.) +``` * Output: @@ -720,8 +754,10 @@ sbase grid-node {start|stop|restart} [OPTIONS] * Options: -``--hub=HUB_IP`` (The Grid Hub IP Address to connect to.) (Default: ``127.0.0.1``) -``-v``, ``--verbose`` (Increases verbosity of logging output.) +```bash +--hub=HUB_IP (Grid Hub IP Address. Default: `127.0.0.1`) +-v / --verbose (Increases verbosity of logging output.) +``` * Output: diff --git a/seleniumbase/console_scripts/sb_commander.py b/seleniumbase/console_scripts/sb_commander.py index 9219efef62f..0ea306327b1 100644 --- a/seleniumbase/console_scripts/sb_commander.py +++ b/seleniumbase/console_scripts/sb_commander.py @@ -156,9 +156,9 @@ def do_pytest_run( if save_screenshots: full_run_command += " --screenshot" - dash_s_needed = False - if "-s" not in additional_options.split(" "): - dash_s_needed = True + capture_needed = False + if "--capture" not in additional_options: + capture_needed = True additional_options = additional_options.strip() if additional_options: @@ -168,8 +168,8 @@ def do_pytest_run( if verbose: full_run_command += " -v" - if dash_s_needed: - full_run_command += " -s" + if capture_needed: + full_run_command += " --capture=tee-sys" print(full_run_command) subprocess.Popen(full_run_command, shell=True) diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 8c94ece629b..2ac488f4caf 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -888,6 +888,13 @@ def __install_pyautogui_if_missing(): _xvfb_display.start() sb_config._virtual_display = _xvfb_display sb_config.headless_active = True + if ( + hasattr(sb_config, "reuse_session") + and sb_config.reuse_session + and hasattr(sb_config, "_vd_list") + and isinstance(sb_config._vd_list, list) + ): + sb_config._vd_list.append(_xvfb_display) def install_pyautogui_if_missing(driver): @@ -1217,6 +1224,13 @@ def _uc_gui_click_captcha( and driver.is_element_present("#challenge-form div > div") ): frame = "#challenge-form div > div" + elif ( + driver.is_element_present('[name*="cf-turnstile-"]') + and driver.is_element_present( + '[style="display: grid;"] div div' + ) + ): + frame = '[style="display: grid;"] div div' elif ( driver.is_element_present('[name*="cf-turnstile-"]') and driver.is_element_present("[class*=spacer] + div div") @@ -3838,6 +3852,12 @@ def get_local_driver( edge_options.add_argument("--guest") if dark_mode: edge_options.add_argument("--enable-features=WebContentsForceDark") + if headless1: + # developer.chrome.com/blog/removing-headless-old-from-chrome + with suppress(Exception): + if int(str(use_version).split(".")[0]) >= 132: + headless1 = False + headless2 = True if headless2: try: if use_version == "latest" or int(use_version) >= 109: @@ -4379,6 +4399,12 @@ def get_local_driver( use_version = find_chromedriver_version_to_use( use_version, driver_version ) + if headless1: + # developer.chrome.com/blog/removing-headless-old-from-chrome + with suppress(Exception): + if int(str(use_version).split(".")[0]) >= 132: + headless1 = False + headless2 = True if headless2: try: if ( diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 08961aa1834..7c9d659555e 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -95,6 +95,7 @@ def test_anything(self): logging.getLogger("urllib3").setLevel(logging.ERROR) urllib3.disable_warnings() LOGGER.setLevel(logging.WARNING) +is_linux = shared_utils.is_linux() is_windows = shared_utils.is_windows() python3_11_or_newer = False if sys.version_info >= (3, 11): @@ -4828,7 +4829,7 @@ def activate_recorder(self): from seleniumbase.js_code.recorder_js import recorder_js if not self.is_chromium(): - if "linux" not in sys.platform: + if not is_linux: c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX cr = colorama.Style.RESET_ALL @@ -5658,7 +5659,7 @@ def __process_recorded_actions(self): c1 = "" c2 = "" cr = "" - if "linux" not in sys.platform: + if not is_linux: c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX cr = colorama.Style.RESET_ALL @@ -5760,7 +5761,7 @@ def __process_recorded_behave_actions(self, srt_actions, colorama): c1 = "" c2 = "" cr = "" - if "linux" not in sys.platform: + if not is_linux: c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX cr = colorama.Style.RESET_ALL @@ -14009,6 +14010,9 @@ def __activate_standard_virtual_display(self): if not self.undetectable: sb_config._virtual_display = self._xvfb_display sb_config.headless_active = True + if self._reuse_session and hasattr(sb_config, "_vd_list"): + if isinstance(sb_config._vd_list, list): + sb_config._vd_list.append(self._xvfb_display) def __activate_virtual_display(self): if self.undetectable and not (self.headless or self.headless2): @@ -14033,6 +14037,9 @@ def __activate_virtual_display(self): self.__activate_standard_virtual_display() else: self.headless_active = True + if self._reuse_session and hasattr(sb_config, "_vd_list"): + if isinstance(sb_config._vd_list, list): + sb_config._vd_list.append(self._xvfb_display) except Exception as e: if hasattr(e, "msg"): print("\n" + str(e.msg)) @@ -14087,7 +14094,7 @@ def __activate_virtual_display_as_needed(self): """This is only needed on Linux. The "--xvfb" arg is still useful, as it prevents headless mode, which is the default mode on Linux unless using another arg.""" - if "linux" in sys.platform and (not self.headed or self.xvfb): + if is_linux and (not self.headed or self.xvfb): pip_find_lock = fasteners.InterProcessLock( constants.PipInstall.FINDLOCK ) @@ -16604,7 +16611,11 @@ def tearDown(self): # (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: + if ( + hasattr(self, "_xvfb_display") + and self._xvfb_display + and not self._reuse_session + ): # Stop the Xvfb virtual display launched from BaseCase try: if hasattr(self._xvfb_display, "stop"): @@ -16619,6 +16630,13 @@ def tearDown(self): hasattr(sb_config, "_virtual_display") and sb_config._virtual_display 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 + ) + ) ): # CDP Mode may launch a 2nd Xvfb virtual display try: diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py index a729070c823..e18a57360fe 100644 --- a/seleniumbase/plugins/pytest_plugin.py +++ b/seleniumbase/plugins/pytest_plugin.py @@ -1371,6 +1371,7 @@ def pytest_addoption(parser): arg_join = " ".join(sys_argv) sb_config._browser_shortcut = None + sb_config._vd_list = [] # SeleniumBase does not support pytest-timeout due to hanging browsers. for arg in sys_argv: @@ -2017,6 +2018,13 @@ def pytest_runtest_teardown(item): hasattr(self, "_xvfb_display") and self._xvfb_display 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 + ) + ) ): self.headless_active = False sb_config.headless_active = False @@ -2026,6 +2034,13 @@ def pytest_runtest_teardown(item): hasattr(sb_config, "_virtual_display") and sb_config._virtual_display 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 + ) + ) ): sb_config._virtual_display.stop() sb_config._virtual_display = None @@ -2139,6 +2154,21 @@ def _perform_pytest_unconfigure_(config): except Exception: pass sb_config.shared_driver = None + with suppress(Exception): + if ( + hasattr(sb_config, "_virtual_display") + and sb_config._virtual_display + 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 isinstance(sb_config._vd_list, list): + for display in sb_config._vd_list: + if display: + with suppress(Exception): + display.stop() if hasattr(sb_config, "log_path") and sb_config.item_count > 0: log_helper.archive_logs_if_set( constants.Logs.LATEST + "/", sb_config.archive_logs @@ -2193,6 +2223,9 @@ def _perform_pytest_unconfigure_(config): the_html_r = the_html_r.replace( ph_link, "%s and %s" % (sb_link, ph_link) ) + the_html_r = the_html_r.replace( + "findAll('.collapsible", "//findAll('.collapsible" + ) the_html_r = the_html_r.replace( "mediaName.innerText", "//mediaName.innerText" ) @@ -2228,6 +2261,9 @@ def _perform_pytest_unconfigure_(config): html_style = html_style.replace( "- 80px);", "- 80px);\n margin-bottom: -42px;" ) + html_style = html_style.replace(".collapsible", ".oldc") + html_style = html_style.replace(" (hide details)", "") + html_style = html_style.replace(" (show details)", "") with open(assets_style, "w", encoding="utf-8") as f: f.write(html_style) with suppress(Exception): @@ -2327,6 +2363,9 @@ def _perform_pytest_unconfigure_(config): html_style = html_style.replace( "- 80px);", "- 80px);\n margin-bottom: -42px;" ) + html_style = html_style.replace(".collapsible", ".oldc") + html_style = html_style.replace(" (hide details)", "") + html_style = html_style.replace(" (show details)", "") with open(assets_style, "w", encoding="utf-8") as f: f.write(html_style) with suppress(Exception): @@ -2394,6 +2433,9 @@ def _perform_pytest_unconfigure_(config): the_html_r = the_html_r.replace( ph_link, "%s and %s" % (sb_link, ph_link) ) + the_html_r = the_html_r.replace( + "findAll('.collapsible", "//findAll('.collapsible" + ) the_html_r = the_html_r.replace( "mediaName.innerText", "//mediaName.innerText" ) diff --git a/setup.py b/setup.py index c9308093c1e..5b4929ca4c7 100755 --- a/setup.py +++ b/setup.py @@ -156,8 +156,9 @@ "certifi>=2024.12.14", "exceptiongroup>=1.2.2", 'websockets~=13.1;python_version<"3.9"', - 'websockets>=14.1;python_version>="3.9"', - 'filelock>=3.16.1', + 'websockets>=14.2;python_version>="3.9"', + 'filelock~=3.16.1;python_version<"3.9"', + 'filelock>=3.17.0;python_version>="3.9"', 'fasteners>=0.19', "mycdp>=1.1.0", "pynose>=1.5.3", @@ -190,7 +191,8 @@ 'trio-websocket==0.11.1', 'wsproto==1.2.0', 'websocket-client==1.8.0', - 'selenium==4.27.1', + 'selenium==4.27.1;python_version<"3.9"', + 'selenium==4.28.0;python_version>="3.9"', 'cssselect==1.2.0', "sortedcontainers==2.4.0", 'execnet==2.1.1',