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> <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> <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">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>.) 🐙 <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 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: with SB(uc=True, test=True, incognito=True) as sb:
url = "https://seleniumbase.io/apps/recaptcha" url = "https://seleniumbase.io/apps/recaptcha"
sb.activate_cdp_mode(url) sb.activate_cdp_mode(url)

View File

@ -1,6 +1,6 @@
from rich.pretty import pprint from rich.pretty import pprint
from seleniumbase import BaseCase from seleniumbase import BaseCase
BaseCase.main(__name__, __file__, "--uc", "--uc-cdp", "-s") BaseCase.main(__name__, __file__, "--uc", "--uc-cdp")
class CDPTests(BaseCase): class CDPTests(BaseCase):

View File

@ -2,7 +2,7 @@
Some sites use scripts to detect Selenium, and then block you. Some sites use scripts to detect Selenium, and then block you.
To evade detection, add --uc as a pytest command-line option.""" To evade detection, add --uc as a pytest command-line option."""
from seleniumbase import BaseCase from seleniumbase import BaseCase
BaseCase.main(__name__, __file__, "--uc", "-s") BaseCase.main(__name__, __file__, "--uc")
class UndetectedTest(BaseCase): 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> <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> <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> <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> <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 . 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 . 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) #### 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 pyquery==2.0.1
readtime==3.0.0 readtime==3.0.0
mkdocs==1.6.1 mkdocs==1.6.1
mkdocs-material==9.5.49 mkdocs-material==9.5.50
mkdocs-exclude-search==0.6.6 mkdocs-exclude-search==0.6.6
mkdocs-simple-hooks==0.1.5 mkdocs-simple-hooks==0.1.5
mkdocs-material-extensions==1.3.1 mkdocs-material-extensions==1.3.1

View File

@ -7,8 +7,9 @@ attrs>=24.3.0
certifi>=2024.12.14 certifi>=2024.12.14
exceptiongroup>=1.2.2 exceptiongroup>=1.2.2
websockets~=13.1;python_version<"3.9" websockets~=13.1;python_version<"3.9"
websockets>=14.1;python_version>="3.9" websockets>=14.2;python_version>="3.9"
filelock>=3.16.1 filelock~=3.16.1;python_version<"3.9"
filelock>=3.17.0;python_version>="3.9"
fasteners>=0.19 fasteners>=0.19
mycdp>=1.1.0 mycdp>=1.1.0
pynose>=1.5.3 pynose>=1.5.3
@ -41,7 +42,8 @@ trio==0.28.0;python_version>="3.9"
trio-websocket==0.11.1 trio-websocket==0.11.1
wsproto==1.2.0 wsproto==1.2.0
websocket-client==1.8.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 cssselect==1.2.0
sortedcontainers==2.4.0 sortedcontainers==2.4.0
execnet==2.1.1 execnet==2.1.1

View File

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

View File

