diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index b1050db6ef47..d3f5e1eb9e8e 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -13,6 +13,7 @@ _Released 6/4/2024 (PENDING)_
**Bugfixes:**
+- Fixed an issue where Cypress did not detect visible elements with width or height in rem as visible. Fixes [#29224](https://github.com/cypress-io/cypress/issues/29093) and [#28638](https://github.com/cypress-io/cypress/issues/28638).
- Fixed a situation where the Launchpad would hang if the project config had not been loaded when the Launchpad first queries the current project. Fixes [#29486](https://github.com/cypress-io/cypress/issues/29486).
- Pre-emptively fix behavior with Chrome for when `unload` events are forcefully deprecated by using `pagehide` as a proxy. Fixes [#29241](https://github.com/cypress-io/cypress/issues/29241).
diff --git a/packages/driver/cypress/e2e/dom/visibility.cy.ts b/packages/driver/cypress/e2e/dom/visibility.cy.ts
index 5dc7495afc30..e2c352430973 100644
--- a/packages/driver/cypress/e2e/dom/visibility.cy.ts
+++ b/packages/driver/cypress/e2e/dom/visibility.cy.ts
@@ -247,7 +247,14 @@ describe('src/cypress/dom/visibility', () => {
this.$parentNoWidth = add(`\
- parent width: 0
+ parent width: 0
+
+
`)
+
+ this.$parentOfDivNoWidth = add(`\
+`)
@@ -368,7 +375,7 @@ describe('src/cypress/dom/visibility', () => {
this.$elOutOfParentBoundsAbove = add(`\
- position: absolute, out of bounds above
+ position: absolute, out of bounds above
\
`)
@@ -733,8 +740,13 @@ describe('src/cypress/dom/visibility', () => {
})
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.$parentNoWidth.find('#parentNoWidth')).to.be.hidden
+ expect(this.$parentNoWidth.find('#parentNoWidth')).to.not.be.visible
+ })
+
+ it('is hidden if parent has overflow: hidden and no width', function () {
+ expect(this.$parentOfDivNoWidth.find('#parentOfDivNoWidth')).to.be.hidden
+ expect(this.$parentOfDivNoWidth.find('#parentOfDivNoWidth')).to.not.be.visible
})
it('is hidden if parent has overflow: hidden and no height', function () {
@@ -833,7 +845,7 @@ describe('src/cypress/dom/visibility', () => {
})
it('is hidden when parent overflow hidden and out of bounds above', function () {
- expect(this.$elOutOfParentBoundsAbove.find('span')).to.be.hidden
+ expect(this.$elOutOfParentBoundsAbove.find('#outOfParentBoundsAbove'), `expected '' to be 'hidden'`).to.be.hidden
})
it('is hidden when parent overflow hidden and out of bounds below', function () {
diff --git a/packages/driver/cypress/e2e/issues/29093.cy.js b/packages/driver/cypress/e2e/issues/29093.cy.js
new file mode 100644
index 000000000000..7f62c965f0d1
--- /dev/null
+++ b/packages/driver/cypress/e2e/issues/29093.cy.js
@@ -0,0 +1,12 @@
+// https://github.com/cypress-io/cypress/issues/29093
+describe('issue 29093', () => {
+ before(() => {
+ cy
+ .viewport('macbook-16')
+ .visit('/fixtures/issue-29093.html')
+ })
+
+ it('can click selection when rem width used', () => {
+ cy.get('#sidebar-left > section').click()
+ })
+})
diff --git a/packages/driver/cypress/fixtures/issue-29093.html b/packages/driver/cypress/fixtures/issue-29093.html
new file mode 100644
index 000000000000..9a09bcaca713
--- /dev/null
+++ b/packages/driver/cypress/fixtures/issue-29093.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ 29093 repro
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/driver/src/dom/visibility.ts b/packages/driver/src/dom/visibility.ts
index 2a92fae2b963..bcc1e541bfa5 100644
--- a/packages/driver/src/dom/visibility.ts
+++ b/packages/driver/src/dom/visibility.ts
@@ -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
@@ -121,7 +121,7 @@ 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.
@@ -129,16 +129,22 @@ const elHasNoEffectiveWidthOrHeight = ($el) => {
// For HTML 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)
}
@@ -146,7 +152,7 @@ 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')
@@ -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'
}
@@ -189,6 +201,10 @@ const elHasDisplayNone = ($el) => {
return $el.css('display') === 'none'
}
+const elHasDisplayContents = ($el) => {
+ return $el.css('display') === 'contents'
+}
+
const elHasDisplayInline = ($el) => {
return $el.css('display') === 'inline'
}
@@ -219,13 +235,24 @@ const canClipContent = function ($el, $ancestor) {
return false
}
+ // fix for 29093
+ if (elHasDisplayContents($ancestor)) {
+ return false
+ }
+
// 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
}
@@ -308,7 +335,7 @@ const elIsNotElementFromPoint = function ($el) {
return true
}
-const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor = getParent($el)) {
+const elIsOutOfBoundsOfAncestorsOverflow = function ($el: JQuery, $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
@@ -316,27 +343,32 @@ const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor = getParent(
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
}
@@ -348,7 +380,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------
@@ -376,11 +408,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
@@ -392,7 +428,7 @@ const parentHasNoOffsetWidthOrHeightAndOverflowHidden = function ($el) {
}
// continue walking
- return parentHasNoOffsetWidthOrHeightAndOverflowHidden(getParent($el))
+ return parentHasNoClientWidthOrHeightAndOverflowHidden(getParent($el))
}
const parentHasDisplayNone = function ($el) {
@@ -463,8 +499,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
@@ -513,24 +549,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.`
}
diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json
index 8feebc04a179..2ddc6a4b55e5 100644
--- a/packages/launchpad/package.json
+++ b/packages/launchpad/package.json
@@ -57,6 +57,7 @@
"gravatar": "1.8.0",
"javascript-time-ago": "2.3.8",
"markdown-it": "13.0.1",
+ "mocha": "10.4.0",
"rollup-plugin-polyfill-node": "^0.7.0",
"type-fest": "^2.3.4",
"unplugin-vue-components": "0.24.1",
diff --git a/packages/launchpad/src/components/code/FileRow.cy.tsx b/packages/launchpad/src/components/code/FileRow.cy.tsx
index 660cb11180f6..8640adff41d4 100644
--- a/packages/launchpad/src/components/code/FileRow.cy.tsx
+++ b/packages/launchpad/src/components/code/FileRow.cy.tsx
@@ -117,10 +117,10 @@ describe('FileRow', () => {
cy.contains(changesRequiredDescription).should('be.visible')
cy.get('pre').should('have.length', 2)
- cy.get('.shiki').should('be.visible')
+ cy.get('div.rounded > div.text-left.cursor-text').should('be.visible')
cy.contains('cypress/integration/command.js').click()
- cy.get('.shiki').should('not.be.visible')
+ cy.get('div.rounded > div.text-left.cursor-text').should('not.exist')
})
it('responds nice to small screens', { viewportWidth: 500 }, () => {