Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 83 additions & 139 deletions README.md

Large diffs are not rendered by default.

50 changes: 30 additions & 20 deletions examples/cdp_mode/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<h3 align="left">⚙️ Stealthy architecture flowchart:</h3>

<img src="https://seleniumbase.github.io/other/sb_architecture.png" width="596" alt="Stealthy architecture flowchart" />
<img src="https://seleniumbase.github.io/other/sb_architecture.png" width="596" style="max-width: 100% !important; height: auto !important;" alt="Stealthy architecture flowchart" />

----

Expand All @@ -22,7 +22,7 @@

ℹ️ Note the differences between <b>UC Mode</b> and <b>CDP Mode</b>:

👤 <b translate="no">UC Mode</b>'s stealth is based on a modified <code>chromedriver</code> (<code>uc_driver</code>) that avoids bot-detection by disconnecting and reconnecting WebDriver from the browser at strategic times.
👤 <b translate="no">UC Mode</b>'s stealth is based on a modified <code>chromedriver</code> (renamed <code>uc_driver</code>) that avoids bot-detection by disconnecting and reconnecting WebDriver from the browser at strategic times.

🐙 <b translate="no">CDP Mode</b>'s stealth comes from using the Chrome DevTools Protocol directly, which is much better at avoiding bot-detection. CDP Mode can also be used to make Playwright stealthy.

Expand All @@ -39,13 +39,13 @@ In <b translate="no">Pure CDP Mode</b>, CDP is used to launch the browser and pe
```python
from seleniumbase import sb_cdp

sb = sb_cdp.Chrome(OPTIONS)
sb.goto(URL)
sb = sb_cdp.Chrome()
sb.goto("https://example.com")
```

ℹ️ The browser quits automatically when Python goes out-of-scope. You can also quit the browser before that by calling `sb.quit()`.

Example from [SeleniumBase/examples/cdp_mode/raw_cdp_gitlab.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_gitlab.py):
📝 Here's [SeleniumBase/examples/cdp_mode/raw_cdp_gitlab.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_gitlab.py):

```python
from seleniumbase import sb_cdp
Expand All @@ -59,11 +59,13 @@ sb.highlight('button:contains("Sign in")')
sb.quit()
```

<img src="https://seleniumbase.github.io/other/cf_sec.jpg" title="SeleniumBase" width="332"> <img src="https://seleniumbase.github.io/other/gitlab_bypass.png" title="SeleniumBase" width="288">
<img src="https://seleniumbase.github.io/other/gitlab_bypass.png" title="SeleniumBase" width="400" style="max-width: 100% !important; height: auto !important;" />

ℹ️ `sb.solve_captcha()` handles CAPTCHAs that aren't automatically bypassed.

Example from [SeleniumBase/examples/cdp_mode/raw_cdp_turnstile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_turnstile.py):
----

📝 Here's [SeleniumBase/examples/cdp_mode/raw_cdp_turnstile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_turnstile.py):

```python
from seleniumbase import sb_cdp
Expand All @@ -77,28 +79,35 @@ sb.post_message("SeleniumBase wasn't detected", duration=3)
sb.quit()
```

Another example: ([SeleniumBase/examples/cdp_mode/raw_cdp_methods.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_methods.py))
<img src="https://seleniumbase.github.io/other/turnstile_click.jpg" title="SeleniumBase" width="440" style="max-width: 100% !important; height: auto !important;" />

----

📝 Here’s [SeleniumBase/examples/cdp_mode/raw_cdp_methods.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_methods.py):

```python
from seleniumbase import sb_cdp

sb = sb_cdp.Chrome()
sb = sb_cdp.Chrome(fast=True)
sb.goto("https://seleniumbase.io/demo_page")
sb.press_keys("input", "Text")
sb.highlight("button")
sb.type("textarea", "Here are some words")
sb.click("button")
sb.type("input", "Quickly type text!")
sb.press_keys("textarea", "Slowly type text!")
sb.click("#myButton")
sb.set_value("input#mySlider", "100")
sb.click_visible_elements("input.checkBoxClassB")
sb.select_option_by_text("#mySelect", "Set to 75%")
sb.gui_hover_and_click("#myDropdown", "#dropOption2")
sb.gui_click_element("#checkBox1")
sb.gui_drag_and_drop("img#logo", "div#drop2")
sb.hover_and_click("#myDropdown", "#dropOption2")
sb.click("#checkBox1")
sb.drag_and_drop("img#logo", "div#drop2")
sb.nested_click("iframe#myFrame3", ".fBox")
sb.sleep(2)
sb.highlight("#myButton")
sb.quit()
```

<img src="https://seleniumbase.github.io/cdn/gif/demo_page_6.gif" width="500" style="max-width: 100% !important; height: auto !important;" alt="SeleniumBase Example" />

