Skip to content

Commit 58e5ba8

Browse files
committed
Update CDP Mode
1 parent 8ad5242 commit 58e5ba8

File tree

5 files changed

+186
-48
lines changed

5 files changed

+186
-48
lines changed

seleniumbase/core/browser_launcher.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
764764
cdp.click_active_element = CDPM.click_active_element
765765
cdp.click_if_visible = CDPM.click_if_visible
766766
cdp.click_visible_elements = CDPM.click_visible_elements
767+
cdp.click_with_offset = CDPM.click_with_offset
767768
cdp.mouse_click = CDPM.mouse_click
768769
cdp.get_parent = CDPM.get_parent
769770
cdp.remove_element = CDPM.remove_element
@@ -793,11 +794,13 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
793794
cdp.set_attributes = CDPM.set_attributes
794795
cdp.is_attribute_present = CDPM.is_attribute_present
795796
cdp.is_online = CDPM.is_online
797+
cdp.solve_captcha = CDPM.solve_captcha
796798
cdp.gui_press_key = CDPM.gui_press_key
797799
cdp.gui_press_keys = CDPM.gui_press_keys
798800
cdp.gui_write = CDPM.gui_write
799801
cdp.gui_click_x_y = CDPM.gui_click_x_y
800802
cdp.gui_click_element = CDPM.gui_click_element
803+
cdp.gui_click_with_offset = CDPM.gui_click_with_offset
801804
cdp.gui_click_captcha = CDPM.gui_click_captcha
802805
cdp.gui_drag_drop_points = CDPM.gui_drag_drop_points
803806
cdp.gui_drag_and_drop = CDPM.gui_drag_and_drop

seleniumbase/core/sb_cdp.py

Lines changed: 99 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ def __add_sync_methods(self, element):
6464
)
6565
element.highlight_overlay = lambda: self.__highlight_overlay(element)
6666
element.mouse_click = lambda: self.__mouse_click(element)
67+
element.click_with_offset = (
68+
lambda *args, **kwargs: self.__mouse_click_with_offset_async(
69+
element, *args, **kwargs
70+
)
71+
)
6772
element.mouse_drag = (
6873
lambda destination: self.__mouse_drag(element, destination)
6974
)
@@ -447,6 +452,15 @@ def __mouse_click(self, element):
447452
self.loop.run_until_complete(self.page.wait())
448453
return result
449454

