Skip to content

Commit

Permalink
Fix isClickable in Edge if element is in ShadowRoot polyfill (webdriv…
Browse files Browse the repository at this point in the history
  • Loading branch information
mgrybyk authored and christian-bromann committed Nov 25, 2019
1 parent db808ff commit 3a26709
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 6 deletions.
34 changes: 29 additions & 5 deletions packages/webdriverio/src/scripts/isElementClickable.js
Expand Up @@ -8,6 +8,11 @@ export default function isElementClickable (elem) {
return false
}

// Edge before switching to Chromium
const isOldEdge = !!window.StyleMedia
// returns true for Chrome and Firefox and false for Safari, Edge and IE
const scrollIntoViewFullSupport = !(window.safari || isOldEdge)

// get overlapping element
function getOverlappingElement (elem, context = document) {
const elemDimension = elem.getBoundingClientRect()
Expand Down Expand Up @@ -36,9 +41,31 @@ export default function isElementClickable (elem) {
return [getOverlappingElement(elem, context), ...getOverlappingRects(elem, context)]
}

// is a node a descendant of a given node
function nodeContains (elem, otherNode) {
// Edge doesn't support neither Shadow Dom nor contains if ShadowRoot polyfill is used
if (isOldEdge) {
let tmpElement = otherNode
while (tmpElement) {
if (tmpElement === elem) {
return true
}

// DocumentFragment / ShadowRoot polyfill like ShadyRoot
if (tmpElement.nodeType === 11 && tmpElement.host) {
tmpElement = tmpElement.host
}
tmpElement = tmpElement.parentNode
}
return false
}

return elem.contains(otherNode)
}

// is one of overlapping elements the `elem` or one of its child
function isOverlappingElementMatch (elementsFromPoint, elem) {
if (elementsFromPoint.some(elementFromPoint => elementFromPoint === elem || elem.contains(elementFromPoint))) {
if (elementsFromPoint.some(elementFromPoint => elementFromPoint === elem || nodeContains(elem, elementFromPoint))) {
return true
}

Expand Down Expand Up @@ -84,9 +111,6 @@ export default function isElementClickable (elem) {
return isElementInViewport(elem) && elem.disabled !== true && isOverlappingElementMatch(getOverlappingElements(elem), elem)
}

// returns true for Chrome and Firefox and false for Safari, Edge and IE
let scrollIntoViewFullSupport = !window.safari || !window.StyleMedia

// scroll to the element if it's not clickable
if (!isClickable(elem)) {
// works well in dialogs, but the element may be still overlapped by some sticky header/footer
Expand All @@ -96,7 +120,7 @@ export default function isElementClickable (elem) {
if (!isClickable(elem)) {
// scroll to element, try put it in the screen center.
// Should definitely work even if element was covered with sticky header/footer
elem.scrollIntoView({ block: 'center', inline: 'center' })
elem.scrollIntoView(scrollIntoViewFullSupport ? { block: 'center', inline: 'center' } : true)

return isClickable(elem)
}
Expand Down
36 changes: 35 additions & 1 deletion packages/webdriverio/tests/scripts/isElementClickable.test.js
Expand Up @@ -46,6 +46,34 @@ describe('isElementClickable script', () => {
expect(isElementClickable(elemMock)).toBe(true)
})

it('should be clickable if in viewport and elementFromPoint is child of elem [Edge]', () => {
const elemMock = {
getBoundingClientRect: () => ({
height: 55,
width: 22,
top: 33,
left: 455
}),
clientHeight: 55,
clientWidth: 22,
getClientRects: () => [{}],
scrollIntoView: jest.fn(),
contains: () => { throw new Error('should not be called in old Edge!') }
}
// emulate old Edge
global.window.StyleMedia = () => { }

let attempts = 0
global.document = { elementFromPoint: () => {
attempts += 1
return { parentNode: attempts > 4 ? elemMock : {} }
} }

expect(isElementClickable(elemMock)).toBe(true)
expect(elemMock.scrollIntoView).toBeCalledWith(false)
expect(elemMock.scrollIntoView).toBeCalledWith(true)
})

it('should be clickable if in viewport and elementFromPoint of the rect matches', () => {
const elemMock = {
getBoundingClientRect: () => ({
Expand Down Expand Up @@ -133,12 +161,14 @@ describe('isElementClickable script', () => {
clientHeight: 55,
clientWidth: 22,
getClientRects: () => [{}],
scrollIntoView: () => { },
scrollIntoView: jest.fn(),
contains: () => false
}
global.document = { elementFromPoint: () => null }

expect(isElementClickable(elemMock)).toBe(false)
expect(elemMock.scrollIntoView).toBeCalledWith({ block: 'nearest', inline: 'nearest' })
expect(elemMock.scrollIntoView).toBeCalledWith({ block: 'center', inline: 'center' })
})

it("should be not clickable if in viewport but elementFromPoint doesn't match [shadowRoot]", () => {
Expand Down Expand Up @@ -180,4 +210,8 @@ describe('isElementClickable script', () => {

expect(isElementClickable(elemMock)).toBe(false)
})

afterEach(() => {
delete global.window.StyleMedia
})
})

0 comments on commit 3a26709

Please sign in to comment.