Skip to content

Commit

Permalink
fix: visibility issue with parent absolute (#29689)
Browse files Browse the repository at this point in the history
  • Loading branch information
senpl authored Dec 2, 2024
1 parent c3660d4 commit d8d493a
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 23 deletions.
2 changes: 1 addition & 1 deletion cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -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_
Expand Down
52 changes: 51 additions & 1 deletion packages/driver/cypress/e2e/dom/visibility.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('<div style=`height: 1000px;`></div><div>Should be in view</div>')
const scrollThisIntoView = add('<div style="height: 1000px;"></div><div>Should be in view</div>')

this.$visHidden = add('<ul style="visibility: hidden;"></ul>')
this.$parentVisHidden = add('<div class="invis" style="visibility: hidden;"><button>parent visibility: hidden</button></div>')
Expand Down Expand Up @@ -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(`
<div id="breaking-container" style="overflow: hidden">
<div>
<div style="position: absolute; bottom: 5px">
<button id="visible-button">Try me</button>
</div>
</div>
</div>
`)

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(`
<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>
`)

expect(el.find('#visible-button')).to.be.visible
})
})

describe('css clip-path', () => {
Expand Down
48 changes: 44 additions & 4 deletions packages/driver/cypress/e2e/dom/visibility_shadow_dom.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ export {} // make typescript see this as a module
const { $ } = Cypress

describe('src/cypress/dom/visibility - shadow dom', () => {
let add
let add: (el: string, shadowEl: string, rootIdentifier: string) => JQuery<HTMLElement>

beforeEach(() => {
cy.visit('/fixtures/empty.html').then((win) => {
win.customElements.define('shadow-root', class extends win.HTMLElement {
constructor () {
super()

// @ts-ignore
this.attachShadow({ mode: 'open' })
this.style.display = 'block'
}
Expand All @@ -20,8 +19,7 @@ describe('src/cypress/dom/visibility - shadow dom', () => {
add = (el, shadowEl, rootIdentifier) => {
const $el = $(el).appendTo(cy.$$('body'))

// @ts-ignore
$(shadowEl).appendTo(cy.$$(rootIdentifier)[0].shadowRoot)
$(shadowEl).appendTo(cy.$$(rootIdentifier)[0].shadowRoot!)

return $el
}
Expand Down Expand Up @@ -574,6 +572,48 @@ describe('src/cypress/dom/visibility - shadow dom', () => {
cy.wrap($outsideParentOutOfBoundsButElInBounds).find('span', { includeShadowDom: true }).should('be.visible')
cy.wrap($outsideParentOutOfBoundsButElInBounds).find('span', { includeShadowDom: true }).should('not.be.hidden')
})

it('is visible when element is statically positioned and parent element is absolutely positioned and ancestor has overflow hidden', function () {
const el = add(
`<div id="breaking-container" style="overflow: hidden">
<div>
<shadow-root id="shadow"></shadow-root>
</div>
</div>`,
`<div style="position: absolute; bottom: 5px">
<button id="visible-button">Try me</button>
</div>`,
'#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(
`<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>
<shadow-root id="shadow"></shadow-root>
</div>
</div>
</div>
</div>`,
`<div style="position: absolute; bottom: 5px">
<button id="visible-button">Try me</button>
</div>`,
'#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', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/driver/src/dom/coordinates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
8 changes: 4 additions & 4 deletions packages/driver/src/dom/elements/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLElement>, $maybeAncestor: JQuery<HTMLElement>) => {
return $jquery.wrap(getAllParents($el[0])).index($maybeAncestor) >= 0
}

export const isChild = ($el, $maybeChild) => {
export const isChild = ($el: JQuery<HTMLElement>, $maybeChild: JQuery<HTMLElement>) => {
let children = $el.children()

if (children.length && children[0].nodeName === 'SHADOW-ROOT') {
Expand All @@ -185,7 +185,7 @@ export const isChild = ($el, $maybeChild) => {
return children.index($maybeChild) >= 0
}

export const isDescendent = ($el1, $el2) => {
export const isDescendent = ($el1: JQuery<HTMLElement>, $el2?: JQuery<HTMLElement>) => {
if (!$el2) {
return false
}
Expand Down Expand Up @@ -328,7 +328,7 @@ export const getContainsSelector = (text, filter = '', options: {
return selectors.join()
}

export const getInputFromLabel = ($el) => {
export const getInputFromLabel = ($el: JQuery<HTMLElement>) => {
if (!$el.is('label')) {
return $([])
}
Expand Down
35 changes: 23 additions & 12 deletions packages/driver/src/dom/visibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,15 @@ const elHasOverflowHidden = function ($el) {
return cssOverflow.includes('hidden')
}

const elHasPositionRelative = ($el) => {
const elHasPositionRelative = ($el: JQuery<HTMLElement>) => {
return $el.css('position') === 'relative'
}

const elHasPositionAbsolute = ($el) => {
const elHasPositionStatic = ($el: JQuery<HTMLElement>) => {
return $el.css('position') == null || $el.css('position') === 'static'
}

const elHasPositionAbsolute = ($el: JQuery<HTMLElement>) => {
return $el.css('position') === 'absolute'
}

Expand All @@ -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<HTMLElement>, $ancestor: JQuery<HTMLElement>) {
// can't clip without overflow properties
if (!elHasClippableOverflow($ancestor)) {
return false
}

// fix for 29605 - display: contents
if (elHasDisplayContents($ancestor)) {
return false
}
Expand All @@ -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
}

Expand All @@ -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<HTMLElement>) {
const doc = $document.getDocumentFromElement($el.get(0))
const elProps = $coordinates.getElementPositioning($el)

Expand All @@ -278,6 +290,8 @@ const elAtCenterPoint = function ($el) {
if (el) {
return $jquery.wrap(el)
}

return undefined
}

const elDescendentsHavePositionFixedOrAbsolute = function ($parent, $child) {
Expand All @@ -298,7 +312,7 @@ const elHasVisibleChild = function ($el) {
})
}

const elIsNotElementFromPoint = function ($el) {
const elIsNotElementFromPoint = function ($el: JQuery<HTMLElement>) {
// 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
Expand Down Expand Up @@ -333,7 +347,6 @@ const elIsOutOfBoundsOfAncestorsOverflow = function ($el: JQuery<any>, $ancestor
return false
}

// fix for 29605 - display: contents
if (elHasDisplayContents($el)) {
return false
}
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit d8d493a

Please sign in to comment.