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: update visibility to support parent overflow:clip without height/width #29778

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b1ea598
fix for 23852
senpl Jul 1, 2024
c3e1f49
changelog update
senpl Jul 1, 2024
3367369
changelog update
senpl Jul 1, 2024
0c6f4e5
fix reason of hide for compatibility
senpl Jul 1, 2024
3ace4ba
add clip to overflow props
senpl Jul 1, 2024
1988b2c
hide reason update message update
senpl Jul 1, 2024
51ce60c
clip fix no longer that general coz of firefox
senpl Jul 3, 2024
66a21ec
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Jul 3, 2024
cb458f2
changelog update
senpl Jul 3, 2024
cfd928d
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
jennifer-shehane Jul 3, 2024
6d9a3ee
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Jul 8, 2024
354e709
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
jennifer-shehane Jul 9, 2024
228c24e
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Jul 12, 2024
a187a4f
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Jul 25, 2024
290603f
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Jul 26, 2024
1dee90a
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Aug 7, 2024
fb247d9
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Aug 8, 2024
8dd9881
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Aug 12, 2024
d0f4c81
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Aug 16, 2024
3a1c49d
Merge branch 'develop' into issue-23852-ChildVisibleWithOverflowClip
senpl Sep 30, 2024
a5ad68d
Merge branch 'release/14.0.0' into issue-23852-ChildVisibleWithOverfl…
senpl Oct 1, 2024
5d1923c
Merge branch 'release/14.0.0' into issue-23852-ChildVisibleWithOverfl…
senpl Oct 28, 2024
da150a3
Merge branch 'release/14.0.0' into issue-23852-ChildVisibleWithOverfl…
jennifer-shehane Oct 29, 2024
d3a779c
Merge branch 'release/14.0.0' into issue-23852-ChildVisibleWithOverfl…
senpl Oct 30, 2024
2e03ee1
pipeline changelog fixes
senpl Oct 30, 2024
e2258cb
Merge branch 'release/14.0.0' into issue-23852-ChildVisibleWithOverfl…
senpl Oct 30, 2024
669b4b7
Merge branch 'release/14.0.0' into issue-23852-ChildVisibleWithOverfl…
mschile Nov 19, 2024
53bd619
update OVERFLOW_PROPS
mschile Nov 19, 2024
0249bfc
update changelog
mschile Nov 19, 2024
8d919ba
Merge branch 'release/14.0.0' into issue-23852-ChildVisibleWithOverfl…
mschile Nov 26, 2024
e9272eb
fix firefox failure
mschile Nov 26, 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 @@ -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).
- 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).

**Dependency Updates:**

Expand Down
75 changes: 43 additions & 32 deletions packages/driver/cypress/e2e/dom/visibility.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
const { $, dom } = Cypress

