Skip to content

Commit 1cc6b08

Browse files
authored
Merge pull request #4068 from seleniumbase/cdp-mode-patch-70
CDP Mode: Patch 70
2 parents 905c5b7 + a15686f commit 1cc6b08

37 files changed

+224
-161
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,19 @@
6666

6767
--------
6868

69-
<p align="left">📗 Here's a test script that performs a Google Search using SeleniumBase UC Mode:<br /><a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_google.py">SeleniumBase/examples/raw_google.py</a> (Results are saved as PDF, HTML, and PNG)</p>
69+
<p align="left">📗 This script performs a Google Search using SeleniumBase UC Mode + CDP Mode:<br /><a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_google.py">SeleniumBase/examples/raw_google.py</a> (Results are saved as PDF, HTML, and PNG)</p>
7070

7171
```python
7272
from seleniumbase import SB
7373

74-
with SB(test=True, uc=True) as sb:
75-
sb.open("https://google.com/ncr")
74+
with SB(uc=True, test=True) as sb:
75+
url = "https://google.com/ncr"
76+
sb.activate_cdp_mode(url)
7677
sb.type('[title="Search"]', "SeleniumBase GitHub page")
7778
sb.click("div:not([jsname]) > * > input")
79+
sb.sleep(2)
7880
print(sb.get_page_title())
79-
sb.sleep(2) # Wait for the "AI Overview" result
81+
sb.sleep(1) # Wait for the "AI Overview" result
8082
if sb.is_text_visible("Generating"):
8183
sb.wait_for_text("AI Overview")
8284
sb.save_as_pdf_to_logs() # Saved to ./latest_logs/
@@ -98,8 +100,8 @@ from seleniumbase import SB
98100
with SB(uc=True, test=True, locale="en") as sb:
99101
url = "https://gitlab.com/users/sign_in"
100102
sb.activate_cdp_mode(url)
101-
sb.sleep(2.2)
102-
sb.uc_gui_click_captcha()
103+
sb.sleep(2)
104+
sb.solve_captcha()
103105
# (The rest is for testing and demo purposes)
104106
sb.assert_text("Username", '[for="user_login"]', timeout=3)
105107
sb.assert_element('label[for="user_login"]')
@@ -118,7 +120,7 @@ from seleniumbase import sb_cdp
118120
url = "https://gitlab.com/users/sign_in"
119121
sb = sb_cdp.Chrome(url)
120122
sb.sleep(2.5)
121-
sb.gui_click_captcha()
123+
sb.solve_captcha()
122124
sb.highlight('h1:contains("GitLab")')
123125
sb.highlight('button:contains("Sign in")')
124126
sb.driver.stop()

examples/cdp_mode/ReadMe.md

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## [<img src="https://seleniumbase.github.io/img/logo6.png" title="SeleniumBase" width="32">](https://github.com/seleniumbase/SeleniumBase/) CDP Mode 🐙
44

5-
🐙 <b translate="no">SeleniumBase</b> <b translate="no">CDP Mode</b> (<a href="https://chromedevtools.github.io/devtools-protocol/" translate="no"><span translate="no">Chrome Devtools Protocol</span></a> Mode) is a special mode inside of <b><a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md" translate="no"><span translate="no">SeleniumBase UC Mode</span></a></b> that lets bots appear human while controlling the browser with <b translate="no">CDP</b> (via <a href="https://github.com/mdmintz/MyCDP" translate="no"><span translate="no">MyCDP</span></a>). Although regular <b translate="no">UC Mode</b> can't perform <span translate="no">WebDriver</span> actions while the <code>driver</code> is disconnected from the browser, <b translate="no">CDP</b> can. <b translate="no">CDP Mode</b> can also be used independently of WebDriver via <b><a href="#Pure_CDP_Mode" translate="no">Pure CDP Mode</a></b> (<code>sb_cdp</code>).
5+
🐙 <b translate="no">SeleniumBase</b> <b translate="no">CDP Mode</b> is a stealth mode of SeleniumBase that uses the <a href="https://chromedevtools.github.io/devtools-protocol/" translate="no"><span translate="no">Chrome Devtools Protocol</span></a> (via <a href="https://github.com/mdmintz/MyCDP" translate="no"><span translate="no">MyCDP</span></a>) to control the web browser. <b translate="no">CDP Mode</b> can be used either as a subset of <b><a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/uc_mode.md" translate="no"><span translate="no">SeleniumBase UC Mode</span></a></b>, or via <b><a href="#Pure_CDP_Mode" translate="no">Pure CDP Mode</a></b> (<code>sb_cdp</code>), which doesn't use WebDriver at all, and has a slightly different setup.
66

77
--------
88

@@ -21,7 +21,7 @@
2121

2222
--------
2323

24-
👤 <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.)
24+
👤 <b translate="no">UC Mode</b> avoids bot-detection by first disconnecting WebDriver from the browser at strategic times, calling special <code><a href="https://github.com/asweigart/pyautogui">PyAutoGUI</a></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.)
2525

2626
🐙 <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>.)
2727

@@ -53,19 +53,17 @@ from seleniumbase import SB
5353
with SB(uc=True, test=True, locale="en") as sb:
5454
url = "https://gitlab.com/users/sign_in"
5555
sb.activate_cdp_mode(url)
56-
sb.sleep(2.2)
57-
sb.uc_gui_click_captcha()
56+
sb.sleep(2)
57+
sb.solve_captcha()
5858
```
5959

