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
76 changes: 28 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<p align="center"><b align="center">Includes stealth options for bypassing bot-detection and CAPTCHAs.<br /></b></p>

<p align="center">
<a href="https://pypi.python.org/pypi/seleniumbase" target="_blank" rel="noopener"><img src="https://img.shields.io/pypi/v/seleniumbase.svg?style=flat-square&color=3399EE" alt="PyPI version" height="20" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/actions" target="_blank" rel="noopener"><img src="https://img.shields.io/github/actions/workflow/status/seleniumbase/SeleniumBase/python-package.yml?branch=master&style=flat-square&label=CI" alt="SeleniumBase GitHub Actions" height="20" /></a> <a href="https://seleniumbase.io" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/docs-seleniumbase.io-11BBAA.svg?style=flat-square" alt="SeleniumBase Docs" height="20" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/LICENSE" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/license-MIT-33A6C6.svg?style=flat-square" title="SeleniumBase" height="20" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/tree/master/mcp_servers" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/MCP-tool_catalog-7C6EE5?style=flat-square" height="20" /></a> <a href="https://pepy.tech/projects/seleniumbase?timeRange=threeMonths&category=version&includeCIDownloads=true&granularity=daily&viewType=line&versions=*" target="_blank" rel="noopener"><img src="https://static.pepy.tech/badge/seleniumbase" alt="SeleniumBase PyPI downloads" height="20" /></a><br />
<a href="https://pypi.python.org/pypi/seleniumbase" target="_blank" rel="noopener"><img src="https://img.shields.io/pypi/v/seleniumbase.svg?style=flat-square&color=3399EE" alt="PyPI version" height="20" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/actions" target="_blank" rel="noopener"><img src="https://img.shields.io/github/actions/workflow/status/seleniumbase/SeleniumBase/python-package.yml?branch=master&style=flat-square&label=CI" alt="SeleniumBase GitHub Actions" height="20" /></a> <a href="https://seleniumbase.io" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/docs-seleniumbase.io-11BBAA.svg?style=flat-square" alt="SeleniumBase Docs" height="20" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/LICENSE" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/license-MIT-33A6C6.svg?style=flat-square" title="SeleniumBase" height="20" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/tree/master/mcp_servers" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/MCP-tool_info-7C6EE5?style=flat-square" height="20" /></a> <a href="https://pepy.tech/projects/seleniumbase?timeRange=threeMonths&category=version&includeCIDownloads=true&granularity=daily&viewType=line&versions=*" target="_blank" rel="noopener"><img src="https://static.pepy.tech/badge/seleniumbase" alt="SeleniumBase PyPI downloads" height="20" /></a><br />
<a href="https://www.youtube.com/playlist?list=PLp9uKicxkBc5UIlGi2BuE3aWC7JyXpD3m" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/docs-📺-D844E8.svg?style=flat-square&logo=YouTube&logoColor=white&label=YouTube" alt="YouTube Channel" height="20" /></a> <a href="https://www.facebook.com/SeleniumBase/" target="_blank" rel="noopener"> <img src="https://img.shields.io/badge/Facebook-4K_followers-1877F2?style=flat-square&logo=facebook&logoColor=white" alt="SeleniumBase Facebook Followers" height="20" /></a> <a href="https://x.com/seleniumbase" target="_blank" rel="noopener"><img src="https://img.shields.io/badge/-1K_followers-1DA1F2?style=flat-square&logo=x&logoColor=white&labelColor=555555" alt="SeleniumBase X Followers" height="20" /></a> <a href="https://discord.gg/EdhQTn3EyE" target="_blank" rel="noopener"><img src="https://img.shields.io/discord/727927627830001734?style=flat-square&color=7289DA&label=Discord&logo=discord&logoColor=white" height="20" /></a> <a href="https://github.com/seleniumbase/SeleniumBase" target="_blank" rel="noopener"><img src="https://badgen.net/github/stars/seleniumbase/SeleniumBase" title="Stars" height="20" /></a>
</p>

Expand Down Expand Up @@ -66,20 +66,30 @@

--------