@ -290,7 +290,9 @@ sbase mkdir ui_tests
* Options: * Options:
``-b`` / ``--basic`` (Only config files. No tests added.) ```bash
-b / --basic (Only config files. No tests added.)
```
* Output: * Output:
@ -350,27 +352,33 @@ sbase mkfile new_test.py
* Options: * Options:
``--uc`` (UC Mode boilerplate using SB context manager) ```bash
`-b` / `--basic` (Basic boilerplate / single-line test) --uc (UC Mode boilerplate using SB context manager)
`-r` / `--rec` (Adds Pdb+ breakpoint for Recorder Mode) -b / --basic (Basic boilerplate / single-line test)
``--url=URL`` (Makes the test start on a specific page) -r / --rec (Adds Pdb+ breakpoint for Recorder Mode)
--url=URL (Makes the test start on a specific page)
```
* Language Options: * Language Options:
``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` ```bash
``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` --en / --English | --zh / --Chinese
``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` --nl / --Dutch | --fr / --French
``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` --it / --Italian | --ja / --Japanese
``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` --ko / --Korean | --pt / --Portuguese
--ru / --Russian | --es / --Spanish
```
* Syntax Formats: * Syntax Formats:
``--bc`` / ``--basecase`` (BaseCase class inheritance) ```bash
``--pf`` / ``--pytest-fixture`` (sb pytest fixture) --bc / --basecase (BaseCase class inheritance)
``--cf`` / ``--class-fixture`` (class + sb pytest fixture) --pf / --pytest-fixture (sb pytest fixture)
``--cm`` / ``--context-manager`` (SB context manager) --cf / --class-fixture (class + sb pytest fixture)
``--dc`` / ``--driver-context`` (DriverContext manager) --cm / --context-manager (SB context manager)
``--dm`` / ``--driver-manager`` (Driver manager) --dc / --driver-context (DriverContext manager)
--dm / --driver-manager (Driver manager)
```
* Output: * Output:
@ -404,13 +412,15 @@ sbase codegen new_test.py --url=wikipedia.org
* Options: * Options:
``--url=URL`` (Sets the initial start page URL.) ```bash
``--edge`` (Use Edge browser instead of Chrome.) --url=URL (Sets the initial start page URL.)
``--gui`` / ``--headed`` (Use headed mode on Linux.) --edge (Use Edge browser instead of Chrome.)
``--uc`` / ``--undetected`` (Use undetectable mode.) --gui / --headed (Use headed mode on Linux.)
``--ee`` (Use SHIFT + ESC to end the recording.) --uc / --undetected (Use undetectable mode.)
``--overwrite`` (Overwrite file when it exists.) --ee (Use SHIFT + ESC to end the recording.)
``--behave`` (Also output Behave/Gherkin files.) --overwrite (Overwrite file when it exists.)
--behave (Also output Behave/Gherkin files.)
```
* Output: * Output:
@ -427,8 +437,10 @@ sbase recorder [OPTIONS]
* Options: * Options:
``--uc`` / ``--undetected`` (Use undetectable mode.) ```bash
``--behave`` (Also output Behave/Gherkin files.) --uc / --undetected (Use undetectable mode.)
--behave (Also output Behave/Gherkin files.)
```
* Output: * Output:
@ -450,11 +462,13 @@ sbase mkpres new_presentation.py --en
* Language Options: * Language Options:
``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` ```bash
``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` --en / --English | --zh / --Chinese
``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` --nl / --Dutch | --fr / --French
``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` --it / --Italian | --ja / --Japanese
``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` --ko / --Korean | --pt / --Portuguese
--ru / --Russian | --es / --Spanish
```
* Output: * Output:
@ -480,11 +494,13 @@ sbase mkchart new_chart.py --en
* Language Options: * Language Options:
``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` ```bash
``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` --en / --English | --zh / --Chinese
``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` --nl / --Dutch | --fr / --French
``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` --it / --Italian | --ja / --Japanese
``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` --ko / --Korean | --pt / --Portuguese
--ru / --Russian | --es / --Spanish
```
* Output: * Output:
@ -504,7 +520,9 @@ sbase print [FILE] [OPTIONS]
* Options: * Options:
``-n`` (Add line Numbers to the rows) ```bash
-n (Add line Numbers to the rows)
```
* Output: * Output:
@ -521,21 +539,27 @@ sbase translate [SB_FILE.py] [LANGUAGE] [ACTION]
* Languages: * Languages:
``--en`` / ``--English`` | ``--zh`` / ``--Chinese`` ```bash
``--nl`` / ``--Dutch`` | ``--fr`` / ``--French`` --en / --English | --zh / --Chinese
``--it`` / ``--Italian`` | ``--ja`` / ``--Japanese`` --nl / --Dutch | --fr / --French
``--ko`` / ``--Korean`` | ``--pt`` / ``--Portuguese`` --it / --Italian | --ja / --Japanese
``--ru`` / ``--Russian`` | ``--es`` / ``--Spanish`` --ko / --Korean | --pt / --Portuguese
--ru / --Russian | --es / --Spanish
```
* Actions: * Actions:
``-p`` / ``--print`` (Print translation output to the screen) ```bash
``-o`` / ``--overwrite`` (Overwrite the file being translated) -p / --print (Print translation output to the screen)
``-c`` / ``--copy`` (Copy the translation to a new ``.py`` file) -o / --overwrite (Overwrite the file being translated)
-c / --copy (Copy the translation to a new ``.py`` file)
```
* Options: * Options:
``-n`` (include line Numbers when using the Print action) ```bash
-n (include line Numbers when using the Print action)
```
* Output: * Output:
@ -573,7 +597,9 @@ sbase inject-objects [SB_FILE.py] [OPTIONS]
* Options: * Options:
``-c``, ``--comments`` (Add object selectors to the comments.) ```bash
-c / --comments (Add object selectors to the comments.)
```
* Output: * Output:
@ -591,7 +617,9 @@ sbase objectify [SB_FILE.py] [OPTIONS]
* Options: * Options:
``-c``, ``--comments`` (Add object selectors to the comments.) ```bash
-c / --comments (Add object selectors to the comments.)
```
* Output: * Output:
@ -611,7 +639,9 @@ sbase revert-objects [SB_FILE.py] [OPTIONS]
* Options: * Options:
``-c``, ``--comments`` (Keep existing comments for the lines.) ```bash
-c / --comments (Keep existing comments for the lines.)
```
* Output: * Output:
@ -639,7 +669,7 @@ Works on both Selenium IDE & Katalon Recorder scripts.
* Usage: * Usage:
``sbase encrypt`` OR ``sbase obfuscate`` ``sbase encrypt`` / ``sbase obfuscate``
* Output: * Output:
@ -650,7 +680,7 @@ Runs the password encryption/obfuscation tool.
* Usage: * Usage:
``sbase decrypt`` OR ``sbase unobfuscate`` ``sbase decrypt`` / ``sbase unobfuscate``
* Output: * Output:
@ -667,9 +697,11 @@ sbase proxy [OPTIONS]
* Options: * Options:
``--hostname=HOSTNAME`` (Set ``hostname``) (Default: ``127.0.0.1``) ```bash
``--port=PORT`` (Set ``port``) (Default: ``8899``) --hostname=HOSTNAME (Set `hostname`) (Default: `127.0.0.1`)
``--help`` / ``-h`` (Display list of all available ``proxy`` options.) --port=PORT (Set `port`) (Default: `8899`)
--help / -h (Display available `proxy` options.)
```
* Output: * Output:
@ -699,8 +731,10 @@ sbase grid-hub {start|stop|restart} [OPTIONS]
* Options: * Options:
``-v``, ``--verbose`` (Increases verbosity of logging output.) ```bash
``--timeout=TIMEOUT`` (Close idle browser windows after TIMEOUT seconds.) -v / --verbose (Increases verbosity of logging output.)
--timeout=TIMEOUT (Close idle browser windows after TIMEOUT seconds.)
```
* Output: * Output:
@ -720,8 +754,10 @@ sbase grid-node {start|stop|restart} [OPTIONS]
* Options: * Options:
``--hub=HUB_IP`` (The Grid Hub IP Address to connect to.) (Default: ``127.0.0.1``) ```bash
``-v``, ``--verbose`` (Increases verbosity of logging output.) --hub=HUB_IP (Grid Hub IP Address. Default: `127.0.0.1`)
-v / --verbose (Increases verbosity of logging output.)
```
* Output: * Output:

View File

@ -156,9 +156,9 @@ def do_pytest_run(
if save_screenshots: if save_screenshots:
full_run_command += " --screenshot" full_run_command += " --screenshot"
dash_s_needed = False capture_needed = False
if "-s" not in additional_options.split(" "): if "--capture" not in additional_options:
dash_s_needed = True capture_needed = True
additional_options = additional_options.strip() additional_options = additional_options.strip()
if additional_options: if additional_options:
@ -168,8 +168,8 @@ def do_pytest_run(
if verbose: if verbose:
full_run_command += " -v" full_run_command += " -v"
if dash_s_needed: if capture_needed:
full_run_command += " -s" full_run_command += " --capture=tee-sys"
print(full_run_command) print(full_run_command)
subprocess.Popen(full_run_command, shell=True) subprocess.Popen(full_run_command, shell=True)

View File

@ -888,6 +888,13 @@ def __install_pyautogui_if_missing():
_xvfb_display.start() _xvfb_display.start()
sb_config._virtual_display = _xvfb_display sb_config._virtual_display = _xvfb_display
sb_config.headless_active = True 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): def install_pyautogui_if_missing(driver):
@ -1217,6 +1224,13 @@ def _uc_gui_click_captcha(
and driver.is_element_present("#challenge-form div > div") and driver.is_element_present("#challenge-form div > div")
): ):
frame = "#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 ( elif (
driver.is_element_present('[name*="cf-turnstile-"]') driver.is_element_present('[name*="cf-turnstile-"]')
and driver.is_element_present("[class*=spacer] + div div") and driver.is_element_present("[class*=spacer] + div div")
@ -3838,6 +3852,12 @@ def get_local_driver(
edge_options.add_argument("--guest") edge_options.add_argument("--guest")
if dark_mode: if dark_mode:
edge_options.add_argument("--enable-features=WebContentsForceDark") 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: if headless2:
try: try:
if use_version == "latest" or int(use_version) >= 109: 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 = find_chromedriver_version_to_use(
use_version, driver_version 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: if headless2:
try: try:
if ( if (

View File

@ -95,6 +95,7 @@ logging.getLogger("requests").setLevel(logging.ERROR)
logging.getLogger("urllib3").setLevel(logging.ERROR) logging.getLogger("urllib3").setLevel(logging.ERROR)
urllib3.disable_warnings() urllib3.disable_warnings()
LOGGER.setLevel(logging.WARNING) LOGGER.setLevel(logging.WARNING)
is_linux = shared_utils.is_linux()
is_windows = shared_utils.is_windows() is_windows = shared_utils.is_windows()
python3_11_or_newer = False python3_11_or_newer = False
if sys.version_info >= (3, 11): if sys.version_info >= (3, 11):
@ -4828,7 +4829,7 @@ class BaseCase(unittest.TestCase):
from seleniumbase.js_code.recorder_js import recorder_js from seleniumbase.js_code.recorder_js import recorder_js
if not self.is_chromium(): if not self.is_chromium():
if "linux" not in sys.platform: if not is_linux:
c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX
c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX
cr = colorama.Style.RESET_ALL cr = colorama.Style.RESET_ALL
@ -5658,7 +5659,7 @@ class BaseCase(unittest.TestCase):
c1 = "" c1 = ""
c2 = "" c2 = ""
cr = "" cr = ""
if "linux" not in sys.platform: if not is_linux:
c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX
c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX
cr = colorama.Style.RESET_ALL cr = colorama.Style.RESET_ALL
@ -5760,7 +5761,7 @@ class BaseCase(unittest.TestCase):
c1 = "" c1 = ""
c2 = "" c2 = ""
cr = "" cr = ""
if "linux" not in sys.platform: if not is_linux:
c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX c1 = colorama.Fore.RED + colorama.Back.LIGHTYELLOW_EX
c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX c2 = colorama.Fore.LIGHTRED_EX + colorama.Back.LIGHTYELLOW_EX
cr = colorama.Style.RESET_ALL cr = colorama.Style.RESET_ALL
@ -14009,6 +14010,9 @@ class BaseCase(unittest.TestCase):
if not self.undetectable: if not self.undetectable:
sb_config._virtual_display = self._xvfb_display sb_config._virtual_display = self._xvfb_display
sb_config.headless_active = True 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): def __activate_virtual_display(self):
if self.undetectable and not (self.headless or self.headless2): if self.undetectable and not (self.headless or self.headless2):
@ -14033,6 +14037,9 @@ class BaseCase(unittest.TestCase):
self.__activate_standard_virtual_display() self.__activate_standard_virtual_display()
else: else:
self.headless_active = True 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: except Exception as e:
if hasattr(e, "msg"): if hasattr(e, "msg"):
print("\n" + str(e.msg)) print("\n" + str(e.msg))
@ -14087,7 +14094,7 @@ class BaseCase(unittest.TestCase):
"""This is only needed on Linux. """This is only needed on Linux.
The "--xvfb" arg is still useful, as it prevents headless mode, The "--xvfb" arg is still useful, as it prevents headless mode,
which is the default mode on Linux unless using another arg.""" 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( pip_find_lock = fasteners.InterProcessLock(
constants.PipInstall.FINDLOCK constants.PipInstall.FINDLOCK
) )
@ -16604,7 +16611,11 @@ class BaseCase(unittest.TestCase):
# (Pynose / Behave / Pure Python) Close all open browser windows # (Pynose / Behave / Pure Python) Close all open browser windows
self.__quit_all_drivers() self.__quit_all_drivers()
# Resume tearDown() for all test runners, (Pytest / Pynose / Behave) # 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 # Stop the Xvfb virtual display launched from BaseCase
try: try:
if hasattr(self._xvfb_display, "stop"): if hasattr(self._xvfb_display, "stop"):
@ -16619,6 +16630,13 @@ class BaseCase(unittest.TestCase):
hasattr(sb_config, "_virtual_display") hasattr(sb_config, "_virtual_display")
and sb_config._virtual_display and sb_config._virtual_display
and hasattr(sb_config._virtual_display, "stop") 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 # CDP Mode may launch a 2nd Xvfb virtual display
try: try:

View File

@ -1371,6 +1371,7 @@ def pytest_addoption(parser):
arg_join = " ".join(sys_argv) arg_join = " ".join(sys_argv)
sb_config._browser_shortcut = None sb_config._browser_shortcut = None
sb_config._vd_list = []
# SeleniumBase does not support pytest-timeout due to hanging browsers. # SeleniumBase does not support pytest-timeout due to hanging browsers.
for arg in sys_argv: for arg in sys_argv:
@ -2017,6 +2018,13 @@ def pytest_runtest_teardown(item):
hasattr(self, "_xvfb_display") hasattr(self, "_xvfb_display")
and self._xvfb_display and self._xvfb_display
and hasattr(self._xvfb_display, "stop") 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 self.headless_active = False
sb_config.headless_active = False sb_config.headless_active = False
@ -2026,6 +2034,13 @@ def pytest_runtest_teardown(item):
hasattr(sb_config, "_virtual_display") hasattr(sb_config, "_virtual_display")
and sb_config._virtual_display and sb_config._virtual_display
and hasattr(sb_config._virtual_display, "stop") 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.stop()
sb_config._virtual_display = None sb_config._virtual_display = None
@ -2139,6 +2154,21 @@ def _perform_pytest_unconfigure_(config):
except Exception: except Exception:
pass pass
sb_config.shared_driver = None 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: if hasattr(sb_config, "log_path") and sb_config.item_count > 0:
log_helper.archive_logs_if_set( log_helper.archive_logs_if_set(
constants.Logs.LATEST + "/", sb_config.archive_logs constants.Logs.LATEST + "/", sb_config.archive_logs
@ -2193,6 +2223,9 @@ def _perform_pytest_unconfigure_(config):
the_html_r = the_html_r.replace( the_html_r = the_html_r.replace(
ph_link, "%s and %s" % (sb_link, ph_link) 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( the_html_r = the_html_r.replace(
"mediaName.innerText", "//mediaName.innerText" "mediaName.innerText", "//mediaName.innerText"
) )
@ -2228,6 +2261,9 @@ def _perform_pytest_unconfigure_(config):
html_style = html_style.replace( html_style = html_style.replace(
"- 80px);", "- 80px);\n margin-bottom: -42px;" "- 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: with open(assets_style, "w", encoding="utf-8") as f:
f.write(html_style) f.write(html_style)
with suppress(Exception): with suppress(Exception):
@ -2327,6 +2363,9 @@ def _perform_pytest_unconfigure_(config):
html_style = html_style.replace( html_style = html_style.replace(
"- 80px);", "- 80px);\n margin-bottom: -42px;" "- 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: with open(assets_style, "w", encoding="utf-8") as f:
f.write(html_style) f.write(html_style)
with suppress(Exception): with suppress(Exception):
@ -2394,6 +2433,9 @@ def _perform_pytest_unconfigure_(config):
the_html_r = the_html_r.replace( the_html_r = the_html_r.replace(
ph_link, "%s and %s" % (sb_link, ph_link) 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( the_html_r = the_html_r.replace(
"mediaName.innerText", "//mediaName.innerText" "mediaName.innerText", "//mediaName.innerText"
) )

View File

@ -156,8 +156,9 @@ setup(
"certifi>=2024.12.14", "certifi>=2024.12.14",
"exceptiongroup>=1.2.2", "exceptiongroup>=1.2.2",
'websockets~=13.1;python_version<"3.9"', 'websockets~=13.1;python_version<"3.9"',
'websockets>=14.1;python_version>="3.9"', 'websockets>=14.2;python_version>="3.9"',
'filelock>=3.16.1', 'filelock~=3.16.1;python_version<"3.9"',
'filelock>=3.17.0;python_version>="3.9"',
'fasteners>=0.19', 'fasteners>=0.19',
"mycdp>=1.1.0", "mycdp>=1.1.0",
"pynose>=1.5.3", "pynose>=1.5.3",
@ -190,7 +191,8 @@ setup(
'trio-websocket==0.11.1', 'trio-websocket==0.11.1',
'wsproto==1.2.0', 'wsproto==1.2.0',
'websocket-client==1.8.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', 'cssselect==1.2.0',
"sortedcontainers==2.4.0", "sortedcontainers==2.4.0",
'execnet==2.1.1', 'execnet==2.1.1',