Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/cdp_mode/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ sb.click_visible_elements(selector, limit=0, scroll=True)
sb.click_nth_element(selector, number, scroll=True)
sb.click_nth_visible_element(selector, number, scroll=True)
sb.click_with_offset(selector, x, y, center=False, scroll=True)
sb.click_and_hold(selector, timeout=None, scroll=True, timeframe=2)
sb.click_link(link_text)
sb.go_back()
sb.go_forward()
Expand Down Expand Up @@ -735,6 +736,7 @@ After finding an element in CDP Mode, you can access `WebElement` methods:
element.clear_input()
element.click()
element.click_with_offset(x, y, center=False)
element.click_and_hold(timeframe=2)
element.flash(duration=0.5, color="EE4488")
element.focus()
element.gui_click(timeframe=0.27)
Expand Down
1 change: 1 addition & 0 deletions examples/cdp_mode/raw_cdp_clearcote.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
sb.click('button[aria-label="Marker 3"]')
sb.press_keys("input", "audit")
sb.set_value('input[type="range"]', "100")
sb.click_and_hold("button[data-interaction-hold]")
sb.select_option_by_text("select", "Mouse")
sb.click('button:contains("Run the audit")')
sb.sleep(6)
Expand Down
3 changes: 2 additions & 1 deletion examples/raw_google.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
sb.activate_cdp_mode()
sb.goto("https://google.com/ncr")
sb.click_if_visible('button:contains("Accept all")')
sb.press_keys('[name="q"]', "SeleniumBase GitHub page\n")
sb.press_keys('[name="q"]', "SeleniumBase GitHub page")
sb.click('[value="Google Search"]')
sb.sleep(4) # The "AI Overview" sometimes loads
print(sb.get_page_title())
sb.save_as_pdf_to_logs()
Expand Down
2 changes: 2 additions & 0 deletions help_docs/cdp_mode_methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ sb.click_visible_elements(selector, limit=0, scroll=True)
sb.click_nth_element(selector, number, scroll=True)
sb.click_nth_visible_element(selector, number, scroll=True)
sb.click_with_offset(selector, x, y, center=False, scroll=True)
sb.click_and_hold(selector, timeout=None, scroll=True, timeframe=2)
sb.click_link(link_text)
sb.go_back()
sb.go_forward()
Expand Down Expand Up @@ -381,6 +382,7 @@ After finding an element in CDP Mode, you can access `WebElement` methods:
element.clear_input()
element.click()
element.click_with_offset(x, y, center=False)
element.click_and_hold(timeframe=2)
element.flash(duration=0.5, color="EE4488")
element.focus()
element.gui_click(timeframe=0.27)
Expand Down
2 changes: 1 addition & 1 deletion seleniumbase/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "4.51.6"
__version__ = "4.51.7"
1 change: 1 addition & 0 deletions seleniumbase/core/browser_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ def uc_open_with_cdp_mode(driver, url=None, **kwargs):
cdp.click_if_visible = CDPM.click_if_visible
cdp.click_visible_elements = CDPM.click_visible_elements
cdp.click_with_offset = CDPM.click_with_offset
cdp.click_and_hold = CDPM.click_and_hold
cdp.mouse_click = CDPM.mouse_click
cdp.get_parent = CDPM.get_parent
cdp.remove_element = CDPM.remove_element
Expand Down
49 changes: 43 additions & 6 deletions seleniumbase/core/sb_cdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ def __add_sync_methods(self, element):
element, *args, **kwargs
)
)
element.click_and_hold = (
lambda *args, **kwargs: (
self.__mouse_click_and_hold(element, *args, **kwargs)
)
)
element.mouse_drag = (
lambda destination: self.__mouse_drag(element, destination)
)
Expand Down Expand Up @@ -612,9 +617,20 @@ def __is_in_viewport(self, element):
self.loop.run_until_complete(element.is_in_viewport_async())
)

def __mouse_click(self, element):
def __mouse_click(self, element, timeframe=0):
result = (
self.loop.run_until_complete(
element.mouse_click_async(timeframe=timeframe)
)
)
self.loop.run_until_complete(self.page.wait(0.2))
return result

