mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
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:
@@ -44,9 +44,14 @@ function buildMockPage(overrides: Record<string, any> = {}): any {
|
||||
|
||||
const makeLocator = () => {
|
||||
const loc: any = {
|
||||
boundingBox: vi.fn(async () => ({ x: 100, y: 100, width: 200, height: 30 })),
|
||||
boundingBox: vi.fn(async () => ({ x: 100, y: 300, width: 200, height: 30 })),
|
||||
scrollIntoViewIfNeeded: vi.fn(async () => {}),
|
||||
isChecked: overrides.isChecked ?? vi.fn(async () => false),
|
||||
waitFor: vi.fn(async () => {}),
|
||||
isVisible: vi.fn(async () => true),
|
||||
isEnabled: vi.fn(async () => true),
|
||||
isEditable: vi.fn(async () => true),
|
||||
evaluate: vi.fn(async () => ({ hit: true })),
|
||||
};
|
||||
loc.first = vi.fn(() => loc);
|
||||
return loc;
|
||||
@@ -687,6 +692,9 @@ describe("isInputElement stealth integration via patchPage", () => {
|
||||
}
|
||||
if (method === "Runtime.evaluate") {
|
||||
stealthEvaluateCalls.push(params.expression);
|
||||
if (params.expression.includes("elementFromPoint")) {
|
||||
return { result: { value: { hit: true } } };
|
||||
}
|
||||
return { result: { value: false } }; // not an input
|
||||
}
|
||||
return {};
|
||||
@@ -696,7 +704,7 @@ describe("isInputElement stealth integration via patchPage", () => {
|
||||
const page = buildMockPage({
|
||||
evaluate: vi.fn(async (...args: any[]) => {
|
||||
evaluateCalls.push(args);
|
||||
return false;
|
||||
return { hit: true };
|
||||
}),
|
||||
});
|
||||
page.context = vi.fn(() => ({
|
||||
|
||||
Reference in New Issue
Block a user