describe('src/cypress/dom/visibility', () => {
const add = (el) => {
return $(el).appendTo(cy.$$('body'))
}

const reasonIs = ($el, str) => {
expect(dom.getReasonIsHidden($el)).to.eq(str)
}

beforeEach(() => {
cy.visit('/fixtures/generic.html')
})
Expand Down Expand Up @@ -163,10 +171,6 @@ describe('src/cypress/dom/visibility', () => {

context('hidden/visible overrides', () => {
beforeEach(function () {
const add = (el) => {
return $(el).appendTo(cy.$$('body'))
}

// ensure all tests run against a scrollable window
const scrollThisIntoView = add('<div style=`height: 1000px;`></div><div>Should be in view</div>')

Expand Down Expand Up @@ -908,6 +912,23 @@ describe('src/cypress/dom/visibility', () => {
expect(this.$elInParentBounds.find('span')).to.be.visible
})

it('is hidden when parent overflow clip and height is 0', function () {
cy.$$('body').empty()

const el = add('<div style="height: 0; overflow: clip;"><div id="hidden">I am not visible</div></div>')

expect(el.find('#hidden')).to.be.hidden
reasonIs(el.find('#hidden'), 'This element `<div#hidden>` is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: `hidden`, `clip`, `scroll` or `auto`')
})

it('is visible when parent overflow clip and height is non-0', function () {
cy.$$('body').empty()

const el = add('<div style="height: 5px; overflow: clip;"><div id="visible">I am visible</div></div>')

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

it('is visible when ancestor is overflow hidden but more distant ancestor is the offset parent', function () {
expect(this.$elIsOutOfBoundsOfAncestorsOverflowButWithinRelativeAncestor.find('span')).to.be.visible
})
Expand Down Expand Up @@ -937,10 +958,6 @@ describe('src/cypress/dom/visibility', () => {
})

describe('css transform', () => {
const add = (el) => {
return $(el).appendTo(cy.$$('body'))
}

describe('element visibility by css transform', () => {
it('is visible when an element is translated a bit', () => {
const el = add(`<div style="transform: translate(10px, 10px)">Translated</div>`)
Expand Down Expand Up @@ -1129,78 +1146,72 @@ describe('src/cypress/dom/visibility', () => {
})

describe('#getReasonIsHidden', () => {
beforeEach(function () {
this.reasonIs = ($el, str) => {
expect(dom.getReasonIsHidden($el)).to.eq(str)
}
})

it('has `display: none`', function () {
this.reasonIs(this.$displayNone, 'This element `<button>` is not visible because it has CSS property: `display: none`')
reasonIs(this.$displayNone, 'This element `<button>` is not visible because it has CSS property: `display: none`')
})

it('has a parent with `display: none`', function () {
this.reasonIs(this.$parentDisplayNone.find('span'), 'This element `<span>` is not visible because its parent `<div#none>` has CSS property: `display: none`')
reasonIs(this.$parentDisplayNone.find('span'), 'This element `<span>` is not visible because its parent `<div#none>` has CSS property: `display: none`')
})

it('has `visibility: hidden`', function () {
this.reasonIs(this.$visHidden, 'This element `<ul>` is not visible because it has CSS property: `visibility: hidden`')
reasonIs(this.$visHidden, 'This element `<ul>` is not visible because it has CSS property: `visibility: hidden`')
})

it('has parent with `visibility: hidden`', function () {
this.reasonIs(this.$parentVisHidden.find('button'), 'This element `<button>` is not visible because its parent `<div.invis>` has CSS property: `visibility: hidden`')
reasonIs(this.$parentVisHidden.find('button'), 'This element `<button>` is not visible because its parent `<div.invis>` has CSS property: `visibility: hidden`')
})

it('has `visibility: collapse`', function () {
this.reasonIs(this.$tableVisCollapse.find('td.collapse'), 'This element `<td.collapse>` is not visible because it has CSS property: `visibility: collapse`')
reasonIs(this.$tableVisCollapse.find('td.collapse'), 'This element `<td.collapse>` is not visible because it has CSS property: `visibility: collapse`')
})

it('has parent with `visibility: collapse`', function () {
this.reasonIs(this.$tableVisCollapse.find('tr.collapse td:first'), 'This element `<td>` is not visible because its parent `<tr.collapse>` has CSS property: `visibility: collapse`')
reasonIs(this.$tableVisCollapse.find('tr.collapse td:first'), 'This element `<td>` is not visible because its parent `<tr.collapse>` has CSS property: `visibility: collapse`')
})

it('has `opacity: 0`', function () {
this.reasonIs(this.$btnOpacityZero, 'This element `<button>` is not visible because it has CSS property: `opacity: 0`')
reasonIs(this.$btnOpacityZero, 'This element `<button>` is not visible because it has CSS property: `opacity: 0`')
})

it('has parent with `opacity: 0`', function () {
this.reasonIs(this.$parentOpacityZero.find('button'), 'This element `<button>` is not visible because its parent `<div>` has CSS property: `opacity: 0`')
reasonIs(this.$parentOpacityZero.find('button'), 'This element `<button>` is not visible because its parent `<div>` has CSS property: `opacity: 0`')
})

it('is detached from the DOM', function () {
this.reasonIs(this.$divDetached, 'This element `<div>` is not visible because it is detached from the DOM')
reasonIs(this.$divDetached, 'This element `<div>` is not visible because it is detached from the DOM')
})

it('has effective zero width', function () {
this.reasonIs(this.$divNoWidth, 'This element `<div>` is not visible because it has an effective width and height of: `0 x 100` pixels.')
reasonIs(this.$divNoWidth, 'This element `<div>` is not visible because it has an effective width and height of: `0 x 100` pixels.')
})

it('has effective zero height', function () {
this.reasonIs(this.$divNoHeight, 'This element `<div>` is not visible because it has an effective width and height of: `50 x 0` pixels.')
reasonIs(this.$divNoHeight, 'This element `<div>` is not visible because it has an effective width and height of: `50 x 0` pixels.')
})

it('has a parent with an effective zero width and overflow: hidden', function () {
this.reasonIs(this.$parentNoHeight.find('span'), 'This element `<span>` is not visible because its parent `<div>` has CSS property: `overflow: hidden` and an effective width and height of: `100 x 0` pixels.')
reasonIs(this.$parentNoHeight.find('span'), 'This element `<span>` is not visible because its parent `<div>` has CSS property: `overflow: hidden` and an effective width and height of: `100 x 0` pixels.')
})

it('element sits outside boundaries of parent with overflow clipping', function () {
this.reasonIs(this.$elOutOfParentBoundsToRight.find('span'), 'This element `<span>` is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: `hidden`, `scroll` or `auto`')
reasonIs(this.$elOutOfParentBoundsToRight.find('span'), 'This element `<span>` is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: `hidden`, `clip`, `scroll` or `auto`')
})

it('is hidden because it is backface', function () {
const el = cy.$$('body').append(`<div id="backface-invisible" style="backface-visibility:hidden; transform: rotateX(180deg)">Hello world</div>`)

this.reasonIs(el.find('#backface-invisible'), `This element \`<div#backface-invisible>\` is not visible because it is rotated and its backface is hidden.`)
reasonIs(el.find('#backface-invisible'), `This element \`<div#backface-invisible>\` is not visible because it is rotated and its backface is hidden.`)
})

it('is hidden by transform', function () {
const el = cy.$$('body').append(`<div id="invisible-transform" style="transform: scaleX(0)">Hello world</div>`)

this.reasonIs(el.find('#invisible-transform'), `This element \`<div#invisible-transform>\` is not visible because it is hidden by transform.`)
reasonIs(el.find('#invisible-transform'), `This element \`<div#invisible-transform>\` is not visible because it is hidden by transform.`)
})

it('element is fixed and being covered', function () {
this.reasonIs(this.$coveredUpPosFixed.find('#coveredUpPosFixed'), `\This element \`<div#coveredUpPosFixed>\` is not visible because it has CSS property: \`position: fixed\` and it's being covered by another element:\n\n\`<div style="position: fixed; bottom: 0; left: 0">on top</div>\``)
reasonIs(this.$coveredUpPosFixed.find('#coveredUpPosFixed'), `\This element \`<div#coveredUpPosFixed>\` is not visible because it has CSS property: \`position: fixed\` and it's being covered by another element:\n\n\`<div style="position: fixed; bottom: 0; left: 0">on top</div>\``)
})

it('needs scroll', function () {
Expand All @@ -1211,13 +1222,13 @@ describe('src/cypress/dom/visibility', () => {
</div>
`)

this.reasonIs(el.find('#needsScroll'), `This element \`<button#needsScroll>\` is not visible because its ancestor has \`position: fixed\` CSS property and it is overflowed by other elements. How about scrolling to the element with \`cy.scrollIntoView()\`?`)
reasonIs(el.find('#needsScroll'), `This element \`<button#needsScroll>\` is not visible because its ancestor has \`position: fixed\` CSS property and it is overflowed by other elements. How about scrolling to the element with \`cy.scrollIntoView()\`?`)
})

it('cannot determine why element is not visible', function () {
// this element is actually visible
// but used here as an example that does not match any of the above
this.reasonIs(this.$divVisible, 'This element `<div>` is not visible.')
reasonIs(this.$divVisible, 'This element `<div>` is not visible.')
})
})
})
Expand Down
13 changes: 9 additions & 4 deletions packages/driver/src/dom/visibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import $elements from './elements'
import $coordinates from './coordinates'
import * as $transform from './transform'

const { isElement, isBody, isHTML, isOption, isOptgroup, getParent, getFirstParentWithTagName, isAncestor, isChild, getAllParents, isDescendent, isUndefinedOrHTMLBodyDoc, elOrAncestorIsFixedOrSticky, isDetached, isFocusable, stringify: stringifyElement } = $elements
const { isElement, isSelect, isBody, isHTML, isOption, isOptgroup, getParent, getFirstParentWithTagName, isAncestor, isChild, getAllParents, isDescendent, isUndefinedOrHTMLBodyDoc, elOrAncestorIsFixedOrSticky, isDetached, isFocusable, stringify: stringifyElement } = $elements

const fixedOrAbsoluteRe = /(fixed|absolute)/

const OVERFLOW_PROPS = ['hidden', 'scroll', 'auto']
const OVERFLOW_PROPS = ['hidden', 'clip', 'scroll', 'auto']

const isVisible = (el) => {
return !isHidden(el, 'isVisible()')
Expand Down Expand Up @@ -59,7 +59,7 @@ const isStrictlyHidden = (el, methodName = 'isStrictlyHidden()', options = { che
// they may have not put the option into a select el,
// in which case it will fall through to regular visibility logic
if ($select && $select.length) {
// if the select is hidden, the options in it are visible too
// if the select is hidden, the options in it are hidden too
return recurse ? recurse($select[0], methodName, options) : isStrictlyHidden($select[0], methodName, options)
}
}
Expand Down Expand Up @@ -232,6 +232,11 @@ const canClipContent = function ($el, $ancestor) {
return false
}

// can't clip if it's a select element
if (isSelect($ancestor[0])) {
return false
}

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

Expand Down Expand Up @@ -574,7 +579,7 @@ export const getReasonIsHidden = function ($el, options = { checkOpacity: true }
}
} else {
if (elIsOutOfBoundsOfAncestorsOverflow($el)) {
return `This element \`${node}\` is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: \`hidden\`, \`scroll\` or \`auto\``
return `This element \`${node}\` is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: \`hidden\`, \`clip\`, \`scroll\` or \`auto\``
}
}

Expand Down