diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md
index d039f40ed15..aa08a70d7fb 100644
--- a/examples/cdp_mode/ReadMe.md
+++ b/examples/cdp_mode/ReadMe.md
@@ -2,7 +2,14 @@
## [
](https://github.com/seleniumbase/SeleniumBase/) CDP Mode 🐙
-🐙 SeleniumBase CDP Mode (Chrome Devtools Protocol Mode) is a special mode inside of SeleniumBase UC Mode that lets bots appear human while controlling the browser with the CDP-Driver. Although regular UC Mode can't perform WebDriver actions while the driver is disconnected from the browser, the CDP-Driver can still perform actions while maintaining its cover. (For Python 3.11 or newer!)
+🐙 SeleniumBase CDP Mode (Chrome Devtools Protocol Mode) is a special mode inside of SeleniumBase UC Mode that lets bots appear human while controlling the browser with the CDP-Driver. Although regular UC Mode can't perform WebDriver actions while the driver is disconnected from the browser, the CDP-Driver can still perform actions while maintaining its cover.
+
+--------
+
+
+
(Watch the CDP Mode tutorial on YouTube! ▶️)
+ +-------- 👤 UC Mode avoids bot-detection by first disconnecting WebDriver from the browser at strategic times, calling specialPyAutoGUI methods to bypass CAPTCHAs (as needed), and finally reconnecting the driver 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 CDP Mode comes in.)
@@ -410,6 +417,8 @@ sb.cdp.assert_url(url)
sb.cdp.assert_url_contains(substring)
sb.cdp.assert_text(text, selector="html")
sb.cdp.assert_exact_text(text, selector="html")
+sb.cdp.assert_true()
+sb.cdp.assert_false()
sb.cdp.scroll_into_view(selector)
sb.cdp.scroll_to_y(y)
sb.cdp.scroll_to_top()
diff --git a/examples/cdp_mode/raw_demo_site.py b/examples/cdp_mode/raw_demo_site.py
new file mode 100644
index 00000000000..2c0dda51404
--- /dev/null
+++ b/examples/cdp_mode/raw_demo_site.py
@@ -0,0 +1,72 @@
+"""Example of using various CDP Mode commands"""
+from seleniumbase import SB
+
+with SB(uc=True, test=True) as sb:
+ url = "https://seleniumbase.io/demo_page"
+ sb.activate_cdp_mode(url)
+
+ # Assert various things
+ sb.cdp.assert_title("Web Testing Page")
+ sb.cdp.assert_element("tbody#tbodyId")
+ sb.cdp.assert_text("Demo Page", "h1")
+
+ # Type text into various text fields and then assert
+ sb.cdp.type("#myTextInput", "This is Automated")
+ sb.cdp.type("textarea.area1", "Testing Time!\n")
+ sb.cdp.type('[name="preText2"]', "Typing Text!")
+ sb.cdp.assert_text("This is Automated", "#myTextInput")
+ sb.cdp.assert_text("Testing Time!\n", "textarea.area1")
+ sb.cdp.assert_text("Typing Text!", '[name="preText2"]')
+
+ # Hover & click a dropdown element and assert results
+ sb.cdp.assert_text("Automation Practice", "h3")
+ sb.cdp.gui_hover_and_click("#myDropdown", "#dropOption2")
+ sb.cdp.assert_text("Link Two Selected", "h3")
+
+ # Click a button and then verify the expected results
+ sb.cdp.assert_text("This Text is Green", "#pText")
+ sb.cdp.click('button:contains("Click Me")')
+ sb.cdp.assert_text("This Text is Purple", "#pText")
+
+ # Verify that a slider control updates a progress bar
+ sb.cdp.assert_element('progress[value="50"]')
+ sb.cdp.set_value("input#mySlider", "100")
+ sb.cdp.assert_element('progress[value="100"]')
+
+ # Verify that a "select" option updates a meter bar
+ sb.cdp.assert_element('meter[value="0.25"]')
+ sb.cdp.select_option_by_text("#mySelect", "Set to 75%")
+ sb.cdp.assert_element('meter[value="0.75"]')
+
+ # Verify that clicking a radio button selects it
+ sb.cdp.assert_false(sb.cdp.is_selected("#radioButton2"))
+ sb.cdp.click("#radioButton2")
+ sb.cdp.assert_true(sb.cdp.is_selected("#radioButton2"))
+
+ # Verify that clicking a checkbox makes it selected
+ sb.cdp.assert_element_not_visible("img#logo")
+ sb.cdp.assert_false(sb.cdp.is_selected("#checkBox1"))
+ sb.cdp.click("#checkBox1")
+ sb.cdp.assert_true(sb.cdp.is_selected("#checkBox1"))
+ sb.cdp.assert_element("img#logo")
+
+ # Verify clicking on multiple elements with one call
+ sb.cdp.assert_false(sb.cdp.is_selected("#checkBox2"))
+ sb.cdp.assert_false(sb.cdp.is_selected("#checkBox3"))
+ sb.cdp.assert_false(sb.cdp.is_selected("#checkBox4"))
+ sb.cdp.click_visible_elements("input.checkBoxClassB")
+ sb.cdp.assert_true(sb.cdp.is_selected("#checkBox2"))
+ sb.cdp.assert_true(sb.cdp.is_selected("#checkBox3"))
+ sb.cdp.assert_true(sb.cdp.is_selected("#checkBox4"))
+
+ # Verify Drag and Drop
+ sb.cdp.assert_element_not_visible("div#drop2 img#logo")
+ sb.cdp.gui_drag_and_drop("img#logo", "div#drop2")
+ sb.cdp.assert_element("div#drop2 img#logo")
+
+ # Click inside an iframe and test highlighting
+ sb.cdp.flash("iframe#myFrame3")
+ sb.cdp.sleep(1)
+ sb.cdp.nested_click("iframe#myFrame3", ".fBox")
+ sb.cdp.sleep(0.5)
+ sb.cdp.highlight("iframe#myFrame3")
diff --git a/examples/presenter/uc_presentation.py b/examples/presenter/uc_presentation.py
index c52dc7ad328..554cefcfa90 100644
--- a/examples/presenter/uc_presentation.py
+++ b/examples/presenter/uc_presentation.py
@@ -1,3 +1,4 @@
+# https://www.youtube.com/watch?v=5dMFI3e85ig
import os
import subprocess
from contextlib import suppress
diff --git a/examples/presenter/uc_presentation_3.py b/examples/presenter/uc_presentation_3.py
index 1b66fef2090..ac9f6b3e610 100644
--- a/examples/presenter/uc_presentation_3.py
+++ b/examples/presenter/uc_presentation_3.py
@@ -1,3 +1,4 @@
+# https://www.youtube.com/watch?v=-EpZlhGWo9k
import sys
from contextlib import suppress
from seleniumbase import BaseCase
diff --git a/examples/presenter/uc_presentation_4.py b/examples/presenter/uc_presentation_4.py
new file mode 100644
index 00000000000..b5700836cb4
--- /dev/null
+++ b/examples/presenter/uc_presentation_4.py
@@ -0,0 +1,1070 @@
+# https://www.youtube.com/watch?v=Mr90iQmNsKM
+from contextlib import suppress
+from seleniumbase import BaseCase
+from seleniumbase import SB
+BaseCase.main(__name__, __file__)
+
+
+class UCPresentationClass(BaseCase):
+ def test_presentation_4(self):
+ self.open("data:,")
+ self.set_window_position(4, 40)
+ self._output_file_saves = False
+ self.create_presentation(theme="serif", transition="fade")
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.begin_presentation(filename="uc_presentation.html")
+
+ with suppress(Exception):
+ self.open("https://www.bostoncodecamp.com/CC37/info")
+ self.create_tour(theme="hopscotch")
+ self.add_tour_step(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "Note: There are different kinds of reCAPTCHA,
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "🔹 By the end of this presentation... 🔹
'
+ )
+ self.add_slide(
+ "About me: (Michael Mintz)
\n" + "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ '
'
+ "
'
+ )
+ self.add_slide(
+ "Playwright using CDP"
+ '
'
+ )
+ self.add_slide(
+ "Selenium using CDP"
+ '
'
+ )
+ self.add_slide(
+ "Microsoft still supports Selenium,
"
+ "even though they have Playwright.
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ "As a birthday gift, BrightData invested a lot of money into "
+ "Selenium (making them an official sponsor)."
+ '
'
+ )
+ self.add_slide(
+ "That's great news for the Selenium community!"
+ '
'
+ )
+ self.add_slide(
+ "Now, let's get back to CDP..."
+ '
'
+ )
+ self.add_slide(
+ "There are lots of GitHub repos using CDP.
'
+ )
+ self.add_slide(
+ "The first major Python implementation of CDP:"
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ "PyCDP was the key ingredient to stealthy automation."
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ "In addition to using CDP for controlling Chrome in a"
+ " stealthy way, you can also achieve stealth by using"
+ " tools that can control the mouse and keyboard.PyAutoGUI is one such tool:"
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ "PyAutoGUI requires a headed browser to work.
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ "List of sites with their invisible anti-bot services:"
+ '
'
+ )
+ self.add_slide(
+ ""
+ "planetminecraft.com/account/sign_in/"
+ ""
+ "cloudflare.com/login"
+ ""
+ "gitlab.com/users/sign_in"
+ "The code for the previous live demo:
" + ""
+ "bing.com/turing/captcha/challenge"
+ ""
+ "pokemon.com/us"
+ ""
+ "walmart.com"
+ ""
+ "albertsons.com/recipes/"
+ ""
+ "easyjet.com/en/"
+ ""
+ "hyatt.com"
+ ""
+ "bestwestern.com/en_US.html"
+ ""
+ "priceline.com"
+ "
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ ""
+ "nike.com"
+ ""
+ "nordstrom.com"
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ "And steathy CDP works well in GitHub Actions."
+ '
'
+ )
+ self.add_slide(
+ "Why does stealthy CDP work in GitHub Actions,
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide("
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ "sbase recorder --uc
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.begin_presentation(filename="uc_presentation.html")
+
+ # import sys
+ # from seleniumbase.console_scripts import sb_recorder
+ # sys.argv.append("--uc")
+ # sb_recorder.main()
+
+ self.create_presentation(theme="serif", transition="none")
+ self.add_slide(
+ "
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ '
'
+ )
+ self.add_slide(
+ "And that's the secret to building a test recorder!"
+ '
'
+ )
+ self.add_slide(
+ "Also note that there are more stealth CDP repos
'
+ )
+ self.add_slide(
+ "👤
"
+ "from the SeleniumBase GitHub repo...
'
+ )
+ self.add_slide(
+ "
'
+ )
+ self.begin_presentation(filename="uc_presentation.html")
diff --git a/help_docs/uc_mode.md b/help_docs/uc_mode.md
index 60cad7c8d23..02921e894ea 100644
--- a/help_docs/uc_mode.md
+++ b/help_docs/uc_mode.md
@@ -23,6 +23,11 @@
----
+
+(Watch the 4th UC Mode tutorial on YouTube! ▶️)
+ +---- + 👤 UC Mode is based on [undetected-chromedriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver). UC Mode includes multiple updates, fixes, and improvements, such as: * Automatically changing user-agents to prevent detection. diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 425f1365c70..ca10f028ed2 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.33.0" +__version__ = "4.33.1" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index 5283b6dc75f..2240410e938 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -612,6 +612,7 @@ def uc_open_with_cdp_mode(driver, url=None): cdp.save_cookies = CDPM.save_cookies cdp.load_cookies = CDPM.load_cookies cdp.clear_cookies = CDPM.clear_cookies + cdp.sleep = CDPM.sleep cdp.bring_active_window_to_front = CDPM.bring_active_window_to_front cdp.bring_to_front = CDPM.bring_active_window_to_front cdp.get_active_element = CDPM.get_active_element @@ -684,6 +685,7 @@ def uc_open_with_cdp_mode(driver, url=None): cdp.select_if_unselected = CDPM.select_if_unselected cdp.unselect_if_selected = CDPM.unselect_if_selected cdp.is_checked = CDPM.is_checked + cdp.is_selected = CDPM.is_selected cdp.is_element_present = CDPM.is_element_present cdp.is_element_visible = CDPM.is_element_visible cdp.wait_for_element_visible = CDPM.wait_for_element_visible @@ -699,6 +701,8 @@ def uc_open_with_cdp_mode(driver, url=None): cdp.assert_url_contains = CDPM.assert_url_contains cdp.assert_text = CDPM.assert_text cdp.assert_exact_text = CDPM.assert_exact_text + cdp.assert_true = CDPM.assert_true + cdp.assert_false = CDPM.assert_false cdp.scroll_into_view = CDPM.scroll_into_view cdp.scroll_to_y = CDPM.scroll_to_y cdp.scroll_to_top = CDPM.scroll_to_top @@ -1167,7 +1171,12 @@ def _uc_gui_click_captcha( frame = "%s div" % frame elif ( driver.is_element_present('[name*="cf-turnstile-"]') - and driver.is_element_present('[class*=spacer] + div div') + and driver.is_element_present("#challenge-form div > div") + ): + frame = "#challenge-form div > div" + elif ( + driver.is_element_present('[name*="cf-turnstile-"]') + and driver.is_element_present("[class*=spacer] + div div") ): frame = '[class*=spacer] + div div' elif ( @@ -1240,8 +1249,8 @@ def _uc_gui_click_captcha( return try: if ctype == "g_rc" and not driver.is_connected(): - x = (i_x + 32) * width_ratio - y = (i_y + 34) * width_ratio + x = (i_x + 29) * width_ratio + y = (i_y + 35) * width_ratio elif visible_iframe: selector = "span" if ctype == "g_rc": @@ -1256,8 +1265,8 @@ def _uc_gui_click_captcha( y = i_y + element.rect["y"] + (element.rect["height"] / 2.0) y += 0.5 else: - x = (i_x + 34) * width_ratio - y = (i_y + 34) * width_ratio + x = (i_x + 32) * width_ratio + y = (i_y + 32) * width_ratio if driver.is_connected(): driver.switch_to.default_content() except Exception: @@ -1497,6 +1506,7 @@ def _uc_gui_handle_captcha_(driver, frame="iframe", ctype=None): tab_count += 1 time.sleep(0.027) active_element_css = js_utils.get_active_element_css(driver) + print(active_element_css) if ( active_element_css.startswith(selector) or active_element_css.endswith(" > div" * 2) @@ -1514,7 +1524,10 @@ def _uc_gui_handle_captcha_(driver, frame="iframe", ctype=None): except Exception: return if ( - driver.is_element_present(".footer .clearfix .ray-id") + ( + driver.is_element_present(".footer .clearfix .ray-id") + or driver.is_element_present("script[data-cf-beacon]") + ) and hasattr(sb_config, "_saved_cf_tab_count") and sb_config._saved_cf_tab_count ): diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index ace9190bbe7..17c1683d4f7 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1681,6 +1681,14 @@ def assert_exact_text( % (text, element.text_all, selector) ) + def assert_true(self, expression): + if not expression: + raise AssertionError("%s is not true") + + def assert_false(self, expression): + if expression: + raise AssertionError("%s is not false") + def scroll_into_view(self, selector): self.find_element(selector).scroll_into_view() self.loop.run_until_complete(self.page.wait()) diff --git a/seleniumbase/undetected/cdp_driver/browser.py b/seleniumbase/undetected/cdp_driver/browser.py index 882de93edca..cadb8fa73bf 100644 --- a/seleniumbase/undetected/cdp_driver/browser.py +++ b/seleniumbase/undetected/cdp_driver/browser.py @@ -6,8 +6,9 @@ import json import logging import os -import pickle import pathlib +import pickle +import re import shutil import urllib.parse import urllib.request @@ -644,7 +645,7 @@ async def get_all( """ connection = None for _tab in self._browser.tabs: - if _tab.closed: + if hasattr(_tab, "closed") and _tab.closed: continue connection = _tab break @@ -674,7 +675,7 @@ async def set_all(self, cookies: List[cdp.network.CookieParam]): """ connection = None for _tab in self._browser.tabs: - if _tab.closed: + if hasattr(_tab, "closed") and _tab.closed: continue connection = _tab break @@ -698,13 +699,11 @@ async def save(self, file: PathLike = ".session.dat", pattern: str = ".*"): - Contain "nowsecure" :type pattern: str """ - import re - pattern = re.compile(pattern) save_path = pathlib.Path(file).resolve() connection = None for _tab in self._browser.tabs: - if _tab.closed: + if hasattr(_tab, "closed") and _tab.closed: continue connection = _tab break @@ -746,15 +745,13 @@ async def load(self, file: PathLike = ".session.dat", pattern: str = ".*"): - Contain "nowsecure" :type pattern: str """ - import re - pattern = re.compile(pattern) save_path = pathlib.Path(file).resolve() cookies = pickle.load(save_path.open("r+b")) included_cookies = [] connection = None for _tab in self._browser.tabs: - if _tab.closed: + if hasattr(_tab, "closed") and _tab.closed: continue connection = _tab break @@ -779,7 +776,7 @@ async def clear(self): """ connection = None for _tab in self._browser.tabs: - if _tab.closed: + if hasattr(_tab, "closed") and _tab.closed: continue connection = _tab break diff --git a/setup.py b/setup.py index 0f6788c33cf..0e72782b069 100755 --- a/setup.py +++ b/setup.py @@ -254,7 +254,7 @@ "pdfminer": [ 'pdfminer.six==20240706', 'cryptography==39.0.2;python_version<"3.9"', - 'cryptography==43.0.3;python_version>="3.9"', + 'cryptography==44.0.0;python_version>="3.9"', 'cffi==1.17.1', "pycparser==2.22", ],