Merge pull request #3441 from seleniumbase/cdp-mode-patch-26

CDP Mode - Patch 26
This commit is contained in:
Michael Mintz 2025-01-21 18:39:01 -05:00 committed by GitHub
commit afd20bcdc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 236 additions and 94 deletions

View File

@ -6,16 +6,21 @@
--------
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=Mr90iQmNsKM"><img src="https://github.com/user-attachments/assets/91e7ff7b-d155-4ba9-b17b-b097825fcf42" title="SeleniumBase on YouTube" width="350" /></a>
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=Mr90iQmNsKM"><img src="https://github.com/user-attachments/assets/91e7ff7b-d155-4ba9-b17b-b097825fcf42" title="SeleniumBase on YouTube" width="320" /></a>
<p>(<b><a href="https://www.youtube.com/watch?v=Mr90iQmNsKM">Watch the CDP Mode tutorial on YouTube! ▶️</a></b>)</p>
--------
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=vt2zsdiNh3U"><img src="https://github.com/user-attachments/assets/82ab2715-727e-4d09-9314-b8905795dc43" title="SeleniumBase on YouTube" width="350" /></a>
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=vt2zsdiNh3U"><img src="https://github.com/user-attachments/assets/82ab2715-727e-4d09-9314-b8905795dc43" title="SeleniumBase on YouTube" width="320" /></a>
<p>(<b><a href="https://www.youtube.com/watch?v=vt2zsdiNh3U">Watch "Hacking websites with CDP" on YouTube! ▶️</a></b>)</p>
--------
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=gEZhTfaIxHQ"><img src="https://github.com/user-attachments/assets/656977e1-5d66-4d1c-9eec-0aaa41f6522f" title="SeleniumBase on YouTube" width="320" /></a>
<p>(<b><a href="https://www.youtube.com/watch?v=gEZhTfaIxHQ">Watch "Web-Scraping with GitHub Actions" on YouTube! ▶️</a></b>)</p>
--------
👤 <b translate="no">UC Mode</b> avoids bot-detection by first disconnecting WebDriver from the browser at strategic times, calling special <code>PyAutoGUI</code> methods to bypass CAPTCHAs (as needed), and finally reconnecting the <code>driver</code> 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 <b translate="no">CDP Mode</b> comes in.)
🐙 <b translate="no">CDP Mode</b> is based on <a href="https://github.com/HyperionGray/python-chrome-devtools-protocol" translate="no">python-cdp</a>, <a href="https://github.com/HyperionGray/trio-chrome-devtools-protocol" translate="no">trio-cdp</a>, and <a href="https://github.com/ultrafunkamsterdam/nodriver" translate="no">nodriver</a>. <code>trio-cdp</code> is an early implementation of <code>python-cdp</code>, and <code>nodriver</code> is a modern implementation of <code>python-cdp</code>. (Refactored <code>Python-CDP</code> code is imported from <a href="https://github.com/mdmintz/MyCDP" translate="no">MyCDP</a>.)

View File

@ -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"]')

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -8,22 +8,22 @@
---
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=5dMFI3e85ig"><img src="http://img.youtube.com/vi/5dMFI3e85ig/0.jpg" title="SeleniumBase on YouTube" width="350" /></a>
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=5dMFI3e85ig"><img src="http://img.youtube.com/vi/5dMFI3e85ig/0.jpg" title="SeleniumBase on YouTube" width="320" /></a>
<p>(<b><a href="https://www.youtube.com/watch?v=5dMFI3e85ig">Watch the 1st UC Mode tutorial on YouTube! ▶️</a></b>)</p>
----
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=2pTpBtaE7SQ"><img src="http://img.youtube.com/vi/2pTpBtaE7SQ/0.jpg" title="SeleniumBase on YouTube" width="350" /></a>
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=2pTpBtaE7SQ"><img src="http://img.youtube.com/vi/2pTpBtaE7SQ/0.jpg" title="SeleniumBase on YouTube" width="320" /></a>
<p>(<b><a href="https://www.youtube.com/watch?v=2pTpBtaE7SQ">Watch the 2nd UC Mode tutorial on YouTube! ▶️</a></b>)</p>
----
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=-EpZlhGWo9k"><img src="http://img.youtube.com/vi/-EpZlhGWo9k/0.jpg" title="SeleniumBase on YouTube" width="350" /></a>
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=-EpZlhGWo9k"><img src="http://img.youtube.com/vi/-EpZlhGWo9k/0.jpg" title="SeleniumBase on YouTube" width="320" /></a>
<p>(<b><a href="https://www.youtube.com/watch?v=-EpZlhGWo9k">Watch the 3rd UC Mode tutorial on YouTube! ▶️</a></b>)</p>
----
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=Mr90iQmNsKM"><img src="http://img.youtube.com/vi/Mr90iQmNsKM/0.jpg" title="SeleniumBase on YouTube" width="350" /></a>
<!-- YouTube View --><a href="https://www.youtube.com/watch?v=Mr90iQmNsKM"><img src="http://img.youtube.com/vi/Mr90iQmNsKM/0.jpg" title="SeleniumBase on YouTube" width="320" /></a>
<p>(<b><a href="https://www.youtube.com/watch?v=Mr90iQmNsKM">Watch the 4th UC Mode tutorial on YouTube! ▶️</a></b>)</p>
----

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "4.33.15"
__version__ = "4.34.0"

View File

@ -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:

View File

@ -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)

View File

@ -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 (

View File

@ -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:

View File

@ -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"
)

View File

@ -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',