ℹ️ Setting `fast=True` lets your automation go at full speed, which may be useful if maximum stealth isn't your goal.

----

### 🐙 <b translate="no">CDP Mode</b> as a subset of UC Mode
Expand All @@ -111,20 +120,21 @@ That disconnects WebDriver from Chrome (which prevents detection), and gives you

> (Calling **`sb.goto(url)`** from UC Mode also activates CDP Mode now.)

Example from [SeleniumBase/examples/cdp_mode/raw_planetmc.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_planetmc.py):
📝 Here's [SeleniumBase/examples/cdp_mode/raw_planetmc.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_planetmc.py):

```python
from seleniumbase import SB

with SB(uc=True, test=True, guest=True) as sb:
sb.activate_cdp_mode()
sb.goto("www.planetminecraft.com/account/sign_in/")
sb.goto("www.planetminecraft.com/account/sign_in")
sb.solve_captcha()
# "LOG IN" is enabled when the CAPTCHA is bypassed
sb.wait_for_element_absent("input[disabled]")
sb.sleep(2)
```

<img src="https://seleniumbase.github.io/other/planet_mc.png" title="SeleniumBase" width="480">
<img src="https://seleniumbase.github.io/other/pmc_captcha.png" width="570" style="max-width: 100% !important; height: auto !important;" alt="Cloudflare Turnstile bypassed successfully" />

To use a WebDriver-only method from UC Mode when WebDriver is disconnected, call:

Expand Down
2 changes: 1 addition & 1 deletion examples/cdp_mode/raw_cdp_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


def main():
sb = sb_cdp.Chrome()
sb = sb_cdp.Chrome(fast=True)
sb.goto("https://seleniumbase.io/simple/login")
sb.type("#username", "demo_user")
sb.type("#password", "secret_pass")
Expand Down
2 changes: 1 addition & 1 deletion examples/cdp_mode/raw_cdp_methods.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from seleniumbase import sb_cdp

