Fix humanize=True false-negative visible check for display:contents elements - #514
Fix humanize=True false-negative visible check for display:contents elements#514thesob wants to merge 1 commit into
Conversation
…lements The isolated-world actionability read (used by ensure_actionable/scroll_to_element since 0.5.6) computed visibility and geometry from an element's own getComputedStyle()/getBoundingClientRect() only. An element with display: contents (e.g. a reset li in a custom dropdown/menu widget) always has an empty box for itself even though its children/text render normally, causing click/fill/etc. to fail with ElementNotVisibleError on a genuinely visible element for the full timeout. Mirrors Playwright's own actionability engine (packages/injected/src/domUtils.ts::computeBox), which recurses into children/text nodes for display:contents elements instead of trusting the element's own empty rect. - Add a shared __visBox helper to the isolated-world resolver JS (Python/JS/.NET) that recurses into children/text nodes for display:contents, used by both the box and actionable reads. - Cross-check a not-visible in-world verdict against Playwright's own is_visible() before failing whenever a non-empty box is present, as defense-in-depth against other such divergences. - Add regression tests across Python (real browser), JS (Node-executed resolver JS), and .NET (Node-executed resolver JS). Regression from 0.5.6.
|
Thanks for the report, detailed investigation, and reproduction. We confirmed the We intentionally excluded the proposed Playwright visibility fallback to preserve consistent selector handling. The replacement is available on the If you’re able to test your original reproduction against that branch, we’d appreciate confirmation that it resolves the issue. Since the underlying fix is now implemented separately, we won’t merge this PR as-is. Thanks again—your investigation directly helped identify the missing behavior. |
Bug
Reported:
humanize=Truepage.click()(and other actions) fail withElementNotVisibleErroron an element that is demonstrably visible and rendered on screen — specifically reproduced on a Bootstrap-style toggled dropdown-menu<li>option. Regression from 0.5.6, when the isolated-world (CDP) actionability re-implementation replaced the previous real-Playwrightis_visible()/bounding_box()calls.Root cause
The isolated-world visibility/geometry reads (
_ACTIONABLE_OP/_BOX_OPinstealth_dom.py, mirrored in the JS and .NET ports) computed visibility and geometry from an element's owngetComputedStyle()+getBoundingClientRect()only.An element with
display: contents(e.g. a reset<li>used by some custom dropdown/menu widgets to strip default list box styling while keeping DOM semantics) is never itself boxed —getBoundingClientRect()on it is always{0,0,0,0}— even though its children/text render normally. This exactly matches Playwright's own actionability engine (packages/injected/src/domUtils.ts::computeBox), which special-casesdisplay: contentsby recursing into children/text nodes instead of trusting the element's own empty rect. CloakBrowser's isolated-world reimplementation never added this special case, so it reportedvisible: false/ no box indefinitely for such elements — a real, static CSS property, not a transient race — explaining why the retry loop never recovered.Fix
__visBox()helper to the isolated-world resolver JS (identical across Python, JS, and .NET) that recurses into child elements/text nodes fordisplay: contents, unioning their rects. Used by both the box read and the actionable (visible/enabled/editable) read._ACTIONABLE_OPnow also returns the box alongsidevisible/enabled/editable.ensure_actionable/stealthActionable/EnsureActionableAsynccross-check avisible: falsein-world verdict against Playwright's ownis_visible()before failing, whenever a non-empty box is present — matching the "only trustnot visiblewhen geometry is also absent/zero" pattern.Tests
TestDisplayContentsActionabilityintests/test_humanize_unit.py) reproducing a toggleddisplay:contentsdropdown; confirmed it fails with the pre-fix code (via git stash) and passes with the fix.display:contentsbehavioral tests injs/tests/stealthDom.test.tsexecuting the actual shipped resolver JS against a DOM stub, plus cross-check tests forensureActionable.dotnet/tests/CloakBrowser.Tests/Human/StealthDomTests.csmirroring the JS test.All existing unit tests pass unmodified (verified for Python and JS; .NET mirrored byte-for-byte from the tested JS/Python logic — could not compile locally, no dotnet SDK available in this environment).
Changelog
Added an
[Unreleased]entry inCHANGELOG.md.