diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index 2a3fbee031f6..b0921401fe31 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -46,6 +46,7 @@ in this [GitHub issue](https://github.com/cypress-io/cypress/issues/30447). Addr
- Elements with `display: contents` will no longer use box model calculations for visibility, and correctly show as visible when it is visible. Fixed in [#29680](https://github.com/cypress-io/cypress/pull/29680). Fixes [#29605](https://github.com/cypress-io/cypress/issues/29605).
- The CSS pseudo-class `:dir()` is now supported when testing in Electron. Addresses [#29766](https://github.com/cypress-io/cypress/issues/29766).
+- Fixed a visibility issue when the element is positioned `static` or `relative` and the element's offset parent is positioned `absolute`, a descendent of the ancestor, and has no clippable overflow. Fixed in [#29689](https://github.com/cypress-io/cypress/pull/29689). Fixes [#28638](https://github.com/cypress-io/cypress/issues/28638).
- Fixed a visibility issue for elements with `textContent` but without a width or height. Fixed in [#29688](https://github.com/cypress-io/cypress/pull/29688). Fixes [#29687](https://github.com/cypress-io/cypress/issues/29687).
- Elements whose parent elements has `overflow: clip` and no height/width will now correctly show as hidden. Fixed in [#29778](https://github.com/cypress-io/cypress/pull/29778). Fixes [#23852](https://github.com/cypress-io/cypress/issues/23852).
@@ -96,7 +97,6 @@ _Released 11/5/2024_
- Updated `mobx` from `5.15.4` to `6.13.5` and `mobx-react` from `6.1.8` to `9.1.1`. Addresses [#30509](https://github.com/cypress-io/cypress/issues/30509).
- Updated `@cypress/request` from `3.0.4` to `3.0.6`. Addressed in [#30488](https://github.com/cypress-io/cypress/pull/30488).
-
## 13.15.1
_Released 10/24/2024_
diff --git a/packages/driver/cypress/e2e/dom/visibility.cy.ts b/packages/driver/cypress/e2e/dom/visibility.cy.ts
index 78ae396ac175..b27c17ddd294 100644
--- a/packages/driver/cypress/e2e/dom/visibility.cy.ts
+++ b/packages/driver/cypress/e2e/dom/visibility.cy.ts
@@ -176,7 +176,7 @@ describe('src/cypress/dom/visibility', () => {
context('hidden/visible overrides', () => {
beforeEach(function () {
// ensure all tests run against a scrollable window
- const scrollThisIntoView = add('
Should be in view
')
+ const scrollThisIntoView = add('
Should be in view
')
this.$visHidden = add('
')
this.$parentVisHidden = add('')
@@ -997,6 +997,56 @@ describe('src/cypress/dom/visibility', () => {
it('is visible when parent is relatively positioned out of bounds but el is relatively positioned back in bounds', function () {
expect(this.$parentOutOfBoundsButElInBounds.find('span')).to.be.visible
})
+
+ it('is visible when element is statically positioned and parent element is absolutely positioned and ancestor has overflow hidden', function () {
+ const add = (el) => {
+ return $(el).appendTo(cy.$$('body'))
+ }
+
+ cy.$$('body').empty()
+
+ const el = add(`
+
+
+
+
+
+
+
+ `)
+
+ expect(el.find('#visible-button')).to.be.visible
+ })
+
+ it('is visible when element is relatively positioned and parent element is absolutely positioned and ancestor has overflow auto', function () {
+ const add = (el) => {
+ return $(el).appendTo(cy.$$('body'))
+ }
+
+ cy.$$('body').empty()
+
+ const el = add(`
+
`,
+ '#shadow',
+ )
+
+ cy.wrap(el).find('#visible-button', { includeShadowDom: true }).should('be.visible')
+ cy.wrap(el).find('#visible-button', { includeShadowDom: true }).should('not.be.hidden')
+ })
+
+ it('is visible when element is relatively positioned and parent element is absolutely positioned and ancestor has overflow auto', function () {
+ const el = add(
+ `
+
+
+
+
Example
+
+
+
+
+
`,
+ `
+
+
`,
+ '#shadow',
+ )
+
+ cy.wrap(el).find('#visible-button', { includeShadowDom: true }).should('be.visible')
+ cy.wrap(el).find('#visible-button', { includeShadowDom: true }).should('not.be.hidden')
+ })
})
describe('css transform', () => {
diff --git a/packages/driver/src/dom/coordinates.ts b/packages/driver/src/dom/coordinates.ts
index 976f6665e9ef..670e8da325d7 100644
--- a/packages/driver/src/dom/coordinates.ts
+++ b/packages/driver/src/dom/coordinates.ts
@@ -3,7 +3,7 @@ import $window from './window'
import $elements from './elements'
import $jquery from './jquery'
-const getElementAtPointFromViewport = (doc, x, y) => {
+const getElementAtPointFromViewport = (doc: Document, x: number, y: number) => {
return $elements.elementFromPoint(doc, x, y)
}
diff --git a/packages/driver/src/dom/elements/find.ts b/packages/driver/src/dom/elements/find.ts
index 16f95fa2017a..caceed6d7295 100644
--- a/packages/driver/src/dom/elements/find.ts
+++ b/packages/driver/src/dom/elements/find.ts
@@ -171,11 +171,11 @@ export const elementFromPoint = (doc, x, y): HTMLElement => {
* By DOM Hierarchy
* Compares two elements to see what their relationship is
*/
-export const isAncestor = ($el, $maybeAncestor) => {
+export const isAncestor = ($el: JQuery, $maybeAncestor: JQuery) => {
return $jquery.wrap(getAllParents($el[0])).index($maybeAncestor) >= 0
}
-export const isChild = ($el, $maybeChild) => {
+export const isChild = ($el: JQuery, $maybeChild: JQuery) => {
let children = $el.children()
if (children.length && children[0].nodeName === 'SHADOW-ROOT') {
@@ -185,7 +185,7 @@ export const isChild = ($el, $maybeChild) => {
return children.index($maybeChild) >= 0
}
-export const isDescendent = ($el1, $el2) => {
+export const isDescendent = ($el1: JQuery, $el2?: JQuery) => {
if (!$el2) {
return false
}
@@ -328,7 +328,7 @@ export const getContainsSelector = (text, filter = '', options: {
return selectors.join()
}
-export const getInputFromLabel = ($el) => {
+export const getInputFromLabel = ($el: JQuery) => {
if (!$el.is('label')) {
return $([])
}
diff --git a/packages/driver/src/dom/visibility.ts b/packages/driver/src/dom/visibility.ts
index a887981fa897..f9373dc6d75a 100644
--- a/packages/driver/src/dom/visibility.ts
+++ b/packages/driver/src/dom/visibility.ts
@@ -206,11 +206,15 @@ const elHasOverflowHidden = function ($el) {
return cssOverflow.includes('hidden')
}
-const elHasPositionRelative = ($el) => {
+const elHasPositionRelative = ($el: JQuery) => {
return $el.css('position') === 'relative'
}
-const elHasPositionAbsolute = ($el) => {
+const elHasPositionStatic = ($el: JQuery) => {
+ return $el.css('position') == null || $el.css('position') === 'static'
+}
+
+const elHasPositionAbsolute = ($el: JQuery) => {
return $el.css('position') === 'absolute'
}
@@ -220,13 +224,12 @@ const elHasClippableOverflow = function ($el) {
OVERFLOW_PROPS.includes($el.css('overflow-x'))
}
-const canClipContent = function ($el, $ancestor) {
+const canClipContent = function ($el: JQuery, $ancestor: JQuery) {
// can't clip without overflow properties
if (!elHasClippableOverflow($ancestor)) {
return false
}
- // fix for 29605 - display: contents
if (elHasDisplayContents($ancestor)) {
return false
}
@@ -248,11 +251,21 @@ const canClipContent = function ($el, $ancestor) {
// even if ancestors' overflow is clippable, if the element's offset parent
// is a child of the ancestor, the ancestor will not clip the element
- // unless the ancestor has position absolute
+ // unless the ancestor has a position that is not absolute
if (elHasPositionAbsolute($offsetParent) && isChild($ancestor, $offsetParent)) {
return false
}
+ // even if ancestors' overflow is clippable,
+ // if the element is position static or relative,
+ // and the element's offset parent is positioned absolute, a descendent of the ancestor, and has no clippable overflow,
+ // then the ancestor will not clip the element
+ if ((elHasPositionStatic($el) || elHasPositionRelative($el))
+ && elHasPositionAbsolute($offsetParent) && isDescendent($ancestor, $offsetParent) && !elHasClippableOverflow($offsetParent)
+ ) {
+ return false
+ }
+
return true
}
@@ -266,8 +279,7 @@ export const isW3CFocusable = (el) => {
return isFocusable(wrap(el)) && isW3CRendered(el)
}
-// @ts-ignore
-const elAtCenterPoint = function ($el) {
+const elAtCenterPoint = function ($el: JQuery) {
const doc = $document.getDocumentFromElement($el.get(0))
const elProps = $coordinates.getElementPositioning($el)
@@ -278,6 +290,8 @@ const elAtCenterPoint = function ($el) {
if (el) {
return $jquery.wrap(el)
}
+
+ return undefined
}
const elDescendentsHavePositionFixedOrAbsolute = function ($parent, $child) {
@@ -298,7 +312,7 @@ const elHasVisibleChild = function ($el) {
})
}
-const elIsNotElementFromPoint = function ($el) {
+const elIsNotElementFromPoint = function ($el: JQuery) {
// if we have a fixed position element that means
// it is fixed 'relative' to the viewport which means
// it MUST be available with elementFromPoint because
@@ -333,7 +347,6 @@ const elIsOutOfBoundsOfAncestorsOverflow = function ($el: JQuery, $ancestor
return false
}
- // fix for 29605 - display: contents
if (elHasDisplayContents($el)) {
return false
}
@@ -400,8 +413,7 @@ const elIsHiddenByAncestors = function ($el, checkOpacity, $origEl = $el) {
}
if (elHasOverflowHidden($parent) && !elHasDisplayContents($parent) && elHasNoEffectiveWidthOrHeight($parent)) {
- // if any of the elements between the parent and origEl
- // have fixed or position absolute
+ // if any of the elements between the parent and origEl have fixed or position absolute
return !elDescendentsHavePositionFixedOrAbsolute($parent, $origEl)
}
@@ -564,7 +576,6 @@ export const getReasonIsHidden = function ($el, options = { checkOpacity: true }
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.`
}
- // nested else --___________--
if (elOrAncestorIsFixedOrSticky($el)) {
if (elIsNotElementFromPoint($el)) {
// show the long element here