diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 970d4539..88e3d6f2 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 00000000..5fc31491 --- /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 00000000..54989667 --- /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 51cfc164..8277f567 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 6fcaf2c2..1e596a1f 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 e00ab35a..555c485d 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 02921e89..ef9d37f7 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 455be9b4..024b4e24 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 4379c934..865cb756 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 b0b6de70..d3037254 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 905605c9..d897bc15 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 04dcc5b5..43faa057 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 9219efef..0ea30632 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 8c94ece6..2ac488f4 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 08961aa1..7c9d6595 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -95,6 +95,7 @@ logging.getLogger("requests").setLevel(logging.ERROR) 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 @@ class BaseCase(unittest.TestCase): 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 @@ class BaseCase(unittest.TestCase): 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 @@ class BaseCase(unittest.TestCase): 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 @@ class BaseCase(unittest.TestCase): 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 @@ class BaseCase(unittest.TestCase): 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 @@ class BaseCase(unittest.TestCase): """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 @@ class BaseCase(unittest.TestCase): # (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 @@ class BaseCase(unittest.TestCase): 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 a729070c..e18a5736 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 c9308093..5b4929ca 100755 --- a/setup.py +++ b/setup.py @@ -156,8 +156,9 @@ setup( "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 @@ setup( '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',