📝 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_cdp_mode.py">an example</a> with <b><code translate="no">seleniumbase</code></b> <b><code translate="no">sb_cdp</code></b> (<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md#-pure-cdp-mode-sb_cdp"><i>Pure CDP Mode</i></a>):<br /><em>(It bypasses both BrowserScan AND Sannysoft Bot-Detection)</em>
📝 [This example](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_cdp_mode.py) uses <b><code translate="no">seleniumbase</code></b> <b><code translate="no">sb_cdp</code></b> (*[Pure CDP Mode](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md#Pure_CDP_Mode)*):<br /><em>(It bypasses BrowserScan, Sannysoft, and Cloudflare bot-detection)</em>

```python
from seleniumbase import sb_cdp

sb = sb_cdp.Chrome()
sb = sb_cdp.Chrome() # Launch a stealth browser
sb.goto("https://browserscan.net/bot-detection")
sb.assert_element('strong:contains("Normal")')
sb.sleep(1)

sb.goto("https://bot.sannysoft.com/")
sb.assert_element(".passed")
sb.assert_element_not_visible(".failed")
sb.sleep(1)
sb.quit()

sb.goto("https://www.planetminecraft.com/account")
sb.type('[name="email"]', "Name123")
sb.type('[name="password"]', "Pass")
sb.click("input#autologin")
sb.solve_captcha() # Works on different CAPTCHAs
sb.assert_element('input[name="login"]')
sb.assert_element_not_visible("input[disabled]")
sb.sleep(1)
sb.quit() # Quit browser before end-of-program
```

<p align="center">
Expand All @@ -92,37 +102,16 @@ sb.quit()
<br /><em>(All Sannysoft bot-detection tests passed successfully)</em>
</p>

--------

📝 Here's <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_cdp_planetmc.py">another example</a> with <b><code translate="no">seleniumbase</code></b> <b><code translate="no">sb_cdp</code></b> (<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/ReadMe.md#-pure-cdp-mode-sb_cdp"><i>Pure CDP Mode</i></a>):<br />(It performs various browser actions and bypasses a Cloudflare Turnstile)

```python
from seleniumbase import sb_cdp

sb = sb_cdp.Chrome(incognito=True)
sb.goto("https://www.planetminecraft.com/account")
sb.assert_element('input[name="login"]')
sb.type('input[name="email"]', "test@example.com")
sb.type('input[name="password"]', "Fake_Password")
sb.click("input#autologin") # The checkbox
if sb.is_element_visible("input[disabled]"):
sb.solve_captcha() # Enables the input
sb.assert_element_absent("input[disabled]")
sb.sleep(1.5)
sb.quit()
```

```
python raw_cdp_planetmc.py
```

<p align="left">
<p align="center">
<img src="https://seleniumbase.github.io/other/pmc_captcha_2.png" width="530" style="max-width: 100% !important; height: auto !important;" alt="Cloudflare Turnstile bypassed successfully" />
<br /><em>(Cloudflare Turnstile bypassed successfully on Planet Minecraft)</em>
</p>

--------

✅ <b><code>sb.solve_captcha()</code></b> handles CAPTCHAs that aren't bypassed automatically.<br />(If called when there's no CAPTCHA on the page, then nothing happens / no-op.)

ℹ️ <code>sb.assert_*</code> methods wait up to 7 seconds for the expected condition to occur.
ℹ️ <code>sb.assert_*</code> methods wait up to 7 seconds for the expected condition.<br />(Raises an exception if the condition isn't met in time.)

--------

Expand Down Expand Up @@ -635,7 +624,7 @@ self.assert_no_js_errors() # Verify there are no JS errors.
self.type("input", "dogs\n") # (The "\n" presses ENTER)
```

Most SeleniumBase scripts can be run with <code translate="no">pytest</code>, <code translate="no">pynose</code>, or pure <code translate="no">python</code>. Not all test runners can run all test formats. For example, tests that use the `sb` pytest fixture can only be run with `pytest`. (See <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md">Syntax Formats</a>) There's also a <a href="https://behave.readthedocs.io/en/stable/gherkin.html#features" target="_blank" rel="noopener">Gherkin</a> test format that runs with <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd/ReadMe.md">behave</a>.
Most SeleniumBase scripts can be run with <code translate="no">pytest</code>, <code translate="no">pynose</code>, or pure <code translate="no">python</code>. Not all test runners can run all test formats. For example, tests that use the `sb` pytest fixture can only be run with `pytest`. (See <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md">Syntax Formats</a>) There's also a <a href="https://behave.readthedocs.io/en/latest/gherkin/" target="_blank" rel="noopener">Gherkin</a> test format that runs with <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/behave_bdd/ReadMe.md">behave</a>.

```zsh
pytest coffee_cart_tests.py --rs
Expand Down Expand Up @@ -797,10 +786,15 @@ pytest test_coffee_cart.py --trace
--sjw # (Skip JS Waits for readyState to be "complete" or Angular to load.)
--wfa # (Wait for AngularJS to be done loading after specific web actions.)
--pls=PLS # (Set pageLoadStrategy on Chrome: "normal", "eager", or "none".)
--headless # (The default headless mode. Linux uses this mode by default.)
--headless1 # (Use Chrome's old headless mode. Fast, but has limitations.)
--headless2 # (Use Chrome's new headless mode, which supports extensions.)
--headless # (Use headless mode. Linux uses this mode by default.)
--chs # (Use Chrome-Headless-Shell, a limited Chrome without stealth.)
--headed # (Run tests in headed/GUI mode on Linux OS, where not default.)
--incognito # (Enable Chrome's Incognito mode.)
--guest # (Enable Chrome's Guest mode.)
--uc # (Use undetected-chromedriver to evade bot-detection.)
--uc-cdp-events # (Capture CDP events in UC Mode. NOT related to CDP Mode.)
--log-cdp # ("goog:loggingPrefs", {"performance": "ALL", "browser": "ALL"})
--remote-debug # (Sync to Chrome Remote Debugger chrome://inspect/#devices)
--xvfb # (Run tests using the Xvfb virtual display server on Linux OS.)
--xvfb-metrics=STRING # (Set Xvfb display size on Linux: "Width,Height".)
--locale=LOCALE_CODE # (Set the Language Locale Code for the web browser.)
Expand Down Expand Up @@ -834,17 +828,11 @@ pytest test_coffee_cart.py --trace
--disable-ws # (Disable Web Security on Chromium-based browsers.)
--enable-ws # (Enable Web Security on Chromium-based browsers.)
--enable-sync # (Enable "Chrome Sync" on websites.)
--uc | --undetected # (Use undetected-chromedriver to evade bot-detection.)
--uc-cdp-events # (Capture CDP events when running in "--undetected" mode.)
--log-cdp # ("goog:loggingPrefs", {"performance": "ALL", "browser": "ALL"})
--remote-debug # (Sync to Chrome Remote Debugger chrome://inspect/#devices)
--ftrace | --final-trace # (Debug Mode after each test. Don't use with CI!)
--dashboard # (Enable the SeleniumBase Dashboard. Saved at: dashboard.html)
--dash-title=STRING # (Set the title shown for the generated dashboard.)
--enable-3d-apis # (Enables WebGL and 3D APIs.)
--swiftshader # (Chrome "--use-gl=angle" / "--use-angle=swiftshader-webgl")
--incognito # (Enable Chrome's Incognito mode.)
--guest # (Enable Chrome's Guest mode.)
--dark # (Enable Chrome's Dark mode.)
--devtools # (Open Chrome's DevTools when the browser opens.)
--rs | --reuse-session # (Reuse browser session for all tests.)
Expand Down Expand Up @@ -1413,21 +1401,13 @@ self.activate_jquery()

```python
self.execute_script("jQuery, window.scrollTo(0, 600)") # Scrolling the page

self.execute_script("jQuery('#annoying-widget').hide()") # Hiding elements on a page

self.execute_script("jQuery('#hidden-widget').show(0)") # Showing hidden elements on a page

self.execute_script("jQuery('#annoying-button a').remove()") # Removing elements on a page

self.execute_script("jQuery('%s').mouseover()" % (mouse_over_item)) # Mouse-over elements on a page

self.execute_script("jQuery('input#the_id').val('my_text')") # Fast text input on a page

self.execute_script("jQuery('div#dropdown a.link').click()") # Click elements on a page

self.execute_script("return jQuery('div#amazing')[0].text") # Returns the css "text" of the element given

self.execute_script("return jQuery('textarea')[2].value") # Returns the css "value" of the 3rd textarea element on the page
```

Expand Down
2 changes: 1 addition & 1 deletion SKILLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ pytest tests/ -n=4 --headless # parallel across 4 workers
pytest tests/ --xvfb # Linux virtual display when a headed browser is needed
```

- Linux runs headless by default; use `--headed` to force a GUI. `--headless2` supports extensions.
- Linux runs headless by default; use `--headed` to force a GUI.
- `--rs` reuses one browser session for all tests (faster, but state leaks between tests; use `--crumbs` to clear cookies between them).
- Set `--driver-version=VER` to pin the driver.
- No `--pdb`, `--trace`, or `--show-report` in CI.
Expand Down
4 changes: 2 additions & 2 deletions examples/cdp_mode/raw_planetmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
sb.assert_text("Sign in to Planet Minecraft")
sb.type('input[name="email"]', "test@example.com")
sb.type('input[name="password"]', "Fake_Password")
sb.click("input#autologin") # The checkbox
sb.click("input#autologin") # Unchecks checkbox
if sb.is_element_visible("input[disabled]"):
sb.solve_captcha() # Enables the input button
sb.solve_captcha() # Enables input button
sb.assert_element_absent("input[disabled]")
sb.sleep(1.5)
7 changes: 4 additions & 3 deletions examples/raw_cdp_mode.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Bypass bot-detection with SeleniumBase CDP Mode"""
from seleniumbase import sb_cdp

sb = sb_cdp.Chrome()
sb = sb_cdp.Chrome() # Launch a stealth browser
sb.goto("https://browserscan.net/bot-detection")
sb.assert_element('strong:contains("Normal")')
sb.sleep(1)
Expand All @@ -14,8 +14,9 @@
sb.goto("https://www.planetminecraft.com/account")
sb.type('[name="email"]', "Name123")
sb.type('[name="password"]', "Pass")
sb.solve_captcha()
sb.click("input#autologin")
sb.solve_captcha() # Works on different CAPTCHAs
sb.assert_element('input[name="login"]')
sb.assert_element_not_visible("input[disabled]")
sb.sleep(1)
sb.quit()
sb.quit() # Quit browser before end-of-program
2 changes: 0 additions & 2 deletions examples/test_apple_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ def test_apple_developer_site_webdriver_instructions(self):
self.goto_if_not_url("about:blank")
print("\n Unsupported mode for this test.")
self.skip("Unsupported mode for this test.")
elif self.is_chromium():
self.get_new_driver(browser=self.browser, headless2=True)
self.goto("https://developer.apple.com/search/")
title = "Testing with WebDriver in Safari"
self.type("input#searchField", title + "\n")
Expand Down
16 changes: 8 additions & 8 deletions examples/test_download_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ def test_download_chromedriver_notes(self):
def test_download_files_from_pypi(self):
if self._multithreaded or not self.undetectable:
self.goto_if_not_url("about:blank")
self.skip("Skipping test in multi-threaded mode.")
self.skip("Skip: Requires stealth and not multi-threaded.")
self.goto("https://pypi.org/project/sbvirtualdisplay/#files")
self.assert_element("span#pip-command")
self.assert_text("Download files", "div#files h2.page-title")
self.assert_text("Download files", "a#files-tab")
self.assert_text("Release files", "div#files h2.page-title")
self.assert_text("Release files", "a#files-tab")
pkg_header = self.get_text('h1[class*="header__name"]').strip()
pkg_name = pkg_header.replace(" ", "-")
whl_file = pkg_name + "-py3-none-any.whl"
Expand All @@ -48,7 +48,7 @@ def test_download_files_from_pypi(self):
or self.browser == "ie"
or self.browser == "edge"
or (self.is_chromium() and self.guest_mode)
or (self.is_chromium() and (self.headless or self.headless2))
or (self.is_chromium() and self.headless)
):
whl_href = self.get_attribute(whl_selector, "href")
tar_href = self.get_attribute(tar_selector, "href")
Expand Down Expand Up @@ -85,12 +85,12 @@ def test_download_files_from_pypi(self):
# Get file sizes in kB to compare actual values with displayed values
whl_file_kb = whl_file_bytes / 1000.0
whl_line_fi = self.get_text('a[href$=".whl"]').strip()
whl_line = self.get_text('div.file:contains("%s")' % whl_line_fi)
whl_display_kb = float(whl_line.split("(")[1].split(" ")[0])
whl_line = self.get_text('td:contains("%s")' % whl_line_fi)
whl_display_kb = float(whl_line.split(" ")[1].split(" ")[0])
tar_gz_file_kb = tar_gz_file_bytes / 1000.0
tar_gz_line_fi = self.get_text('a[href$=".tar.gz"]').strip()
tar_gz_line = self.get_text('div.file:contains("%s")' % tar_gz_line_fi)
tar_gz_display_kb = float(tar_gz_line.split("(")[1].split(" ")[0])
tar_gz_line = self.get_text('tr:contains("%s")' % tar_gz_line_fi)
tar_gz_display_kb = float(tar_gz_line.split(" ")[1].split(" ")[0])

# Verify downloaded files are the correct size (account for rounding)
self.assert_true(
Expand Down
7 changes: 5 additions & 2 deletions examples/test_download_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ def test_download_images_via_screenshot(self):
count = 0
for src in unique_src_values:
self.goto(src)
if not self.headless and not self.headless2:
self.sleep(0.3)
image = self.find_element("img")
if src.startswith("data:") or ";base64" in src:
# Special Cases: SVGs, etc. Convert to PNG.
Expand All @@ -53,6 +51,11 @@ def test_download_images_via_screenshot(self):
filename = src.split("/")[-1]
folder = "downloaded_files"
file_path = os.path.join(folder, filename)
# Too much screen flicker if not slowed
if not self.headless:
self.sleep(0.15)
image.screenshot(file_path)
if not self.headless:
self.sleep(0.15)
self.assert_downloaded_file(filename)
print(file_path)
Loading
Loading