6060
<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">
6161

62-
(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).)
63-
64-
ℹ️ 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.
62+
(If the CAPTCHA wasn't bypassed automatically when going to the URL, then `sb.solve_captcha()` gets the job done.)
6563

6664
--------
6765

68-
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:
66+
`sb.cdp.gui_click_element(selector)` lets you click on elements using `PyAutoGUI`. Example:
6967

7068
```python
7169
from seleniumbase import SB
@@ -86,14 +84,17 @@ Eg. `sb.cdp.gui_click_element("#turnstile-widget div")`
8684

8785
<img src="https://seleniumbase.github.io/other/above_shadow.png" title="SeleniumBase" width="480">
8886

89-
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))
87+
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))
88+
89+
ℹ️ 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.
9090

9191
--------
9292

9393
### 🐙 Here are a few common `sb.cdp` methods:
9494

9595
* `sb.cdp.click(selector)` (Uses the CDP API to click)
9696
* `sb.cdp.click_if_visible(selector)` (Click if visible)
97+
* `sb.cdp.solve_captcha()` (Uses CDP to click a CAPTCHA)
9798
* `sb.cdp.gui_click_element(selector)` (Uses `PyAutoGUI`)
9899
* `sb.cdp.type(selector, text)` (Type text into a selector)
99100
* `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:
137138
url = "https://www.pokemon.com/us"
138139
sb.activate_cdp_mode(url)
139140
sb.sleep(3.2)
140-
sb.cdp.click("button#onetrust-accept-btn-handler")
141+
sb.click("button#onetrust-accept-btn-handler")
141142
sb.sleep(1.2)
142-
sb.cdp.click("a span.icon_pokeball")
143+
sb.click("a span.icon_pokeball")
143144
sb.sleep(2.5)
144-
sb.cdp.click('b:contains("Show Advanced Search")')
145+
sb.click('b:contains("Show Advanced Search")')
145146
sb.sleep(2.5)
146-
sb.cdp.click('span[data-type="type"][data-value="electric"]')
147+
sb.click('span[data-type="type"][data-value="electric"]')
147148
sb.sleep(0.5)
148149
sb.scroll_into_view("a#advSearch")
149150
sb.sleep(0.5)
150-
sb.cdp.click("a#advSearch")
151+
sb.click("a#advSearch")
151152
sb.sleep(1.2)
152-
sb.cdp.click('img[src*="img/pokedex/detail/025.png"]')
153-
sb.cdp.assert_text("Pikachu", 'div[class*="title"]')
154-
sb.cdp.assert_element('img[alt="Pikachu"]')
155-
sb.cdp.scroll_into_view("div.pokemon-ability-info")
153+
sb.click('img[src*="img/pokedex/detail/025.png"]')
154+
sb.assert_text("Pikachu", 'div[class*="title"]')
155+
sb.assert_element('img[alt="Pikachu"]')
156+
sb.scroll_into_view("div.pokemon-ability-info")
156157
sb.sleep(1.2)
157158
sb.cdp.flash('div[class*="title"]')
158159
sb.cdp.flash('img[alt="Pikachu"]')
159160
sb.cdp.flash("div.pokemon-ability-info")
160-
name = sb.cdp.get_text("label.styled-select")
161-
info = sb.cdp.get_text("div.version-descriptions p.active")
161+
name = sb.get_text("label.styled-select")
162+
info = sb.get_text("div.version-descriptions p.active")
162163
print("*** %s: ***\n* %s" % (name, info))
163164
sb.sleep(2)
164165
sb.cdp.highlight_overlay("div.pokemon-ability-info")
165166
sb.sleep(2)
166-
sb.cdp.open("https://events.pokemon.com/EventLocator/")
167+
sb.open("https://events.pokemon.com/EventLocator/")
167168
sb.sleep(2)
168-
sb.cdp.click('span:contains("Championship")')
169+
sb.click('span:contains("Championship")')
169170
sb.sleep(2)
170-
events = sb.cdp.select_all("div.event-info__title")
171+
events = sb.select_all("div.event-info__title")
171172
print("*** Pokémon Championship Events: ***")
172173
for event in events:
173174
print("* " + event.text)
@@ -202,9 +203,9 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb:
202203
sb.click("button.be-button-shop")
203204
sb.sleep(6)
204205
card_info = 'div[data-booking-status="BOOKABLE"] [class*="HotelCard_info"]'
205-
hotels = sb.cdp.select_all(card_info)
206+
hotels = sb.select_all(card_info)
206207
print("Hyatt Hotels in %s:" % location)
207-
print("(" + sb.cdp.get_text('span[class*="summary_destination"]') + ")")
208+
print("(" + sb.get_text('span[class*="summary_destination"]') + ")")
208209
if len(hotels) == 0:
209210
print("No availability over the selected dates!")
210211
for hotel in hotels:
@@ -234,26 +235,26 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb:
234235
url = "https://www.bestwestern.com/en_US.html"
235236
sb.activate_cdp_mode(url)
236237
sb.sleep(2.5)
237-
sb.cdp.click_if_visible(".onetrust-close-btn-handler")
238+
sb.click_if_visible(".onetrust-close-btn-handler")
238239
sb.sleep(1)
239-
sb.cdp.click("input#destination-input")
240+
sb.click("input#destination-input")
240241
sb.sleep(2)
241242
location = "Palm Springs, CA, USA"
242-
sb.cdp.press_keys("input#destination-input", location)
243+
sb.press_keys("input#destination-input", location)
243244
sb.sleep(1)
244-
sb.cdp.click("ul#google-suggestions li")
245+
sb.click("ul#google-suggestions li")
245246
sb.sleep(1)
246-
sb.cdp.click("button#btn-modify-stay-update")
247+
sb.click("button#btn-modify-stay-update")
247248
sb.sleep(4)
248-
sb.cdp.click("label#available-label")
249+
sb.click("label#available-label")
249250
sb.sleep(2.5)
250251
print("Best Western Hotels in %s:" % location)
251-
summary_details = sb.cdp.get_text("#summary-details-column")
252+
summary_details = sb.get_text("#summary-details-column")
252253
dates = summary_details.split("DESTINATION")[-1]
253254
dates = dates.split(" CHECK-OUT")[0].strip() + " CHECK-OUT"
254255
dates = dates.replace(" ", " ")
255256
print("(Dates: %s)" % dates)
256-
flip_cards = sb.cdp.select_all(".flipCard")
257+
flip_cards = sb.select_all(".flipCard")
257258
for i, flip_card in enumerate(flip_cards):
258259
hotel = flip_card.query_selector(".hotelName")
259260
price = flip_card.query_selector(".priceSection")
@@ -291,12 +292,12 @@ with SB(uc=True, test=True, ad_block=True) as sb:
291292
if sb.is_element_visible("#px-captcha"):
292293
sb.cdp.gui_click_and_hold("#px-captcha", 4.2)
293294
sb.sleep(3.2)
294-
sb.cdp.remove_elements('[data-testid="skyline-ad"]')
295-
sb.cdp.remove_elements('[data-testid="sba-container"]')
295+
sb.remove_elements('[data-testid="skyline-ad"]')
296+
sb.remove_elements('[data-testid="sba-container"]')
296297
print('*** Walmart Search for "%s":' % search)
297298
print(' (Results must contain "%s".)' % required_text)
298299
unique_item_text = []
299-
items = sb.cdp.find_elements('div[data-testid="list-view"]')
300+
items = sb.find_elements('div[data-testid="list-view"]')
300301
for item in items:
301302
if required_text in item.text:
302303
description = item.querySelector(
@@ -571,8 +572,10 @@ from seleniumbase import sb_cdp
571572

572573
url = "https://seleniumbase.io/apps/turnstile"
573574
sb = sb_cdp.Chrome(url)
574-
sb.gui_click_captcha()
575-
sb.sleep(2)
575+
sb.solve_captcha()
576+
sb.assert_element("img#captcha-success")
577+
sb.set_messenger_theme(location="top_left")
578+
sb.post_message("SeleniumBase wasn't detected", duration=3)
576579
sb.driver.stop()
577580
```
578581

@@ -611,6 +614,7 @@ After finding an element in CDP Mode, you can access `WebElement` methods:
611614
```python
612615
element.clear_input()
613616
element.click()
617+
element.click_with_offset(x, y, center=False)
614618
element.flash(duration=0.5, color="EE4488")
615619
element.focus()
616620
element.gui_click(timeframe=0.25)

examples/cdp_mode/raw_ahrefs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
submit_button = 'span:contains("Check Authority")'
77
sb.activate_cdp_mode(url) # The bot-check is later
88
sb.type(input_field, "github.com/seleniumbase/SeleniumBase")
9-
sb.cdp.scroll_down(36)
9+
sb.scroll_down(36)
1010
sb.click(submit_button)
1111
sb.sleep(1)
12-
sb.uc_gui_click_captcha()
12+
sb.solve_captcha()
1313
sb.sleep(3)
1414
sb.wait_for_text_not_visible("Checking", timeout=15)
1515
sb.click_if_visible('button[data-cky-tag="close-button"]')

examples/cdp_mode/raw_bestwestern.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,26 @@
44
url = "https://www.bestwestern.com/en_US.html"
55
sb.activate_cdp_mode(url)
66
sb.sleep(2.5)
7-
sb.cdp.click_if_visible(".onetrust-close-btn-handler")
7+
sb.click_if_visible(".onetrust-close-btn-handler")
88
sb.sleep(1)
9-
sb.cdp.click("input#destination-input")
9+
sb.click("input#destination-input")
1010
sb.sleep(2)
1111
location = "Palm Springs, CA, USA"
12-
sb.cdp.press_keys("input#destination-input", location)
12+
sb.press_keys("input#destination-input", location)
1313
sb.sleep(1)
14-
sb.cdp.click("ul#google-suggestions li")
14+
sb.click("ul#google-suggestions li")
1515
sb.sleep(1)
16-
sb.cdp.click("button#btn-modify-stay-update")
16+
sb.click("button#btn-modify-stay-update")
1717
sb.sleep(4)
18-
sb.cdp.click("label#available-label")
18+
sb.click("label#available-label")
1919
sb.sleep(2.5)
2020
print("Best Western Hotels in %s:" % location)
21-
summary_details = sb.cdp.get_text("#summary-details-column")
21+
summary_details = sb.get_text("#summary-details-column")
2222
dates = summary_details.split("DESTINATION")[-1]
2323
dates = dates.split(" CHECK-OUT")[0].strip() + " CHECK-OUT"
2424
dates = dates.replace(" ", " ")
2525
print("(Dates: %s)" % dates)
26-
flip_cards = sb.cdp.select_all(".flipCard")
26+
flip_cards = sb.select_all(".flipCard")
2727
for i, flip_card in enumerate(flip_cards):
2828
hotel = flip_card.query_selector(".hotelName")
2929
price = flip_card.query_selector(".priceSection")

examples/cdp_mode/raw_browserscan.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
sb.sleep(1)
77
sb.cdp.flash("Test Results", duration=4)
88
sb.sleep(1)
9-
sb.cdp.assert_element('strong:contains("Normal")')
9+
sb.assert_element('strong:contains("Normal")')
1010
sb.cdp.flash('strong:contains("Normal")', duration=4, pause=4)

examples/cdp_mode/raw_cdp_copilot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
sb.sleep(1.1)
1717
sb.click('button[data-testid="submit-button"]')
1818
sb.sleep(2.5)
19-
sb.gui_click_captcha()
20-
sb.sleep(2.5)
21-
sb.gui_click_captcha()
19+
sb.solve_captcha()
2220
sb.sleep(3.5)
21+
sb.solve_captcha()
22+
sb.sleep(2.5)
2323
stop_button = '[data-testid="stop-button"]'
2424
thumbs_up = 'button[data-testid*="-thumbs-up-"]'
2525
sb.wait_for_element_absent(stop_button, timeout=50)

examples/cdp_mode/raw_cdp_with_sb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
print(sb.get_title())
2424
print("************")
2525
for i in range(8):
26-
sb.cdp.scroll_down(50)
26+
sb.scroll_down(50)
2727
sb.sleep(0.2)
2828
cards = sb.select_all('span[data-automation*="product-list-card"]')
2929
for card in cards:

examples/cdp_mode/raw_consecutive_c.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
url = "https://sms-man.com/login"
66
sb.activate_cdp_mode(url)
77
sb.sleep(2.2)
8-
sb.uc_gui_click_captcha()
9-
sb.sleep(2.6)
10-
sb.uc_gui_click_captcha()
8+
if not sb.is_element_present('input[name="email"]'):
9+
sb.solve_captcha()
10+
sb.sleep(1)
11+
sb.wait_for_element('[name="email"]', timeout=3)
12+
sb.sleep(2)
13+
sb.solve_captcha()
1114
sb.sleep(2)

examples/cdp_mode/raw_copilot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
sb.sleep(1.1)
1818
sb.click('button[data-testid="submit-button"]')
1919
sb.sleep(2.5)
20-
sb.uc_gui_click_captcha()
21-
sb.sleep(2.5)
22-
sb.uc_gui_click_captcha()
20+
sb.solve_captcha()
2321
sb.sleep(3.5)
22+
sb.solve_captcha()
23+
sb.sleep(2.5)
2424
stop_button = '[data-testid="stop-button"]'
2525
thumbs_up = 'button[data-testid*="-thumbs-up-"]'
2626
sb.wait_for_element_absent(stop_button, timeout=50)

examples/cdp_mode/raw_gitlab.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
with SB(uc=True, test=True, locale="en") as sb:
44
url = "https://gitlab.com/users/sign_in"
55
sb.activate_cdp_mode(url)
6-
sb.sleep(2.2)
6+
sb.sleep(2)
77
sb.solve_captcha()
88
# (The rest is for testing and demo purposes)
99
sb.assert_text("Username", '[for="user_login"]', timeout=3)

0 commit comments

Comments
 (0)