455+
def __mouse_click_with_offset_async(self, element, *args, **kwargs):
456+
result = (
457+
self.loop.run_until_complete(
458+
element.mouse_click_with_offset_async(*args, **kwargs)
459+
)
460+
)
461+
self.loop.run_until_complete(self.page.wait())
462+
return result
463+
450464
def __mouse_drag(self, element, destination):
451465
return (
452466
self.loop.run_until_complete(element.mouse_drag_async(destination))
@@ -689,10 +703,16 @@ def click(self, selector, timeout=None):
689703
self.__slow_mode_pause_if_set()
690704
element = self.find_element(selector, timeout=timeout)
691705
element.scroll_into_view()
692-
if element.tag_name == "div" or element.tag_name == "input":
693-
element.mouse_click() # Simulated click (not PyAutoGUI)
706+
tag_name = element.tag_name
707+
if tag_name:
708+
tag_name = tag_name.lower().strip()
709+
if (
710+
tag_name in ["a", "button", "canvas", "div", "input", "li", "span"]
711+
and "contains(" not in selector
712+
):
713+
element.mouse_click() # Simulated click (NOT PyAutoGUI)
694714
else:
695-
element.click()
715+
element.click() # Standard CDP click
696716
self.__slow_mode_pause_if_set()
697717
self.loop.run_until_complete(self.page.wait())
698718

@@ -738,7 +758,7 @@ def click_visible_elements(self, selector, limit=0):
738758
element.scroll_into_view()
739759
element.click()
740760
click_count += 1
741-
time.sleep(0.042)
761+
time.sleep(0.044)
742762
self.__slow_mode_pause_if_set()
743763
self.loop.run_until_complete(self.page.wait())
744764
except Exception:
@@ -1757,11 +1777,32 @@ def gui_click_element(self, selector, timeframe=0.25):
17571777
self.__slow_mode_pause_if_set()
17581778
self.loop.run_until_complete(self.page.wait())
17591779

1760-
def _on_a_cf_turnstile_page(self):
1761-
time.sleep(0.042)
1762-
source = self.get_page_source()
1780+
def gui_click_with_offset(
1781+
self, selector, x, y, timeframe=0.25, center=False
1782+
):
1783+
"""Click an element at an {X,Y}-offset location.
1784+
{0,0} is the top-left corner of the element.
1785+
If center==True, {0,0} becomes the center of the element.
1786+
The timeframe is the time spent moving the mouse."""
1787+
if center:
1788+
px, py = self.get_gui_element_center(selector)
1789+
self.gui_click_x_y(px + x, py + y, timeframe=timeframe)
1790+
else:
1791+
element_rect = self.get_gui_element_rect(selector)
1792+
px = element_rect["x"]
1793+
py = element_rect["y"]
1794+
self.gui_click_x_y(px + x, py + y, timeframe=timeframe)
1795+
1796+
def click_with_offset(self, selector, x, y, center=False):
1797+
element = self.find_element(selector)
1798+
element.scroll_into_view()
1799+
element.click_with_offset(x=x, y=y, center=center)
1800+
self.__slow_mode_pause_if_set()
1801+
self.loop.run_until_complete(self.page.wait())
1802+
1803+
def _on_a_cf_turnstile_page(self, source=None):
17631804
if not source or len(source) < 400:
1764-
time.sleep(0.22)
1805+
time.sleep(0.2)
17651806
source = self.get_page_source()
17661807
if (
17671808
'data-callback="onCaptchaSuccess"' in source
@@ -1773,20 +1814,21 @@ def _on_a_cf_turnstile_page(self):
17731814
return True
17741815
return False
17751816

1776-
def _on_a_g_recaptcha_page(self):
1777-
time.sleep(0.042)
1778-
source = self.get_page_source()
1817+
def _on_a_g_recaptcha_page(self, source=None):
17791818
if not source or len(source) < 400:
1780-
time.sleep(0.22)
1819+
time.sleep(0.2)
17811820
source = self.get_page_source()
17821821
if (
17831822
'id="recaptcha-token"' in source
17841823
or 'title="reCAPTCHA"' in source
17851824
):
17861825
return True
1826+
elif "/recaptcha/api.js" in source:
1827+
time.sleep(1.6) # Still loading
1828+
return True
17871829
return False
17881830

1789-
def __gui_click_recaptcha(self):
1831+
def __gui_click_recaptcha(self, use_cdp=False):
17901832
selector = None
17911833
if self.is_element_visible('iframe[title="reCAPTCHA"]'):
17921834
selector = 'iframe[title="reCAPTCHA"]'
@@ -1797,19 +1839,39 @@ def __gui_click_recaptcha(self):
17971839
element_rect = self.get_gui_element_rect(selector, timeout=1)
17981840
e_x = element_rect["x"]
17991841
e_y = element_rect["y"]
1800-
x = e_x + 29
1801-
y = e_y + 35
1842+
x_offset = 26
1843+
y_offset = 35
1844+
if shared_utils.is_windows():
1845+
x_offset = 29
1846+
x = e_x + x_offset
1847+
y = e_y + y_offset
18021848
sb_config._saved_cf_x_y = (x, y)
18031849
time.sleep(0.08)
1804-
self.gui_click_x_y(x, y)
1850+
if use_cdp:
1851+
self.sleep(0.03)
1852+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
1853+
with gui_lock: # Prevent issues with multiple processes
1854+
self.bring_active_window_to_front()
1855+
time.sleep(0.056)
1856+
self.click_with_offset(selector, x_offset, y_offset)
1857+
time.sleep(0.056)
1858+
else:
1859+
self.gui_click_x_y(x, y)
1860+
1861+
def solve_captcha(self):
1862+
self.__click_captcha(use_cdp=True)
18051863

18061864
def gui_click_captcha(self):
1807-
if self._on_a_cf_turnstile_page():
1808-
pass
1809-
elif self._on_a_g_recaptcha_page():
1810-
self.__gui_click_recaptcha()
1865+
self.__click_captcha(use_cdp=False)
1866+
1867+
def __click_captcha(self, use_cdp=False):
1868+
"""Uses PyAutoGUI unless use_cdp == True"""
1869+
self.sleep(0.056)
1870+
source = self.get_page_source()
1871+
if self._on_a_g_recaptcha_page(source):
1872+
self.__gui_click_recaptcha(use_cdp)
18111873
return
1812-
else:
1874+
elif not self._on_a_cf_turnstile_page(source):
18131875
return
18141876
selector = None
18151877
if (
@@ -1970,14 +2032,24 @@ def gui_click_captcha(self):
19702032
element_rect = self.get_gui_element_rect(selector, timeout=1)
19712033
e_x = element_rect["x"]
19722034
e_y = element_rect["y"]
1973-
x = e_x + 32
1974-
if not shared_utils.is_windows():
1975-
y = e_y + 32
1976-
else:
1977-
y = e_y + 28
2035+
x_offset = 32
2036+
y_offset = 32
2037+
if shared_utils.is_windows():
2038+
y_offset = 28
2039+
x = e_x + x_offset
2040+
y = e_y + y_offset
19782041
sb_config._saved_cf_x_y = (x, y)
19792042
time.sleep(0.08)
1980-
self.gui_click_x_y(x, y)
2043+
if use_cdp:
2044+
self.sleep(0.03)
2045+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
2046+
with gui_lock: # Prevent issues with multiple processes
2047+
self.bring_active_window_to_front()
2048+
time.sleep(0.056)
2049+
self.click_with_offset(selector, x_offset, y_offset)
2050+
time.sleep(0.056)
2051+
else:
2052+
self.gui_click_x_y(x, y)
19812053

19822054
def __gui_drag_drop(self, x1, y1, x2, y2, timeframe=0.25, uc_lock=False):
19832055
self.__install_pyautogui_if_missing()

seleniumbase/fixtures/base_case.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5008,6 +5008,9 @@ def activate_cdp_mode(self, url=None, **kwargs):
50085008
self.get_new_driver(undetectable=True)
50095009
self.driver.uc_open_with_cdp_mode(url, **kwargs)
50105010
self.cdp = self.driver.cdp
5011+
if hasattr(self.cdp, "solve_captcha"):
5012+
self.solve_captcha = self.cdp.solve_captcha
5013+
self.undetectable = True
50115014

50125015
def activate_recorder(self):
50135016
"""Activate Recorder Mode on the current tab/window.
@@ -11452,6 +11455,7 @@ def __is_cdp_swap_needed(self):
1145211455
if cdp_swap_needed:
1145311456
if not self.cdp:
1145411457
self.cdp = self.driver.cdp
11458+
self.undetectable = True
1145511459
return True
1145611460
else:
1145711461
return False
@@ -13970,6 +13974,9 @@ def __click_with_offset(
1397013974
timeout=None,
1397113975
center=None,
1397213976
):
13977+
if self.__is_cdp_swap_needed():
13978+
self.cdp.click_with_offset(selector, x, y, center=center)
13979+
return
1397313980
self.wait_for_ready_state_complete()
1397413981
if self.__needs_minimum_wait():
1397513982
time.sleep(0.14)

seleniumbase/undetected/cdp_driver/connection.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import logging
88
import sys
99
import types
10-
from asyncio import iscoroutine, iscoroutinefunction
1110
from typing import (
1211
Optional,
1312
Generator,
@@ -666,8 +665,8 @@ async def listener_loop(self):
666665
for callback in callbacks:
667666
try:
668667
if (
669-
iscoroutinefunction(callback)
670-
or iscoroutine(callback)
668+
inspect.iscoroutinefunction(callback)
669+
or inspect.iscoroutine(callback)
671670
):
672671
try:
673672
await callback(event, self.connection)

0 commit comments

Comments
 (0)