def __mouse_click_and_hold(self, element, timeframe=2):
result = (
self.loop.run_until_complete(element.mouse_click_async())
self.loop.run_until_complete(
element.mouse_click_async(timeframe=timeframe)
)
)
self.loop.run_until_complete(self.page.wait(0.2))
return result
Expand Down Expand Up @@ -646,7 +662,7 @@ def __press_keys(self, element, text):
text = text[:-1]
for key in text:
element.send_keys(key)
time.sleep(float(0.042 + (random.random() / 110.0)))
time.sleep(float(0.0422 + (random.random() / 112.0)))
if submit:
element.send_keys("\r\n")
time.sleep(0.044)
Expand Down Expand Up @@ -945,8 +961,20 @@ def click_visible_elements(self, selector, limit=0, scroll=True):
except Exception:
break

def click_and_hold(self, selector, timeout=None, scroll=True, timeframe=2):
"""(Simulate a mouse click & hold)"""
if not timeout:
timeout = settings.SMALL_TIMEOUT
self.__slow_mode_pause_if_set()
element = self.find_element(selector, timeout=timeout)
if scroll:
element.scroll_into_view()
element.click_and_hold(timeframe)
self.__slow_mode_pause_if_set()
self.loop.run_until_complete(self.page.wait(0.2))

def mouse_click(self, selector, timeout=None, scroll=True):
"""(Attempt simulating a mouse click)"""
"""(Simulate a mouse click)"""
if not timeout:
timeout = settings.SMALL_TIMEOUT
self.__slow_mode_pause_if_set()
Expand Down Expand Up @@ -1132,7 +1160,7 @@ def press_keys(self, selector, text, timeout=None):
except AttributeError:
element = self.select(selector, timeout=0.1)
element.send_keys(key)
time.sleep(float(0.042 + (random.random() / 110.0)))
time.sleep(float(0.0422 + (random.random() / 112.0)))
if submit:
element.send_keys("\r\n")
time.sleep(0.044)
Expand Down Expand Up @@ -2060,7 +2088,7 @@ def gui_press_keys(self, keys):
self.__make_sure_pyautogui_lock_is_writable()
for key in keys:
pyautogui.press(key)
time.sleep(float(0.042 + (random.random() / 110.0)))
time.sleep(float(0.0422 + (random.random() / 112.0)))
self.__slow_mode_pause_if_set()
self.loop.run_until_complete(self.page.sleep(0.025))

Expand Down Expand Up @@ -2188,6 +2216,15 @@ def quit(self):
driver = self.driver
if hasattr(driver, "cdp_base"):
driver = driver.cdp_base
if (
self.driver.config.uses_custom_data_dir
and shared_utils.is_windows()
):
# Let Windows save the session to a custom use_data_dir
self.loop.run_until_complete(
driver.connection.send(mycdp.browser.close())
)
time.sleep(0.3)
driver.quit()

def _on_a_cf_turnstile_page(self, source=None):
Expand Down
2 changes: 2 additions & 0 deletions seleniumbase/fixtures/base_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -5111,6 +5111,8 @@ def activate_cdp_mode(self, url=None, **kwargs):
self.add_handler = self.cdp.add_handler
if hasattr(self.cdp, "clear_input"):
self.clear_input = self.cdp.clear_input
if hasattr(self.cdp, "click_and_hold"):
self.click_and_hold = self.cdp.click_and_hold
if hasattr(self.cdp, "close_active_tab"):
self.close_active_tab = self.cdp.close_active_tab
if hasattr(self.cdp, "find_element_by_text"):
Expand Down
4 changes: 3 additions & 1 deletion seleniumbase/plugins/sb_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,9 @@ def SB(
and hasattr(driver.cdp, "loop")
):
asyncio.set_event_loop(driver.cdp.loop)
tasks = [tab.aclose() for tab in driver.cdp.get_tabs()]
tasks = []
for tab in driver.cdp.get_tabs():
tasks.append(tab.aclose())
tasks.append(driver.cdp.driver.connection.aclose())
driver.cdp.loop.run_until_complete(asyncio.gather(*tasks))
driver.cdp.loop.close()
Expand Down
52 changes: 43 additions & 9 deletions seleniumbase/undetected/cdp_driver/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import mycdp.overlay
import mycdp.page
import mycdp.runtime
from seleniumbase.fixtures import shared_utils

