feat(humanize): add Playwright-style actionability checks (#228)

* feat(humanize): add Playwright-style actionability checks to all interaction methods

Humanized locator/page methods now perform pre-action validation matching
Playwright's native behavior: attached, visible, enabled, editable, stable,
and receives-pointer-events checks with retry loop and backoff.

- New error hierarchy: ActionabilityError base with ElementNotAttachedError,
  ElementNotVisibleError, ElementNotStableError, ElementNotEnabledError,
  ElementNotEditableError, ElementNotReceivingEventsError
- force=True parameter skips all actionability checks (matches Playwright)
- Shared deadline across all steps (checks + scroll + stable + pointer)
- Post-scroll stability check only runs when scroll actually happened
- Chained methods (type/fill/check/uncheck/press) skip inner click checks
  but still run pointer-events check at actual click coordinates
- Frame methods now forward kwargs (force, timeout, human_config)
- Locator patches forward force via _forward_kwargs
- Python sync + async, JS/TS implementation

* fix(humanize): forward human_config in all chained methods, use evaluate args in handle pointer checks

- Add human_config=kwargs.get("human_config") to check/uncheck/select_option/press inner calls (sync+async+JS)
- Convert check_pointer_events_handle from f-string interpolation to evaluate args pattern (sync+async+JS)

* fix(humanize): strip custom kwargs before forwarding to Playwright select_option

originals.select_option(**kwargs) passes human_config/force to Playwright
which rejects unknown kwargs with TypeError.
This commit is contained in:
Cloak-HQ
2026-05-15 20:57:17 +02:00
committed by GitHub
parent 6f4f92e7c7
commit b0ea580cba
15 changed files with 1704 additions and 181 deletions
+5 -1
View File
@@ -1010,7 +1010,11 @@ class TestPatchPageStealthWiring:
fake_box = {"x": 100, "y": 200, "width": 200, "height": 30}
with mock_patch(
"cloakbrowser.human.scroll_to_element",
return_value=(fake_box, 200.0, 215.0),
return_value=(fake_box, 200.0, 215.0, False),
), mock_patch(
"cloakbrowser.human.ensure_actionable",
), mock_patch(
"cloakbrowser.human.check_pointer_events",
):
try:
page.click("#btn")