Skip to content

Commit 5872a66

Browse files
committed
Update UC Mode
1 parent 620b53e commit 5872a66

File tree

4 files changed

+215
-41
lines changed

4 files changed

+215
-41
lines changed

help_docs/method_summary.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,11 @@ driver.uc_gui_write(text) # Similar to uc_gui_press_keys(), but faster
10761076

10771077
driver.uc_gui_click_x_y(x, y, timeframe=0.25) # PyAutoGUI click screen
10781078

1079-
driver.uc_gui_click_cf(frame="iframe", retry=False, blind=False) # (*)
1079+
driver.uc_gui_click_captcha(frame="iframe", retry=False, blind=False)
1080+
1081+
driver.uc_gui_click_rc(frame="iframe", retry=False, blind=False) # reC
1082+
1083+
driver.uc_gui_click_cf(frame="iframe", retry=False, blind=False) # CFT
10801084

10811085
driver.uc_gui_handle_cf(frame="iframe") # PyAutoGUI click CF Turnstile
10821086

help_docs/uc_mode.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ with SB(uc=True, test=True) as sb:
6868
from seleniumbase import SB
6969

7070
with SB(uc=True, test=True) as sb:
71-
url = "seleniumbase.io/apps/turnstile"
71+
url = "https://seleniumbase.io/apps/turnstile"
7272
sb.uc_open_with_reconnect(url, reconnect_time=2)
7373
sb.uc_gui_handle_cf()
7474
sb.assert_element("img#captcha-success", timeout=3)
@@ -78,7 +78,7 @@ with SB(uc=True, test=True) as sb:
7878

7979
<img src="https://seleniumbase.github.io/other/turnstile_click.jpg" title="SeleniumBase" width="440">
8080

81-
If running on a Linux server, `uc_gui_handle_cf()` might not be good enough. Switch to `uc_gui_click_cf()` to be more stealthy.
81+
If running on a Linux server, `uc_gui_handle_cf()` might not be good enough. Switch to `uc_gui_click_cf()` to be more stealthy. You can also use `uc_gui_click_captcha()` as a generic CAPTCHA-clicker, which auto-detects between CF Turnstile and reCAPTCHA.
8282

8383
👤 Here's an example <b>where the CAPTCHA appears after submitting a form</b>:
8484

@@ -192,6 +192,10 @@ driver.uc_gui_write(text)
192192

193193
driver.uc_gui_click_x_y(x, y, timeframe=0.25)
194194

195+
driver.uc_gui_click_captcha(frame="iframe", retry=False, blind=False)
196+
197+
driver.uc_gui_click_rc(frame="iframe", retry=False, blind=False)
198+
195199
driver.uc_gui_click_cf(frame="iframe", retry=False, blind=False)
196200

197201
driver.uc_gui_handle_cf(frame="iframe")
@@ -235,6 +239,10 @@ driver.reconnect("breakpoint")
235239

236240
👤 `driver.uc_gui_click_cf(frame="iframe", retry=False, blind=False)` has three args. (All optional). The first one, `frame`, lets you specify the iframe in case the CAPTCHA is not located in the first iframe on the page. The second one, `retry`, lets you retry the click after reloading the page if the first one didn't work (and a CAPTCHA is still present after the page reload). The third arg, `blind`, will retry after a page reload (if the first click failed) by clicking at the last known coordinates of the CAPTCHA checkbox without confirming first with Selenium that a CAPTCHA is still on the page.
237241

242+
👤 `driver.uc_gui_click_rc(frame="iframe", retry=False, blind=False)` is for reCAPTCHA. This may only work a few times before not working anymore... not because Selenium was detected, but because reCAPTCHA uses advanced AI to detect unusual activity, unlike the CF Turnstile, which only uses basic detection.
243+
244+
👤 `driver.uc_gui_click_captcha()` auto-detects the CAPTCHA type before trying to click it. This is a generic method for both CF Turnstile and Google reCAPTCHA. It will use the code from `uc_gui_click_cf()` and `uc_gui_click_rc()` as needed.
245+
238246
👤 To find out if <b translate="no">UC Mode</b> will work at all on a specific site (before adjusting for timing), load your site with the following script:
239247

240248
```python

seleniumbase/core/browser_launcher.py

Lines changed: 196 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ def get_gui_element_position(driver, selector):
660660
return (viewport_x, viewport_y)
661661

662662

663-
def uc_gui_click_x_y(driver, x, y, timeframe=0.25, uc_lock=True):
663+
def _uc_gui_click_x_y(driver, x, y, timeframe=0.25, uc_lock=False):
664664
install_pyautogui_if_missing(driver)
665665
import pyautogui
666666
pyautogui = get_configured_pyautogui(pyautogui)
@@ -678,38 +678,79 @@ def uc_gui_click_x_y(driver, x, y, timeframe=0.25, uc_lock=True):
678678
with gui_lock: # Prevent issues with multiple processes
679679
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
680680
if timeframe >= 0.25:
681-
time.sleep(0.0555) # Wait if moving at human-speed
681+
time.sleep(0.056) # Wait if moving at human-speed
682682
if "--debug" in sys.argv:
683683
print(" <DEBUG> pyautogui.click(%s, %s)" % (x, y))
684684
pyautogui.click(x=x, y=y)
685685
else:
686686
# Called from a method where the gui_lock is already active
687687
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
688688
if timeframe >= 0.25:
689-
time.sleep(0.0555) # Wait if moving at human-speed
689+
time.sleep(0.056) # Wait if moving at human-speed
690690
if "--debug" in sys.argv:
691691
print(" <DEBUG> pyautogui.click(%s, %s)" % (x, y))
692692
pyautogui.click(x=x, y=y)
693693

694694

695-
def on_a_cf_turnstile_page(driver):
695+
def uc_gui_click_x_y(driver, x, y, timeframe=0.25):
696+
_uc_gui_click_x_y(driver, x, y, timeframe=timeframe, uc_lock=True)
697+
698+
699+
def _on_a_cf_turnstile_page(driver):
696700
source = driver.get_page_source()
697701
if (
698-
"//challenges.cloudflare.com" in source
699-
or 'aria-label="Cloudflare"' in source
702+
'data-callback="onCaptchaSuccess"' in source
703+
or "cf-turnstile-wrapper" in source
700704
):
701705
return True
702706
return False
703707

704708

705-
def uc_gui_click_cf(driver, frame="iframe", retry=False, blind=False):
706-
if not on_a_cf_turnstile_page(driver):
707-
return
709+
def _on_a_g_recaptcha_page(driver):
710+
source = driver.get_page_source()
711+
if (
712+
'id="recaptcha-token"' in source
713+
or 'title="reCAPTCHA"' in source
714+
):
715+
return True
716+
return False
717+
718+
719+
def _uc_gui_click_captcha(
720+
driver,
721+
frame="iframe",
722+
retry=False,
723+
blind=False,
724+
ctype=None,
725+
):
726+
_on_a_captcha_page = None
727+
if ctype == "cf_t":
728+
if not _on_a_cf_turnstile_page(driver):
729+
return
730+
else:
731+
_on_a_captcha_page = _on_a_cf_turnstile_page
732+
elif ctype == "g_rc":
733+
if not _on_a_g_recaptcha_page(driver):
734+
return
735+
else:
736+
_on_a_captcha_page = _on_a_g_recaptcha_page
737+
else:
738+
if _on_a_g_recaptcha_page(driver):
739+
ctype = "g_rc"
740+
_on_a_captcha_page = _on_a_g_recaptcha_page
741+
elif _on_a_cf_turnstile_page(driver):
742+
ctype = "cf_t"
743+
_on_a_captcha_page = _on_a_cf_turnstile_page
744+
else:
745+
return
708746
install_pyautogui_if_missing(driver)
709747
import pyautogui
710748
pyautogui = get_configured_pyautogui(pyautogui)
749+
i_x = None
750+
i_y = None
711751
x = None
712752
y = None
753+
visible_iframe = True
713754
gui_lock = fasteners.InterProcessLock(
714755
constants.MultiBrowser.PYAUTOGUILOCK
715756
)
@@ -725,22 +766,54 @@ def uc_gui_click_cf(driver, frame="iframe", retry=False, blind=False):
725766
page_actions.switch_to_window(
726767
driver, driver.current_window_handle, 2, uc_lock=False
727768
)
769+
if ctype == "cf_t":
770+
if (
771+
driver.is_element_present(".cf-turnstile-wrapper iframe")
772+
or driver.is_element_present(
773+
'[data-callback="onCaptchaSuccess"] iframe'
774+
)
775+
):
776+
pass
777+
else:
778+
visible_iframe = False
779+
if driver.is_element_present(".cf-turnstile-wrapper"):
780+
frame = ".cf-turnstile-wrapper"
781+
elif driver.is_element_present(
782+
'[data-callback="onCaptchaSuccess"]'
783+
):
784+
frame = '[data-callback="onCaptchaSuccess"]'
785+
else:
786+
return
728787
if not is_in_frame or needs_switch:
729788
# Currently not in frame (or nested frame outside CF one)
730789
try:
731-
i_x, i_y = get_gui_element_position(driver, "iframe")
732-
driver.switch_to_frame(frame)
790+
i_x, i_y = get_gui_element_position(driver, frame)
791+
if visible_iframe:
792+
driver.switch_to_frame(frame)
733793
except Exception:
734-
if driver.is_element_present("iframe"):
735-
i_x, i_y = get_gui_element_position(driver, "iframe")
736-
driver.switch_to_frame("iframe")
737-
else:
738-
return
794+
if visible_iframe:
795+
if driver.is_element_present("iframe"):
796+
i_x, i_y = get_gui_element_position(driver, "iframe")
797+
driver.switch_to_frame("iframe")
798+
else:
799+
return
800+
if not i_x or not i_y:
801+
return
739802
try:
740-
selector = "span"
741-
element = driver.wait_for_element_present(selector, timeout=2.5)
742-
x = i_x + element.rect["x"] + int(element.rect["width"] / 2) + 1
743-
y = i_y + element.rect["y"] + int(element.rect["height"] / 2) + 1
803+
if visible_iframe:
804+
selector = "span"
805+
if ctype == "g_rc":
806+
selector = "span.recaptcha-checkbox"
807+
element = driver.wait_for_element_present(
808+
selector, timeout=2.5
809+
)
810+
x = i_x + element.rect["x"] + int(element.rect["width"] / 2)
811+
x += 1
812+
y = i_y + element.rect["y"] + int(element.rect["height"] / 2)
813+
y += 1
814+
else:
815+
x = i_x + 34
816+
y = i_y + 34
744817
driver.switch_to.default_content()
745818
except Exception:
746819
try:
@@ -751,46 +824,91 @@ def uc_gui_click_cf(driver, frame="iframe", retry=False, blind=False):
751824
try:
752825
if x and y:
753826
sb_config._saved_cf_x_y = (x, y)
754-
uc_gui_click_x_y(driver, x, y, timeframe=0.842, uc_lock=False)
827+
_uc_gui_click_x_y(driver, x, y, timeframe=0.95)
755828
except Exception:
756829
pass
757830
reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.5
758831
if IS_LINUX:
759-
reconnect_time = constants.UC.RECONNECT_TIME
832+
reconnect_time = constants.UC.RECONNECT_TIME + 0.15
760833
if not x or not y:
761834
reconnect_time = 1 # Make it quick (it already failed)
762835
driver.reconnect(reconnect_time)
763836
if blind:
764837
retry = True
765-
if retry and x and y and on_a_cf_turnstile_page(driver):
838+
if retry and x and y and _on_a_captcha_page(driver):
766839
with gui_lock: # Prevent issues with multiple processes
767840
# Make sure the window is on top
768841
page_actions.switch_to_window(
769842
driver, driver.current_window_handle, 2, uc_lock=False
770843
)
771-
driver.switch_to_frame("iframe")
772-
if driver.is_element_visible("#success-icon"):
773-
driver.switch_to.parent_frame()
844+
if not driver.is_element_present("iframe"):
774845
return
846+
else:
847+
try:
848+
driver.switch_to_frame(frame)
849+
except Exception:
850+
try:
851+
driver.switch_to_frame("iframe")
852+
except Exception:
853+
return
854+
checkbox_success = None
855+
if ctype == "cf_t":
856+
checkbox_success = "#success-icon"
857+
elif ctype == "g_rc":
858+
checkbox_success = "span.recaptcha-checkbox-checked"
859+
else:
860+
return # If this line is reached, ctype wasn't set
861+
if driver.is_element_visible("#success-icon"):
862+
driver.switch_to.parent_frame(checkbox_success)
863+
return
775864
if blind:
776865
driver.uc_open_with_disconnect(driver.current_url, 3.8)
777-
uc_gui_click_x_y(driver, x, y, timeframe=1.05, uc_lock=False)
866+
_uc_gui_click_x_y(driver, x, y, timeframe=1.05)
778867
else:
779868
driver.uc_open_with_reconnect(driver.current_url, 3.8)
780-
if on_a_cf_turnstile_page(driver):
869+
if _on_a_captcha_page(driver):
781870
driver.disconnect()
782-
uc_gui_click_x_y(
783-
driver, x, y, timeframe=1.05, uc_lock=False
784-
)
871+
_uc_gui_click_x_y(driver, x, y, timeframe=1.05)
785872
driver.reconnect(reconnect_time)
786873

787874

875+
def uc_gui_click_captcha(driver, frame="iframe", retry=False, blind=False):
876+
_uc_gui_click_captcha(
877+
driver,
878+
frame=frame,
879+
retry=retry,
880+
blind=blind,
881+
ctype=None,
882+
)
883+
884+
885+
def uc_gui_click_rc(driver, frame="iframe", retry=False, blind=False):
886+
_uc_gui_click_captcha(
887+
driver,
888+
frame=frame,
889+
retry=retry,
890+
blind=blind,
891+
ctype="g_rc",
892+
)
893+
894+
895+
def uc_gui_click_cf(driver, frame="iframe", retry=False, blind=False):
896+
_uc_gui_click_captcha(
897+
driver,
898+
frame=frame,
899+
retry=retry,
900+
blind=blind,
901+
ctype="cf_t",
902+
)
903+
904+
788905
def uc_gui_handle_cf(driver, frame="iframe"):
789-
if not on_a_cf_turnstile_page(driver):
906+
if not _on_a_cf_turnstile_page(driver):
790907
return
791908
install_pyautogui_if_missing(driver)
792909
import pyautogui
793910
pyautogui = get_configured_pyautogui(pyautogui)
911+
visible_iframe = True
794912
gui_lock = fasteners.InterProcessLock(
795913
constants.MultiBrowser.PYAUTOGUILOCK
796914
)
@@ -806,16 +924,46 @@ def uc_gui_handle_cf(driver, frame="iframe"):
806924
page_actions.switch_to_window(
807925
driver, driver.current_window_handle, 2, uc_lock=False
808926
)
927+
if (
928+
driver.is_element_present(".cf-turnstile-wrapper iframe")
929+
or driver.is_element_present(
930+
'[data-callback="onCaptchaSuccess"] iframe'
931+
)
932+
):
933+
pass
934+
else:
935+
visible_iframe = False
936+
if driver.is_element_present(".cf-turnstile-wrapper"):
937+
frame = ".cf-turnstile-wrapper"
938+
elif driver.is_element_present(
939+
'[data-callback="onCaptchaSuccess"]'
940+
):
941+
frame = '[data-callback="onCaptchaSuccess"]'
942+
else:
943+
return
809944
if not is_in_frame or needs_switch:
810945
# Currently not in frame (or nested frame outside CF one)
811946
try:
812-
driver.switch_to_frame(frame)
947+
if visible_iframe:
948+
driver.switch_to_frame(frame)
813949
except Exception:
814-
if driver.is_element_present("iframe"):
815-
driver.switch_to_frame("iframe")
816-
else:
817-
return
950+
if visible_iframe:
951+
if driver.is_element_present("iframe"):
952+
driver.switch_to_frame("iframe")
953+
else:
954+
return
818955
try:
956+
found_checkbox = False
957+
for i in range(10):
958+
pyautogui.press("\t")
959+
time.sleep(0.02)
960+
active_element_css = js_utils.get_active_element_css(driver)
961+
if active_element_css == "div.cf-turnstile-wrapper":
962+
found_checkbox = True
963+
break
964+
time.sleep(0.02)
965+
if not found_checkbox:
966+
return
819967
driver.execute_script('document.querySelector("input").focus()')
820968
except Exception:
821969
try:
@@ -829,7 +977,7 @@ def uc_gui_handle_cf(driver, frame="iframe"):
829977
pass
830978
reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.5
831979
if IS_LINUX:
832-
reconnect_time = constants.UC.RECONNECT_TIME
980+
reconnect_time = constants.UC.RECONNECT_TIME + 0.15
833981
driver.reconnect(reconnect_time)
834982

835983

@@ -4166,6 +4314,16 @@ def get_local_driver(
41664314
driver, *args, **kwargs
41674315
)
41684316
)
4317+
driver.uc_gui_click_captcha = (
4318+
lambda *args, **kwargs: uc_gui_click_captcha(
4319+
driver, *args, **kwargs
4320+
)
4321+
)
4322+
driver.uc_gui_click_rc = (
4323+
lambda *args, **kwargs: uc_gui_click_rc(
4324+
driver, *args, **kwargs
4325+
)
4326+
)
41694327
driver.uc_gui_click_cf = (
41704328
lambda *args, **kwargs: uc_gui_click_cf(
41714329
driver, *args, **kwargs

0 commit comments

Comments
 (0)