Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: visibility issue with parent absolute #29689

Merged
merged 43 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d2ffbec
fix 28638
senpl Jun 17, 2024
46e07c0
fix changelog
senpl Jun 17, 2024
72b37f6
changelog update as required by pipeline
senpl Jun 17, 2024
1366587
pipeline changelog fix2
senpl Jun 17, 2024
a7d3b83
Merge branch 'develop' into issue-28638-parentAbsolute
jennifer-shehane Jun 21, 2024
9494a26
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jun 27, 2024
43e3b62
changelog for pipeline
senpl Jun 27, 2024
d206d96
test fixes
senpl Jun 27, 2024
a5e7b11
fix only for firefox. coz in firefox position relative not detected a…
senpl Jun 27, 2024
e4eaa52
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jun 28, 2024
c5548ee
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jul 1, 2024
b7aba89
fix to go back to previous extra spaces
senpl Jul 2, 2024
d8a11d1
Merge branch 'issue-28638-parentAbsolute' of https://github.com/senpl…
senpl Jul 2, 2024
53aeba9
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jul 2, 2024
9749a4d
fix pipeline
senpl Jul 2, 2024
95db079
changelog new
senpl Jul 2, 2024
d1c658f
changelog cleanup
senpl Jul 2, 2024
ca8da1b
pipeline tests fix
senpl Jul 2, 2024
bb57298
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jul 3, 2024
2d1c953
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jul 8, 2024
6af9ba6
Merge branch 'develop' into issue-28638-parentAbsolute
jennifer-shehane Jul 8, 2024
c34636c
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jul 11, 2024
90d4f6a
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jul 12, 2024
de42b7d
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jul 25, 2024
07f5b2c
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Jul 26, 2024
397b5c1
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Aug 7, 2024
595dc90
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Aug 8, 2024
51e9113
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Aug 12, 2024
4b82d6f
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Aug 16, 2024
b1ce61f
Merge branch 'develop' into issue-28638-parentAbsolute
senpl Sep 30, 2024
5f8714a
Merge branch 'release/14.0.0' into issue-28638-parentAbsolute
senpl Oct 28, 2024
2fbc79f
Merge branch 'release/14.0.0' into issue-28638-parentAbsolute
jennifer-shehane Oct 29, 2024
e242703
Merge branch 'release/14.0.0' into issue-28638-parentAbsolute
senpl Oct 30, 2024
9d31e48
changelog fix
senpl Oct 30, 2024
50ab0df
Merge branch 'release/14.0.0' into issue-28638-parentAbsolute
jennifer-shehane Nov 6, 2024
2a0bf4a
updates
mschile Nov 26, 2024
32f8b5d
removing files
mschile Nov 26, 2024
97950dd
Merge branch 'release/14.0.0' into issue-28638-parentAbsolute
mschile Nov 26, 2024
c78d180
update
mschile Nov 26, 2024
be2129a
Update cli/CHANGELOG.md
mschile Dec 2, 2024
8030f25
Merge branch 'release/14.0.0' into issue-28638-parentAbsolute
mschile Dec 2, 2024
056c410
Merge branch 'release/14.0.0' into issue-28638-parentAbsolute
mschile Dec 2, 2024
926a5de
Update CHANGELOG.md
mschile Dec 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ _Released 6/18/2024_
- Fixed an issue where `inlineSourceMaps` was still being used when `sourceMaps` was provided in a users typescript config for typescript version 5. Fixes [#26203](https://github.com/cypress-io/cypress/issues/26203).
- When capture protocol script fails verification, an appropriate error is now displayed. Previously, an error regarding Test Replay archive location was shown. Addressed in [#29603](https://github.com/cypress-io/cypress/pull/29603).
- Fixed an issue where receiving HTTP responses with invalid headers raised an error. Now cypress removes the invalid headers and gives a warning in the console with debug mode on. Fixes [#28865](https://github.com/cypress-io/cypress/issues/28865).
- Fixed an issue where "isVisible" is incorrectly assessed for the absolutely positioned elements if the ancestor has overflow and static position [#29689](https://github.com/cypress-io/cypress/pull/29689).
mschile marked this conversation as resolved.
Show resolved Hide resolved

**Misc:**

Expand Down
12 changes: 12 additions & 0 deletions packages/driver/cypress/e2e/issues/28638.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// https://github.com/cypress-io/cypress/issues/28638
describe('issue 28638', () => {
before(() => {
cy
.viewport(400, 400)
.visit('/fixtures/issue-28638.html')
})

it('can click with parent position absolute', () => {
cy.get('#visible-button').click()
})
})
19 changes: 19 additions & 0 deletions packages/driver/cypress/fixtures/issue-28638.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<body>
<div style="height: 200px; position: relative; display: flex">
<div style="border: 5px solid red">
<div
id="breaking-container"
style="overflow: auto; border: 5px solid green"
>
<div>
<h1>Example</h1>
<div style="position: absolute; bottom: 5px">
<button id="visible-button" style="position: relative">
Try me
</button>
</div>
</div>
</div>
</div>
</div>
</body>
99 changes: 63 additions & 36 deletions packages/driver/src/dom/visibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const isStrictlyHidden = (el, methodName = 'isStrictlyHidden()', options = { che
}

// in Cypress-land we consider the element hidden if
// either its offsetHeight or offsetWidth is 0 because
// either its clientHeight or clientWidth is 0 because
// it is impossible for the user to interact with this element
if (elHasNoEffectiveWidthOrHeight($el)) {
// https://github.com/cypress-io/cypress/issues/6183
Expand Down Expand Up @@ -121,32 +121,38 @@ const elHasNoEffectiveWidthOrHeight = ($el) => {
// Is the element's CSS width OR height, including any borders,
// padding, and vertical scrollbars (if rendered) less than 0?
//
// elOffsetWidth:
// elClientWidth:
// If the element is hidden (for example, by setting style.display
// on the element or one of its ancestors to "none"), then 0 is returned.

// $el[0].getClientRects().length:
// For HTML <area> elements, SVG elements that do not render anything themselves,
// display:none elements, and generally any elements that are not directly rendered,
// an empty list is returned.

const el = $el[0]
const style = getComputedStyle(el)
const transform = style.getPropertyValue('transform')
const width = elOffsetWidth($el)
const height = elOffsetHeight($el)
const overflowHidden = elHasOverflowHidden($el)
let transform

if ($el[0].style.transform) {
const style = getComputedStyle(el)

transform = style.getPropertyValue('transform')
} else {
transform = 'none'
}

const width = elClientWidth($el)
const height = elClientHeight($el)

return isZeroLengthAndTransformNone(width, height, transform) ||
isZeroLengthAndOverflowHidden(width, height, overflowHidden) ||
isZeroLengthAndOverflowHidden(width, height, elHasOverflowHidden($el)) ||
(el.getClientRects().length <= 0)
}

const isZeroLengthAndTransformNone = (width, height, transform) => {
// From https://github.com/cypress-io/cypress/issues/5974,
// we learned that when an element has non-'none' transform style value like "translate(0, 0)",
// it is visible even with `height: 0` or `width: 0`.
// That's why we're checking `transform === 'none'` together with elOffsetWidth/Height.
// That's why we're checking `transform === 'none'` together with elClientWidth/Height.

return (width <= 0 && transform === 'none') ||
(height <= 0 && transform === 'none')
Expand All @@ -157,22 +163,28 @@ const isZeroLengthAndOverflowHidden = (width, height, overflowHidden) => {
(height <= 0 && overflowHidden)
}

const elHasNoOffsetWidthOrHeight = ($el) => {
return (elOffsetWidth($el) <= 0) || (elOffsetHeight($el) <= 0)
const elHasNoClientWidthOrHeight = ($el) => {
return (elClientWidth($el) <= 0) || (elClientHeight($el) <= 0)
}

const elOffsetWidth = ($el) => {
return $el[0].offsetWidth
const elementBoundingRect = ($el) => $el[0].getBoundingClientRect()

const elClientHeight = ($el) => {
return elementBoundingRect($el).height
}

const elOffsetHeight = ($el) => {
return $el[0].offsetHeight
const elClientWidth = ($el) => {
return elementBoundingRect($el).width
}

const elHasVisibilityHiddenOrCollapse = ($el) => {
return elHasVisibilityHidden($el) || elHasVisibilityCollapse($el)
}

const elHasVisibilityVisible = ($el) => {
return $el.css('visibility') === 'visible'
}

const elHasVisibilityHidden = ($el) => {
return $el.css('visibility') === 'hidden'
}
Expand Down Expand Up @@ -221,11 +233,17 @@ const canClipContent = function ($el, $ancestor) {

// the closest parent with position relative, absolute, or fixed
const $offsetParent = $el.offsetParent()
const isClosestAncsestor = isAncestor($ancestor, $offsetParent)

// fix for 28638 - when element postion is relative and it's parent absolute
if (elHasPositionRelative($el) && isClosestAncsestor && elHasPositionAbsolute($ancestor)) {
return false
}

// even if ancestors' overflow is clippable, if the element's offset parent
// is a parent of the ancestor, the ancestor will not clip the element
// unless the element is position relative
if (!elHasPositionRelative($el) && isAncestor($ancestor, $offsetParent)) {
if (!elHasPositionRelative($el) && isClosestAncsestor) {
return false
}

Expand Down Expand Up @@ -308,35 +326,40 @@ const elIsNotElementFromPoint = function ($el) {
return true
}

const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor = getParent($el)) {
const elIsOutOfBoundsOfAncestorsOverflow = function ($el: JQuery<any>, $ancestor = getParent($el)) {
// no ancestor, not out of bounds!
// if we've reached the top parent, which is not a normal DOM el
// then we're in bounds all the way up, return false
if (isUndefinedOrHTMLBodyDoc($ancestor)) {
return false
}

if (elHasPositionRelative($el) && elHasPositionAbsolute($ancestor)) {
return false
}

if (canClipContent($el, $ancestor)) {
const elProps = $coordinates.getElementPositioning($el)
const ancestorProps = $coordinates.getElementPositioning($ancestor)
const ancestorProps = $ancestor.get(0).getBoundingClientRect()

if (elHasPositionAbsolute($el) && (ancestorProps.width === 0 || ancestorProps.height === 0)) {
return elIsOutOfBoundsOfAncestorsOverflow($el, getParent($ancestor))
}

const elProps = $el.get(0).getBoundingClientRect()

// target el is out of bounds
if (
// target el is to the right of the ancestor's visible area
(elProps.fromElWindow.left >= (ancestorProps.width + ancestorProps.fromElWindow.left)) ||
(elProps.left >= (ancestorProps.width + ancestorProps.left)) ||

// target el is to the left of the ancestor's visible area
((elProps.fromElWindow.left + elProps.width) <= ancestorProps.fromElWindow.left) ||
((elProps.left + elProps.width) <= ancestorProps.left) ||

// target el is under the ancestor's visible area
(elProps.fromElWindow.top >= (ancestorProps.height + ancestorProps.fromElWindow.top)) ||
(elProps.top >= (ancestorProps.height + ancestorProps.top)) ||

// target el is above the ancestor's visible area
((elProps.fromElWindow.top + elProps.height) <= ancestorProps.fromElWindow.top)
((elProps.top + elProps.height) <= ancestorProps.top)
) {
return true
}
Expand All @@ -348,7 +371,7 @@ const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor = getParent(
const elIsHiddenByAncestors = function ($el, checkOpacity, $origEl = $el) {
// walk up to each parent until we reach the body
// if any parent has opacity: 0
// or has an effective offsetHeight of 0
// or has an effective clientHeight of 0
// and its set overflow: hidden then our child element
// is effectively hidden
// -----UNLESS------
Expand Down Expand Up @@ -376,11 +399,15 @@ const elIsHiddenByAncestors = function ($el, checkOpacity, $origEl = $el) {
return !elDescendentsHavePositionFixedOrAbsolute($parent, $origEl)
}

if (elHasVisibilityVisible($parent)) {
return false
}

// continue to recursively walk up the chain until we reach body or html
return elIsHiddenByAncestors($parent, checkOpacity, $origEl)
}

const parentHasNoOffsetWidthOrHeightAndOverflowHidden = function ($el) {
const parentHasNoClientWidthOrHeightAndOverflowHidden = function ($el) {
// if we've walked all the way up to body or html then return false
if (isUndefinedOrHTMLBodyDoc($el)) {
return false
Expand All @@ -392,7 +419,7 @@ const parentHasNoOffsetWidthOrHeightAndOverflowHidden = function ($el) {
}

// continue walking
return parentHasNoOffsetWidthOrHeightAndOverflowHidden(getParent($el))
return parentHasNoClientWidthOrHeightAndOverflowHidden(getParent($el))
}

const parentHasDisplayNone = function ($el) {
Expand Down Expand Up @@ -463,8 +490,8 @@ export const getReasonIsHidden = function ($el, options = { checkOpacity: true }
// either being covered or there is no el

const node = stringifyElement($el, 'short')
let width = elOffsetWidth($el)
let height = elOffsetHeight($el)
let width = elClientWidth($el)
let height = elClientHeight($el)
let $parent
let parentNode

Expand Down Expand Up @@ -513,24 +540,24 @@ export const getReasonIsHidden = function ($el, options = { checkOpacity: true }
return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`opacity: 0\``
}

if (elHasNoOffsetWidthOrHeight($el)) {
return `This element \`${node}\` is not visible because it has an effective width and height of: \`${width} x ${height}\` pixels.`
}

const transformResult = $transform.detectVisibility($el)

if (transformResult === 'transformed') {
return `This element \`${node}\` is not visible because it is hidden by transform.`
}

if (elHasNoClientWidthOrHeight($el)) {
return `This element \`${node}\` is not visible because it has an effective width and height of: \`${width} x ${height}\` pixels.`
}

if (transformResult === 'backface') {
return `This element \`${node}\` is not visible because it is rotated and its backface is hidden.`
}

if ($parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden(getParent($el))) {
if ($parent = parentHasNoClientWidthOrHeightAndOverflowHidden(getParent($el))) {
parentNode = stringifyElement($parent, 'short')
width = elOffsetWidth($parent)
height = elOffsetHeight($parent)
width = elClientWidth($parent)
height = elClientHeight($parent)

return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`overflow: hidden\` and an effective width and height of: \`${width} x ${height}\` pixels.`
}
Expand Down
Loading