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: Fix getting parents/ancestors for shadow dom elements #8106

Merged
merged 6 commits into from Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/driver/cypress/fixtures/shadow-dom.html
Expand Up @@ -35,6 +35,10 @@
<cy-test-element id="shadow-element-9" content="Shadow Content 9" rootAddition="
<div class='shadow-div'>Shadow Content 9</div>
"></cy-test-element>
<div class="focusable" tabindex="1">
<cy-test-element innerClass="inside-focusable" content="Inside Focusable"></cy-test-element>
</div>
<cy-test-element id="shadow-element-10" innerClass="inside-non-visible" content="Not Visible" style="display:block; transform-style: preserve-3d; backface-visibility: hidden; transform: rotateY(180deg);"></cy-test-element>

<script type="text/javascript">
if (window.customElements) {
Expand All @@ -43,7 +47,7 @@
super()

const root = this.attachShadow({ mode: 'open' })
const content = this.getAttribute('content')
const content = this.getAttribute('content') || 'Shadow Content'
const className = this.hasAttribute('innerClass') ? this.getAttribute('innerClass') : 'shadow-content'
const rootAddition = this.hasAttribute('rootAddition') ? this.getAttribute('rootAddition') : 'shadow-content'

Expand Down
Expand Up @@ -3806,6 +3806,11 @@ describe('shadow dom', () => {
.rightclick()
})

it('focuses the correct ancestor when it is outside shadow dom', () => {
cy.get('.inside-focusable', { includeShadowDom: true }).click()
cy.get('.focusable').should('be.focused')
})

// https://github.com/cypress-io/cypress/issues/7679
it('does not hang when experimentalShadowDomSupport is false and clicking on custom element', () => {
Cypress.config('experimentalShadowDomSupport', false)
Expand Down
Expand Up @@ -799,6 +799,17 @@ describe('src/cy/commands/actions/scroll', () => {
})
})

describe('shadow dom', () => {
beforeEach(() => {
cy.visit('/fixtures/shadow-dom.html')
})

// https://github.com/cypress-io/cypress/issues/7986
it('does not hang', () => {
cy.get('.shadow-1', { includeShadowDom: true }).scrollIntoView()
})
})

describe('assertion verification', () => {
beforeEach(function () {
cy.on('log:added', (attrs, log) => {
Expand Down
15 changes: 12 additions & 3 deletions packages/driver/cypress/integration/e2e/visibility_spec.js
Expand Up @@ -29,11 +29,20 @@ describe('visibility', () => {
})
})

// https://github.com/cypress-io/cypress/issues/7794
describe('with shadow dom and fixed position ancestor', () => {
it('does not hang when checking visibility', () => {
describe('with shadow dom', () => {
// https://github.com/cypress-io/cypress/issues/7794
it('fixed position ancestor does not hang when checking visibility', () => {
cy.visit('/fixtures/issue-7794.html')
cy.get('.container-2').should('be.visible')
})

// TODO: move with tests added in this PR when it merges: https://github.com/cypress-io/cypress/pull/8166
it('non-visible ancestor causes element to not be visible', () => {
cy.visit('/fixtures/shadow-dom.html')
cy
.get('#shadow-element-10')
.find('.shadow-div', { includeShadowDom: true })
.should('not.be.visible')
})
})
})
2 changes: 1 addition & 1 deletion packages/driver/src/cy/commands/actions/scroll.js
Expand Up @@ -7,7 +7,7 @@ const $utils = require('../../../cypress/utils')
const $errUtils = require('../../../cypress/error_utils')

const findScrollableParent = ($el, win) => {
const $parent = $el.parent()
const $parent = $dom.getParent($el)

// if we're at the body, we just want to pass in
// window into jQuery scrollTo
Expand Down
2 changes: 1 addition & 1 deletion packages/driver/src/cy/commands/traversals.js
Expand Up @@ -49,7 +49,7 @@ const autoShadowTraversals = {
},
parent: (cy, $el) => {
const parents = $el.map((i, el) => {
return $elements.getParent(el)
return $elements.getParentNode(el)
})

return sortedUnique(cy, parents)
Expand Down
8 changes: 5 additions & 3 deletions packages/driver/src/dom/document.ts
Expand Up @@ -4,13 +4,15 @@ const docNode = window.Node.DOCUMENT_NODE
const docFragmentNode = window.Node.DOCUMENT_FRAGMENT_NODE

//TODO: make this not allow jquery
const isDocument = (obj: Node): obj is Document => {
const isDocument = (obj: Node | JQuery): obj is Document => {
try {
let node = obj as Node

if ($jquery.isJquery(obj)) {
obj = obj[0]
node = obj[0]
}

return obj?.nodeType === docNode || obj?.nodeType === docFragmentNode
return node?.nodeType === docNode || node?.nodeType === docFragmentNode
} catch (error) {
return false
}
Expand Down
24 changes: 18 additions & 6 deletions packages/driver/src/dom/elements.ts
Expand Up @@ -611,7 +611,7 @@ const isWithinShadowRoot = (node: HTMLElement) => {
return isShadowRoot(node.getRootNode())
}

const getParent = (el) => {
const getParentNode = (el) => {
// if the element has a direct parent element,
// simply return it.
if (el.parentElement) {
Expand All @@ -629,9 +629,13 @@ const getParent = (el) => {
return null
}

const getParent = ($el: JQuery): JQuery => {
return $(getParentNode($el[0]))
}

const getAllParents = (el: HTMLElement, untilSelector?: string) => {
const collectParents = (parents, node) => {
const parent = getParent(node)
const parent = getParentNode(node)

if (!parent || untilSelector && $(parent).is(untilSelector)) {
return parents
Expand Down Expand Up @@ -901,7 +905,7 @@ const isDescendent = ($el1, $el2) => {

const findParent = (el, condition) => {
const collectParent = (node) => {
const parent = getParent(node)
const parent = getParentNode(node)

if (!parent) return null

Expand All @@ -925,17 +929,17 @@ const getFirstFocusableEl = ($el: JQuery<HTMLElement>) => {
return $el
}

const parent = $el.parent()
const $parent = getParent($el)

// if we have no parent then just return
// the window since that can receive focus
if (!parent.length) {
if (!$parent.length) {
const win = $window.getWindowByElement($el.get(0))

return $(win)
}

return getFirstFocusableEl($el.parent())
return getFirstFocusableEl(getParent($el))
}

const getActiveElByDocument = ($el: JQuery<HTMLElement>): HTMLElement | null => {
Expand Down Expand Up @@ -1247,6 +1251,12 @@ const elementFromPoint = (doc, x, y) => {
return elFromPoint
}

const getShadowRoot = ($el: JQuery): JQuery<Node> => {
const root = $el[0].getRootNode()

return $(root)
}

const findAllShadowRoots = (root: Node): Node[] => {
const collectRoots = (roots, nodes, node) => {
const currentRoot = roots.pop()
Expand Down Expand Up @@ -1360,5 +1370,7 @@ export {
getFirstStickyPositionParent,
getFirstScrollableParent,
getParent,
getParentNode,
getAllParents,
getShadowRoot,
}
3 changes: 2 additions & 1 deletion packages/driver/src/dom/transform.ts
@@ -1,4 +1,5 @@
import _ from 'lodash'
import { getParent } from './elements'
import { isDocument } from './document'

export const detectVisibility = ($el: any) => {
Expand Down Expand Up @@ -39,7 +40,7 @@ const extractTransformInfoFromElements = ($el: any, list: TransformInfo[] = []):
list.push(info)
}

const $parent = $el.parent()
const $parent = getParent($el)

if (!$parent.length || isDocument($parent)) {
return list
Expand Down