mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
fix(humanize): use shared deadline for timeout budget in frame and ElementHandle methods (#307)
Frame-level methods (click, dblclick, hover, dragAndDrop) passed the raw timeout to each sequential operation independently, causing 3x actual wait time when elements don't exist. ElementHandle methods had a similar 2x issue between actionability and pointer-events checks. Port the deadline + remainingMs() pattern already used by page-level methods. Also fix bot detection test selector after site added a hidden duplicate submit button.
This commit is contained in:
@@ -1257,13 +1257,16 @@ def _patch_single_element_handle_sync(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_CLICK, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_CLICK, timeout=_remaining_ms(), force=force)
|
||||||
info = _move_to_element(call_cfg)
|
info = _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return _orig_click(**kwargs)
|
return _orig_click(**kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
human_click(raw_mouse, info['is_inp'], call_cfg)
|
human_click(raw_mouse, info['is_inp'], call_cfg)
|
||||||
|
|
||||||
# --- el.dblclick() ---
|
# --- el.dblclick() ---
|
||||||
@@ -1271,13 +1274,16 @@ def _patch_single_element_handle_sync(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_CLICK, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_CLICK, timeout=_remaining_ms(), force=force)
|
||||||
info = _move_to_element(call_cfg)
|
info = _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return _orig_dblclick(**kwargs)
|
return _orig_dblclick(**kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
raw_mouse.down(click_count=2)
|
raw_mouse.down(click_count=2)
|
||||||
sleep_ms(rand(30, 60))
|
sleep_ms(rand(30, 60))
|
||||||
raw_mouse.up(click_count=2)
|
raw_mouse.up(click_count=2)
|
||||||
@@ -1287,8 +1293,11 @@ def _patch_single_element_handle_sync(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_HOVER, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_HOVER, timeout=_remaining_ms(), force=force)
|
||||||
info = _move_to_element(call_cfg)
|
info = _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return _orig_hover(**kwargs)
|
return _orig_hover(**kwargs)
|
||||||
@@ -1298,13 +1307,16 @@ def _patch_single_element_handle_sync(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_INPUT, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_INPUT, timeout=_remaining_ms(), force=force)
|
||||||
info = _move_to_element(call_cfg)
|
info = _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return _orig_type(text, **kwargs)
|
return _orig_type(text, **kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
human_click(raw_mouse, info['is_inp'], call_cfg)
|
human_click(raw_mouse, info['is_inp'], call_cfg)
|
||||||
sleep_ms(rand(100, 250))
|
sleep_ms(rand(100, 250))
|
||||||
human_type(page, raw_keyboard, text, call_cfg, cdp_session=cdp_session)
|
human_type(page, raw_keyboard, text, call_cfg, cdp_session=cdp_session)
|
||||||
@@ -1314,13 +1326,16 @@ def _patch_single_element_handle_sync(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_INPUT, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_INPUT, timeout=_remaining_ms(), force=force)
|
||||||
info = _move_to_element(call_cfg)
|
info = _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return _orig_fill(value, **kwargs)
|
return _orig_fill(value, **kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
human_click(raw_mouse, info['is_inp'], call_cfg)
|
human_click(raw_mouse, info['is_inp'], call_cfg)
|
||||||
sleep_ms(rand(100, 250))
|
sleep_ms(rand(100, 250))
|
||||||
originals.keyboard_press(_SELECT_ALL)
|
originals.keyboard_press(_SELECT_ALL)
|
||||||
@@ -1367,8 +1382,11 @@ def _patch_single_element_handle_sync(
|
|||||||
def _human_el_select_option(value: Any = None, **kwargs: Any) -> Any:
|
def _human_el_select_option(value: Any = None, **kwargs: Any) -> Any:
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_FOCUS, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_FOCUS, timeout=_remaining_ms(), force=force)
|
||||||
info = _move_to_element()
|
info = _move_to_element()
|
||||||
if info is None:
|
if info is None:
|
||||||
return _orig_select_option(value, **kwargs)
|
return _orig_select_option(value, **kwargs)
|
||||||
@@ -1380,8 +1398,11 @@ def _patch_single_element_handle_sync(
|
|||||||
def _human_el_check(**kwargs: Any) -> None:
|
def _human_el_check(**kwargs: Any) -> None:
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=_remaining_ms(), force=force)
|
||||||
try:
|
try:
|
||||||
if el.is_checked():
|
if el.is_checked():
|
||||||
return
|
return
|
||||||
@@ -1391,15 +1412,18 @@ def _patch_single_element_handle_sync(
|
|||||||
if info is None:
|
if info is None:
|
||||||
return _orig_check(**kwargs)
|
return _orig_check(**kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
human_click(raw_mouse, info['is_inp'], cfg)
|
human_click(raw_mouse, info['is_inp'], cfg)
|
||||||
|
|
||||||
# --- el.uncheck() ---
|
# --- el.uncheck() ---
|
||||||
def _human_el_uncheck(**kwargs: Any) -> None:
|
def _human_el_uncheck(**kwargs: Any) -> None:
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=_remaining_ms(), force=force)
|
||||||
try:
|
try:
|
||||||
if not el.is_checked():
|
if not el.is_checked():
|
||||||
return
|
return
|
||||||
@@ -1409,15 +1433,18 @@ def _patch_single_element_handle_sync(
|
|||||||
if info is None:
|
if info is None:
|
||||||
return _orig_uncheck(**kwargs)
|
return _orig_uncheck(**kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
human_click(raw_mouse, info['is_inp'], cfg)
|
human_click(raw_mouse, info['is_inp'], cfg)
|
||||||
|
|
||||||
# --- el.set_checked() ---
|
# --- el.set_checked() ---
|
||||||
def _human_el_set_checked(checked: bool, **kwargs: Any) -> None:
|
def _human_el_set_checked(checked: bool, **kwargs: Any) -> None:
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=timeout, force=force)
|
ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=_remaining_ms(), force=force)
|
||||||
try:
|
try:
|
||||||
current = el.is_checked()
|
current = el.is_checked()
|
||||||
if current == checked:
|
if current == checked:
|
||||||
@@ -1429,7 +1456,7 @@ def _patch_single_element_handle_sync(
|
|||||||
return _orig_set_checked(checked, **kwargs)
|
return _orig_set_checked(checked, **kwargs)
|
||||||
if info:
|
if info:
|
||||||
if not force:
|
if not force:
|
||||||
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
human_click(raw_mouse, info['is_inp'], cfg)
|
human_click(raw_mouse, info['is_inp'], cfg)
|
||||||
|
|
||||||
# --- el.tap() ---
|
# --- el.tap() ---
|
||||||
@@ -2158,13 +2185,16 @@ def _patch_single_element_handle_async(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_CLICK, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_CLICK, timeout=_remaining_ms(), force=force)
|
||||||
info = await _move_to_element(call_cfg)
|
info = await _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return await _orig_click(**kwargs)
|
return await _orig_click(**kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
await async_human_click(raw_mouse, info['is_inp'], call_cfg)
|
await async_human_click(raw_mouse, info['is_inp'], call_cfg)
|
||||||
|
|
||||||
# --- el.dblclick() ---
|
# --- el.dblclick() ---
|
||||||
@@ -2172,13 +2202,16 @@ def _patch_single_element_handle_async(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_CLICK, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_CLICK, timeout=_remaining_ms(), force=force)
|
||||||
info = await _move_to_element(call_cfg)
|
info = await _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return await _orig_dblclick(**kwargs)
|
return await _orig_dblclick(**kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
await raw_mouse.down(click_count=2)
|
await raw_mouse.down(click_count=2)
|
||||||
await async_sleep_ms(rand(30, 60))
|
await async_sleep_ms(rand(30, 60))
|
||||||
await raw_mouse.up(click_count=2)
|
await raw_mouse.up(click_count=2)
|
||||||
@@ -2188,8 +2221,11 @@ def _patch_single_element_handle_async(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_HOVER, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_HOVER, timeout=_remaining_ms(), force=force)
|
||||||
info = await _move_to_element(call_cfg)
|
info = await _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return await _orig_hover(**kwargs)
|
return await _orig_hover(**kwargs)
|
||||||
@@ -2199,13 +2235,16 @@ def _patch_single_element_handle_async(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_INPUT, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_INPUT, timeout=_remaining_ms(), force=force)
|
||||||
info = await _move_to_element(call_cfg)
|
info = await _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return await _orig_type(text, **kwargs)
|
return await _orig_type(text, **kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
await async_human_click(raw_mouse, info['is_inp'], call_cfg)
|
await async_human_click(raw_mouse, info['is_inp'], call_cfg)
|
||||||
await async_sleep_ms(rand(100, 250))
|
await async_sleep_ms(rand(100, 250))
|
||||||
cdp = await _get_cdp()
|
cdp = await _get_cdp()
|
||||||
@@ -2216,13 +2255,16 @@ def _patch_single_element_handle_async(
|
|||||||
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
call_cfg = merge_config(cfg, kwargs.get("human_config"))
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_INPUT, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_INPUT, timeout=_remaining_ms(), force=force)
|
||||||
info = await _move_to_element(call_cfg)
|
info = await _move_to_element(call_cfg)
|
||||||
if info is None:
|
if info is None:
|
||||||
return await _orig_fill(value, **kwargs)
|
return await _orig_fill(value, **kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
await async_human_click(raw_mouse, info['is_inp'], call_cfg)
|
await async_human_click(raw_mouse, info['is_inp'], call_cfg)
|
||||||
await async_sleep_ms(rand(100, 250))
|
await async_sleep_ms(rand(100, 250))
|
||||||
await originals.keyboard_press(_SELECT_ALL)
|
await originals.keyboard_press(_SELECT_ALL)
|
||||||
@@ -2269,8 +2311,11 @@ def _patch_single_element_handle_async(
|
|||||||
async def _human_el_select_option(value: Any = None, **kwargs: Any) -> Any:
|
async def _human_el_select_option(value: Any = None, **kwargs: Any) -> Any:
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_FOCUS, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_FOCUS, timeout=_remaining_ms(), force=force)
|
||||||
info = await _move_to_element()
|
info = await _move_to_element()
|
||||||
if info is None:
|
if info is None:
|
||||||
return await _orig_select_option(value, **kwargs)
|
return await _orig_select_option(value, **kwargs)
|
||||||
@@ -2282,8 +2327,11 @@ def _patch_single_element_handle_async(
|
|||||||
async def _human_el_check(**kwargs: Any) -> None:
|
async def _human_el_check(**kwargs: Any) -> None:
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=_remaining_ms(), force=force)
|
||||||
try:
|
try:
|
||||||
if await el.is_checked():
|
if await el.is_checked():
|
||||||
return
|
return
|
||||||
@@ -2293,15 +2341,18 @@ def _patch_single_element_handle_async(
|
|||||||
if info is None:
|
if info is None:
|
||||||
return await _orig_check(**kwargs)
|
return await _orig_check(**kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
await async_human_click(raw_mouse, info['is_inp'], cfg)
|
await async_human_click(raw_mouse, info['is_inp'], cfg)
|
||||||
|
|
||||||
# --- el.uncheck() ---
|
# --- el.uncheck() ---
|
||||||
async def _human_el_uncheck(**kwargs: Any) -> None:
|
async def _human_el_uncheck(**kwargs: Any) -> None:
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=_remaining_ms(), force=force)
|
||||||
try:
|
try:
|
||||||
if not await el.is_checked():
|
if not await el.is_checked():
|
||||||
return
|
return
|
||||||
@@ -2311,15 +2362,18 @@ def _patch_single_element_handle_async(
|
|||||||
if info is None:
|
if info is None:
|
||||||
return await _orig_uncheck(**kwargs)
|
return await _orig_uncheck(**kwargs)
|
||||||
if not force:
|
if not force:
|
||||||
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
await async_human_click(raw_mouse, info['is_inp'], cfg)
|
await async_human_click(raw_mouse, info['is_inp'], cfg)
|
||||||
|
|
||||||
# --- el.set_checked() ---
|
# --- el.set_checked() ---
|
||||||
async def _human_el_set_checked(checked: bool, **kwargs: Any) -> None:
|
async def _human_el_set_checked(checked: bool, **kwargs: Any) -> None:
|
||||||
force = kwargs.get("force", False)
|
force = kwargs.get("force", False)
|
||||||
timeout = kwargs.get("timeout", 30000)
|
timeout = kwargs.get("timeout", 30000)
|
||||||
|
deadline = time.monotonic() + timeout / 1000.0
|
||||||
|
def _remaining_ms():
|
||||||
|
return max(0, (deadline - time.monotonic()) * 1000)
|
||||||
if not force:
|
if not force:
|
||||||
await async_ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=timeout, force=force)
|
await async_ensure_actionable_handle(page, el, CHECKS_CHECK, timeout=_remaining_ms(), force=force)
|
||||||
try:
|
try:
|
||||||
current = await el.is_checked()
|
current = await el.is_checked()
|
||||||
if current == checked:
|
if current == checked:
|
||||||
@@ -2331,7 +2385,7 @@ def _patch_single_element_handle_async(
|
|||||||
return await _orig_set_checked(checked, **kwargs)
|
return await _orig_set_checked(checked, **kwargs)
|
||||||
if info:
|
if info:
|
||||||
if not force:
|
if not force:
|
||||||
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(timeout, 5000))
|
await async_check_pointer_events_handle(page, el, cursor.x, cursor.y, timeout=min(_remaining_ms(), 5000))
|
||||||
await async_human_click(raw_mouse, info['is_inp'], cfg)
|
await async_human_click(raw_mouse, info['is_inp'], cfg)
|
||||||
|
|
||||||
# --- el.tap() ---
|
# --- el.tap() ---
|
||||||
|
|||||||
@@ -196,10 +196,12 @@ export function patchSingleElementHandle(
|
|||||||
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
||||||
const force = options?.force ?? false;
|
const force = options?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_CLICK, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_CLICK, remainingMs(), force);
|
||||||
const info = await moveToElement(callCfg);
|
const info = await moveToElement(callCfg);
|
||||||
if (!info) return origElClick(options);
|
if (!info) return origElClick(options);
|
||||||
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(timeout, 5000));
|
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(remainingMs(), 5000));
|
||||||
await humanClick(raw, info.isInp, callCfg);
|
await humanClick(raw, info.isInp, callCfg);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -216,10 +218,12 @@ export function patchSingleElementHandle(
|
|||||||
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
||||||
const force = options?.force ?? false;
|
const force = options?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_CLICK, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_CLICK, remainingMs(), force);
|
||||||
const info = await moveToElement(callCfg);
|
const info = await moveToElement(callCfg);
|
||||||
if (!info) return origElDblclick(options);
|
if (!info) return origElDblclick(options);
|
||||||
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(timeout, 5000));
|
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(remainingMs(), 5000));
|
||||||
await raw.down({ clickCount: 2 });
|
await raw.down({ clickCount: 2 });
|
||||||
await sleep(rand(30, 60));
|
await sleep(rand(30, 60));
|
||||||
await raw.up({ clickCount: 2 });
|
await raw.up({ clickCount: 2 });
|
||||||
@@ -235,7 +239,9 @@ export function patchSingleElementHandle(
|
|||||||
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
||||||
const force = options?.force ?? false;
|
const force = options?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_HOVER, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_HOVER, remainingMs(), force);
|
||||||
const info = await moveToElement(callCfg);
|
const info = await moveToElement(callCfg);
|
||||||
if (!info) return origElHover(options);
|
if (!info) return origElHover(options);
|
||||||
};
|
};
|
||||||
@@ -248,10 +254,12 @@ export function patchSingleElementHandle(
|
|||||||
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
||||||
const force = (options as any)?.force ?? false;
|
const force = (options as any)?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_INPUT, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_INPUT, remainingMs(), force);
|
||||||
const info = await moveToElement(callCfg);
|
const info = await moveToElement(callCfg);
|
||||||
if (!info) return origElType(text, options);
|
if (!info) return origElType(text, options);
|
||||||
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(timeout, 5000));
|
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(remainingMs(), 5000));
|
||||||
await humanClick(raw, info.isInp, callCfg);
|
await humanClick(raw, info.isInp, callCfg);
|
||||||
await sleep(rand(100, 250));
|
await sleep(rand(100, 250));
|
||||||
let cdpSession: CDPSession | null = null;
|
let cdpSession: CDPSession | null = null;
|
||||||
@@ -267,10 +275,12 @@ export function patchSingleElementHandle(
|
|||||||
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
||||||
const force = options?.force ?? false;
|
const force = options?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_INPUT, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_INPUT, remainingMs(), force);
|
||||||
const info = await moveToElement(callCfg);
|
const info = await moveToElement(callCfg);
|
||||||
if (!info) return origElFill(value, options);
|
if (!info) return origElFill(value, options);
|
||||||
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(timeout, 5000));
|
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(remainingMs(), 5000));
|
||||||
await humanClick(raw, info.isInp, callCfg);
|
await humanClick(raw, info.isInp, callCfg);
|
||||||
await sleep(rand(100, 250));
|
await sleep(rand(100, 250));
|
||||||
await originals.keyboardPress(SELECT_ALL);
|
await originals.keyboardPress(SELECT_ALL);
|
||||||
@@ -298,7 +308,9 @@ export function patchSingleElementHandle(
|
|||||||
}) => {
|
}) => {
|
||||||
const force = options?.force ?? false;
|
const force = options?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_FOCUS, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_FOCUS, remainingMs(), force);
|
||||||
const info = await moveToElement();
|
const info = await moveToElement();
|
||||||
if (!info) return origElSelectOption(values, options);
|
if (!info) return origElSelectOption(values, options);
|
||||||
await humanClick(raw, false, cfg);
|
await humanClick(raw, false, cfg);
|
||||||
@@ -316,14 +328,16 @@ export function patchSingleElementHandle(
|
|||||||
}) => {
|
}) => {
|
||||||
const force = options?.force ?? false;
|
const force = options?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_CHECK, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_CHECK, remainingMs(), force);
|
||||||
try {
|
try {
|
||||||
const checked = await el.isChecked();
|
const checked = await el.isChecked();
|
||||||
if (checked) return;
|
if (checked) return;
|
||||||
} catch {}
|
} catch {}
|
||||||
const info = await moveToElement();
|
const info = await moveToElement();
|
||||||
if (!info) return origElCheck(options);
|
if (!info) return origElCheck(options);
|
||||||
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(timeout, 5000));
|
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(remainingMs(), 5000));
|
||||||
await humanClick(raw, info.isInp, cfg);
|
await humanClick(raw, info.isInp, cfg);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -337,14 +351,16 @@ export function patchSingleElementHandle(
|
|||||||
}) => {
|
}) => {
|
||||||
const force = options?.force ?? false;
|
const force = options?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_CHECK, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_CHECK, remainingMs(), force);
|
||||||
try {
|
try {
|
||||||
const checked = await el.isChecked();
|
const checked = await el.isChecked();
|
||||||
if (!checked) return;
|
if (!checked) return;
|
||||||
} catch {}
|
} catch {}
|
||||||
const info = await moveToElement();
|
const info = await moveToElement();
|
||||||
if (!info) return origElUncheck(options);
|
if (!info) return origElUncheck(options);
|
||||||
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(timeout, 5000));
|
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(remainingMs(), 5000));
|
||||||
await humanClick(raw, info.isInp, cfg);
|
await humanClick(raw, info.isInp, cfg);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -359,14 +375,16 @@ export function patchSingleElementHandle(
|
|||||||
}) => {
|
}) => {
|
||||||
const force = options?.force ?? false;
|
const force = options?.force ?? false;
|
||||||
const timeout = options?.timeout ?? 30000;
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!force) await ensureActionableHandle(el, CHECKS_CHECK, timeout, force);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
if (!force) await ensureActionableHandle(el, CHECKS_CHECK, remainingMs(), force);
|
||||||
try {
|
try {
|
||||||
const current = await el.isChecked();
|
const current = await el.isChecked();
|
||||||
if (current === checked) return;
|
if (current === checked) return;
|
||||||
} catch {}
|
} catch {}
|
||||||
const info = await moveToElement();
|
const info = await moveToElement();
|
||||||
if (!info) return origElSetChecked(checked, options);
|
if (!info) return origElSetChecked(checked, options);
|
||||||
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(timeout, 5000));
|
if (!force) await checkPointerEventsHandle(el, cursor.x, cursor.y, Math.min(remainingMs(), 5000));
|
||||||
await humanClick(raw, info.isInp, cfg);
|
await humanClick(raw, info.isInp, cfg);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-12
@@ -691,7 +691,12 @@ function patchSingleFrame(
|
|||||||
const origFrameTap = (frame as any).tap?.bind(frame);
|
const origFrameTap = (frame as any).tap?.bind(frame);
|
||||||
const origFrameDragAndDrop = frame.dragAndDrop.bind(frame);
|
const origFrameDragAndDrop = frame.dragAndDrop.bind(frame);
|
||||||
|
|
||||||
const moveToFrameSelector = async (selector: string, options?: HumanActionOptions, inputBias = false) => {
|
const moveToFrameSelector = async (
|
||||||
|
selector: string,
|
||||||
|
options: HumanActionOptions | undefined,
|
||||||
|
inputBias: boolean,
|
||||||
|
remainingMs: () => number,
|
||||||
|
) => {
|
||||||
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
const callCfg = mergeConfig(cfg, options?.human_config ?? options);
|
||||||
if (callCfg.idle_between_actions) {
|
if (callCfg.idle_between_actions) {
|
||||||
await humanIdle(raw, cursor.x, cursor.y, callCfg);
|
await humanIdle(raw, cursor.x, cursor.y, callCfg);
|
||||||
@@ -699,9 +704,9 @@ function patchSingleFrame(
|
|||||||
|
|
||||||
const locator = firstFrameLocator(frame, selector);
|
const locator = firstFrameLocator(frame, selector);
|
||||||
if (typeof locator.scrollIntoViewIfNeeded === 'function') {
|
if (typeof locator.scrollIntoViewIfNeeded === 'function') {
|
||||||
await locator.scrollIntoViewIfNeeded({ timeout: options?.timeout }).catch(() => undefined);
|
await locator.scrollIntoViewIfNeeded({ timeout: Math.max(1, remainingMs()) }).catch(() => undefined);
|
||||||
}
|
}
|
||||||
const box = await locator.boundingBox({ timeout: options?.timeout ?? 30000 }).catch(() => null);
|
const box = await locator.boundingBox({ timeout: Math.max(1, remainingMs()) }).catch(() => null);
|
||||||
if (!box) return null;
|
if (!box) return null;
|
||||||
|
|
||||||
const isInput = inputBias || await isFrameInputElement(frame, selector);
|
const isInput = inputBias || await isFrameInputElement(frame, selector);
|
||||||
@@ -713,23 +718,32 @@ function patchSingleFrame(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const frameClick = async (selector: string, options?: HumanActionOptions) => {
|
const frameClick = async (selector: string, options?: HumanActionOptions) => {
|
||||||
const moved = await moveToFrameSelector(selector, options);
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!moved) return origFrameClick(selector, options);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
const moved = await moveToFrameSelector(selector, options, false, remainingMs);
|
||||||
|
if (!moved) return origFrameClick(selector, { ...options, timeout: Math.max(1, remainingMs()) });
|
||||||
await humanClick(raw, moved.isInput, moved.callCfg);
|
await humanClick(raw, moved.isInput, moved.callCfg);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFrameCdp = async () => stealth.getCdpSession().catch(() => null);
|
const getFrameCdp = async () => stealth.getCdpSession().catch(() => null);
|
||||||
|
|
||||||
const frameHover = async (selector: string, options?: HumanActionOptions) => {
|
const frameHover = async (selector: string, options?: HumanActionOptions) => {
|
||||||
const moved = await moveToFrameSelector(selector, options, false);
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!moved) return origFrameHover(selector, options);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
const moved = await moveToFrameSelector(selector, options, false, remainingMs);
|
||||||
|
if (!moved) return origFrameHover(selector, { ...options, timeout: Math.max(1, remainingMs()) });
|
||||||
};
|
};
|
||||||
|
|
||||||
(frame as any).click = frameClick;
|
(frame as any).click = frameClick;
|
||||||
|
|
||||||
(frame as any).dblclick = async (selector: string, options?: HumanActionOptions) => {
|
(frame as any).dblclick = async (selector: string, options?: HumanActionOptions) => {
|
||||||
const moved = await moveToFrameSelector(selector, options);
|
const timeout = options?.timeout ?? 30000;
|
||||||
if (!moved) return origFrameDblclick(selector, options);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
||||||
|
const moved = await moveToFrameSelector(selector, options, false, remainingMs);
|
||||||
|
if (!moved) return origFrameDblclick(selector, { ...options, timeout: Math.max(1, remainingMs()) });
|
||||||
await raw.down({ clickCount: 2 });
|
await raw.down({ clickCount: 2 });
|
||||||
await sleep(rand(30, 60));
|
await sleep(rand(30, 60));
|
||||||
await raw.up({ clickCount: 2 });
|
await raw.up({ clickCount: 2 });
|
||||||
@@ -820,8 +834,11 @@ function patchSingleFrame(
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
trial?: boolean;
|
trial?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const srcBox = await firstFrameLocator(frame, source).boundingBox({ timeout: options?.timeout ?? 30000 }).catch(() => null);
|
const timeout = options?.timeout ?? 30000;
|
||||||
const tgtBox = await firstFrameLocator(frame, target).boundingBox({ timeout: options?.timeout ?? 30000 }).catch(() => null);
|
const deadline = Date.now() + timeout;
|
||||||
|
const remainingMs = () => Math.max(1, deadline - Date.now());
|
||||||
|
const srcBox = await firstFrameLocator(frame, source).boundingBox({ timeout: remainingMs() }).catch(() => null);
|
||||||
|
const tgtBox = await firstFrameLocator(frame, target).boundingBox({ timeout: remainingMs() }).catch(() => null);
|
||||||
|
|
||||||
if (srcBox && tgtBox) {
|
if (srcBox && tgtBox) {
|
||||||
const sx = srcBox.x + srcBox.width / 2;
|
const sx = srcBox.x + srcBox.width / 2;
|
||||||
@@ -837,7 +854,7 @@ function patchSingleFrame(
|
|||||||
await sleep(rand(80, 150));
|
await sleep(rand(80, 150));
|
||||||
await originals.mouseUp();
|
await originals.mouseUp();
|
||||||
} else {
|
} else {
|
||||||
return origFrameDragAndDrop(source, target, options);
|
return origFrameDragAndDrop(source, target, { ...options, timeout: Math.max(1, remainingMs()) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1479,3 +1479,83 @@ describe("el.scrollIntoViewIfNeeded humanization", () => {
|
|||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Issue #307: frame.click timeout should not multiply
|
||||||
|
// =========================================================================
|
||||||
|
describe("frame.click timeout budget (#307)", () => {
|
||||||
|
it("total wait time should not exceed the specified timeout", async () => {
|
||||||
|
const { patchPage } = await import("../src/human/index.js");
|
||||||
|
|
||||||
|
const TIMEOUT_MS = 500;
|
||||||
|
const delay = (ms: number) => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
|
// Build a frame where the element does NOT exist:
|
||||||
|
// scrollIntoViewIfNeeded and boundingBox each wait until their
|
||||||
|
// individual timeout before failing, and origFrameClick does the same.
|
||||||
|
const frameLoc: any = {
|
||||||
|
boundingBox: vi.fn(async (opts?: { timeout?: number }) => {
|
||||||
|
await delay(opts?.timeout ?? 30000);
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
scrollIntoViewIfNeeded: vi.fn(async (opts?: { timeout?: number }) => {
|
||||||
|
await delay(opts?.timeout ?? 30000);
|
||||||
|
throw new Error("timeout");
|
||||||
|
}),
|
||||||
|
evaluate: vi.fn(async () => ({ hit: true })),
|
||||||
|
isChecked: vi.fn(async () => false),
|
||||||
|
};
|
||||||
|
frameLoc.first = vi.fn(() => frameLoc);
|
||||||
|
|
||||||
|
const origClickFn = vi.fn(async (_sel: string, opts?: any) => {
|
||||||
|
await delay(opts?.timeout ?? 30000);
|
||||||
|
throw new Error("timeout");
|
||||||
|
});
|
||||||
|
|
||||||
|
const childFrame: any = {
|
||||||
|
click: origClickFn,
|
||||||
|
dblclick: vi.fn(async () => {}),
|
||||||
|
hover: vi.fn(async () => {}),
|
||||||
|
type: vi.fn(async () => {}),
|
||||||
|
fill: vi.fn(async () => {}),
|
||||||
|
check: vi.fn(async () => {}),
|
||||||
|
uncheck: vi.fn(async () => {}),
|
||||||
|
selectOption: vi.fn(async () => {}),
|
||||||
|
press: vi.fn(async () => {}),
|
||||||
|
pressSequentially: vi.fn(async () => {}),
|
||||||
|
tap: vi.fn(async () => {}),
|
||||||
|
clear: vi.fn(async () => {}),
|
||||||
|
dragAndDrop: vi.fn(async () => {}),
|
||||||
|
locator: vi.fn(() => frameLoc),
|
||||||
|
childFrames: vi.fn(() => []),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mainFrame = {
|
||||||
|
...buildMockFrame(),
|
||||||
|
childFrames: vi.fn(() => [childFrame]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const page = buildMockPage({ mainFrameReturn: mainFrame });
|
||||||
|
const cfg = resolveConfig("default", {
|
||||||
|
mouse_min_steps: 1,
|
||||||
|
mouse_max_steps: 1,
|
||||||
|
idle_between_actions: false,
|
||||||
|
});
|
||||||
|
const cursor = { x: 0, y: 0, initialized: true };
|
||||||
|
patchPage(page as any, cfg, cursor as any);
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
try {
|
||||||
|
await (childFrame as any).click("#does-not-exist", { timeout: TIMEOUT_MS });
|
||||||
|
} catch {
|
||||||
|
// expected — element doesn't exist
|
||||||
|
}
|
||||||
|
const elapsed = Date.now() - start;
|
||||||
|
|
||||||
|
// With the bug, elapsed ≈ 3 * TIMEOUT_MS (scrollIntoView + boundingBox + origClick).
|
||||||
|
// Fixed: elapsed should be ≈ 1 * TIMEOUT_MS (shared deadline).
|
||||||
|
// Allow 1.8x as upper bound to account for test overhead but catch the 3x bug.
|
||||||
|
expect(elapsed).toBeLessThan(TIMEOUT_MS * 1.8);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -708,7 +708,7 @@ class TestBrowserBotDetection:
|
|||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
page.locator('#password').fill('SecurePass!123')
|
page.locator('#password').fill('SecurePass!123')
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
page.locator('button[type="submit"]').click()
|
page.locator('#loginForm button[type="submit"]').click()
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
body = page.locator('body').text_content()
|
body = page.locator('body').text_content()
|
||||||
assert '"superHumanSpeed": true' not in body
|
assert '"superHumanSpeed": true' not in body
|
||||||
@@ -725,7 +725,7 @@ class TestBrowserBotDetection:
|
|||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
page.locator('#email').fill('test@example.com')
|
page.locator('#email').fill('test@example.com')
|
||||||
page.locator('#password').fill('MyPassword!99')
|
page.locator('#password').fill('MyPassword!99')
|
||||||
page.locator('button[type="submit"]').click()
|
page.locator('#loginForm button[type="submit"]').click()
|
||||||
elapsed_ms = int((time.time() - t0) * 1000)
|
elapsed_ms = int((time.time() - t0) * 1000)
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
assert elapsed_ms > 3000
|
assert elapsed_ms > 3000
|
||||||
@@ -1828,6 +1828,66 @@ class TestScrollIntoViewIfNeeded:
|
|||||||
assert cursor.x == 200 and cursor.y == 200
|
assert cursor.x == 200 and cursor.y == 200
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Issue #307: frame/page click timeout should not multiply
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
class TestTimeoutBudget307:
|
||||||
|
"""Verify timeout budget is shared across sequential operations."""
|
||||||
|
|
||||||
|
def test_page_click_total_time_within_budget(self):
|
||||||
|
"""page.click on a missing element should not exceed ~1x the timeout."""
|
||||||
|
import cloakbrowser.human as h
|
||||||
|
from cloakbrowser.human import _CursorState
|
||||||
|
from cloakbrowser.human.config import resolve_config
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
TIMEOUT_MS = 500
|
||||||
|
cfg = resolve_config("default", {"idle_between_actions": False})
|
||||||
|
cursor = _CursorState()
|
||||||
|
cursor.initialized = True
|
||||||
|
cursor.x = 100
|
||||||
|
cursor.y = 100
|
||||||
|
|
||||||
|
page = MagicMock()
|
||||||
|
page.click = MagicMock()
|
||||||
|
page.dblclick = MagicMock()
|
||||||
|
page.hover = MagicMock()
|
||||||
|
page.type = MagicMock()
|
||||||
|
page.fill = MagicMock()
|
||||||
|
page.goto = MagicMock()
|
||||||
|
page.is_checked = MagicMock(return_value=False)
|
||||||
|
page.viewport_size = {"width": 1280, "height": 720}
|
||||||
|
page.evaluate = MagicMock(return_value={"hit": True})
|
||||||
|
page.context.new_cdp_session = MagicMock(side_effect=Exception("no cdp"))
|
||||||
|
page.mouse = MagicMock()
|
||||||
|
page.keyboard = MagicMock()
|
||||||
|
page.query_selector = MagicMock(return_value=None)
|
||||||
|
page.query_selector_all = MagicMock(return_value=[])
|
||||||
|
page.wait_for_selector = MagicMock(return_value=None)
|
||||||
|
page.main_frame = MagicMock()
|
||||||
|
page.main_frame.child_frames = []
|
||||||
|
|
||||||
|
loc = MagicMock()
|
||||||
|
loc.wait_for = MagicMock(side_effect=lambda **kw: time.sleep(kw.get("timeout", 30000) / 1000.0))
|
||||||
|
loc.is_visible = MagicMock(return_value=False)
|
||||||
|
loc.first = loc
|
||||||
|
page.locator = MagicMock(return_value=loc)
|
||||||
|
|
||||||
|
h.patch_page(page, cfg, cursor)
|
||||||
|
|
||||||
|
start = time.monotonic()
|
||||||
|
try:
|
||||||
|
page.click("#does-not-exist", timeout=TIMEOUT_MS)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
elapsed_ms = (time.monotonic() - start) * 1000
|
||||||
|
|
||||||
|
assert elapsed_ms < TIMEOUT_MS * 1.8, (
|
||||||
|
f"expected <{TIMEOUT_MS * 1.8}ms, got {elapsed_ms:.0f}ms"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Direct runner (backwards compat)
|
# Direct runner (backwards compat)
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user