sb = sb_cdp.Chrome()
sb = sb_cdp.Chrome(fast=True)
sb.goto("https://seleniumbase.io/demo_page")
sb.type("input", "Quickly type text!")
sb.press_keys("textarea", "Slowly type text!")
Expand Down
4 changes: 2 additions & 2 deletions examples/cdp_mode/raw_demo_site.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Example of using various CDP Mode commands."""
from seleniumbase import SB

with SB(uc=True, test=True) as sb:
with SB(uc=True, test=True, fast=True) as sb:
sb.activate_cdp_mode()
sb.goto("https://seleniumbase.io/demo_page")

Expand Down Expand Up @@ -66,7 +66,7 @@

# Click inside an iframe and test highlighting
sb.flash("iframe#myFrame3")
sb.sleep(1)
sb.sleep(0.5)
sb.nested_click("iframe#myFrame3", ".fBox")
sb.sleep(0.5)
sb.highlight("iframe#myFrame3")
4 changes: 2 additions & 2 deletions examples/cdp_mode/raw_drag_and_drop.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from seleniumbase import SB

with SB(uc=True, test=True, incognito=True) as sb:
with SB(uc=True, test=True, incognito=True, fast=True) as sb:
sb.activate_cdp_mode()
sb.goto("https://seleniumbase.io/other/drag_and_drop")
sb.assert_element_not_visible("#div1 img#drag1")
sb.drag_and_drop("#drag1", "#div1")
sb.assert_element("#div1 img#drag1")
sb.sleep(1)

with SB(uc=True, test=True, incognito=True) as sb:
with SB(uc=True, test=True, incognito=True, fast=True) as sb:
sb.activate_cdp_mode()
sb.goto("https://jqueryui.com/draggable/")
sb.switch_to_frame("iframe")
Expand Down
3 changes: 1 addition & 2 deletions examples/cdp_mode/raw_geolocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
@decorators.print_runtime("Geolocation CDP Example")
def main():
location = (48.87645, 2.26340)
sb = sb_cdp.Chrome(geoloc=location)
sb = sb_cdp.Chrome(geoloc=location, fast=True)
sb.goto("https://www.openstreetmap.org/")
sb.sleep(2)
sb.click('a[aria-label="Show My Location"]')
sb.assert_url_contains("48.876450/2.263400")
sb.sleep(5)
Expand Down
1 change: 1 addition & 0 deletions examples/raw_parameter_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
sb.rec_print = False
sb.report_on = False
sb.is_pytest = False
sb.fast_mode = False
sb.slow_mode = False
sb.demo_mode = False
sb.time_limit = None
Expand Down
1 change: 1 addition & 0 deletions help_docs/customizing_test_runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ pytest my_first_test.py --settings-file=custom_settings.py
--archive-logs # (Archive existing log files instead of deleting them.)
--archive-downloads # (Archive old downloads instead of deleting them.)
--time-limit=SECONDS # (Safely fail any test that exceeds the time limit.)
--fast # (Run the automation at full speed. May impact stealth.)
--slow # (Slow down the automation. Faster than using Demo Mode.)
--demo # (Slow down and visually see test actions as they occur.)
--demo-sleep=SECONDS # (Set the wait time after Slow & Demo Mode actions.)
Expand Down
4 changes: 2 additions & 2 deletions mcp_servers/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "seleniumbase-mcp"
version = "1.4.0dev0"
version = "1.4.1dev0"
description = "MCP server exposing SeleniumBase CDP Mode as tools for MCP clients."
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
dependencies = [
"mcp[cli]>=2.2.0,<3.0.0",
"mcp[cli]>=2.3.0,<3.0.0",
"seleniumbase",
]
keywords = [
Expand Down
2 changes: 1 addition & 1 deletion mcp_servers/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mcp[cli]>=2.2.0,<3.0.0
mcp[cli]>=2.3.0,<3.0.0
-e ..

# `-e ..` installs SeleniumBase itself from the repo root (one directory
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ certifi>=2026.7.22
exceptiongroup>=1.3.1
websockets~=16.1.1;python_version=="3.10"
websockets>=16.1.1;python_version>="3.11"
filelock>=4.0.8
filelock>=4.0.12
fasteners>=0.20
mycdp>=1.4.0
pynose>=1.5.5
typing-extensions>=4.16.0
sbvirtualdisplay>=1.4.0
MarkupSafe>=3.0.3
MarkupSafe>=3.0.4
Jinja2>=3.1.6
six>=1.17.0
parse>=1.22.2
Expand Down Expand Up @@ -68,5 +68,5 @@ coverage>=7.16.2
pytest-cov>=7.1.0
flake8==7.4.1
mccabe==0.7.0
pyflakes==4.0.1
pyflakes==4.0.2
pycodestyle==2.15.0
2 changes: 1 addition & 1 deletion seleniumbase/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "4.55.0"
__version__ = "4.55.1"
6 changes: 6 additions & 0 deletions seleniumbase/behave/behave_sb.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
-D archive-logs (Archive existing log files instead of deleting them.)
-D archive-downloads (Archive old downloads instead of deleting them.)
-D time-limit=SECONDS (Safely fail any test that exceeds the time limit.)
-D fast (Run the automation at full speed. May impact stealth.)
-D slow (Slow down the automation. Faster than using Demo Mode.)
-D demo (Slow down and visually see test actions as they occur.)
-D demo-sleep=SECONDS (Set the wait time after Slow & Demo Mode actions.)
Expand Down Expand Up @@ -221,6 +222,7 @@ def get_configured_sb(context):
sb.rec_print = False
sb.report_on = False
sb.is_pytest = False
sb.fast_mode = False
sb.slow_mode = False
sb.demo_mode = False
sb.time_limit = None
Expand Down Expand Up @@ -718,6 +720,10 @@ def get_configured_sb(context):
sb.recorder_mode = True
sb.recorder_ext = True
continue
# Handle: -D fast / fast-mode / fast_mode
if low_key in ["fast", "fast-mode", "fast_mode"]:
sb.fast_mode = True
continue
# Handle: -D slow / slowmo / slow-mode / slow_mode
if low_key in ["slow", "slowmo", "slow-mode", "slow_mode"]:
sb.slow_mode = True
Expand Down
13 changes: 11 additions & 2 deletions seleniumbase/core/browser_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,15 @@ def __is_cdp_swap_needed(driver):
return shared_utils.is_cdp_swap_needed(driver)


def __is_using_fast_mode():
if (
(hasattr(sb_config, "fast_mode") and sb_config.fast_mode)
or "--fast" in sys.argv
):
return True
return False


def uc_execute_cdp_cmd(driver, *args, **kwargs):
if not driver.is_connected():
driver.connect()
Expand Down Expand Up @@ -670,7 +679,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
):
# CDP Mode was already initialized
driver.cdp.open(url, **kwargs)
if not safe_url:
if not safe_url and not __is_using_fast_mode():
time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)
if IS_WINDOWS:
time.sleep(constants.UC.EXTRA_WINDOWS_WAIT)
Expand Down Expand Up @@ -755,7 +764,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
shared_utils.make_writable(constants.MultiBrowser.PYAUTOGUILOCK)
loop.run_until_complete(page.activate())
loop.run_until_complete(page.wait())
if not safe_url:
if not safe_url and not __is_using_fast_mode():
time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)
if IS_WINDOWS:
time.sleep(constants.UC.EXTRA_WINDOWS_WAIT)
Expand Down
Loading
Loading