logger = logging.getLogger(__name__)
IS_LINUX = shared_utils.is_linux()
if typing.TYPE_CHECKING:
from .tab import Tab

Expand Down Expand Up @@ -494,7 +496,7 @@ async def mouse_click_async(
button: str = "left",
buttons: typing.Optional[int] = 1,
modifiers: typing.Optional[int] = 0,
hold: bool = False,
timeframe: float = 0.0,
):
"""
Native click (on element).
Expand Down Expand Up @@ -525,9 +527,9 @@ async def mouse_click_async(
asyncio.create_task(self.flash_async(0.25))'''
with suppress(Exception):
await self.mouse_move_async()
await asyncio.sleep(random.uniform(0.003, 0.005))
x = center[0] + random.uniform(-0.8, 0.8)
y = center[1] + random.uniform(-0.8, 0.8)
await asyncio.sleep(random.uniform(0.0035, 0.0048))
x = center[0] + random.uniform(-0.85, 0.85)
y = center[1] + random.uniform(-0.85, 0.85)
asyncio.create_task(
self._tab.send(
cdp.input_.dispatch_mouse_event(
Expand All @@ -542,13 +544,31 @@ async def mouse_click_async(
)
),
)
await asyncio.sleep(random.uniform(0.011, 0.015))
if not timeframe or timeframe <= 0:
# If 0 (or less), hold for a small amount of time
await asyncio.sleep(random.uniform(0.0126, 0.0152))
x += random.uniform(-0.12, 0.12)
y += random.uniform(-0.12, 0.12)
else:
end_time = asyncio.get_running_loop().time() + timeframe
while asyncio.get_running_loop().time() < end_time:
await self._tab.send(
cdp.input_.dispatch_mouse_event(
type_="mouseMoved",
x=x + random.uniform(-0.12, 0.12),
y=y + random.uniform(-0.12, 0.12),
button=cdp.input_.MouseButton(button),
buttons=buttons,
force=0.5,
)
)
await asyncio.sleep(random.uniform(0.120, 0.280))
asyncio.create_task(
self._tab.send(
cdp.input_.dispatch_mouse_event(
"mouseReleased",
x=x + random.uniform(-0.12, 0.12),
y=y + random.uniform(-0.12, 0.12),
x=x,
y=y,
modifiers=modifiers,
button=cdp.input_.MouseButton(button),
buttons=buttons,
Expand Down Expand Up @@ -591,7 +611,7 @@ async def mouse_click_with_offset_async(
logger.debug("Clicking on location: %.2f, %.2f" % (x_pos, y_pos))
with suppress(Exception):
await self.mouse_move_async()
await asyncio.sleep(random.uniform(0.003, 0.005))
await asyncio.sleep(random.uniform(0.0035, 0.0048))
script1 = 'sessionStorage.getItem("pxsid") !== null;'
script2 = 'sessionStorage.getItem("PIM-SESSION-ID") !== null;'
using_px = True
Expand Down Expand Up @@ -809,6 +829,20 @@ async def send_keys_async(self, text: str):
if self.tag_name.lower() == "textarea" and text.endswith("\r\n"):
text = text[0:-1]
await self.apply("(elem) => elem.focus()")
if (
IS_LINUX
and not (
self.tag_name.lower() == "textarea"
and text.endswith("\r") or text.endswith("\n")
)
):
[
await self._tab.send(
cdp.input_.dispatch_key_event(type_="char", text=char)
)
for char in list(text)
]
return
# Map non-alphanumeric symbols to the correct CDP virtual key code.
# Prevents collisions with control keys. Eg. ord('.') = 46 = VK_DELETE.
SYMBOL_MAP = {
Expand Down Expand Up @@ -898,7 +932,7 @@ async def send_keys_async(self, text: str):
)
)
# 4. Trigger keyup DOM event
await asyncio.sleep(random.uniform(0.011, 0.015))
await asyncio.sleep(random.uniform(0.0126, 0.0152))
await self._tab.send(
cdp.input_.dispatch_key_event(
type_="keyUp",
Expand Down