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

Visibility false to style='transform: scale(0,0)' or style='tra… #5590

Merged
merged 14 commits into from
Dec 5, 2019
Merged
57 changes: 57 additions & 0 deletions packages/driver/src/dom/visibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ const isHidden = (el, name = 'isHidden()') => {
return true // is hidden
}

// when an element is scaled to 0 in one axis
// it is not visible to users.
// So, it is hidden.
if (elIsHiddenByTransform($el)) {
return true
}

if (elIsBackface($el)) {
return true
}
Expand Down Expand Up @@ -154,6 +161,48 @@ const elHasVisibilityCollapse = ($el) => {
return $el.css('visibility') === 'collapse'
}

// This function checks 2 things that can happen: scale and rotate
const elIsHiddenByTransform = ($el) => {
// We need to see the final calculation of the element.
const el = $el[0]

const style = window.getComputedStyle(el)
const transform = style.getPropertyValue('transform')

// Test scaleZ(0)
// width or height of getBoundingClientRect aren't 0 when scaleZ(0).
// But it is invisible.
// Experiment -> https://codepen.io/sainthkh/pen/LYYQGpm
// That's why we're checking transfomation matrix here.
//
// To understand how this part works,
// you need to understand tranformation matrix first.
// Matrix is hard to explain with only text. So, check these articles.
//
// https://www.useragentman.com/blog/2011/01/07/css3-matrix-transform-for-the-mathematically-challenged/
// https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions
//
if (transform.startsWith('matrix3d')) {
const m3d = transform.substring(8).match(numberRegex)

// Z Axis values
if (+m3d[2] === 0 && +m3d[6] === 0 && +m3d[10] === 0) {
return true
}
}

// Other cases
if (transform !== 'none') {
const { width, height } = el.getBoundingClientRect()

if (width === 0 || height === 0) {
return true
}
}

return false
}

const elHasDisplayNone = ($el) => {
return $el.css('display') === 'none'
}
Expand Down Expand Up @@ -310,6 +359,10 @@ const elIsHiddenByAncestors = function ($el, $origEl = $el) {
return !elDescendentsHavePositionFixedOrAbsolute($parent, $origEl)
}

if (elIsHiddenByTransform($parent)) {
return true
}

if (elIsBackface($parent)) {
return true
}
Expand Down Expand Up @@ -430,6 +483,10 @@ const getReasonIsHidden = function ($el) {
return `This element '${node}' is not visible because it has an effective width and height of: '${width} x ${height}' pixels.`
}

if (elIsHiddenByTransform($el)) {
return `This element '${node}' is not visible because it is hidden by transform.`
chrisbreiding marked this conversation as resolved.
Show resolved Hide resolved
}

if (elIsBackface($el)) {
return `This element '${node}' is not visible because it is rotated and its backface is hidden.`
}
Expand Down
113 changes: 111 additions & 2 deletions packages/driver/test/cypress/integration/dom/visibility_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -801,8 +801,105 @@ describe('src/cypress/dom/visibility', () => {
})

describe('css transform', () => {
// TODO: why is this skipped?
it.skip('is hidden when outside parents transform scale', function () {
describe('element visibility by css transform', () => {
const add = (el) => {
return $(el).appendTo(cy.$$('body'))
}

it('is visible when an element is translated a bit', () => {
const el = add(`<div style="transform: translate(10px, 10px)">Translated</div>`)

expect(el).to.be.visible
})

it('is visible when an element is only skewed', () => {
const el = add(`<div style="transform: skew(10deg, 15deg)">Skewed</div>`)

expect(el).to.be.visible
})

it('is visible when an element is only rotated', () => {
const el = add(`<div style="transform: rotate(20deg)">Rotated</div>`)

expect(el).to.be.visible
})

it('is visible when an element is scaled by non-zero', () => {
const el = add(`<div style="transform: scale(2, 3)">Scaled</div>`)

expect(el).to.be.visible
})

it('is visible when an element is transformed in multiple ways but not scaled to zero', () => {
const el = add(`<div style="transform: translate(10px, 15px) skew(30deg) rotate(30deg) scale(4, 1)">Multiple transform</div>`)

expect(el).to.be.visible
})

it('is visible when an element is rotateZ(90deg)', () => {
const el = add(`<div style="transform: rotateZ(90deg)">rotateZ(90deg)</div>`)

expect(el).to.be.visible
})

it('is hidden when an element is scaled to X axis in 0', () => {
const el = add(`<div style="transform: scaleX(0)">ScaleX(0)</div>`)

expect(el).to.be.hidden
})

it('is hidden when an element is scaled to Y axis in 0', () => {
const el = add(`<div style="transform: scaleY(0)">ScaleY(0)</div>`)

expect(el).to.be.hidden
})

it('is hidden when an element is scaled to Z axis in 0', () => {
const el = add(`<div style="transform: scaleZ(0)">ScaleZ(0)</div>`)

expect(el).to.be.hidden
})

it('is hidden when an element is transformed in multiple ways but scaled to 0 in one axis', () => {
const el = add(`<div style="transform: translate(15px, 30px) skew(20deg) rotate(40deg) scale(0, 0)">Multiple 2</div>`)

expect(el).to.be.hidden
})

it('is hidden when an element is rotateX(90deg)', () => {
const el = add(`<div style="transform: rotateX(90deg)">rotateX(90deg)</div>`)

expect(el).to.be.hidden
})

it('is hidden when an element is rotateY(90deg)', () => {
const el = add(`<div style="transform: rotateX(90deg)">rotateY(90deg)</div>`)

expect(el).to.be.hidden
})

it('is hidden when an element is rotateX(90deg) rotateY(90deg)', () => {
const el = add(`<div style="transform: rotateX(90deg) rotateY(90deg)">rotateX(90deg)</div>`)

expect(el).to.be.hidden
})

it('is hidden when an element is transformed in multiple ways but rotated to 90 deg in X or Y axis', () => {
const el = add(`<div style="transform: rotateX(90deg) skew(30deg, 50deg) translate(15px, 60px) scale(3.5)">rotateX(90deg)</div>`)

expect(el).to.be.hidden

const el2 = add(`<div style="transform: rotateY(90deg) skew(30deg, 50deg) translate(15px, 60px) scale(3.5)">rotateX(90deg)</div>`)

expect(el2).to.be.hidden

const el3 = add(`<div style="transform: rotateX(90deg) rotateY(90deg) skew(30deg, 50deg) translate(15px, 60px) scale(3.5)">rotateX(90deg)</div>`)

expect(el3).to.be.hidden
})
})

it('is hidden when outside parents transform scale', function () {
expect(this.$parentWithTransformScaleElOutsideScale.find('span')).to.be.hidden
})

Expand Down Expand Up @@ -922,6 +1019,18 @@ describe('src/cypress/dom/visibility', () => {
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\'')
})

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.`)
})

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.`)
})

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 its being covered by another element:
Expand Down