diff --git a/cloakbrowser/human/__init__.py b/cloakbrowser/human/__init__.py index 7c80be0..c3a3890 100644 --- a/cloakbrowser/human/__init__.py +++ b/cloakbrowser/human/__init__.py @@ -421,7 +421,7 @@ def _patch_locator_class_sync(): native_kwargs = {k: v for k, v in kwargs.items() if k != "human_config"} return _orig_scroll_into_view(self, **native_kwargs) return - timeout = kwargs.get("timeout", 2000) + timeout = kwargs.get("timeout", 30000) try: _, nx, ny = human_scroll_into_view( page, raw, @@ -648,7 +648,7 @@ def _patch_locator_class_async(): native_kwargs = {k: v for k, v in kwargs.items() if k != "human_config"} await _orig_scroll_into_view(self, **native_kwargs) return - timeout = kwargs.get("timeout", 2000) + timeout = kwargs.get("timeout", 30000) async def _get_box(): return await self.bounding_box(timeout=timeout) @@ -868,7 +868,7 @@ def patch_page(page: Any, cfg: HumanConfig, cursor: _CursorState) -> None: def _human_click(selector: str, **kwargs: Any) -> None: _ensure_cursor_init() call_cfg = merge_config(cfg, kwargs.get("human_config")) - timeout = kwargs.get("timeout", 2000) + timeout = kwargs.get("timeout", 30000) if call_cfg.idle_between_actions: human_idle(raw_mouse, rand(call_cfg.idle_between_duration[0], call_cfg.idle_between_duration[1]), cursor.x, cursor.y, call_cfg) box, cx, cy = scroll_to_element( @@ -886,7 +886,7 @@ def patch_page(page: Any, cfg: HumanConfig, cursor: _CursorState) -> None: def _human_dblclick(selector: str, **kwargs: Any) -> None: _ensure_cursor_init() call_cfg = merge_config(cfg, kwargs.get("human_config")) - timeout = kwargs.get("timeout", 2000) + timeout = kwargs.get("timeout", 30000) if call_cfg.idle_between_actions: human_idle(raw_mouse, rand(call_cfg.idle_between_duration[0], call_cfg.idle_between_duration[1]), cursor.x, cursor.y, call_cfg) box, cx, cy = scroll_to_element( @@ -906,7 +906,7 @@ def patch_page(page: Any, cfg: HumanConfig, cursor: _CursorState) -> None: def _human_hover(selector: str, **kwargs: Any) -> None: _ensure_cursor_init() call_cfg = merge_config(cfg, kwargs.get("human_config")) - timeout = kwargs.get("timeout", 2000) + timeout = kwargs.get("timeout", 30000) if call_cfg.idle_between_actions: human_idle(raw_mouse, rand(call_cfg.idle_between_duration[0], call_cfg.idle_between_duration[1]), cursor.x, cursor.y, call_cfg) box, cx, cy = scroll_to_element( @@ -1621,7 +1621,7 @@ def patch_page_async(page: Any, cfg: HumanConfig, cursor: _CursorState) -> None: async def _human_click(selector: str, **kwargs: Any) -> None: await _ensure_cursor_init() call_cfg = merge_config(cfg, kwargs.get("human_config")) - timeout = kwargs.get("timeout", 2000) + timeout = kwargs.get("timeout", 30000) if call_cfg.idle_between_actions: await async_human_idle(raw_mouse, rand(call_cfg.idle_between_duration[0], call_cfg.idle_between_duration[1]), cursor.x, cursor.y, call_cfg) box, cx, cy = await async_scroll_to_element( @@ -1639,7 +1639,7 @@ def patch_page_async(page: Any, cfg: HumanConfig, cursor: _CursorState) -> None: async def _human_dblclick(selector: str, **kwargs: Any) -> None: await _ensure_cursor_init() call_cfg = merge_config(cfg, kwargs.get("human_config")) - timeout = kwargs.get("timeout", 2000) + timeout = kwargs.get("timeout", 30000) if call_cfg.idle_between_actions: await async_human_idle(raw_mouse, rand(call_cfg.idle_between_duration[0], call_cfg.idle_between_duration[1]), cursor.x, cursor.y, call_cfg) box, cx, cy = await async_scroll_to_element( @@ -1659,7 +1659,7 @@ def patch_page_async(page: Any, cfg: HumanConfig, cursor: _CursorState) -> None: async def _human_hover(selector: str, **kwargs: Any) -> None: await _ensure_cursor_init() call_cfg = merge_config(cfg, kwargs.get("human_config")) - timeout = kwargs.get("timeout", 2000) + timeout = kwargs.get("timeout", 30000) if call_cfg.idle_between_actions: await async_human_idle(raw_mouse, rand(call_cfg.idle_between_duration[0], call_cfg.idle_between_duration[1]), cursor.x, cursor.y, call_cfg) box, cx, cy = await async_scroll_to_element( diff --git a/cloakbrowser/human/scroll.py b/cloakbrowser/human/scroll.py index e756625..8010065 100644 --- a/cloakbrowser/human/scroll.py +++ b/cloakbrowser/human/scroll.py @@ -18,7 +18,7 @@ def _is_in_viewport(bounds: dict, viewport_height: int, cfg: HumanConfig) -> boo return top_edge >= zone_top and bottom_edge <= zone_bottom -def _get_element_box(page: Any, selector: str, timeout: float = 2000) -> Optional[dict]: +def _get_element_box(page: Any, selector: str, timeout: float = 30000) -> Optional[dict]: """Locate ``selector`` and return its bounding box. The ``timeout`` is forwarded to Playwright's ``boundingBox(timeout=...)`` @@ -68,10 +68,7 @@ def human_scroll_into_view( box = get_box() if box is None: - sleep_ms(200) - box = get_box() - if box is None: - raise RuntimeError("Element not found while scrolling into view") + raise RuntimeError("Element not found while scrolling into view") if _is_in_viewport(box, viewport_height, cfg): return box, cursor_x, cursor_y @@ -151,13 +148,13 @@ def scroll_to_element( selector: str, cursor_x: float, cursor_y: float, cfg: HumanConfig, - timeout: float = 2000, + timeout: float = 30000, ) -> Tuple[dict, float, float]: """Selector-based humanized scroll. ``timeout`` is forwarded to ``locator.bounding_box(timeout=...)`` so callers such as ``page.click('#x', timeout=5000)`` can wait longer for slow elements - (#172). Default stays at 2000ms when not specified. + (#172). Default matches Playwright's 30000ms when not specified. """ return human_scroll_into_view( page, raw, diff --git a/cloakbrowser/human/scroll_async.py b/cloakbrowser/human/scroll_async.py index a609dfe..d59f21f 100644 --- a/cloakbrowser/human/scroll_async.py +++ b/cloakbrowser/human/scroll_async.py @@ -16,7 +16,7 @@ from .scroll import _is_in_viewport async def _get_element_box_async( - page: Any, selector: str, timeout: float = 2000, + page: Any, selector: str, timeout: float = 30000, ) -> Optional[dict]: """Async variant. ``timeout`` is forwarded to Playwright's ``boundingBox(timeout=...)`` so callers can extend it for slow-loading @@ -64,10 +64,7 @@ async def async_human_scroll_into_view( box = await get_box() if box is None: - await async_sleep_ms(200) - box = await get_box() - if box is None: - raise RuntimeError("Element not found while scrolling into view") + raise RuntimeError("Element not found while scrolling into view") if _is_in_viewport(box, viewport_height, cfg): return box, cursor_x, cursor_y @@ -147,13 +144,13 @@ async def async_scroll_to_element( selector: str, cursor_x: float, cursor_y: float, cfg: HumanConfig, - timeout: float = 2000, + timeout: float = 30000, ) -> Tuple[dict, float, float]: """Selector-based humanized scroll (async). ``timeout`` is forwarded to ``locator.bounding_box(timeout=...)`` so callers such as ``page.click('#x', timeout=5000)`` can wait longer for slow elements - (#172). Default stays at 2000ms when not specified. + (#172). Default matches Playwright's 30000ms when not specified. """ async def _get(): return await _get_element_box_async(page, selector, timeout) diff --git a/js/src/human-puppeteer/scroll.ts b/js/src/human-puppeteer/scroll.ts index 3c274b8..8ef8bab 100644 --- a/js/src/human-puppeteer/scroll.ts +++ b/js/src/human-puppeteer/scroll.ts @@ -57,12 +57,12 @@ export async function smoothWheel( /** * Poll ``page.$(selector)`` for up to ``timeout`` ms, returning the element's - * bounding box when found. ``timeout`` defaults to 2000ms when not specified. + * bounding box when found. ``timeout`` defaults to 30000ms when not specified. */ async function getElementBox( page: Page, selector: string, - timeout: number = 2000, + timeout: number = 30000, ): Promise { const start = Date.now(); const pollInterval = 100; @@ -97,11 +97,7 @@ export async function humanScrollIntoView( if (!viewport) throw new Error('Viewport size not available'); let box = await getBox(); - if (!box) { - await sleep(200); - box = await getBox(); - if (!box) throw new Error('Element not found while scrolling into view'); - } + if (!box) throw new Error('Element not found while scrolling into view'); if (isInViewport(box, viewport.height, cfg)) { return { box, cursorX, cursorY }; @@ -188,7 +184,7 @@ export async function humanScrollIntoView( * * ``timeout`` controls how long we poll ``page.$(selector)`` before giving up, * so callers like ``page.click('#x', { timeout: 5000 })`` can wait longer for - * slow-loading elements (#172). Default stays 2000ms when not specified. + * slow-loading elements (#172). Default matches Playwright's 30000ms when not specified. */ export async function scrollToElement( page: Page, diff --git a/js/src/human/scroll.ts b/js/src/human/scroll.ts index a40376f..c6761d3 100644 --- a/js/src/human/scroll.ts +++ b/js/src/human/scroll.ts @@ -57,11 +57,7 @@ export async function humanScrollIntoView( if (!viewport) throw new Error('Viewport size not available'); let box = await getBox(); - if (!box) { - await sleep(200); - box = await getBox(); - if (!box) throw new Error('Element not found while scrolling into view'); - } + if (!box) throw new Error('Element not found while scrolling into view'); if (isInViewport(box, viewport.height, cfg)) { return { box, cursorX, cursorY }; @@ -151,7 +147,7 @@ export async function humanScrollIntoView( * * ``timeout`` is forwarded to Playwright's ``boundingBox({ timeout })`` so * callers like ``page.click('#x', { timeout: 5000 })`` can wait longer for - * slow-loading elements (#172). Default stays 2000ms when not specified. + * slow-loading elements (#172). Default matches Playwright's 30000ms when not specified. */ export async function scrollToElement( page: Page, @@ -172,7 +168,7 @@ export async function scrollToElement( async function getElementBox( page: Page, selector: string, - timeout: number = 2000, + timeout: number = 30000, ): Promise { const el = page.locator(selector).first(); try { diff --git a/js/tests/humanize.test.ts b/js/tests/humanize.test.ts index 7c1d2b2..836ae38 100644 --- a/js/tests/humanize.test.ts +++ b/js/tests/humanize.test.ts @@ -1108,7 +1108,7 @@ describe("page.click(selector, { timeout }) forwards timeout to scroll", () => { expect(boundingBox).toHaveBeenCalledWith({ timeout: 5000 }); }); - it("default timeout stays 2000ms when not specified", async () => { + it("default timeout matches Playwright's 30000ms when not specified", async () => { const { scrollToElement } = await import("../src/human/scroll.js"); const cfg = resolveConfig("default"); @@ -1125,7 +1125,7 @@ describe("page.click(selector, { timeout }) forwards timeout to scroll", () => { }; await scrollToElement(page, raw, "#x", 0, 0, cfg); - expect(boundingBox).toHaveBeenCalledWith({ timeout: 2000 }); + expect(boundingBox).toHaveBeenCalledWith({ timeout: 30000 }); }); it("page.click({ timeout }) reaches scrollToElement", async () => { diff --git a/js/tests/stealth.puppeteer.test.ts b/js/tests/stealth.puppeteer.test.ts index 2d162a5..d84c12a 100644 --- a/js/tests/stealth.puppeteer.test.ts +++ b/js/tests/stealth.puppeteer.test.ts @@ -1671,11 +1671,13 @@ describe("Puppeteer: isInputElement stealth integration via patchPage", () => { }), }); + const mockEl = buildMockElementHandle(); const page = buildMockPage({ evaluate: vi.fn(async (...args: any[]) => { evaluateCalls.push(args); return false; }), + $: vi.fn(async () => mockEl), }); page.createCDPSession = vi.fn(async () => mockCdp); diff --git a/tests/test_humanize_unit.py b/tests/test_humanize_unit.py index db3138a..6fec0e9 100644 --- a/tests/test_humanize_unit.py +++ b/tests/test_humanize_unit.py @@ -1305,7 +1305,7 @@ class TestPerCallTimeoutForwarding: not silently use the hardcoded 2000ms in scroll.""" def test_get_element_box_default_timeout(self): - """Default timeout stays 2000ms for backwards compatibility.""" + """Default timeout matches Playwright's 30000ms.""" from cloakbrowser.human.scroll import _get_element_box from unittest.mock import MagicMock @@ -1315,7 +1315,7 @@ class TestPerCallTimeoutForwarding: page.locator = MagicMock(return_value=MagicMock(first=loc)) _get_element_box(page, "#x") - loc.bounding_box.assert_called_once_with(timeout=2000) + loc.bounding_box.assert_called_once_with(timeout=30000) def test_get_element_box_custom_timeout(self): """Caller can pass a custom timeout that overrides the default.""" @@ -1387,7 +1387,7 @@ class TestPerCallTimeoutForwarding: page.main_frame.child_frames = [] captured = {} - def fake_scroll(page_arg, raw, selector, cx, cy, cfg_arg, timeout=2000): + def fake_scroll(page_arg, raw, selector, cx, cy, cfg_arg, timeout=30000): captured["timeout"] = timeout return ({"x": 100, "y": 100, "width": 50, "height": 30}, cx, cy)