diff --git a/README.md b/README.md index ac0f892ddc1..085cec4c7e8 100755 --- a/README.md +++ b/README.md @@ -66,17 +66,19 @@ -------- -

📗 Here's a test script that performs a Google Search using SeleniumBase UC Mode:
SeleniumBase/examples/raw_google.py (Results are saved as PDF, HTML, and PNG)

+

📗 This script performs a Google Search using SeleniumBase UC Mode + CDP Mode:
SeleniumBase/examples/raw_google.py (Results are saved as PDF, HTML, and PNG)

```python from seleniumbase import SB -with SB(test=True, uc=True) as sb: - sb.open("https://google.com/ncr") +with SB(uc=True, test=True) as sb: + url = "https://google.com/ncr" + sb.activate_cdp_mode(url) sb.type('[title="Search"]', "SeleniumBase GitHub page") sb.click("div:not([jsname]) > * > input") + sb.sleep(2) print(sb.get_page_title()) - sb.sleep(2) # Wait for the "AI Overview" result + sb.sleep(1) # Wait for the "AI Overview" result if sb.is_text_visible("Generating"): sb.wait_for_text("AI Overview") sb.save_as_pdf_to_logs() # Saved to ./latest_logs/ @@ -98,8 +100,8 @@ from seleniumbase import SB with SB(uc=True, test=True, locale="en") as sb: url = "https://gitlab.com/users/sign_in" sb.activate_cdp_mode(url) - sb.sleep(2.2) - sb.uc_gui_click_captcha() + sb.sleep(2) + sb.solve_captcha() # (The rest is for testing and demo purposes) sb.assert_text("Username", '[for="user_login"]', timeout=3) sb.assert_element('label[for="user_login"]') @@ -118,7 +120,7 @@ from seleniumbase import sb_cdp url = "https://gitlab.com/users/sign_in" sb = sb_cdp.Chrome(url) sb.sleep(2.5) -sb.gui_click_captcha() +sb.solve_captcha() sb.highlight('h1:contains("GitLab")') sb.highlight('button:contains("Sign in")') sb.driver.stop() diff --git a/examples/cdp_mode/ReadMe.md b/examples/cdp_mode/ReadMe.md index 5d35dda1d2e..18e194b60db 100644 --- a/examples/cdp_mode/ReadMe.md +++ b/examples/cdp_mode/ReadMe.md @@ -2,7 +2,7 @@ ## [](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 CDP (via MyCDP). Although regular UC Mode can't perform WebDriver actions while the driver is disconnected from the browser, CDP can. CDP Mode can also be used independently of WebDriver via Pure CDP Mode (sb_cdp). +🐙 SeleniumBase CDP Mode is a stealth mode of SeleniumBase that uses the Chrome Devtools Protocol (via MyCDP) to control the web browser. CDP Mode can be used either as a subset of SeleniumBase UC Mode, or via Pure CDP Mode (sb_cdp), which doesn't use WebDriver at all, and has a slightly different setup. -------- @@ -21,7 +21,7 @@ -------- -👤 UC Mode avoids bot-detection by first disconnecting WebDriver from the browser at strategic times, calling special PyAutoGUI 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.) +👤 UC Mode avoids bot-detection by first disconnecting WebDriver from the browser at strategic times, calling special PyAutoGUI 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.) 🐙 CDP Mode is based on python-cdp, trio-cdp, and nodriver. trio-cdp is an early implementation of python-cdp, and nodriver is a modern implementation of python-cdp. (Refactored Python-CDP code is imported from MyCDP.) @@ -53,19 +53,17 @@ from seleniumbase import SB with SB(uc=True, test=True, locale="en") as sb: url = "https://gitlab.com/users/sign_in" sb.activate_cdp_mode(url) - sb.sleep(2.2) - sb.uc_gui_click_captcha() + sb.sleep(2) + sb.solve_captcha() ``` -(If the CAPTCHA wasn't bypassed automatically when going to the URL, then `sb.uc_gui_click_captcha()` gets the job done with a mouse click from [PyAutoGUI](https://github.com/asweigart/pyautogui).) - -ℹ️ Note that `PyAutoGUI` is an optional dependency. If calling a method that uses it when not already installed, then `SeleniumBase` installs `PyAutoGUI` at run-time. +(If the CAPTCHA wasn't bypassed automatically when going to the URL, then `sb.solve_captcha()` gets the job done.) -------- -You can also use `sb.cdp.gui_click_element(selector)` to click on elements using `PyAutoGUI`. (This is useful when clicking inside `#shadow-root`.) Example: +`sb.cdp.gui_click_element(selector)` lets you click on elements using `PyAutoGUI`. Example: ```python from seleniumbase import SB @@ -86,7 +84,9 @@ Eg. `sb.cdp.gui_click_element("#turnstile-widget div")` -In most cases, `sb.uc_gui_click_captcha()` is good enough for CF Turnstiles without needing `sb.cdp.gui_click_element(selector)`. (See [SeleniumBase/examples/cdp_mode/raw_planetmc.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_planetmc.py)) +In most cases, `sb.solve_captcha()` is good enough for CF Turnstiles without needing `sb.cdp.gui_click_element(selector)`. (See [SeleniumBase/examples/cdp_mode/raw_planetmc.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/cdp_mode/raw_planetmc.py)) + +ℹ️ Note that `PyAutoGUI` is an optional dependency. If calling a method that uses it when not already installed, then `SeleniumBase` installs `PyAutoGUI` at run-time. -------- @@ -94,6 +94,7 @@ In most cases, `sb.uc_gui_click_captcha()` is good enough for CF Turnstiles with * `sb.cdp.click(selector)` (Uses the CDP API to click) * `sb.cdp.click_if_visible(selector)` (Click if visible) +* `sb.cdp.solve_captcha()` (Uses CDP to click a CAPTCHA) * `sb.cdp.gui_click_element(selector)` (Uses `PyAutoGUI`) * `sb.cdp.type(selector, text)` (Type text into a selector) * `sb.cdp.press_keys(selector, text)` (Human-speed `type`) @@ -137,37 +138,37 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb: url = "https://www.pokemon.com/us" sb.activate_cdp_mode(url) sb.sleep(3.2) - sb.cdp.click("button#onetrust-accept-btn-handler") + sb.click("button#onetrust-accept-btn-handler") sb.sleep(1.2) - sb.cdp.click("a span.icon_pokeball") + sb.click("a span.icon_pokeball") sb.sleep(2.5) - sb.cdp.click('b:contains("Show Advanced Search")') + sb.click('b:contains("Show Advanced Search")') sb.sleep(2.5) - sb.cdp.click('span[data-type="type"][data-value="electric"]') + sb.click('span[data-type="type"][data-value="electric"]') sb.sleep(0.5) sb.scroll_into_view("a#advSearch") sb.sleep(0.5) - sb.cdp.click("a#advSearch") + sb.click("a#advSearch") sb.sleep(1.2) - sb.cdp.click('img[src*="img/pokedex/detail/025.png"]') - sb.cdp.assert_text("Pikachu", 'div[class*="title"]') - sb.cdp.assert_element('img[alt="Pikachu"]') - sb.cdp.scroll_into_view("div.pokemon-ability-info") + sb.click('img[src*="img/pokedex/detail/025.png"]') + sb.assert_text("Pikachu", 'div[class*="title"]') + sb.assert_element('img[alt="Pikachu"]') + sb.scroll_into_view("div.pokemon-ability-info") sb.sleep(1.2) sb.cdp.flash('div[class*="title"]') sb.cdp.flash('img[alt="Pikachu"]') sb.cdp.flash("div.pokemon-ability-info") - name = sb.cdp.get_text("label.styled-select") - info = sb.cdp.get_text("div.version-descriptions p.active") + name = sb.get_text("label.styled-select") + info = sb.get_text("div.version-descriptions p.active") print("*** %s: ***\n* %s" % (name, info)) sb.sleep(2) sb.cdp.highlight_overlay("div.pokemon-ability-info") sb.sleep(2) - sb.cdp.open("https://events.pokemon.com/EventLocator/") + sb.open("https://events.pokemon.com/EventLocator/") sb.sleep(2) - sb.cdp.click('span:contains("Championship")') + sb.click('span:contains("Championship")') sb.sleep(2) - events = sb.cdp.select_all("div.event-info__title") + events = sb.select_all("div.event-info__title") print("*** Pokémon Championship Events: ***") for event in events: print("* " + event.text) @@ -202,9 +203,9 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb: sb.click("button.be-button-shop") sb.sleep(6) card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]' - hotels = sb.cdp.select_all(card_info) + hotels = sb.select_all(card_info) print("Hyatt Hotels in %s:" % location) - print("(" + sb.cdp.get_text('span[class*="summary_destination"]') + ")") + print("(" + sb.get_text('span[class*="summary_destination"]') + ")") if len(hotels) == 0: print("No availability over the selected dates!") for hotel in hotels: @@ -234,26 +235,26 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb: url = "https://www.bestwestern.com/en_US.html" sb.activate_cdp_mode(url) sb.sleep(2.5) - sb.cdp.click_if_visible(".onetrust-close-btn-handler") + sb.click_if_visible(".onetrust-close-btn-handler") sb.sleep(1) - sb.cdp.click("input#destination-input") + sb.click("input#destination-input") sb.sleep(2) location = "Palm Springs, CA, USA" - sb.cdp.press_keys("input#destination-input", location) + sb.press_keys("input#destination-input", location) sb.sleep(1) - sb.cdp.click("ul#google-suggestions li") + sb.click("ul#google-suggestions li") sb.sleep(1) - sb.cdp.click("button#btn-modify-stay-update") + sb.click("button#btn-modify-stay-update") sb.sleep(4) - sb.cdp.click("label#available-label") + sb.click("label#available-label") sb.sleep(2.5) print("Best Western Hotels in %s:" % location) - summary_details = sb.cdp.get_text("#summary-details-column") + summary_details = sb.get_text("#summary-details-column") dates = summary_details.split("DESTINATION")[-1] dates = dates.split(" CHECK-OUT")[0].strip() + " CHECK-OUT" dates = dates.replace(" ", " ") print("(Dates: %s)" % dates) - flip_cards = sb.cdp.select_all(".flipCard") + flip_cards = sb.select_all(".flipCard") for i, flip_card in enumerate(flip_cards): hotel = flip_card.query_selector(".hotelName") price = flip_card.query_selector(".priceSection") @@ -291,12 +292,12 @@ with SB(uc=True, test=True, ad_block=True) as sb: if sb.is_element_visible("#px-captcha"): sb.cdp.gui_click_and_hold("#px-captcha", 4.2) sb.sleep(3.2) - sb.cdp.remove_elements('[data-testid="skyline-ad"]') - sb.cdp.remove_elements('[data-testid="sba-container"]') + sb.remove_elements('[data-testid="skyline-ad"]') + sb.remove_elements('[data-testid="sba-container"]') print('*** Walmart Search for "%s":' % search) print(' (Results must contain "%s".)' % required_text) unique_item_text = [] - items = sb.cdp.find_elements('div[data-testid="list-view"]') + items = sb.find_elements('div[data-testid="list-view"]') for item in items: if required_text in item.text: description = item.querySelector( @@ -571,8 +572,10 @@ from seleniumbase import sb_cdp url = "https://seleniumbase.io/apps/turnstile" sb = sb_cdp.Chrome(url) -sb.gui_click_captcha() -sb.sleep(2) +sb.solve_captcha() +sb.assert_element("img#captcha-success") +sb.set_messenger_theme(location="top_left") +sb.post_message("SeleniumBase wasn't detected", duration=3) sb.driver.stop() ``` @@ -611,6 +614,7 @@ After finding an element in CDP Mode, you can access `WebElement` methods: ```python element.clear_input() element.click() +element.click_with_offset(x, y, center=False) element.flash(duration=0.5, color="EE4488") element.focus() element.gui_click(timeframe=0.25) diff --git a/examples/cdp_mode/raw_ahrefs.py b/examples/cdp_mode/raw_ahrefs.py index f2d080487b2..c3e1f4b5cbb 100644 --- a/examples/cdp_mode/raw_ahrefs.py +++ b/examples/cdp_mode/raw_ahrefs.py @@ -6,10 +6,10 @@ submit_button = 'span:contains("Check Authority")' sb.activate_cdp_mode(url) # The bot-check is later sb.type(input_field, "github.com/seleniumbase/SeleniumBase") - sb.cdp.scroll_down(36) + sb.scroll_down(36) sb.click(submit_button) sb.sleep(1) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.sleep(3) sb.wait_for_text_not_visible("Checking", timeout=15) sb.click_if_visible('button[data-cky-tag="close-button"]') diff --git a/examples/cdp_mode/raw_bestwestern.py b/examples/cdp_mode/raw_bestwestern.py index d22db30b6b2..a695fca66e2 100644 --- a/examples/cdp_mode/raw_bestwestern.py +++ b/examples/cdp_mode/raw_bestwestern.py @@ -4,26 +4,26 @@ url = "https://www.bestwestern.com/en_US.html" sb.activate_cdp_mode(url) sb.sleep(2.5) - sb.cdp.click_if_visible(".onetrust-close-btn-handler") + sb.click_if_visible(".onetrust-close-btn-handler") sb.sleep(1) - sb.cdp.click("input#destination-input") + sb.click("input#destination-input") sb.sleep(2) location = "Palm Springs, CA, USA" - sb.cdp.press_keys("input#destination-input", location) + sb.press_keys("input#destination-input", location) sb.sleep(1) - sb.cdp.click("ul#google-suggestions li") + sb.click("ul#google-suggestions li") sb.sleep(1) - sb.cdp.click("button#btn-modify-stay-update") + sb.click("button#btn-modify-stay-update") sb.sleep(4) - sb.cdp.click("label#available-label") + sb.click("label#available-label") sb.sleep(2.5) print("Best Western Hotels in %s:" % location) - summary_details = sb.cdp.get_text("#summary-details-column") + summary_details = sb.get_text("#summary-details-column") dates = summary_details.split("DESTINATION")[-1] dates = dates.split(" CHECK-OUT")[0].strip() + " CHECK-OUT" dates = dates.replace(" ", " ") print("(Dates: %s)" % dates) - flip_cards = sb.cdp.select_all(".flipCard") + flip_cards = sb.select_all(".flipCard") for i, flip_card in enumerate(flip_cards): hotel = flip_card.query_selector(".hotelName") price = flip_card.query_selector(".priceSection") diff --git a/examples/cdp_mode/raw_browserscan.py b/examples/cdp_mode/raw_browserscan.py index 2f60149955d..cbbc3e1c369 100644 --- a/examples/cdp_mode/raw_browserscan.py +++ b/examples/cdp_mode/raw_browserscan.py @@ -6,5 +6,5 @@ sb.sleep(1) sb.cdp.flash("Test Results", duration=4) sb.sleep(1) - sb.cdp.assert_element('strong:contains("Normal")') + sb.assert_element('strong:contains("Normal")') sb.cdp.flash('strong:contains("Normal")', duration=4, pause=4) diff --git a/examples/cdp_mode/raw_cdp_copilot.py b/examples/cdp_mode/raw_cdp_copilot.py index 555f5458faf..5e5d68ee8f5 100644 --- a/examples/cdp_mode/raw_cdp_copilot.py +++ b/examples/cdp_mode/raw_cdp_copilot.py @@ -16,10 +16,10 @@ sb.sleep(1.1) sb.click('button[data-testid="submit-button"]') sb.sleep(2.5) -sb.gui_click_captcha() -sb.sleep(2.5) -sb.gui_click_captcha() +sb.solve_captcha() sb.sleep(3.5) +sb.solve_captcha() +sb.sleep(2.5) stop_button = '[data-testid="stop-button"]' thumbs_up = 'button[data-testid*="-thumbs-up-"]' sb.wait_for_element_absent(stop_button, timeout=50) diff --git a/examples/cdp_mode/raw_cdp_with_sb.py b/examples/cdp_mode/raw_cdp_with_sb.py index ccf3955485d..621308afb63 100644 --- a/examples/cdp_mode/raw_cdp_with_sb.py +++ b/examples/cdp_mode/raw_cdp_with_sb.py @@ -23,7 +23,7 @@ print(sb.get_title()) print("************") for i in range(8): - sb.cdp.scroll_down(50) + sb.scroll_down(50) sb.sleep(0.2) cards = sb.select_all('span[data-automation*="product-list-card"]') for card in cards: diff --git a/examples/cdp_mode/raw_consecutive_c.py b/examples/cdp_mode/raw_consecutive_c.py index 6f0da1c2a9e..ed7251089be 100644 --- a/examples/cdp_mode/raw_consecutive_c.py +++ b/examples/cdp_mode/raw_consecutive_c.py @@ -5,7 +5,10 @@ url = "https://sms-man.com/login" sb.activate_cdp_mode(url) sb.sleep(2.2) - sb.uc_gui_click_captcha() - sb.sleep(2.6) - sb.uc_gui_click_captcha() + if not sb.is_element_present('input[name="email"]'): + sb.solve_captcha() + sb.sleep(1) + sb.wait_for_element('[name="email"]', timeout=3) + sb.sleep(2) + sb.solve_captcha() sb.sleep(2) diff --git a/examples/cdp_mode/raw_copilot.py b/examples/cdp_mode/raw_copilot.py index ebcaffe19d5..b280c58cf0c 100644 --- a/examples/cdp_mode/raw_copilot.py +++ b/examples/cdp_mode/raw_copilot.py @@ -17,10 +17,10 @@ sb.sleep(1.1) sb.click('button[data-testid="submit-button"]') sb.sleep(2.5) - sb.uc_gui_click_captcha() - sb.sleep(2.5) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.sleep(3.5) + sb.solve_captcha() + sb.sleep(2.5) stop_button = '[data-testid="stop-button"]' thumbs_up = 'button[data-testid*="-thumbs-up-"]' sb.wait_for_element_absent(stop_button, timeout=50) diff --git a/examples/cdp_mode/raw_gitlab.py b/examples/cdp_mode/raw_gitlab.py index 5d36c7ed91c..0d9b6a1018d 100644 --- a/examples/cdp_mode/raw_gitlab.py +++ b/examples/cdp_mode/raw_gitlab.py @@ -3,7 +3,7 @@ with SB(uc=True, test=True, locale="en") as sb: url = "https://gitlab.com/users/sign_in" sb.activate_cdp_mode(url) - sb.sleep(2.2) + sb.sleep(2) sb.solve_captcha() # (The rest is for testing and demo purposes) sb.assert_text("Username", '[for="user_login"]', timeout=3) diff --git a/examples/cdp_mode/raw_glassdoor.py b/examples/cdp_mode/raw_glassdoor.py index 0dfd2086a2e..f50fc0b6af8 100644 --- a/examples/cdp_mode/raw_glassdoor.py +++ b/examples/cdp_mode/raw_glassdoor.py @@ -4,7 +4,7 @@ url = "https://www.glassdoor.com/Reviews/index.htm" sb.activate_cdp_mode(url) sb.sleep(2.2) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.highlight('[data-test="global-nav-glassdoor-logo"]') sb.highlight('[data-test="site-header-companies"]') sb.highlight('[data-test="search-button"]') diff --git a/examples/cdp_mode/raw_indeed.py b/examples/cdp_mode/raw_indeed.py index a69ec0e9cb2..3b8f27cca27 100644 --- a/examples/cdp_mode/raw_indeed.py +++ b/examples/cdp_mode/raw_indeed.py @@ -6,7 +6,7 @@ search_box = "input#company-search" if not sb.is_element_present(search_box): sb.sleep(2) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.sleep(1) company = "NASA Jet Propulsion Laboratory" sb.click(search_box) @@ -18,14 +18,14 @@ sb.sleep(1) if not sb.is_element_present(name_header): sb.sleep(2) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.sleep(1) - sb.cdp.highlight(name_header) + sb.highlight(name_header) sb.sleep(1) sb.cdp.highlight('h2:contains("About the company")') sb.sleep(1) for i in range(10): - sb.cdp.scroll_down(12) + sb.scroll_down(12) sb.sleep(0.14) info = sb.find_element('[data-testid="AboutSection-section"]') soup = sb.get_beautiful_soup(info.get_html()).get_text("\n").strip() diff --git a/examples/cdp_mode/raw_kohls.py b/examples/cdp_mode/raw_kohls.py index d5d4b11a7c0..e53d68a2936 100644 --- a/examples/cdp_mode/raw_kohls.py +++ b/examples/cdp_mode/raw_kohls.py @@ -7,23 +7,23 @@ search = "Mickey Mouse Blanket" req_1 = "Mickey" req_2 = "Blanket" - sb.cdp.press_keys('input[name="search"]', search + "\n") + sb.press_keys('input[name="search"]', search + "\n") sb.sleep(5) item_selector = 'div[data-testid*="wallet-wrapper"]' if not sb.is_element_present(item_selector): item_selector = "li.products_grid" - for item in sb.cdp.find_elements(item_selector): + for item in sb.find_elements(item_selector): if "Sponsored" in item.text: item.remove_from_dom() - sb.cdp.remove_elements("#tce-sticky-wrapper") - sb.cdp.remove_elements("li.sponsored-product") - sb.cdp.remove_elements("#tce-dec-ces-3-banner") + sb.remove_elements("#tce-sticky-wrapper") + sb.remove_elements("li.sponsored-product") + sb.remove_elements("#tce-dec-ces-3-banner") print('*** Kohls Search for "%s":' % search) print(' (Results must contain "%s" and "%s".)' % (req_1, req_2)) title_selector = "div.prod_nameBlock p" if not sb.is_element_present(title_selector): title_selector = 'a[class*="sm:text"][href*="/product/"]' - for item in sb.cdp.find_elements(title_selector): + for item in sb.find_elements(title_selector): if item: item.flash(color="44CC88") title = item.text diff --git a/examples/cdp_mode/raw_pokemon.py b/examples/cdp_mode/raw_pokemon.py index 5abf36f6770..87762cc40e1 100644 --- a/examples/cdp_mode/raw_pokemon.py +++ b/examples/cdp_mode/raw_pokemon.py @@ -4,37 +4,37 @@ url = "https://www.pokemon.com/us" sb.activate_cdp_mode(url) sb.sleep(3.2) - sb.cdp.click("button#onetrust-accept-btn-handler") + sb.click("button#onetrust-accept-btn-handler") sb.sleep(1.2) - sb.cdp.click("a span.icon_pokeball") + sb.click("a span.icon_pokeball") sb.sleep(2.5) - sb.cdp.click('b:contains("Show Advanced Search")') + sb.click('b:contains("Show Advanced Search")') sb.sleep(2.5) - sb.cdp.click('span[data-type="type"][data-value="electric"]') + sb.click('span[data-type="type"][data-value="electric"]') sb.sleep(0.5) sb.scroll_into_view("a#advSearch") sb.sleep(0.5) - sb.cdp.click("a#advSearch") + sb.click("a#advSearch") sb.sleep(1.2) - sb.cdp.click('img[src*="img/pokedex/detail/025.png"]') - sb.cdp.assert_text("Pikachu", 'div[class*="title"]') - sb.cdp.assert_element('img[alt="Pikachu"]') - sb.cdp.scroll_into_view("div.pokemon-ability-info") + sb.click('img[src*="img/pokedex/detail/025.png"]') + sb.assert_text("Pikachu", 'div[class*="title"]') + sb.assert_element('img[alt="Pikachu"]') + sb.scroll_into_view("div.pokemon-ability-info") sb.sleep(1.2) sb.cdp.flash('div[class*="title"]') sb.cdp.flash('img[alt="Pikachu"]') sb.cdp.flash("div.pokemon-ability-info") - name = sb.cdp.get_text("label.styled-select") - info = sb.cdp.get_text("div.version-descriptions p.active") + name = sb.get_text("label.styled-select") + info = sb.get_text("div.version-descriptions p.active") print("*** %s: ***\n* %s" % (name, info)) sb.sleep(2) sb.cdp.highlight_overlay("div.pokemon-ability-info") sb.sleep(2) - sb.cdp.open("https://events.pokemon.com/EventLocator/") + sb.open("https://events.pokemon.com/EventLocator/") sb.sleep(2) - sb.cdp.click('span:contains("Championship")') + sb.click('span:contains("Championship")') sb.sleep(2) - events = sb.cdp.select_all("div.event-info__title") + events = sb.select_all("div.event-info__title") print("*** Pokémon Championship Events: ***") for event in events: print("* " + event.text) diff --git a/examples/cdp_mode/raw_req_sb.py b/examples/cdp_mode/raw_req_sb.py index 9686725780c..aab553f6bd2 100644 --- a/examples/cdp_mode/raw_req_sb.py +++ b/examples/cdp_mode/raw_req_sb.py @@ -27,5 +27,5 @@ async def request_paused_handler(event, tab): sb.activate_cdp_mode("about:blank") sb.cdp.add_handler(mycdp.fetch.RequestPaused, request_paused_handler) url = "https://gettyimages.com/photos/firefly-2003-nathan" - sb.cdp.open(url) + sb.open(url) sb.sleep(5) diff --git a/examples/cdp_mode/raw_res_nike.py b/examples/cdp_mode/raw_res_nike.py index 06dcbdaca6a..a984d05f20d 100644 --- a/examples/cdp_mode/raw_res_nike.py +++ b/examples/cdp_mode/raw_res_nike.py @@ -31,12 +31,12 @@ async def receive_handler(event: mycdp.network.ResponseReceived): sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler) sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler) sb.sleep(2.5) - sb.cdp.click('[data-testid="user-tools-container"] search') + sb.click('[data-testid="user-tools-container"] search') sb.sleep(1.5) search = "Nike Air Force 1" - sb.cdp.press_keys('input[type="search"]', search) + sb.press_keys('input[type="search"]', search) sb.sleep(4) - elements = sb.cdp.select_all('ul[data-testid*="products"] figure .details') + elements = sb.select_all('ul[data-testid*="products"] figure .details') if elements: print('**** Found results for "%s": ****' % search) for element in elements: diff --git a/examples/cdp_mode/raw_res_sb.py b/examples/cdp_mode/raw_res_sb.py index 7ea86cd083f..9bf17d1a327 100644 --- a/examples/cdp_mode/raw_res_sb.py +++ b/examples/cdp_mode/raw_res_sb.py @@ -30,5 +30,5 @@ async def receive_handler(event: mycdp.network.ResponseReceived): sb.cdp.add_handler(mycdp.network.RequestWillBeSent, send_handler) sb.cdp.add_handler(mycdp.network.ResponseReceived, receive_handler) url = "https://seleniumbase.io/apps/calculator" - sb.cdp.open(url) + sb.open(url) sb.sleep(1) diff --git a/examples/cdp_mode/raw_socialblade.py b/examples/cdp_mode/raw_socialblade.py index 3c577848d37..bc0174422cc 100644 --- a/examples/cdp_mode/raw_socialblade.py +++ b/examples/cdp_mode/raw_socialblade.py @@ -6,7 +6,7 @@ sb.activate_cdp_mode(url) sb.sleep(1.5) if not sb.is_element_visible('input[placeholder*="Search"]'): - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.sleep(0.5) channel_name = "michaelmintz" channel_title = "Michael Mintz" diff --git a/examples/cdp_mode/raw_walmart.py b/examples/cdp_mode/raw_walmart.py index c9bc0f93190..e5cfb029abe 100644 --- a/examples/cdp_mode/raw_walmart.py +++ b/examples/cdp_mode/raw_walmart.py @@ -16,12 +16,12 @@ if sb.is_element_visible("#px-captcha"): sb.cdp.gui_click_and_hold("#px-captcha", 4.2) sb.sleep(3.2) - sb.cdp.remove_elements('[data-testid="skyline-ad"]') - sb.cdp.remove_elements('[data-testid="sba-container"]') + sb.remove_elements('[data-testid="skyline-ad"]') + sb.remove_elements('[data-testid="sba-container"]') print('*** Walmart Search for "%s":' % search) print(' (Results must contain "%s".)' % required_text) unique_item_text = [] - items = sb.cdp.find_elements('div[data-testid="list-view"]') + items = sb.find_elements('div[data-testid="list-view"]') for item in items: if required_text in item.text: description = item.querySelector( diff --git a/examples/raw_ahrefs.py b/examples/raw_ahrefs.py index 2849374ca14..c990429afd0 100644 --- a/examples/raw_ahrefs.py +++ b/examples/raw_ahrefs.py @@ -4,10 +4,11 @@ url = "https://ahrefs.com/website-authority-checker" input_field = 'input[placeholder="Enter domain"]' submit_button = 'span:contains("Check Authority")' - sb.uc_open_with_reconnect(url) # The bot-check is later + sb.activate_cdp_mode(url) sb.type(input_field, "github.com/seleniumbase/SeleniumBase") - sb.uc_click(submit_button, reconnect_time=3.25) - sb.uc_gui_click_captcha() + sb.click(submit_button) + sb.sleep(2) + sb.solve_captcha() sb.wait_for_text_not_visible("Checking", timeout=15) sb.click_if_visible('button[data-cky-tag="close-button"]') sb.highlight('p:contains("github.com/seleniumbase/SeleniumBase")') diff --git a/examples/raw_bing_captcha.py b/examples/raw_bing_captcha.py index 962bec73be9..052d7cb4160 100644 --- a/examples/raw_bing_captcha.py +++ b/examples/raw_bing_captcha.py @@ -4,5 +4,6 @@ url = "https://www.bing.com/turing/captcha/challenge" sb.activate_cdp_mode(url) sb.sleep(1) - sb.uc_gui_click_captcha() - sb.sleep(1) + sb.solve_captcha() + sb.sleep(2) + breakpoint() diff --git a/examples/raw_cf.py b/examples/raw_cf.py index e6152bc1ba0..7d6cd617f35 100644 --- a/examples/raw_cf.py +++ b/examples/raw_cf.py @@ -1,14 +1,9 @@ -"""SB Manager using UC Mode & PyAutoGUI for bypassing CAPTCHAs.""" +"""SB Manager using CDP Mode for bypassing CAPTCHAs.""" from seleniumbase import SB with SB(uc=True, test=True, locale="en") as sb: url = "https://www.cloudflare.com/login" - sb.uc_open_with_reconnect(url, 5.5) - sb.uc_gui_handle_captcha() # PyAutoGUI press Tab and Spacebar - sb.sleep(2.5) - -with SB(uc=True, test=True, locale="en") as sb: - url = "https://www.cloudflare.com/login" - sb.uc_open_with_reconnect(url, 5.5) - sb.uc_gui_click_captcha() # PyAutoGUI click. (Linux needs it) + sb.activate_cdp_mode(url) + sb.sleep(3.5) + sb.solve_captcha() sb.sleep(2.5) diff --git a/examples/raw_games.py b/examples/raw_games.py index 4f3652f7596..47f5fa8cbc9 100644 --- a/examples/raw_games.py +++ b/examples/raw_games.py @@ -1,14 +1,14 @@ -"""SB Manager using UC Mode for evading bot-detection.""" +"""SB Manager using CDP Mode for evading bot-detection.""" from seleniumbase import SB -with SB(uc=True, test=True, disable_csp=True) as sb: +with SB(uc=True, test=True) as sb: url = "https://steamdb.info/" - sb.uc_open_with_reconnect(url, 3) - sb.uc_click("a.header-login span", 3) - sb.uc_gui_click_captcha() + sb.activate_cdp_mode(url) + sb.sleep(1) + sb.click("a.header-login span") + sb.sleep(2) + sb.solve_captcha() sb.assert_text("Sign in", "button#js-sign-in", timeout=4) - sb.uc_click("button#js-sign-in", 2) + sb.click("button#js-sign-in") sb.highlight("div.page_content form") sb.highlight('button:contains("Sign in")', scroll=False) - sb.set_messenger_theme(location="top_center") - sb.post_message("SeleniumBase wasn't detected", duration=4) diff --git a/examples/raw_gitlab.py b/examples/raw_gitlab.py index 731bf79d962..033399f43d6 100644 --- a/examples/raw_gitlab.py +++ b/examples/raw_gitlab.py @@ -2,8 +2,10 @@ with SB(uc=True, test=True) as sb: url = "https://gitlab.com/users/sign_in" - sb.uc_open_with_reconnect(url) - sb.uc_gui_click_captcha() # Only if needed + sb.activate_cdp_mode(url) + sb.sleep(2) + sb.solve_captcha() + # (The rest is for testing and demo purposes) sb.assert_element('label[for="user_login"]') sb.assert_element('input[data-testid*="username"]') sb.assert_element('input[data-testid*="password"]') diff --git a/examples/raw_google.py b/examples/raw_google.py index 046e2ff7250..b20bf701b2d 100644 --- a/examples/raw_google.py +++ b/examples/raw_google.py @@ -1,11 +1,13 @@ from seleniumbase import SB -with SB(test=True, uc=True) as sb: - sb.open("https://google.com/ncr") +with SB(uc=True, test=True) as sb: + url = "https://google.com/ncr" + sb.activate_cdp_mode(url) sb.type('[title="Search"]', "SeleniumBase GitHub page") sb.click("div:not([jsname]) > * > input") + sb.sleep(2) print(sb.get_page_title()) - sb.sleep(2) # Wait for the "AI Overview" result + sb.sleep(1) # Wait for the "AI Overview" result if sb.is_text_visible("Generating"): sb.wait_for_text("AI Overview") sb.save_as_pdf_to_logs() # Saved to ./latest_logs/ diff --git a/examples/raw_gui_click.py b/examples/raw_gui_click.py index a56a091c0bb..ec96cb51ae5 100644 --- a/examples/raw_gui_click.py +++ b/examples/raw_gui_click.py @@ -2,7 +2,7 @@ with SB(uc=True, test=True) as sb: url = "seleniumbase.io/apps/form_turnstile" - sb.uc_open_with_reconnect(url, 1.1) + sb.activate_cdp_mode(url) sb.press_keys("#name", "SeleniumBase") sb.press_keys("#email", "test@test.test") sb.press_keys("#phone", "1-555-555-5555") @@ -13,6 +13,7 @@ sb.highlight_click('input[value="AR"] + span') sb.click('input[value="cc"] + span') sb.scroll_to('div[class*="cf-turnstile"]') + sb.scroll_down(40) sb.uc_gui_click_captcha() sb.highlight("img#captcha-success", timeout=3) sb.highlight_click('button:contains("Request & Pay")') diff --git a/examples/raw_order_tickets.py b/examples/raw_order_tickets.py index caabfd1c036..082285e9d73 100644 --- a/examples/raw_order_tickets.py +++ b/examples/raw_order_tickets.py @@ -1,8 +1,17 @@ from seleniumbase import SB -with SB(uc=True, test=True, ad_block=True) as sb: - url = "https://www.thaiticketmajor.com/concert/#" - sb.uc_open_with_reconnect(url, 6.111) - sb.uc_click("button.btn-signin", 4.1) - sb.uc_gui_click_captcha() - sb.sleep(2) +with SB(uc=True, test=True, guest=True) as sb: + url = "https://www.ticketmaster.com" + sb.activate_cdp_mode(url) + input_field = 'input[name="q"]' + sb.wait_for_element(input_field) + sb.sleep(1.6) + query = "Jerry Seinfeld" + sb.press_keys(input_field, query) + sb.sleep(1.6) + sb.click('a:contains("%s")' % query) + sb.sleep(4.2) + print('*** TicketMaster Search for "%s":' % query) + item_selector = '[data-testid="eventList"] li' + for item in sb.find_elements(item_selector): + print("* " + item.text) diff --git a/examples/raw_pixelscan.py b/examples/raw_pixelscan.py index 0f7ee0d6f5b..ce328ad50c9 100644 --- a/examples/raw_pixelscan.py +++ b/examples/raw_pixelscan.py @@ -2,7 +2,8 @@ with SB(uc=True, incognito=True, test=True) as sb: url = "https://pixelscan.net/fingerprint-check" - sb.driver.uc_open_with_reconnect(url, 7) + sb.activate_cdp_mode(url) + sb.sleep(3) sb.remove_elements(".bg-bannerBg") # Remove top banner sb.remove_elements("pxlscn-ad1") # Remove an ad banner sb.remove_elements("pxlscn-ad2") # Remove an ad banner @@ -13,11 +14,10 @@ no_automation_detected = "No automation framework detected" sb.assert_text(no_automation_detected, "pxlscn-bot-detection") consistent_selector = 'div.bg-consistentBg [alt="Good"]' - consistent_selector = 'div.bg-consistentBg [alt="Good"]' - sb.highlight(consistent_selector, loops=8, scroll=False) + sb.highlight(consistent_selector, loops=8) sb.sleep(1) fingerprint_masking_div = "pxlscn-fingerprint-masking div" - sb.highlight(fingerprint_masking_div, loops=9, scroll=False) + sb.highlight(fingerprint_masking_div, loops=9) sb.sleep(1) - sb.highlight("pxlscn-bot-detection", loops=10, scroll=False) + sb.highlight("pxlscn-bot-detection", loops=10) sb.sleep(2) diff --git a/examples/raw_pyautogui.py b/examples/raw_pyautogui.py index d77aa257e7c..bb7e0d648b0 100644 --- a/examples/raw_pyautogui.py +++ b/examples/raw_pyautogui.py @@ -2,8 +2,8 @@ with SB(uc=True, test=True) as sb: url = "https://seleniumbase.io/apps/turnstile" - sb.uc_open_with_reconnect(url) - sb.uc_gui_handle_captcha() # Only if needed + sb.activate_cdp_mode(url) + sb.uc_gui_handle_captcha() # Cycle with TAB, then 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) diff --git a/examples/raw_test_scripts.py b/examples/raw_test_scripts.py index 3391f25bb0d..63497b78da3 100644 --- a/examples/raw_test_scripts.py +++ b/examples/raw_test_scripts.py @@ -1,12 +1,10 @@ """Context Manager Test. Runs with "python". (pytest not needed)""" from seleniumbase import SB -with SB(test=True, uc=True) as sb: +with SB(uc=True, test=True) as sb: sb.open("https://google.com/ncr") sb.type('[name="q"]', "SeleniumBase on GitHub\n") - sb.click('a[href*="github.com/seleniumbase"]') - sb.highlight("div.Layout-main") - sb.highlight("div.Layout-sidebar") + sb.highlight('a[href*="github.com/seleniumbase"]') sb.sleep(0.5) with SB(test=True, rtf=True, demo=True) as sb: diff --git a/help_docs/syntax_formats.md b/help_docs/syntax_formats.md index 6b2ff5abc97..0a2c1e1af76 100644 --- a/help_docs/syntax_formats.md +++ b/help_docs/syntax_formats.md @@ -864,12 +864,10 @@ Here's another example, which uses test mode: ```python from seleniumbase import SB -with SB(test=True, uc=True) as sb: +with SB(uc=True, test=True) as sb: sb.open("https://google.com/ncr") sb.type('[name="q"]', "SeleniumBase on GitHub\n") - sb.click('a[href*="github.com/seleniumbase"]') - sb.highlight("div.Layout-main") - sb.highlight("div.Layout-sidebar") + sb.highlight('a[href*="github.com/seleniumbase"]') sb.sleep(0.5) with SB(test=True, rtf=True, demo=True) as sb: @@ -896,7 +894,7 @@ with SB(uc=True, test=True) as sb: url = "www.planetminecraft.com/account/sign_in/" sb.activate_cdp_mode(url) sb.sleep(2) - sb.uc_gui_click_captcha() + sb.solve_captcha() sb.wait_for_element_absent("input[disabled]") sb.sleep(2) ``` diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 01ba471b559..28779e4d90f 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -1,11 +1,11 @@ # mkdocs dependencies for generating the seleniumbase.io website # Minimum Python version: 3.10 (for generating docs only) -regex>=2025.10.23 +regex>=2025.11.3 pymdown-extensions>=10.16.1 pipdeptree>=2.29.0 python-dateutil>=2.8.2 -Markdown==3.9 +Markdown==3.10 click==8.3.0 ghp-import==2.1.0 watchdog==6.0.0 diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index c814b73e687..578d6525627 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.44.5" +__version__ = "4.44.6" diff --git a/seleniumbase/behave/behave_sb.py b/seleniumbase/behave/behave_sb.py index 991ec5fc088..c3f30699de3 100644 --- a/seleniumbase/behave/behave_sb.py +++ b/seleniumbase/behave/behave_sb.py @@ -113,6 +113,7 @@ from contextlib import suppress from seleniumbase import config as sb_config from seleniumbase.config import settings +from seleniumbase.core import detect_b_ver from seleniumbase.core import download_helper from seleniumbase.core import log_helper from seleniumbase.core import proxy_helper @@ -890,6 +891,13 @@ def get_configured_sb(context): "\nOnly ONE default browser is allowed!\n" "%s browsers were selected: %s" % (len(browsers), browsers) ) + if sb.browser in ["opera", "brave", "comet", "atlas"]: + bin_loc = detect_b_ver.get_binary_location(sb.browser) + if bin_loc and os.path.exists(bin_loc): + sb_config._cdp_browser = sb.browser + sb_config._cdp_bin_loc = bin_loc + sb_config.binary_location = bin_loc + sb.binary_location = bin_loc # Recorder Mode can still optimize scripts in "-D headless2" mode. if sb.recorder_ext and sb.headless: sb.headless = False diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index d565e438958..53e9125ffbb 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -1803,6 +1803,16 @@ def gui_click_with_offset( def click_with_offset(self, selector, x, y, center=False): element = self.find_element(selector) element.scroll_into_view() + if "--debug" in sys.argv: + displayed_selector = "`%s`" % selector + if '"' not in selector: + displayed_selector = '"%s"' % selector + elif "'" not in selector: + displayed_selector = "'%s'" % selector + print( + " sb.click_with_offset(%s, %s, %s, center=%s)" + % (displayed_selector, x, y, center) + ) element.click_with_offset(x=x, y=y, center=center) self.__slow_mode_pause_if_set() self.loop.run_until_complete(self.page.wait()) @@ -1875,10 +1885,12 @@ def __click_captcha(self, use_cdp=False): """Uses PyAutoGUI unless use_cdp == True""" self.sleep(0.056) source = self.get_page_source() - if self._on_a_g_recaptcha_page(source): + if self._on_a_cf_turnstile_page(source): + pass + elif self._on_a_g_recaptcha_page(source): self.__gui_click_recaptcha(use_cdp) return - elif not self._on_a_cf_turnstile_page(source): + else: return selector = None if ( @@ -2078,13 +2090,17 @@ def __gui_drag_drop(self, x1, y1, x2, y2, timeframe=0.25, uc_lock=False): if uc_lock: gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes + if "--debug" in sys.argv: + print(" pyautogui.moveTo(%s, %s)" % (x1, y1)) pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad) self.__add_light_pause() if "--debug" in sys.argv: - print(" pyautogui.moveTo(%s, %s)" % (x1, y1)) + print(" pyautogui.dragTo(%s, %s)" % (x2, y2)) pyautogui.dragTo(x2, y2, button="left", duration=timeframe) else: # Called from a method where the gui_lock is already active + if "--debug" in sys.argv: + print(" pyautogui.moveTo(%s, %s)" % (x1, y1)) pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad) self.__add_light_pause() if "--debug" in sys.argv: @@ -2163,16 +2179,16 @@ def __gui_hover_x_y(self, x, y, timeframe=0.25, uc_lock=False): if uc_lock: gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) with gui_lock: # Prevent issues with multiple processes - pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) - time.sleep(0.056) if "--debug" in sys.argv: print(" pyautogui.moveTo(%s, %s)" % (x, y)) + pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) + time.sleep(0.056) else: # Called from a method where the gui_lock is already active - pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) - time.sleep(0.056) if "--debug" in sys.argv: print(" pyautogui.moveTo(%s, %s)" % (x, y)) + pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad) + time.sleep(0.056) def gui_hover_x_y(self, x, y, timeframe=0.25): gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK) diff --git a/seleniumbase/undetected/cdp_driver/browser.py b/seleniumbase/undetected/cdp_driver/browser.py index c14f1e02b4b..527aaa22213 100644 --- a/seleniumbase/undetected/cdp_driver/browser.py +++ b/seleniumbase/undetected/cdp_driver/browser.py @@ -293,6 +293,7 @@ async def get( _cdp_platform = None _cdp_geolocation = None _cdp_recorder = None + _cdp_ad_block = None if ( hasattr(sb_config, "_cdp_timezone") and sb_config._cdp_timezone ): @@ -311,6 +312,11 @@ async def get( and sb_config._cdp_geolocation ): _cdp_geolocation = sb_config._cdp_geolocation + if ( + hasattr(sb_config, "ad_block_on") + and sb_config.ad_block_on + ): + _cdp_ad_block = sb_config.ad_block_on if "timezone" in kwargs: _cdp_timezone = kwargs["timezone"] elif "tzone" in kwargs: @@ -347,6 +353,22 @@ async def get( accept_language=_cdp_locale, platform=_cdp_platform, ) + if _cdp_ad_block: + await connection.send(cdp.page.navigate("about:blank")) + await connection.send(cdp.network.enable()) + await connection.send(cdp.network.set_blocked_urls( + urls=[ + "*googlesyndication.com*", + "*googletagmanager.com*", + "*google-analytics.com*", + "*amazon-adsystem.com*", + "*adsafeprotected.com*", + "*doubleclick.net*", + "*fastclick.net*", + "*snigelweb.com*", + "*2mdn.net*", + ] + )) if _cdp_geolocation: await connection.send(cdp.page.navigate("about:blank")) await connection.set_geolocation(_cdp_geolocation) diff --git a/seleniumbase/undetected/cdp_driver/cdp_util.py b/seleniumbase/undetected/cdp_driver/cdp_util.py index aeadd7f5e20..1d9894a7238 100644 --- a/seleniumbase/undetected/cdp_driver/cdp_util.py +++ b/seleniumbase/undetected/cdp_driver/cdp_util.py @@ -548,6 +548,7 @@ async def start( proxy_scheme, ) if ad_block: + sb_config.ad_block_on = True incognito = False guest = False ad_block_zip = AD_BLOCK_ZIP_PATH