Merge pull request #3441 from seleniumbase/cdp-mode-patch-26
CDP Mode - Patch 26
This commit is contained in:
commit
afd20bcdc5
|
@ -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>.)
|
||||||
|
|
|
@ -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"]')
|
|
@ -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)
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
# seleniumbase package
|
# seleniumbase package
|
||||||
__version__ = "4.33.15"
|
__version__ = "4.34.0"
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -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',
|
||||||
|
|
Loading…
Reference in New Issue