Skip to content

feat(humanize): add Playwright ElementHandle support and fix async tests#133

Merged
Cloak-HQ merged 1 commit intoCloakHQ:mainfrom
evelaa123:feature/humanize
Apr 10, 2026
Merged

feat(humanize): add Playwright ElementHandle support and fix async tests#133
Cloak-HQ merged 1 commit intoCloakHQ:mainfrom
evelaa123:feature/humanize

Conversation

@evelaa123
Copy link
Copy Markdown
Contributor

@evelaa123 evelaa123 commented Apr 10, 2026

Summary

  • ElementHandle Support: Now query_selector (Python) and $ (JS) return patched handles. Interactions like el.click(), el.fill(), and el.hover() use humanized Bézier curves and realistic timing.
  • Full Parity: Works across JavaScript and Python (both Sync and Async APIs).
  • Bug Fixes:
    • Fixed the Python test suite (missing asyncio imports and asyncio.run conflicts).
    • Fixed a crash when passing an empty string as a proxy. (in test)
  • Status: All 61 Python tests and 91 JS tests are passing.

How it works

  • Automatic Patching: The library intercepts query_selector, query_selector_all, and wait_for_selector. Any handle they return is automatically wrapped with humanized methods.
  • Shared State: All handles share the same cursor position with the Page, so movement is continuous and natural.
  • Recursive: If you search for a child element inside an ElementHandle, that child is also automatically humanized.

Note

I've verified everything with the visual test suite. Since I haven't worked extensively with Playwright's ElementHandle internals before, please double-check the patching logic.

Having real good time doing this. Learning smth new

@Cloak-HQ
Copy link
Copy Markdown
Contributor

Thanks for this, great addition! One question on the el.focus() behavior.

The humanized el.focus() does _move_to_element() + human_click(), while stock Playwright el.focus() is programmatic (no click). We ran a side-by-side test:

Scenario Stock Playwright Humanized (PR)
focus() on <input> ['input-focused'] ['input-focused', 'input-clicked']
focus() on <button type=submit> focus only, no submit form submitted
focus() on button with onclick focus only, panel stays closed panel toggled
focus() on <a href> ['link-focused'] JS context lost (navigation attempt)

Was the click intentional (human-like "focus by clicking")? Or should it move the cursor then call _orig_focus() to keep the programmatic semantics? Something like:

def _human_el_focus() -> None:
    _move_to_element()  # human-like cursor movement
    _orig_focus()       # programmatic focus, no click side-effects

@evelaa123
Copy link
Copy Markdown
Contributor Author

My bad, the click in focus() wasn't intentional. Fixed — now it does moveToElement() + origFocus() exactly as you suggested. Updated in Python sync/async and JS.

@Cloak-HQ Cloak-HQ merged commit 2be8cdc into CloakHQ:main Apr 10, 2026
2 checks passed
@Cloak-HQ
Copy link
Copy Markdown
Contributor

Merged, thanks! All tests pass including integration after rebase.

One note for a follow-up: moveToElement() in both JS and Python calls boundingBox() without scrolling the element into view first. If the element is below the fold, boundingBox() returns null and it falls back to the raw unpatched method with no humanization.

The page-level click path handles this correctly via scrollToElement(). The ElementHandle path should do something similar. Same gap exists in the Puppeteer ElementHandle from #129. Would be great to fix both together.

@evelaa123
Copy link
Copy Markdown
Contributor Author

No problem, will be fixid soon. Thanks for details!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants