diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index 06bad21ac65d..e17d325415bb 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -5,6 +5,11 @@ _Released 12/3/2024 (PENDING)_
**Breaking Changes:**
+- Visibility checks are no longer inverse of element 'is.hidden' checks. Element now can be visible, but still not interactable. Use 'is.hidden' to make sure element can be used.
+
+**Bugfixes:**
+
+- Change visibility check to use checkVisibility browser API. Fixed in [#29741](https://github.com/cypress-io/cypress/pull/29741). Fixes [#28187](https://github.com/cypress-io/cypress/issues/28187).
- Removed support for Node.js 16 and Node.js 21. Addresses [#29930](https://github.com/cypress-io/cypress/issues/29930).
- Prebuilt binaries for Linux are no longer compatible with Linux distributions based on glibc <2.28, for example: Ubuntu 14-18, RHEL 7, CentOS 7, Amazon Linux 2. Addresses [#29601](https://github.com/cypress-io/cypress/issues/29601).
- Cypress now only officially supports the latest 3 major versions of Chrome, Firefox, and Edge - older browser versions may still work, but we recommend keeping your browsers up to date to ensure compatibility with Cypress. A warning will no longer be displayed on browser selection in the Launchpad for any 'unsupported' browser versions. Additionally, the undocumented `minSupportedVersion` property has been removed from `Cypress.browser`. Addressed in [#30462](https://github.com/cypress-io/cypress/pull/30462).
@@ -1184,4 +1189,4 @@ _Released 1/24/2023_
- Video output link in `cypress run` mode has been added to it's own line to
make the video output link more easily clickable in the terminal. Addresses
- [#23913](https://github.com/cypress-io/cypress/issues/23913).
+ [#23913](https://github.com/cypress-io/cypress/issues/23913).
\ No newline at end of file
diff --git a/packages/driver/cypress/e2e/dom/visibility.cy.ts b/packages/driver/cypress/e2e/dom/visibility.cy.ts
index fe82cfb0bd02..2c72fa2095f3 100644
--- a/packages/driver/cypress/e2e/dom/visibility.cy.ts
+++ b/packages/driver/cypress/e2e/dom/visibility.cy.ts
@@ -170,18 +170,18 @@ describe('src/cypress/dom/visibility', () => {
// ensure all tests run against a scrollable window
const scrollThisIntoView = add('
-covering the element with pointer-events: none\
`)
this.$parentDisplayInlineChildDisplayBlock = add(`\
@@ -644,14 +624,14 @@ describe('src/cypress/dom/visibility', () => {
describe('option and optgroup', () => {
it('is visible if option in visible select', function () {
- expect(this.$optionInSelect.find('option').is(':hidden')).to.be.false
- expect(this.$optionInSelect.find('option').is(':visible')).to.be.true
+ expect(this.$optionInSelect.find('option#optionInSelect').is(':hidden')).to.be.false
+ expect(this.$optionInSelect.find('option#optionInSelect').is(':visible')).to.be.true
- expect(this.$optionInSelect.find('option')).not.to.be.hidden
- expect(this.$optionInSelect.find('option')).to.be.visible
+ expect(this.$optionInSelect.find('option#optionInSelect')).not.to.be.hidden
+ expect(this.$optionInSelect.find('option#optionInSelect')).to.be.visible
- cy.wrap(this.$optionInSelect.find('option')).should('not.be.hidden')
- cy.wrap(this.$optionInSelect.find('option')).should('be.visible')
+ cy.wrap(this.$optionInSelect.find('option#optionInSelect')).should('not.be.hidden')
+ cy.wrap(this.$optionInSelect.find('option#optionInSelect')).should('be.visible')
})
it('is visible if optgroup in visible select', function () {
@@ -687,8 +667,7 @@ describe('src/cypress/dom/visibility', () => {
cy.wrap(this.$optionHiddenInSelect.find('#hidden-opt')).should('not.be.visible')
})
- // TODO(webkit): fix+unskip
- it('follows regular visibility logic if option outside of select', { browser: '!webkit' }, function () {
+ it('follows regular visibility logic if option outside of select', function () {
expect(this.$optionOutsideSelect.find('#option-hidden').is(':hidden')).to.be.true
expect(this.$optionOutsideSelect.find('#option-hidden')).to.be.hidden
cy.wrap(this.$optionOutsideSelect.find('#option-hidden')).should('be.hidden')
@@ -738,23 +717,24 @@ describe('src/cypress/dom/visibility', () => {
describe('width and height', () => {
it('is hidden if offsetWidth is 0', function () {
expect(this.$divNoWidth.is(':hidden')).to.be.true
- expect(this.$divNoWidth.is(':visible')).to.be.false
-
expect(this.$divNoWidth).to.be.hidden
- expect(this.$divNoWidth).to.not.be.visible
-
cy.wrap(this.$divNoWidth).should('be.hidden')
+ })
+
+ it('is not visible if offsetWidth is 0', function () {
+ expect(this.$divNoWidth.is(':visible')).to.be.false
+ expect(this.$divNoWidth).to.not.be.visible
cy.wrap(this.$divNoWidth).should('not.be.visible')
})
it('is hidden if parent has overflow: hidden and no width', function () {
- expect(this.$parentNoWidth.find('span')).to.be.hidden
- expect(this.$parentNoWidth.find('span')).to.not.be.visible
+ expect(this.$parentNoWidthOnly.find('span#parentNoWidthOnly')).to.be.hidden
+ expect(this.$parentNoWidthOnly.find('span#parentNoWidthOnly')).to.not.be.visible
})
it('is hidden if parent has overflow: hidden and no height', function () {
- expect(this.$parentNoHeight.find('span')).to.be.hidden
- expect(this.$parentNoHeight.find('span')).to.not.be.visible
+ expect(this.$parentNoHeight.find('span#parentNoHeight')).to.be.hidden
+ expect(this.$parentNoHeight.find('span#parentNoHeight')).to.not.be.visible
})
it('is hidden if ancestor has overflow:hidden and no width', function () {
@@ -778,68 +758,6 @@ describe('src/cypress/dom/visibility', () => {
})
})
- describe('css position', () => {
- it('is visible if child has position: absolute', function () {
- expect(this.$childPosAbs.find('span')).to.be.visible
- expect(this.$childPosAbs.find('span')).not.be.hidden
- })
-
- it('is visible if child has position: fixed', function () {
- expect(this.$childPosFixed.find('button')).to.be.visible
- expect(this.$childPosFixed.find('button')).not.to.be.hidden
- })
-
- it('is visible if descendent from parent has position: fixed', function () {
- expect(this.$descendentPosFixed.find('button')).to.be.visible
- expect(this.$descendentPosFixed.find('button')).not.to.be.hidden
- })
-
- it('is visible if has position: fixed and descendent is found', function () {
- expect(this.$descendantInPosFixed.find('#descendantInPosFixed')).to.be.visible
- expect(this.$descendantInPosFixed.find('#descendantInPosFixed')).not.to.be.hidden
- })
-
- it('is hidden if position: fixed and covered up', function () {
- expect(this.$coveredUpPosFixed.find('#coveredUpPosFixed')).to.be.hidden
- expect(this.$coveredUpPosFixed.find('#coveredUpPosFixed')).not.to.be.visible
- })
-
- it('is hidden if position: fixed and off screen', function () {
- expect(this.$offScreenPosFixed).to.be.hidden
- expect(this.$offScreenPosFixed).not.to.be.visible
- })
-
- it('is visible if descendent from parent has position: absolute', function () {
- expect(this.$descendentPosAbs.find('span')).to.be.visible
- expect(this.$descendentPosAbs.find('span')).to.not.be.hidden
- })
-
- it('is hidden if only the parent has position absolute', function () {
- expect(this.$parentPosAbs.find('span')).to.be.hidden
- expect(this.$parentPosAbs.find('span')).to.not.be.visible
- })
-
- it('is visible if position: fixed and parent has pointer-events: none', function () {
- expect(this.$parentPointerEventsNone.find('span')).to.be.visible
- expect(this.$parentPointerEventsNone.find('span')).to.not.be.hidden
- })
-
- it('is not visible if covered when position: fixed and parent has pointer-events: none', function () {
- expect(this.$parentPointerEventsNoneCovered.find('span')).to.be.hidden
- expect(this.$parentPointerEventsNoneCovered.find('span')).to.not.be.visible
- })
-
- it('is visible if pointer-events: none and parent has position: fixed', function () {
- expect(this.$childPointerEventsNone.find('span')).to.be.visible
- expect(this.$childPointerEventsNone.find('span')).to.not.be.hidden
- })
-
- it('is visible when position: sticky', () => {
- cy.visit('fixtures/sticky.html')
- cy.get('#button').should('be.visible')
- })
- })
-
describe('css display', function () {
// https://github.com/cypress-io/cypress/issues/6183
it('parent is visible if display inline and child has display block', function () {
@@ -850,8 +768,8 @@ describe('src/cypress/dom/visibility', () => {
describe('css overflow', () => {
it('is hidden when parent overflow auto and no width/height', function () {
- expect(this.$parentNoWidthHeightOverflowAuto.find('span')).to.not.be.visible
- expect(this.$parentNoWidthHeightOverflowAuto.find('span')).to.be.hidden
+ expect(this.$parentNoWidthHeightOverflowAuto.find('span#parentNoWidthHeightOverflowAuto')).to.not.be.visible
+ expect(this.$parentNoWidthHeightOverflowAuto.find('span#parentNoWidthHeightOverflowAuto')).to.be.hidden
})
it('is hidden when parent overflow hidden and out of bounds to left', function () {
@@ -1144,7 +1062,7 @@ describe('src/cypress/dom/visibility', () => {
})
it('has `visibility: hidden`', function () {
- this.reasonIs(this.$visHidden, 'This element `
` is not visible because it has CSS property: `visibility: hidden`')
+ this.reasonIs(this.$visHidden, 'This element `
` is not visible because it has CSS property: `visibility: hidden`')
})
it('has parent with `visibility: hidden`', function () {
@@ -1172,7 +1090,7 @@ describe('src/cypress/dom/visibility', () => {
})
it('has effective zero width', function () {
- this.reasonIs(this.$divNoWidth, 'This element `
` is not visible because it has an effective width and height of: `0 x 100` pixels.')
+ this.reasonIs(this.$divNoWidth, 'This element `
` is not visible because it has an effective width and height of: `0 x 100` pixels.')
})
it('has effective zero height', function () {
@@ -1180,7 +1098,7 @@ describe('src/cypress/dom/visibility', () => {
})
it('has a parent with an effective zero width and overflow: hidden', function () {
- this.reasonIs(this.$parentNoHeight.find('span'), 'This element `` is not visible because its parent `
` has CSS property: `overflow: hidden` and an effective width and height of: `100 x 0` pixels.')
+ this.reasonIs(this.$parentNoHeight.find('span#parentNoHeight'), 'This element `` is not visible because its parent `
` 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 () {
diff --git a/packages/driver/cypress/e2e/dom/visibility_css_position.cy.ts b/packages/driver/cypress/e2e/dom/visibility_css_position.cy.ts
new file mode 100644
index 000000000000..607f0dbdd204
--- /dev/null
+++ b/packages/driver/cypress/e2e/dom/visibility_css_position.cy.ts
@@ -0,0 +1,187 @@
+// @ts-ignore
+const { $ } = Cypress
+
+describe('src/cypress/dom/visibility_css_position', () => {
+ beforeEach(() => {
+ cy.visit('/fixtures/generic.html')
+ })
+
+ 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('