diff --git a/src/api/test-controller/execution-context.js b/src/api/test-controller/execution-context.js index e56670a0b4c..d81593fd780 100644 --- a/src/api/test-controller/execution-context.js +++ b/src/api/test-controller/execution-context.js @@ -2,7 +2,7 @@ import { createContext } from 'vm'; import Module from 'module'; import path from 'path'; import exportableLib from '../exportable-lib'; -import NODE_MODULES from '../../shared/node-modules-folder-name'; +import NODE_MODULES from '../../utils/node-modules-folder-name'; const OPTIONS_KEY = Symbol('options'); diff --git a/src/client/automation/cursor.js b/src/client/automation/cursor.js deleted file mode 100644 index fe300cde6f9..00000000000 --- a/src/client/automation/cursor.js +++ /dev/null @@ -1,7 +0,0 @@ -import testCafeUI from './deps/testcafe-ui'; -import isIframeWindow from '../../utils/is-window-in-iframe'; -import Cursor from '../../shared/actions/cursor'; - -const cursorUI = !isIframeWindow(window) ? testCafeUI.cursorUI : testCafeUI.iframeCursorUI; - -export default new Cursor(window.top, cursorUI); diff --git a/src/shared/actions/cursor.ts b/src/client/automation/cursor/cursor.ts similarity index 70% rename from src/shared/actions/cursor.ts rename to src/client/automation/cursor/cursor.ts index d73f298385b..efe118595e1 100644 --- a/src/shared/actions/cursor.ts +++ b/src/client/automation/cursor/cursor.ts @@ -1,26 +1,25 @@ -import { CursorUI } from './types'; -import AxisValues, { AxisValuesData } from '../utils/values/axis-values'; -import { SharedWindow } from '../types'; +import { CursorUI } from '../types'; +import AxisValues, { AxisValuesData } from '../../core/utils/values/axis-values'; // @ts-ignore -import { Promise } from '../../client/driver/deps/hammerhead'; +import { Promise } from '../../driver/deps/hammerhead'; -export default class Cursor { - private _activeWindow: W; +export default class Cursor { + private _activeWindow: Window; private _x: number; private _y: number; private readonly _ui: CursorUI; - public constructor (activeWin: W, ui: CursorUI) { + public constructor (activeWin: Window, ui: CursorUI) { this._ui = ui; - // NOTE: the default position should be outside of the page (GH-794) + // NOTE: the default position should be outside the page (GH-794) this._x = -1; this._y = -1; this._activeWindow = activeWin; } - private _ensureActiveWindow (win: W): void { + private _ensureActiveWindow (win: Window): void { if (this._activeWindow === win || this._activeWindow === win.parent) return; @@ -28,17 +27,17 @@ export default class Cursor { this._activeWindow = win; } - public isActive (currWin: W): boolean { + public isActive (currWin: Window): boolean { this._ensureActiveWindow(currWin); return this._activeWindow === currWin; } - public setActiveWindow (win: W): void { + public setActiveWindow (win: Window): void { this._activeWindow = win; } - public getActiveWindow (currWin: W): W { + public getActiveWindow (currWin: Window): Window { this._ensureActiveWindow(currWin); return this._activeWindow; diff --git a/src/client/automation/cursor/index.js b/src/client/automation/cursor/index.js new file mode 100644 index 00000000000..f5be559f99c --- /dev/null +++ b/src/client/automation/cursor/index.js @@ -0,0 +1,7 @@ +import testCafeUI from '../deps/testcafe-ui'; +import isIframeWindow from '../../../utils/is-window-in-iframe'; +import Cursor from './cursor'; + +const cursorUI = !isIframeWindow(window) ? testCafeUI.cursorUI : testCafeUI.iframeCursorUI; + +export default new Cursor(window.top, cursorUI); diff --git a/src/client/automation/get-element.js b/src/client/automation/get-element.js deleted file mode 100644 index 98de24e5a41..00000000000 --- a/src/client/automation/get-element.js +++ /dev/null @@ -1,107 +0,0 @@ -import hammerhead from './deps/hammerhead'; -import testCafeCore from './deps/testcafe-core'; -import testCafeUI from './deps/testcafe-ui'; -import isIframeWindow from '../../utils/is-window-in-iframe'; - -const browserUtils = hammerhead.utils.browser; -const Promise = hammerhead.Promise; -const nativeMethods = hammerhead.nativeMethods; -const positionUtils = testCafeCore.positionUtils; -const domUtils = testCafeCore.domUtils; - - -function fromPoint (point/*: AxisValuesData*/, underTopShadowUIElement) { - let topElement = null; - - return testCafeUI.hide(underTopShadowUIElement) - .then(() => positionUtils.getElementFromPoint(point)) - .then(el => { - topElement = el; - - return testCafeUI.show(underTopShadowUIElement); - }) - .then(() => topElement); -} - -function ensureImageMap (imgElement, areaElement) { - const mapElement = domUtils.closest(areaElement, 'map'); - - return mapElement && mapElement.name === imgElement.useMap.substring(1) ? areaElement : imgElement; -} - -function findElementOrNonEmptyChildFromPoint (x, y, element) { - const topElement = positionUtils.getElementFromPoint(x, y); - const isNonEmptyChild = domUtils.containsElement(element, topElement) && - nativeMethods.nodeTextContentGetter.call(topElement).length; - - if (topElement && topElement === element || isNonEmptyChild) - return topElement; - - return null; -} - -function correctTopElementByExpectedElement (topElement, expectedElement) { - const expectedElementDefined = expectedElement && domUtils.isDomElement(expectedElement); - - if (!expectedElementDefined || !topElement || topElement === expectedElement) - return topElement; - - const isTREFElement = domUtils.getTagName(expectedElement) === 'tref'; - - // NOTE: 'document.elementFromPoint' can't find these types of elements - if (isTREFElement) - return expectedElement; - - // NOTE: T299665 - Incorrect click automation for images with an associated map element in Firefox - // All browsers return the element from document.getElementFromPoint, but - // Firefox returns the element. We should accomplish this for Firefox as well. - const isImageMapArea = domUtils.getTagName(expectedElement) === 'area' && domUtils.isImgElement(topElement); - - if (browserUtils.isFirefox && isImageMapArea) - return ensureImageMap(topElement, expectedElement); - - - // NOTE: try to find a multi-line link by its rectangle (T163678) - const isLinkOrChildExpected = domUtils.isAnchorElement(expectedElement) || - domUtils.getParents(expectedElement, 'a').length; - - const isTopElementChildOfLink = isLinkOrChildExpected && - domUtils.containsElement(expectedElement, topElement) && - nativeMethods.nodeTextContentGetter.call(topElement).length; - - const shouldSearchForMultilineLink = isLinkOrChildExpected && !isTopElementChildOfLink && - nativeMethods.nodeTextContentGetter.call(expectedElement).length; - - if (!shouldSearchForMultilineLink) - return topElement; - - const linkRect = expectedElement.getBoundingClientRect(); - - return findElementOrNonEmptyChildFromPoint(linkRect.right - 1, linkRect.top + 1, expectedElement) || - findElementOrNonEmptyChildFromPoint(linkRect.left + 1, linkRect.bottom - 1, expectedElement) || - topElement; -} - -export default function getElementFromPoint (point/*: AxisValuesData*/, expectedElement) { - return fromPoint(point) - .then(topElement => { - let foundElement = topElement; - - // NOTE: when trying to get an element by elementFromPoint in iframe and the target - // element is under any of shadow-ui elements, you will get null (only in IE). - // In this case, you should hide a top window's shadow-ui root to obtain an element. - let resChain = Promise.resolve(topElement); - - if (!foundElement && isIframeWindow(window) && point.x > 0 && point.y > 0) { - resChain = resChain - .then(() => fromPoint(point, true)) - .then(element => { - foundElement = element; - - return element; - }); - } - - return resChain.then(element => correctTopElementByExpectedElement(element, expectedElement)); - }); -} diff --git a/src/shared/actions/get-element.ts b/src/client/automation/get-element.ts similarity index 69% rename from src/shared/actions/get-element.ts rename to src/client/automation/get-element.ts index 848578c3770..0e521ddef33 100644 --- a/src/shared/actions/get-element.ts +++ b/src/client/automation/get-element.ts @@ -1,21 +1,19 @@ -import { adapter } from '../adapter'; -import isIframeWindow from './utils/is-window-iframe'; -import { AxisValuesData } from '../utils/values/axis-values'; -import { SharedWindow } from '../types'; +import { AxisValuesData } from '../core/utils/values/axis-values'; // @ts-ignore -import { Promise, utils } from '../../client/driver/deps/hammerhead'; -// @ts-ignore -import * as domUtils from '../../client/core/utils/dom'; +import { Promise, utils } from '../driver/deps/hammerhead'; +import * as domUtils from '../core/utils/dom'; +import * as positionUtils from '../core/utils/position'; +import getElementExceptUI from './utils/get-element-except-ui'; -function ensureImageMap (imgElement: E, areaElement: E): Promise { +function ensureImageMap (imgElement: Element, areaElement: Element): Promise { return Promise.resolve(domUtils.closest(areaElement, 'map')) .then((mapElement: HTMLMapElement) => { return mapElement && mapElement.name === domUtils.getImgMapName(imgElement) ? areaElement : imgElement; }); } -function findElementOrNonEmptyChildFromPoint (point: AxisValuesData, element?: E): Promise { - return Promise.resolve(adapter.position.getElementFromPoint(point)) +function findElementOrNonEmptyChildFromPoint (point: AxisValuesData, element?: HTMLElement): Promise { + return Promise.resolve(positionUtils.getElementFromPoint(point)) .then((topElement: HTMLElement) => { return Promise.resolve(domUtils.containsElement(element, topElement)) .then((containsEl: HTMLElement) => containsEl && domUtils.getNodeText(topElement)) @@ -23,7 +21,7 @@ function findElementOrNonEmptyChildFromPoint (point: AxisValuesData, }); } -function correctTopElementByExpectedElement (topElement: E, expectedElement?: E): Promise | E { +function correctTopElementByExpectedElement (topElement: Element, expectedElement?: HTMLElement): Promise | HTMLElement { if (!expectedElement || !topElement || domUtils.isNodeEqual(topElement, expectedElement)) return topElement; @@ -56,24 +54,24 @@ function correctTopElementByExpectedElement (topElement: E, expectedElement?: if (!shouldSearchForMultilineLink) return topElement; - return Promise.resolve(adapter.position.getClientDimensions(expectedElement)) + return Promise.resolve(positionUtils.getClientDimensions(expectedElement)) .then((linkRect: any) => findElementOrNonEmptyChildFromPoint({ x: linkRect.right - 1, y: linkRect.top + 1 }, expectedElement) .then((el: any) => el || findElementOrNonEmptyChildFromPoint({ x: linkRect.left + 1, y: linkRect.bottom - 1 }, expectedElement)) .then((el: any) => el || topElement)); }); } -export default function getElementFromPoint (point: AxisValuesData, win: W, expectedEl?: E): Promise { - return adapter.getElementExceptUI(point) - .then((topElement: E) => { +export default function getElementFromPoint (point: AxisValuesData, win?: Window, expectedEl?: HTMLElement): Promise { + return getElementExceptUI(point) + .then((topElement: HTMLElement) => { // NOTE: when trying to get an element by elementFromPoint in iframe and the target // element is under any of shadow-ui elements, you will get null (only in IE). // In this case, you should hide a top window's shadow-ui root to obtain an element. let resChain = Promise.resolve(topElement); - if (!topElement && isIframeWindow(win) && point.x > 0 && point.y > 0) - resChain = resChain.then(() => adapter.getElementExceptUI(point, true)); + if (!topElement && utils.dom.isIframeWindow(win || window) && point.x > 0 && point.y > 0) + resChain = resChain.then(() => getElementExceptUI(point, true)); - return resChain.then((element: E) => correctTopElementByExpectedElement(element, expectedEl)); + return resChain.then((element: HTMLElement) => correctTopElementByExpectedElement(element, expectedEl)); }); } diff --git a/src/client/automation/index.js b/src/client/automation/index.js index 0d9e3b1f172..b1f097aa1db 100644 --- a/src/client/automation/index.js +++ b/src/client/automation/index.js @@ -1,11 +1,8 @@ -// NOTE: Initializer should be the first -import './shared-adapter-initializer'; - import hammerhead from './deps/hammerhead'; import DispatchEventAutomation from './playback/dispatch-event'; import SetScrollAutomation from './playback/set-scroll'; import ScrollIntoViewAutomation from './playback/scroll-into-view'; -import ClickAutomation from '../../shared/actions/automations/click'; +import ClickAutomation from './playback/click'; import SelectChildClickAutomation from './playback/click/select-child'; import DblClickAutomation from './playback/dblclick'; import DragToOffsetAutomation from './playback/drag/to-offset'; @@ -22,8 +19,8 @@ import { ClickOptions, TypeOptions, } from '../../test-run/commands/options'; -import AutomationSettings from '../../shared/actions/automations/settings'; -import { getOffsetOptions } from '../../shared/actions/utils/offsets'; +import AutomationSettings from './settings'; +import { getOffsetOptions } from '../core/utils/offsets'; import { getNextFocusableElement } from './playback/press/utils'; import SHORTCUT_TYPE from './playback/press/shortcut-type'; import { getSelectionCoordinatesByPosition } from './playback/select/utils'; @@ -31,7 +28,7 @@ import getElementFromPoint from './get-element'; import calculateSelectTextArguments from './playback/select/calculate-select-text-arguments'; import ERROR_TYPES from '../../shared/errors/automation-errors'; import cursor from './cursor'; -import MoveAutomation from '../../shared/actions/automations/move'; +import MoveAutomation from './move'; const exports = {}; diff --git a/src/shared/actions/automations/last-hovered-element-holder.ts b/src/client/automation/last-hovered-element-holder.ts similarity index 100% rename from src/shared/actions/automations/last-hovered-element-holder.ts rename to src/client/automation/last-hovered-element-holder.ts diff --git a/src/shared/actions/automations/move.ts b/src/client/automation/move.ts similarity index 67% rename from src/shared/actions/automations/move.ts rename to src/client/automation/move.ts index 517c951a970..fc181bad3a7 100644 --- a/src/shared/actions/automations/move.ts +++ b/src/client/automation/move.ts @@ -1,12 +1,10 @@ -import { adapter } from '../../adapter'; - -import nextTick from '../../utils/next-tick'; +import nextTick from '../core/utils/next-tick'; import AutomationSettings from './settings'; import { Modifiers, MoveOptions, ScrollOptions, -} from '../../../test-run/commands/options'; +} from '../../test-run/commands/options'; import lastHoveredElementHolder from './last-hovered-element-holder'; import { @@ -16,46 +14,47 @@ import { Promise, // @ts-ignore utils, -} from '../../../client/driver/deps/hammerhead'; - -// @ts-ignore -import { domUtils } from '../../../client/automation/deps/testcafe-core'; +} from '../driver/deps/hammerhead'; + +import * as domUtils from '../core/utils/dom'; +import * as styleUtils from '../core/utils/style'; +import * as positionUtils from '../core/utils/position'; +import * as eventUtils from '../core/utils/event'; +import ScrollAutomation from '../core/scroll'; +import getElementExceptUI from './utils/get-element-except-ui'; +import getAutomationPoint from './utils/get-automation-point'; +import AxisValues, { AxisValuesData } from '../core/utils/values/axis-values'; +import Cursor from './cursor/cursor'; +import { whilst } from '../core/utils/promise'; +import getDevicePoint from './utils/get-device-point'; +import createEventSequence from './playback/move/event-sequence/create-event-sequence'; +import sendRequestToFrame from '../core/utils/send-request-to-frame'; const MOVE_REQUEST_CMD = 'automation|move|request'; const MOVE_RESPONSE_CMD = 'automation|move|response'; -import getAutomationPoint from '../utils/get-automation-point'; -import AxisValues, { AxisValuesData } from '../../utils/values/axis-values'; -import { SharedWindow } from '../../types'; -import Cursor from '../cursor'; -import { whilst } from '../../utils/promise'; -import getDevicePoint from '../utils/get-device-point'; - -interface MoveAutomationTarget { - element: E; +interface MoveAutomationTarget { + element: HTMLElement; offset: AxisValuesData; } -export default class MoveAutomation { - private touchMode: boolean; - private moveEvent: string; - +export default class MoveAutomation { + private readonly touchMode: boolean; + private readonly moveEvent: string; private automationSettings: AutomationSettings; - - private element: E; - private window: W; - private offset: AxisValuesData; - private cursor: Cursor; - private speed: number; - private cursorSpeed: number; - - private minMovingTime: number; - private modifiers: Modifiers; - private skipScrolling: boolean; + private readonly element: HTMLElement; + private readonly window: Window; + private readonly offset: AxisValuesData; + private cursor: Cursor; + private readonly speed: number; + private readonly cursorSpeed: number; + private readonly minMovingTime: number; + private readonly modifiers: Modifiers; + private readonly skipScrolling: boolean; private skipDefaultDragBehavior: boolean; private firstMovingStepOccured: boolean; - protected constructor (el: E, offset: AxisValuesData, moveOptions: MoveOptions, win: W, cursor: Cursor) { + protected constructor (el: HTMLElement, offset: AxisValuesData, moveOptions: MoveOptions, win: Window, cursor: Cursor) { this.touchMode = utils.featureDetection.isTouchDevice; this.moveEvent = this.touchMode ? 'touchmove' : 'mousemove'; @@ -77,16 +76,16 @@ export default class MoveAutomation { this.firstMovingStepOccured = false; } - public static async create (el: E, moveOptions: MoveOptions, win: W, cursor: Cursor): Promise> { + public static async create (el: HTMLElement, moveOptions: MoveOptions, win: Window, cursor: Cursor): Promise { const { element, offset } = await MoveAutomation.getTarget(el, win, new AxisValues(moveOptions.offsetX, moveOptions.offsetY)); return new MoveAutomation(element, offset, moveOptions, win, cursor); } - private static getTarget (element: E, window: W, offset: AxisValuesData): Promise> { + private static getTarget (element: HTMLElement, window: Window, offset: AxisValuesData): Promise { // NOTE: if the target point (considering offsets) is out of // the element change the target element to the document element - return Promise.resolve(adapter.position.containsOffset(element, offset.x, offset.y)) + return Promise.resolve(positionUtils.containsOffset(element, offset.x, offset.y)) .then((containsOffset: boolean) => { if (!containsOffset) { return Promise.all([ @@ -105,7 +104,7 @@ export default class MoveAutomation { } private _getTargetClientPoint (): Promise> { - return Promise.resolve(adapter.style.getElementScroll(this.element)) + return Promise.resolve(styleUtils.getElementScroll(this.element)) .then((scroll: any) => { if (domUtils.isHtmlElement(this.element)) { return AxisValues.create(this.offset) @@ -113,7 +112,7 @@ export default class MoveAutomation { .round(Math.round); } - return Promise.resolve(adapter.position.getClientPosition(this.element)) + return Promise.resolve(positionUtils.getClientPosition(this.element)) .then((clientPosition: any) => { const isDocumentBody = domUtils.isBodyElement(this.element); // @ts-ignore @@ -127,29 +126,27 @@ export default class MoveAutomation { }); } - private _getEventSequenceOptions (currPosition: AxisValues): Promise { - const button = adapter.event.BUTTONS_PARAMETER.noButton; - - return getDevicePoint(currPosition) - .then((devicePoint: any) => { - const eventOptions = { - clientX: currPosition.x, - clientY: currPosition.y, - screenX: devicePoint?.x, - screenY: devicePoint?.y, - buttons: button, - ctrl: this.modifiers.ctrl, - alt: this.modifiers.alt, - shift: this.modifiers.shift, - meta: this.modifiers.meta, - }; - - return { eventOptions, eventSequenceOptions: { moveEvent: this.moveEvent } }; - }); + protected _getEventSequenceOptions (currPosition: AxisValues): Promise { + const button = eventUtils.BUTTONS_PARAMETER.noButton; + const devicePoint = getDevicePoint(currPosition); + + const eventOptions = { + clientX: currPosition.x, + clientY: currPosition.y, + screenX: devicePoint?.x, + screenY: devicePoint?.y, + buttons: button, + ctrl: this.modifiers.ctrl, + alt: this.modifiers.alt, + shift: this.modifiers.shift, + meta: this.modifiers.meta, + }; + + return { eventOptions, eventSequenceOptions: { moveEvent: this.moveEvent } }; } - private async _runEventSequence (currentElement: Element, { eventOptions, eventSequenceOptions }: any): Promise { - const eventSequence = await adapter.createEventSequence(false, this.firstMovingStepOccured, eventSequenceOptions); + private _runEventSequence (currentElement: Element, { eventOptions, eventSequenceOptions }: any): Promise { + const eventSequence = createEventSequence(false, this.firstMovingStepOccured, eventSequenceOptions); return eventSequence.run( currentElement, @@ -161,20 +158,17 @@ export default class MoveAutomation { } private _emulateEvents (currentElement: Element, currPosition: AxisValues): Promise { - return this._getEventSequenceOptions(currPosition) - .then((options: any) => { - return this._runEventSequence(currentElement, options); - }) - .then(() => { - this.firstMovingStepOccured = true; + const options = this._getEventSequenceOptions(currPosition); - lastHoveredElementHolder.set(currentElement); - }); + this._runEventSequence(currentElement, options); + this.firstMovingStepOccured = true; + + lastHoveredElementHolder.set(currentElement); } private _movingStep (currPosition: AxisValues): Promise { return this.cursor.move(currPosition) - .then(() => adapter.getElementExceptUI(this.cursor.getPosition())) + .then(() => getElementExceptUI(this.cursor.getPosition())) // NOTE: in touch mode, events are simulated for the element for which mousedown was simulated (GH-372) .then((topElement: HTMLElement) => { const currentElement = this._getCorrectedTopElement(topElement); @@ -232,9 +226,10 @@ export default class MoveAutomation { if (this.skipScrolling) return Promise.resolve(false); - const scrollOptions = new ScrollOptions({ offsetX: this.offset.x, offsetY: this.offset.y }, false); + const scrollOptions = new ScrollOptions({ offsetX: this.offset.x, offsetY: this.offset.y }, false); + const scrollAutomation = new ScrollAutomation(this.element, scrollOptions); - return adapter.scroll(this.element, scrollOptions); + return scrollAutomation.run(); } private _moveToCurrentFrame (endPoint: AxisValues): Promise { @@ -263,7 +258,7 @@ export default class MoveAutomation { .then((frame: any) => { iframe = frame; - return Promise.resolve(adapter.position.getIframeClientCoordinates(frame)) + return Promise.resolve(positionUtils.getIframeClientCoordinates(frame)) .then((rect: any) => { msg.left = rect.left; msg.top = rect.top; @@ -276,7 +271,7 @@ export default class MoveAutomation { return void 0; }) .then(() => { - return adapter.getElementExceptUI(this.cursor.getPosition()); + return getElementExceptUI(this.cursor.getPosition()); }) .then((topElement: any) => { iframeUnderCursor = topElement === iframe; @@ -284,12 +279,12 @@ export default class MoveAutomation { if (activeWindow.parent === this.window) msg.iframeUnderCursor = iframeUnderCursor; - return adapter.sendRequestToFrame(msg, MOVE_RESPONSE_CMD, activeWindow); + return sendRequestToFrame(msg, MOVE_RESPONSE_CMD, activeWindow); }) .then((message: any) => { this.cursor.setActiveWindow(this.window); - if (iframeUnderCursor || domUtils.isIframeWindow(this.window)) + if (iframeUnderCursor || utils.dom.isIframeWindow(this.window)) return this.cursor.move(message); return void 0; @@ -300,7 +295,7 @@ export default class MoveAutomation { return this._scroll() .then(() => Promise.all([ this._getTargetClientPoint(), - adapter.style.getWindowDimensions(this.window), + styleUtils.getWindowDimensions(this.window), ])) .then(([endPoint, boundary]: [any, any]) => { if (!boundary.contains(endPoint)) diff --git a/src/client/automation/playback/click/browser-click-strategy.ts b/src/client/automation/playback/click/browser-click-strategy.ts index 0f78f091e6f..9e1769c8462 100644 --- a/src/client/automation/playback/click/browser-click-strategy.ts +++ b/src/client/automation/playback/click/browser-click-strategy.ts @@ -1,12 +1,11 @@ import hammerhead from '../../deps/hammerhead'; import testCafeCore from '../../deps/testcafe-core'; import { focusAndSetSelection } from '../../utils/utils'; -import nextTick from '../../utils/next-tick'; +import nextTick from '../../../core/utils/next-tick'; import createClickCommand from './click-command'; -import { MouseEventArgs } from '../../../../shared/actions/automations/visible-element-automation'; -import { MouseClickEventState } from '../../../../shared/actions/automations/click/index'; -import { MouseClickStrategyBase } from '../../../../shared/actions/automations/click/mouse-click-strategy-base'; +import { MouseEventArgs } from '../../visible-element-automation'; +import { MouseClickEventState } from './index'; // @ts-ignore const Promise = hammerhead.Promise; @@ -48,17 +47,15 @@ function _getElementForClick (mouseDownElement: E, topElement: E, mouseDownEl return arrayUtils.equals(mouseDownElementParentNodes, topElementParentNodes) ? mouseDownElement : null; } -export class MouseClickStrategy extends MouseClickStrategyBase { - public targetElementParentNodes: E[]; - public activeElementBeforeMouseDown: E | null; - public element: E; +export class MouseClickStrategy { + public targetElementParentNodes: Element[]; + public activeElementBeforeMouseDown: Element | null; + public element: Element; public caretPos: number; - public mouseDownElement: E | null; - public eventState: MouseClickEventState; - - public constructor (element: E, caretPos: number) { - super(); + public mouseDownElement: Element | null; + public eventState: MouseClickEventState; + public constructor (element: Element, caretPos: number) { this.element = element; this.caretPos = caretPos; this.targetElementParentNodes = []; @@ -75,7 +72,7 @@ export class MouseClickStrategy extends MouseClickStrategyBase { }; } - public mousedown (eventArgs: MouseEventArgs): Promise { + public mousedown (eventArgs: MouseEventArgs): Promise { this.targetElementParentNodes = domUtils.getParents(eventArgs.element); this.mouseDownElement = eventArgs.element; @@ -105,7 +102,7 @@ export class MouseClickStrategy extends MouseClickStrategyBase { .then(() => this._focus(eventArgs)); } - public mouseup (element: E, eventArgs: MouseEventArgs): Promise> { + public mouseup (element: HTMLElement, eventArgs: MouseEventArgs): Promise { eventArgs.element = element; this.eventState.clickElement = _getElementForClick(this.mouseDownElement, element, this.targetElementParentNodes); @@ -130,7 +127,7 @@ export class MouseClickStrategy extends MouseClickStrategyBase { return this._click(eventArgs); } - public async _click (eventArgs: MouseEventArgs): hammerhead.Promise> { + public async _click (eventArgs: MouseEventArgs): hammerhead.Promise { const clickCommand = createClickCommand(this.eventState, eventArgs); if (!this._isTouchEventWasCancelled()) @@ -156,7 +153,7 @@ export class MouseClickStrategy extends MouseClickStrategyBase { eventUtils.bind(this.element, 'mousedown', onmousedown); } - private _bindBlurHandler (element: E): void { + private _bindBlurHandler (element: Element): void { const onblur = (): void => { this.eventState.blurRaised = true; eventUtils.unbind(element, 'blur', onblur, true); @@ -165,7 +162,7 @@ export class MouseClickStrategy extends MouseClickStrategyBase { eventUtils.bind(element, 'blur', onblur, true); } - private _ensureActiveElementBlur (element: E): Promise { + private _ensureActiveElementBlur (element: Element): Promise { // NOTE: In some cases, mousedown may lead to active element change (browsers raise blur). // We simulate the blur event if the active element was changed after the mousedown, and // the blur event does not get raised automatically (B239273, B253520) @@ -195,7 +192,7 @@ export class MouseClickStrategy extends MouseClickStrategyBase { }); } - private _focus (eventArgs: MouseEventArgs): Promise { + private _focus (eventArgs: MouseEventArgs): Promise { if (this.eventState.simulateDefaultBehavior === false) return Promise.resolve(); @@ -210,7 +207,7 @@ export class MouseClickStrategy extends MouseClickStrategyBase { return focusAndSetSelection(elementForFocus, simulateFocus, this.caretPos); } - private _raiseTouchEvents (eventArgs: MouseEventArgs): void { + private _raiseTouchEvents (eventArgs: MouseEventArgs): void { if (featureDetection.isTouchDevice) { this.eventState.touchStartCancelled = !eventSimulator.touchstart(eventArgs.element, eventArgs.options); this.eventState.touchEndCancelled = !eventSimulator.touchend(eventArgs.element, eventArgs.options); @@ -218,6 +215,6 @@ export class MouseClickStrategy extends MouseClickStrategyBase { } } -export function createMouseClickStrategy (element: Element, caretPos: number): MouseClickStrategy { - return new MouseClickStrategy(element, caretPos); +export function createMouseClickStrategy (element: Element, caretPos: number): MouseClickStrategy { + return new MouseClickStrategy(element, caretPos); } diff --git a/src/shared/actions/automations/click/index.ts b/src/client/automation/playback/click/index.ts similarity index 62% rename from src/shared/actions/automations/click/index.ts rename to src/client/automation/playback/click/index.ts index c0ffa8f0cbe..c881ccb10cd 100644 --- a/src/shared/actions/automations/click/index.ts +++ b/src/client/automation/playback/click/index.ts @@ -1,44 +1,41 @@ -import { adapter } from '../../../adapter'; - -import VisibleElementAutomation, { MouseEventArgs } from '../visible-element-automation'; -import { SharedWindow } from '../../../types'; -import Cursor from '../../cursor'; +import VisibleElementAutomation, { MouseEventArgs } from '../../visible-element-automation'; +import Cursor from '../../cursor/cursor'; import { ClickOptions, Modifiers } from '../../../../test-run/commands/options'; -import delay from '../../../utils/delay'; -import { MouseClickStrategyBase } from './mouse-click-strategy-base'; +import delay from '../../../core/utils/delay'; // @ts-ignore -import { utils } from '../../../../client/automation/deps/hammerhead'; +import { utils, Promise } from '../../deps/hammerhead'; +import { createMouseClickStrategy, MouseClickStrategy } from './browser-click-strategy'; -export interface MouseClickEventState { +export interface MouseClickEventState { mousedownPrevented: boolean; blurRaised: boolean; simulateDefaultBehavior: boolean; - clickElement: E | null; + clickElement: Element | null; touchStartCancelled: boolean; touchEndCancelled: boolean; } -export default class ClickAutomation extends VisibleElementAutomation { +export default class ClickAutomation extends VisibleElementAutomation { private modifiers: Modifiers; - public strategy: MouseClickStrategyBase; + public strategy: MouseClickStrategy; - protected constructor (element: E, clickOptions: ClickOptions, win: W, cursor: Cursor) { + protected constructor (element: HTMLElement, clickOptions: ClickOptions, win: Window, cursor: Cursor) { super(element, clickOptions, win, cursor); this.modifiers = clickOptions.modifiers; - this.strategy = adapter.automations.click.createMouseClickStrategy(this.element, clickOptions.caretPos); + this.strategy = createMouseClickStrategy(this.element, clickOptions.caretPos); } - private _mousedown (eventArgs: MouseEventArgs): Promise { + private _mousedown (eventArgs: MouseEventArgs): Promise { return this.strategy.mousedown(eventArgs); } - private _mouseup (element: E, eventArgs: MouseEventArgs): Promise> { + private _mouseup (element: HTMLElement, eventArgs: MouseEventArgs): Promise { return this.strategy.mouseup(element, eventArgs); } - private run (useStrictElementCheck: boolean): Promise | null> { - let eventArgs: MouseEventArgs; + private run (useStrictElementCheck: boolean): Promise { + let eventArgs: MouseEventArgs; return this ._ensureElement(useStrictElementCheck) @@ -53,7 +50,7 @@ export default class ClickAutomation extends VisibleE screenX: devicePoint?.x, screenY: devicePoint?.y, }, this.modifiers), - } as unknown as MouseEventArgs; + } as unknown as MouseEventArgs; // NOTE: we should raise mouseup event with 'mouseActionStepDelay' after we trigger diff --git a/src/client/automation/playback/click/select-child.js b/src/client/automation/playback/click/select-child.js index 05b1264573d..3de1572b39d 100644 --- a/src/client/automation/playback/click/select-child.js +++ b/src/client/automation/playback/click/select-child.js @@ -3,8 +3,8 @@ import testCafeCore from '../../deps/testcafe-core'; import testCafeUI from '../../deps/testcafe-ui'; import MoveAutomation from '../move/move'; import { MoveOptions } from '../../../../test-run/commands/options'; -import { getDefaultAutomationOffsets } from '../../../../shared/actions/utils/offsets'; -import AutomationSettings from '../../../../shared/actions/automations/settings'; +import { getDefaultAutomationOffsets } from '../../../core/utils/offsets'; +import AutomationSettings from '../../settings'; import cursor from '../../cursor'; const Promise = hammerhead.Promise; diff --git a/src/client/automation/playback/dblclick.js b/src/client/automation/playback/dblclick.js index c3fc01c823d..39a95b34e9c 100644 --- a/src/client/automation/playback/dblclick.js +++ b/src/client/automation/playback/dblclick.js @@ -1,10 +1,10 @@ import hammerhead from '../deps/hammerhead'; import testCafeCore from '../deps/testcafe-core'; import { ClickOptions } from '../../../test-run/commands/options'; -import VisibleElementAutomation from '../../../shared/actions/automations/visible-element-automation'; -import ClickAutomation from '../../../shared/actions/automations/click'; -import AutomationSettings from '../../../shared/actions/automations/settings'; -import cursor from '../cursor'; +import VisibleElementAutomation from '../visible-element-automation'; +import ClickAutomation from './click'; +import AutomationSettings from '../settings'; +import cursor from '../cursor/index'; const featureDetection = hammerhead.utils.featureDetection; const browserUtils = hammerhead.utils.browser; diff --git a/src/client/automation/playback/drag/base.js b/src/client/automation/playback/drag/base.js index 49ca218e13c..862191e6451 100644 --- a/src/client/automation/playback/drag/base.js +++ b/src/client/automation/playback/drag/base.js @@ -6,7 +6,7 @@ import { delay, } from '../../deps/testcafe-core'; import getElementFromPoint from '../../get-element'; -import VisibleElementAutomation from '../../../../shared/actions/automations/visible-element-automation'; +import VisibleElementAutomation from '../../visible-element-automation'; import DragMoveAutomation from '../move/drag-move'; import { MoveOptions } from '../../../../test-run/commands/options'; import cursor from '../../cursor'; @@ -66,21 +66,22 @@ export default class DragAutomationBase extends VisibleElementAutomation { throw new Error('Not implemented'); } - async _drag () { - const { element, offsets, endPoint } = await this._getDestination(); - - this.endPoint = endPoint; - - const dragOptions = new MoveOptions({ - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - modifiers: this.modifiers, - speed: this.speed, - minMovingTime: MIN_MOVING_TIME, - skipDefaultDragBehavior: this.simulateDefaultBehavior === false, - }, false); - - return DragMoveAutomation.create(element, dragOptions, window, cursor) + _drag () { + return this._getDestination() + .then(({ element, offsets, endPoint }) => { + this.endPoint = endPoint; + + const dragOptions = new MoveOptions({ + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + modifiers: this.modifiers, + speed: this.speed, + minMovingTime: MIN_MOVING_TIME, + skipDefaultDragBehavior: this.simulateDefaultBehavior === false, + }, false); + + return DragMoveAutomation.create(element, dragOptions, window, cursor); + }) .then(moveAutomation => { return moveAutomation.run(); }) diff --git a/src/client/automation/playback/drag/to-element.js b/src/client/automation/playback/drag/to-element.js index 91c3af10625..43268039236 100644 --- a/src/client/automation/playback/drag/to-element.js +++ b/src/client/automation/playback/drag/to-element.js @@ -1,6 +1,6 @@ import testCafeCore from '../../deps/testcafe-core'; import DragAutomationBase from './base'; -import { getOffsetOptions } from '../../../../shared/actions/utils/offsets'; +import { getOffsetOptions } from '../../../core/utils/offsets'; const positionUtils = testCafeCore.positionUtils; @@ -17,7 +17,7 @@ export default class DragToElementAutomation extends DragAutomationBase { async _getDestination () { const element = this.destinationElement; const elementRect = positionUtils.getElementRectangle(element); - const offsets = await getOffsetOptions(element, this.destinationOffsetX, this.destinationOffsetY); + const offsets = getOffsetOptions(element, this.destinationOffsetX, this.destinationOffsetY); const endPoint = { x: elementRect.left + offsets.offsetX, diff --git a/src/client/automation/playback/drag/to-offset.js b/src/client/automation/playback/drag/to-offset.js index bc60fa84193..f29494bdb22 100644 --- a/src/client/automation/playback/drag/to-offset.js +++ b/src/client/automation/playback/drag/to-offset.js @@ -13,8 +13,8 @@ export default class DragToOffsetAutomation extends DragAutomationBase { this.dragOffsetY = offsetY; } - _getDestination () { - const startPoint = getAutomationPoint(this.element, this.offsetX, this.offsetY); + async _getDestination () { + const startPoint = await getAutomationPoint(this.element, { x: this.offsetX, y: this.offsetY }); const maxX = styleUtils.getWidth(document); const maxY = styleUtils.getHeight(document); diff --git a/src/client/automation/playback/hover.js b/src/client/automation/playback/hover.js index 5e696b038f5..217e28dead0 100644 --- a/src/client/automation/playback/hover.js +++ b/src/client/automation/playback/hover.js @@ -1,4 +1,4 @@ -import VisibleElementAutomation from '../../../shared/actions/automations/visible-element-automation'; +import VisibleElementAutomation from '../visible-element-automation'; import cursor from '../cursor'; diff --git a/src/client/automation/playback/move/drag-move.js b/src/client/automation/playback/move/drag-move.js index fbda8a404a3..38133838033 100644 --- a/src/client/automation/playback/move/drag-move.js +++ b/src/client/automation/playback/move/drag-move.js @@ -3,9 +3,9 @@ import testCafeCore from '../../deps/testcafe-core'; import getElementFromPoint from '../../get-element'; import DragAndDropState from '../drag/drag-and-drop-state'; import createEventSequence from './event-sequence/create-event-sequence'; -import lastHoveredElementHolder from '../../../../shared/actions/automations/last-hovered-element-holder'; +import lastHoveredElementHolder from '../../last-hovered-element-holder'; import MoveAutomation from './move'; -import AxisValues from '../../../../shared/utils/values/axis-values'; +import AxisValues from '../../../core/utils/values/axis-values'; const nativeMethods = hammerhead.nativeMethods; const featureDetection = hammerhead.utils.featureDetection; @@ -51,14 +51,13 @@ export default class DragMoveAutomation extends MoveAutomation { } _getEventSequenceOptions (currPosition) { - return super._getEventSequenceOptions(currPosition) - .then(({ eventOptions, eventSequenceOptions }) => { - eventOptions.dataTransfer = this.dragAndDropState.dataTransfer; - eventOptions.buttons = eventUtils.BUTTONS_PARAMETER.leftButton; - eventSequenceOptions.holdLeftButton = true; + const { eventOptions, eventSequenceOptions } = super._getEventSequenceOptions(currPosition); - return { eventOptions, eventSequenceOptions }; - }); + eventOptions.dataTransfer = this.dragAndDropState.dataTransfer; + eventOptions.buttons = eventUtils.BUTTONS_PARAMETER.leftButton; + eventSequenceOptions.holdLeftButton = true; + + return { eventOptions, eventSequenceOptions }; } _getCorrectedTopElement (topElement) { diff --git a/src/shared/utils/get-line-rect-intersection.ts b/src/client/automation/playback/move/get-line-rect-intersection.ts similarity index 89% rename from src/shared/utils/get-line-rect-intersection.ts rename to src/client/automation/playback/move/get-line-rect-intersection.ts index 2f8fead21db..5331bfc6496 100644 --- a/src/shared/utils/get-line-rect-intersection.ts +++ b/src/client/automation/playback/move/get-line-rect-intersection.ts @@ -1,6 +1,6 @@ -import { getLineXByYCoord, getLineYByXCoord } from './position'; -import { BoundaryValuesData } from './values/boundary-values'; -import AxisValues, { AxisValuesData } from './values/axis-values'; +import { getLineXByYCoord, getLineYByXCoord } from '../../../core/utils/get-line-by'; +import { BoundaryValuesData } from '../../../core/utils/values/boundary-values'; +import AxisValues, { AxisValuesData } from '../../../core/utils/values/axis-values'; function findIntersectionHorizontal (startLinePoint: AxisValuesData, endLinePoint: AxisValuesData, rectSide: BoundaryValuesData): AxisValues | null { diff --git a/src/client/automation/playback/move/move.js b/src/client/automation/playback/move/move.js index 48f5f58e7c9..7013961fcd4 100644 --- a/src/client/automation/playback/move/move.js +++ b/src/client/automation/playback/move/move.js @@ -4,12 +4,12 @@ import { MoveOptions } from '../../../../test-run/commands/options'; import cursor from '../../cursor'; -import getLineRectIntersection from '../../../../shared/utils/get-line-rect-intersection'; +import getLineRectIntersection from './get-line-rect-intersection'; -import lastHoveredElementHolder from '../../../../shared/actions/automations/last-hovered-element-holder'; -import AxisValues from '../../../../shared/utils/values/axis-values'; +import lastHoveredElementHolder from '../../last-hovered-element-holder'; +import AxisValues from '../../../core/utils/values/axis-values'; -import MoveAutomation from '../../../../shared/actions/automations/move'; +import MoveAutomation from '../../move'; const eventSimulator = hammerhead.eventSandbox.eventSimulator; const messageSandbox = hammerhead.eventSandbox.message; diff --git a/src/client/automation/playback/press/index.js b/src/client/automation/playback/press/index.js index 41e70ef0a13..63795460657 100644 --- a/src/client/automation/playback/press/index.js +++ b/src/client/automation/playback/press/index.js @@ -10,7 +10,7 @@ import { import KeyPressSimulator from './key-press-simulator'; import supportedShortcutHandlers from './shortcuts'; import { getActualKeysAndEventKeyProperties, getDeepActiveElement } from './utils'; -import AutomationSettings from '../../../../shared/actions/automations/settings'; +import AutomationSettings from '../../settings'; import isIframeWindow from '../../../../utils/is-window-in-iframe'; const Promise = hammerhead.Promise; diff --git a/src/client/automation/playback/rclick.js b/src/client/automation/playback/rclick.js index 9c916049c24..8f13e8a2cc1 100644 --- a/src/client/automation/playback/rclick.js +++ b/src/client/automation/playback/rclick.js @@ -1,9 +1,9 @@ import hammerhead from '../deps/hammerhead'; import testCafeCore from '../deps/testcafe-core'; -import VisibleElementAutomation from '../../../shared/actions/automations/visible-element-automation'; +import VisibleElementAutomation from '../visible-element-automation'; import { focusAndSetSelection, focusByRelatedElement } from '../utils/utils'; import cursor from '../cursor'; -import nextTick from '../utils/next-tick'; +import nextTick from '../../core/utils/next-tick'; const Promise = hammerhead.Promise; diff --git a/src/client/automation/playback/scroll-into-view.ts b/src/client/automation/playback/scroll-into-view.ts index 019189e72d4..061ede4ab39 100644 --- a/src/client/automation/playback/scroll-into-view.ts +++ b/src/client/automation/playback/scroll-into-view.ts @@ -1,9 +1,8 @@ -import VisibleElementAutomation from '../../../shared/actions/automations/visible-element-automation'; +import VisibleElementAutomation from '../visible-element-automation'; import { OffsetOptions } from '../../../test-run/commands/options'; -import { SharedWindow } from '../../../shared/types'; -import cursor from '../cursor'; +import cursor from '../cursor/index'; -export default class ScrollIntoViewAutomation extends VisibleElementAutomation { +export default class ScrollIntoViewAutomation extends VisibleElementAutomation { public constructor (element: HTMLElement, offsetOptions: OffsetOptions) { super(element, offsetOptions, window, cursor); } diff --git a/src/client/automation/playback/select/base.js b/src/client/automation/playback/select/base.js index 2ecfb5482b1..2a6b85ae089 100644 --- a/src/client/automation/playback/select/base.js +++ b/src/client/automation/playback/select/base.js @@ -1,6 +1,6 @@ import hammerhead from '../../deps/hammerhead'; import testCafeCore from '../../deps/testcafe-core'; -import VisibleElementAutomation from '../../../../shared/actions/automations/visible-element-automation'; +import VisibleElementAutomation from '../../visible-element-automation'; import getElementFromPoint from '../../get-element'; import * as selectUtils from './utils'; import MoveAutomation from '../move/move'; diff --git a/src/client/automation/playback/set-scroll.ts b/src/client/automation/playback/set-scroll.ts index 355c49af5de..1587f26fadf 100644 --- a/src/client/automation/playback/set-scroll.ts +++ b/src/client/automation/playback/set-scroll.ts @@ -1,8 +1,7 @@ import hammerhead from '../deps/hammerhead'; -import VisibleElementAutomation from '../../../shared/actions/automations/visible-element-automation'; +import VisibleElementAutomation from '../visible-element-automation'; import { OffsetOptions } from '../../../test-run/commands/options'; -import { SharedWindow } from '../../../shared/types'; -import cursor from '../cursor'; +import cursor from '../cursor/index'; const Promise = hammerhead.Promise; @@ -37,9 +36,9 @@ function calculatePosition (el: HTMLElement, position: ScrollPosition): number[] return positions[position]; } -export default class SetScrollAutomation extends VisibleElementAutomation { - private scrollLeft: number; - private scrollTop: number; +export default class SetScrollAutomation extends VisibleElementAutomation { + private readonly scrollLeft: number; + private readonly scrollTop: number; public constructor (element: HTMLElement, { x, y, position, byX, byY }: SetScrollAutomationOptions, offsetOptions: OffsetOptions) { super(element, offsetOptions, window, cursor); diff --git a/src/client/automation/playback/type/index.js b/src/client/automation/playback/type/index.js index e132a307490..e552c311d87 100644 --- a/src/client/automation/playback/type/index.js +++ b/src/client/automation/playback/type/index.js @@ -1,12 +1,12 @@ import hammerhead from '../../deps/hammerhead'; import testCafeCore from '../../deps/testcafe-core'; import { ClickOptions } from '../../../../test-run/commands/options'; -import ClickAutomation from '../../../../shared/actions/automations/click'; +import ClickAutomation from '../click'; import typeText from './type-text'; import getKeyCode from '../../utils/get-key-code'; import getKeyIdentifier from '../../utils/get-key-identifier'; -import { getDefaultAutomationOffsets } from '../../../../shared/actions/utils/offsets'; -import AutomationSettings from '../../../../shared/actions/automations/settings'; +import { getDefaultAutomationOffsets } from '../../../core/utils/offsets'; +import AutomationSettings from '../../settings'; import getKeyProperties from '../../utils/get-key-properties'; import cursor from '../../cursor'; diff --git a/src/client/automation/playback/type/type-text.js b/src/client/automation/playback/type/type-text.js index 2944baca613..9d82e6f943e 100644 --- a/src/client/automation/playback/type/type-text.js +++ b/src/client/automation/playback/type/type-text.js @@ -1,6 +1,6 @@ import hammerhead from '../../deps/hammerhead'; import testCafeCore from '../../deps/testcafe-core'; -import nextTick from '../../utils/next-tick'; +import nextTick from '../../../core/utils/next-tick'; const browserUtils = hammerhead.utils.browser; const eventSandbox = hammerhead.sandbox.event; diff --git a/src/shared/actions/automations/settings.ts b/src/client/automation/settings.ts similarity index 100% rename from src/shared/actions/automations/settings.ts rename to src/client/automation/settings.ts diff --git a/src/client/automation/shared-adapter-initializer.ts b/src/client/automation/shared-adapter-initializer.ts deleted file mode 100644 index 7280e74b990..00000000000 --- a/src/client/automation/shared-adapter-initializer.ts +++ /dev/null @@ -1,31 +0,0 @@ -import testCafeCore from './deps/testcafe-core'; -import { initializeAdapter } from '../../shared/adapter/index'; -import { ScrollOptions } from '../../test-run/commands/options'; -import getElementExceptUI from './utils/get-element-except-ui'; -import ensureMouseEventAfterScroll from './utils/ensure-mouse-event-after-scroll'; -import createEventSequence from './playback/move/event-sequence/create-event-sequence'; -import { createMouseClickStrategy } from './playback/click/browser-click-strategy'; -import cursor from './cursor'; - - -const { positionUtils: position, ScrollAutomation, styleUtils: style, eventUtils: event } = testCafeCore; - -initializeAdapter({ - scroll: (el: any, scrollOptions: ScrollOptions) => new ScrollAutomation(el, scrollOptions).run(), - getElementExceptUI: getElementExceptUI, - position, style, event, - createEventSequence, - sendRequestToFrame: testCafeCore.sendRequestToFrame, - ensureMouseEventAfterScroll, - - automations: { - click: { - createMouseClickStrategy, - }, - - _ensureWindowAndCursorForLegacyTests (automation) { - automation.window = automation.window || window; - automation.cursor = cursor; - }, - }, -}); diff --git a/src/shared/actions/types.d.ts b/src/client/automation/types.d.ts similarity index 85% rename from src/shared/actions/types.d.ts rename to src/client/automation/types.d.ts index f572a07e8d4..2434e033850 100644 --- a/src/shared/actions/types.d.ts +++ b/src/client/automation/types.d.ts @@ -1,6 +1,6 @@ import { ActionCommandBase } from '../../test-run/commands/base'; -import EventEmitter from '../utils/event-emitter'; -import { AxisValuesData } from '../utils/values/axis-values'; +import EventEmitter from '../core/utils/event-emitter'; +import { AxisValuesData } from '../core/utils/values/axis-values'; export interface AutomationHandler { create: (cmd: ActionCommandBase, elements: any[]) => Automation; diff --git a/src/client/automation/utils/ensure-mouse-event-after-scroll.ts b/src/client/automation/utils/ensure-mouse-event-after-scroll.ts index 0fcae61b29a..ba0db7b4188 100644 --- a/src/client/automation/utils/ensure-mouse-event-after-scroll.ts +++ b/src/client/automation/utils/ensure-mouse-event-after-scroll.ts @@ -1,5 +1,5 @@ -import lastHoveredElementHolder from '../../../shared/actions/automations/last-hovered-element-holder'; -import getDevicePoint from '../../../shared/actions/utils/get-device-point'; +import lastHoveredElementHolder from '../last-hovered-element-holder'; +import getDevicePoint from './get-device-point'; import testCafeCore from '../deps/testcafe-core'; import hammerhead from '../deps/hammerhead'; import { MoveBehaviour } from '../playback/move/event-sequence/event-behaviors'; diff --git a/src/client/automation/utils/get-automation-point.js b/src/client/automation/utils/get-automation-point.js deleted file mode 100644 index cd46e7c8d7f..00000000000 --- a/src/client/automation/utils/get-automation-point.js +++ /dev/null @@ -1,16 +0,0 @@ -import { positionUtils } from '../deps/testcafe-core'; -import hammerhead from '../deps/hammerhead'; - -const browserUtils = hammerhead.utils.browser; - -export default function getAutomationPoint (element, offsetX, offsetY) { - const roundFn = browserUtils.isFirefox ? Math.ceil : Math.round; - const elementOffset = positionUtils.getOffsetPosition(element, roundFn); - const left = element === document.documentElement ? 0 : elementOffset.left; - const top = element === document.documentElement ? 0 : elementOffset.top; - - return { - x: left + offsetX, - y: top + offsetY, - }; -} diff --git a/src/client/automation/utils/get-automation-point.ts b/src/client/automation/utils/get-automation-point.ts new file mode 100644 index 00000000000..059bf71936f --- /dev/null +++ b/src/client/automation/utils/get-automation-point.ts @@ -0,0 +1,19 @@ +import AxisValues, { AxisValuesData } from '../../core/utils/values/axis-values'; +// @ts-ignore +import { Promise, utils } from '../../core/deps/hammerhead'; +import * as domUtils from '../../core/utils/dom'; +import * as positionUtils from '../../core/utils/position'; + +export default function getAutomationPoint (element: HTMLElement, offset: AxisValuesData): Promise> { + return Promise.resolve(domUtils.isDocumentElement(element)) + .then((isDocEl: boolean) => { + if (isDocEl) + return new AxisValues(0, 0); + + const roundFn = utils.browser.isFirefox ? Math.ceil : Math.round; + + return Promise.resolve(positionUtils.getOffsetPosition(element, roundFn)) + .then((elementOffset: any) => AxisValues.create(elementOffset)); + }) + .then((point: any) => point.add(offset)); +} diff --git a/src/client/automation/utils/get-device-point.js b/src/client/automation/utils/get-device-point.js deleted file mode 100644 index 5ab0b10407a..00000000000 --- a/src/client/automation/utils/get-device-point.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function getDevicePoint (clientPoint) { - if (!clientPoint) - return null; - - const screenLeft = window.screenLeft || window.screenX; - const screenTop = window.screenTop || window.screenY; - const x = screenLeft + clientPoint.x; - const y = screenTop + clientPoint.y; - - return { x, y }; -} diff --git a/src/client/automation/utils/get-device-point.ts b/src/client/automation/utils/get-device-point.ts new file mode 100644 index 00000000000..11fed048e91 --- /dev/null +++ b/src/client/automation/utils/get-device-point.ts @@ -0,0 +1,17 @@ +import AxisValues from '../../core/utils/values/axis-values'; +// @ts-ignore +import { Promise } from '../../driver/deps/hammerhead'; +import * as positionUtils from '../../core/utils/position'; + +export default function getDevicePoint (clientPoint: AxisValues): Promise | null> { + if (!clientPoint) + return null; + + const windowPosition = positionUtils.getWindowPosition(); + const screenLeft = windowPosition.x; + const screenTop = windowPosition.y; + const x = screenLeft + clientPoint.x; + const y = screenTop + clientPoint.y; + + return new AxisValues(x, y); +} diff --git a/src/client/automation/utils/get-element-except-ui.ts b/src/client/automation/utils/get-element-except-ui.ts index 3d6ef6c812d..5c4a7430efd 100644 --- a/src/client/automation/utils/get-element-except-ui.ts +++ b/src/client/automation/utils/get-element-except-ui.ts @@ -1,11 +1,11 @@ import testCafeCore from '../deps/testcafe-core'; import testCafeUI from '../deps/testcafe-ui'; -import { AxisValuesData } from '../../../shared/utils/values/axis-values'; +import { AxisValuesData } from '../../core/utils/values/axis-values'; const positionUtils = testCafeCore.positionUtils; -export default function getElementFromPoint (point: AxisValuesData, underTopShadowUIElement = false): Promise { +export default function getElementFromPoint (point: AxisValuesData, underTopShadowUIElement = false): Promise { return testCafeUI.hide(underTopShadowUIElement) .then(() => { const topElement = positionUtils.getElementFromPoint(point); diff --git a/src/client/automation/utils/next-tick.js b/src/client/automation/utils/next-tick.js deleted file mode 100644 index 369d7908fe9..00000000000 --- a/src/client/automation/utils/next-tick.js +++ /dev/null @@ -1,9 +0,0 @@ -import hammerhead from '../deps/hammerhead'; - -const Promise = hammerhead.Promise; -const nativeMethods = hammerhead.nativeMethods; - -export default function () { - return new Promise(resolve => nativeMethods.setTimeout.call(window, resolve, 0)); -} - diff --git a/src/client/automation/utils/screen-point-to-client.js b/src/client/automation/utils/screen-point-to-client.js deleted file mode 100644 index 13df5df0924..00000000000 --- a/src/client/automation/utils/screen-point-to-client.js +++ /dev/null @@ -1,12 +0,0 @@ -import { positionUtils, styleUtils } from '../deps/testcafe-core'; - -export default function convertToClient (element, point) { - const elementScroll = styleUtils.getElementScroll(element); - - if (!/html/i.test(element.tagName) && styleUtils.hasScroll(element)) { - point.x -= elementScroll.left; - point.y -= elementScroll.top; - } - - return positionUtils.offsetToClientCoords(point); -} diff --git a/src/client/automation/utils/screen-point-to-client.ts b/src/client/automation/utils/screen-point-to-client.ts new file mode 100644 index 00000000000..cbb0340b855 --- /dev/null +++ b/src/client/automation/utils/screen-point-to-client.ts @@ -0,0 +1,16 @@ +import AxisValues, { AxisValuesData } from '../../core/utils/values/axis-values'; +import * as style from '../../core/utils/style'; +import * as positionUtils from '../../core/utils/position'; +import * as scrollUtils from '../../core/utils/scroll'; + +export default async function convertToClient (element: any, point: AxisValuesData): Promise> { + const elementScroll = await style.getElementScroll(element); + const hasScroll = scrollUtils.hasScroll(element); + + if (!/html/i.test(element.tagName) && hasScroll) { + point.x -= elementScroll.left; + point.y -= elementScroll.top; + } + + return positionUtils.offsetToClientCoords(point); +} diff --git a/src/shared/actions/automations/visible-element-automation.ts b/src/client/automation/visible-element-automation.ts similarity index 63% rename from src/shared/actions/automations/visible-element-automation.ts rename to src/client/automation/visible-element-automation.ts index 60fbfa6e63f..34eca3380cc 100644 --- a/src/shared/actions/automations/visible-element-automation.ts +++ b/src/client/automation/visible-element-automation.ts @@ -1,50 +1,53 @@ -import { adapter } from '../../adapter'; -import getAutomationPoint from '../utils/get-automation-point'; -import screenPointToClient from '../utils/screen-point-to-client'; -import getDevicePoint from '../utils/get-device-point'; -import { getOffsetOptions } from '../utils/offsets'; -import getElementFromPoint from '../../../shared/actions/get-element'; -import AUTOMATION_ERROR_TYPES from '../../../shared/errors/automation-errors'; -import AutomationSettings from '../../../shared/actions/automations/settings'; +import getAutomationPoint from './utils/get-automation-point'; +import screenPointToClient from './utils/screen-point-to-client'; +import getDevicePoint from './utils/get-device-point'; +import { getOffsetOptions } from '../core/utils/offsets'; +import getElementFromPoint from './get-element'; +import AUTOMATION_ERROR_TYPES from '../../shared/errors/automation-errors'; +import AutomationSettings from './settings'; import MoveAutomation from './move'; -import { SharedWindow } from '../../types'; -import AxisValues, { AxisValuesData } from '../../utils/values/axis-values'; -import Cursor from '../cursor'; -import delay from '../../utils/delay'; -import SharedEventEmitter from '../../../shared/utils/event-emitter'; +import AxisValues, { AxisValuesData } from '../core/utils/values/axis-values'; +import Cursor from './cursor/cursor'; +import cursorInstance from './cursor'; +import delay from '../core/utils/delay'; +import SharedEventEmitter from '../core/utils/event-emitter'; import { MoveOptions, ScrollOptions, OffsetOptions, -} from '../../../test-run/commands/options'; +} from '../../test-run/commands/options'; // @ts-ignore -import { utils } from '../../../client/core/deps/hammerhead'; -// @ts-ignore -import * as domUtils from '../../../client/core/utils/dom'; +import { utils } from '../core/deps/hammerhead'; +import * as domUtils from '../core/utils/dom'; +import * as positionUtils from '../core/utils/position'; +import ScrollAutomation from '../core/scroll/index'; +import { Dictionary } from '../../configuration/interfaces'; +import ensureMouseEventAfterScroll from './utils/ensure-mouse-event-after-scroll'; + -interface ElementStateArgsBase { - element: E | null; +interface ElementStateArgsBase { + element: HTMLElement | null; clientPoint: AxisValues | null; screenPoint: AxisValues | null; devicePoint?: AxisValues | null; } -interface ElementStateArgs extends ElementStateArgsBase { +interface ElementStateArgs extends ElementStateArgsBase { isTarget: boolean; inMoving: boolean; } -class ElementState implements ElementStateArgs { - public element: E | null; +class ElementState implements ElementStateArgs { + public element: HTMLElement | null; public clientPoint: AxisValues | null; public screenPoint: AxisValues | null; public devicePoint: AxisValues | null; public isTarget: boolean; public inMoving: boolean; - protected constructor ({ element = null, clientPoint = null, screenPoint = null, isTarget = false, inMoving = false, devicePoint = null }: ElementStateArgs) { + protected constructor ({ element = null, clientPoint = null, screenPoint = null, isTarget = false, inMoving = false, devicePoint = null }: ElementStateArgs) { this.element = element; this.clientPoint = clientPoint; this.screenPoint = screenPoint; @@ -53,22 +56,22 @@ class ElementState implements ElementStateArgs { this.inMoving = inMoving; } - public static async create ({ element, clientPoint, screenPoint, isTarget, inMoving }: ElementStateArgs): Promise> { + public static async create ({ element, clientPoint, screenPoint, isTarget, inMoving }: ElementStateArgs): Promise { let devicePoint = null; if (clientPoint) devicePoint = await getDevicePoint(clientPoint); - const state = new ElementState({ element, clientPoint, screenPoint, isTarget, inMoving, devicePoint }); + const state = new ElementState({ element, clientPoint, screenPoint, isTarget, inMoving, devicePoint }); return state; } } -export interface MouseEventArgs { +export interface MouseEventArgs { point: AxisValuesData | null; screenPoint: AxisValuesData | null; - element: E | null; + element: HTMLElement | null; options: { clientX: number; clientY: number; @@ -82,15 +85,15 @@ export interface MouseEventArgs { } | null; } -export default class VisibleElementAutomation extends SharedEventEmitter { - protected element: E; - private window: W; - protected cursor: Cursor - private TARGET_ELEMENT_FOUND_EVENT: string; +export default class VisibleElementAutomation extends SharedEventEmitter { + protected element: HTMLElement; + public window: Window; + public cursor: Cursor; + private readonly TARGET_ELEMENT_FOUND_EVENT: string; protected automationSettings: AutomationSettings; - private options: OffsetOptions; + private readonly options: OffsetOptions; - protected constructor (element: E, offsetOptions: OffsetOptions, win: W, cursor: Cursor) { + protected constructor (element: HTMLElement, offsetOptions: OffsetOptions, win: Window, cursor: Cursor) { super(); this.TARGET_ELEMENT_FOUND_EVENT = 'automation|target-element-found-event'; @@ -103,13 +106,18 @@ export default class VisibleElementAutomation extends this.cursor = cursor; // NOTE: only for legacy API - adapter.automations._ensureWindowAndCursorForLegacyTests(this); + this._ensureWindowAndCursorForLegacyTests(this); + } + + private _ensureWindowAndCursorForLegacyTests (automation: VisibleElementAutomation): void { + automation.window = automation.window || window; // eslint-disable-line no-undef + automation.cursor = cursorInstance; } - protected async _getElementForEvent (eventArgs: MouseEventArgs): Promise { - const expectedElement = await adapter.position.containsOffset(this.element, this.options.offsetX, this.options.offsetY) ? this.element : null; + protected async _getElementForEvent (eventArgs: MouseEventArgs): Promise { + const expectedElement = positionUtils.containsOffset(this.element, this.options.offsetX, this.options.offsetY) ? this.element : null; - return getElementFromPoint(eventArgs.point as AxisValuesData, this.window, expectedElement); + return getElementFromPoint(eventArgs.point as AxisValuesData, this.window, expectedElement as HTMLElement); } private async _moveToElement (): Promise { @@ -124,24 +132,25 @@ export default class VisibleElementAutomation extends private _scrollToElement (): Promise { let wasScrolled = false; const scrollOptions = new ScrollOptions(this.options, false); + const scrollAutomation = new ScrollAutomation(this.element, scrollOptions); - return adapter.scroll(this.element, scrollOptions) - .then(scrollWasPerformed => { - wasScrolled = scrollWasPerformed; + return scrollAutomation.run() + .then((scrollWasPerformed: boolean | Dictionary) => { + wasScrolled = !!scrollWasPerformed; return delay(this.automationSettings.mouseActionStepDelay); }) .then(() => getElementFromPoint(this.cursor.getPosition(), this.window)) - .then(currentElement => { - return adapter.ensureMouseEventAfterScroll(currentElement, this.element, wasScrolled); + .then((currentElement: Element) => { + return ensureMouseEventAfterScroll(currentElement, this.element, wasScrolled); }) .then(() => { return wasScrolled; }); } - private async _getElementOffset (): Promise<{ offsetX: number; offsetY: number }> { - const defaultOffsets = await getOffsetOptions(this.element); + private _getElementOffset (): { offsetX: number; offsetY: number } { + const defaultOffsets = getOffsetOptions(this.element); let { offsetX, offsetY } = this.options; @@ -151,22 +160,22 @@ export default class VisibleElementAutomation extends return { offsetX, offsetY }; } - private async _wrapAction (action: () => Promise): Promise> { - const { offsetX: x, offsetY: y } = await this._getElementOffset(); + private async _wrapAction (action: () => Promise): Promise { + const { offsetX: x, offsetY: y } = this._getElementOffset(); const screenPointBeforeAction = await getAutomationPoint(this.element, { x, y }); - const clientPositionBeforeAction = await adapter.position.getClientPosition(this.element); + const clientPositionBeforeAction = await positionUtils.getClientPosition(this.element); await action(); const screenPointAfterAction = await getAutomationPoint(this.element, { x, y }); - const clientPositionAfterAction = await adapter.position.getClientPosition(this.element); + const clientPositionAfterAction = await positionUtils.getClientPosition(this.element); const clientPoint = await screenPointToClient(this.element, screenPointAfterAction); - const expectedElement = await adapter.position.containsOffset(this.element, x, y) ? this.element : null; + const expectedElement = await positionUtils.containsOffset(this.element, x, y) ? this.element : null; - const element = await getElementFromPoint(clientPoint, this.window, expectedElement); + const element = await getElementFromPoint(clientPoint, this.window, expectedElement as HTMLElement); if (!element) { - return ElementState.create({ + return ElementState.create({ element: null, clientPoint: null, screenPoint: null, @@ -202,7 +211,7 @@ export default class VisibleElementAutomation extends }); } - private static _checkElementState (state: ElementState, useStrictElementCheck: boolean): ElementState { + private static _checkElementState (state: ElementState, useStrictElementCheck: boolean): ElementState { if (!state.element) throw new Error(AUTOMATION_ERROR_TYPES.elementIsInvisibleError); @@ -212,7 +221,7 @@ export default class VisibleElementAutomation extends return state; } - protected _ensureElement (useStrictElementCheck: boolean, skipCheckAfterMoving = false, skipMoving = false): Promise> { + protected _ensureElement (useStrictElementCheck: boolean, skipCheckAfterMoving = false, skipMoving = false): Promise { return this ._wrapAction(() => this._scrollToElement()) .then(state => VisibleElementAutomation._checkElementState(state, useStrictElementCheck)) @@ -237,7 +246,7 @@ export default class VisibleElementAutomation extends }); } - private async _contains (parent: E, child: E): Promise { + private async _contains (parent: Element, child: Element): Promise { const parents = await domUtils.getParents(child); for (const el of parents) { diff --git a/src/client/core/barriers/emitters/client-request.ts b/src/client/core/barriers/emitters/client-request.ts index b38b8dd8b4d..810ef50fa43 100644 --- a/src/client/core/barriers/emitters/client-request.ts +++ b/src/client/core/barriers/emitters/client-request.ts @@ -1,5 +1,5 @@ import hammerhead from '../../deps/hammerhead'; -import EventEmitter from '../../../../shared/utils/event-emitter'; +import EventEmitter from '../../utils/event-emitter'; import { ClientRequestEmitter } from '../../../../shared/types'; diff --git a/src/client/core/barriers/emitters/script-execution.ts b/src/client/core/barriers/emitters/script-execution.ts index 1e73c9d9720..486ff38910c 100644 --- a/src/client/core/barriers/emitters/script-execution.ts +++ b/src/client/core/barriers/emitters/script-execution.ts @@ -1,5 +1,5 @@ import hammerhead from '../../deps/hammerhead'; -import EventEmitter from '../../../../shared/utils/event-emitter'; +import EventEmitter from '../../utils/event-emitter'; import { ScriptExecutionEmitter } from '../../../../shared/types'; diff --git a/src/client/core/index.js b/src/client/core/index.js index 972168f3288..3c199e104d6 100644 --- a/src/client/core/index.js +++ b/src/client/core/index.js @@ -1,7 +1,3 @@ -// NOTE: Initializer should be the first -import './scroll/adapter/initializer'; -import './shared-adapter-initializer'; - import hammerhead from './deps/hammerhead'; import KEY_MAPS from './utils/key-maps'; @@ -19,7 +15,7 @@ import * as domUtils from './utils/dom'; import * as contentEditable from './utils/content-editable'; import * as positionUtils from './utils/position'; import * as styleUtils from './utils/style'; -import * as scrollUtils from './utils/shared/scroll'; +import * as scrollUtils from './utils/scroll'; import * as eventUtils from './utils/event'; import * as arrayUtils from './utils/array'; import * as promiseUtils from './utils/promise'; @@ -37,11 +33,6 @@ import * as browser from '../browser'; import selectorTextFilter from '../../client-functions/selectors/selector-text-filter'; import selectorAttributeFilter from '../../client-functions/selectors/selector-attribute-filter'; -import { initializeAdapter as initializeUtilsAdapter } from './utils/shared/adapter/index'; -import utilsAdapterInitializer from './utils/shared/adapter/initializer'; - - -initializeUtilsAdapter(utilsAdapterInitializer); const exports = {}; diff --git a/src/client/core/scroll/adapter/index.ts b/src/client/core/scroll/adapter/index.ts deleted file mode 100644 index 526dc18034f..00000000000 --- a/src/client/core/scroll/adapter/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ScrollCoreAdapter } from '../types'; - -// @ts-ignore -const emptyAdapter: ScrollCoreAdapter = {}; - -export default emptyAdapter; - -export function initializeAdapter (initializer: ScrollCoreAdapter): void { - // eslint-disable-next-line no-restricted-globals - const keys = Object.keys(initializer) as (keyof ScrollCoreAdapter)[]; - - for (const key of keys) - // @ts-ignore - emptyAdapter[key] = initializer[key]; -} diff --git a/src/client/core/scroll/adapter/initializer.ts b/src/client/core/scroll/adapter/initializer.ts deleted file mode 100644 index 50d3bf5104c..00000000000 --- a/src/client/core/scroll/adapter/initializer.ts +++ /dev/null @@ -1,11 +0,0 @@ -// @ts-ignore -import { Promise } from '../../deps/hammerhead'; -import { initializeAdapter } from './index'; -import controller from '../controller'; - - -initializeAdapter({ - PromiseCtor: Promise, - - controller, -}); diff --git a/src/client/core/scroll/index.ts b/src/client/core/scroll/index.ts index 3705883b4f8..9130dc64c38 100644 --- a/src/client/core/scroll/index.ts +++ b/src/client/core/scroll/index.ts @@ -1,23 +1,17 @@ -import utilsAdapter from '../utils/shared/adapter/index'; -import scrollAdapter from './adapter/index'; -import { hasScroll, getScrollableParents } from '../utils/shared/scroll'; -import * as positionUtils from '../utils/shared/position'; -import * as promiseUtils from '../../../shared/utils/promise'; -import { isFixedElement } from '../utils/shared/style'; +import { hasScroll, getScrollableParents } from '../utils/scroll'; +import * as positionUtils from '../utils/position'; +import * as promiseUtils from '../../core/utils/promise'; import isIframeWindow from '../../../utils/is-window-in-iframe'; -import AxisValues, { LeftTopValues } from '../../../shared/utils/values/axis-values'; -import Dimensions from '../../../shared/utils/values/dimensions'; +import AxisValues, { LeftTopValues } from '../utils/values/axis-values'; +import Dimensions from '../utils/values/dimensions'; import { Dictionary } from '../../../configuration/interfaces'; import { ScrollOptions } from '../../../test-run/commands/options'; - - -export interface ScrollResultProxyless { - scrollWasPerformed: boolean; - offsetX: number; - offsetY: number; - maxScrollMargin: LeftTopValues; -} - +import * as domUtils from '../utils/dom'; +import * as styleUtils from '../utils/style'; +import sendRequestToFrame from '../utils/send-request-to-frame'; +// @ts-ignore +import { Promise } from '../deps/hammerhead'; +import scrollController from './controller'; const DEFAULT_MAX_SCROLL_MARGIN = 50; const SCROLL_MARGIN_INCREASE_STEP = 20; @@ -26,14 +20,14 @@ export default class ScrollAutomation { public static readonly SCROLL_REQUEST_CMD = 'automation|scroll|request'; public static readonly SCROLL_RESPONSE_CMD = 'automation|scroll|response'; - private readonly _element: Element; + private readonly _element: HTMLElement; private readonly _offsets: AxisValues; private readonly _skipParentFrames: boolean; private readonly _scrollToCenter: boolean; private _maxScrollMargin: LeftTopValues; private _scrollWasPerformed: boolean; - public constructor (element: Element, scrollOptions: ScrollOptions, maxScrollMargin?: LeftTopValues) { + public constructor (element: HTMLElement, scrollOptions: ScrollOptions, maxScrollMargin?: LeftTopValues) { this._element = element; this._offsets = new AxisValues(scrollOptions.offsetX, scrollOptions.offsetY); this._scrollToCenter = !!scrollOptions.scrollToCenter; @@ -45,31 +39,31 @@ export default class ScrollAutomation { } private static _isScrollValuesChanged (scrollElement: Element | Document, originalScroll: LeftTopValues): boolean { - return utilsAdapter.style.getScrollLeft(scrollElement) !== originalScroll.left || - utilsAdapter.style.getScrollTop(scrollElement) !== originalScroll.top; + return styleUtils.getScrollLeft(scrollElement) !== originalScroll.left || + styleUtils.getScrollTop(scrollElement) !== originalScroll.top; } private _setScroll (element: Element, { left, top }: LeftTopValues): Promise { - const scrollElement = utilsAdapter.dom.isHtmlElement(element) ? utilsAdapter.dom.findDocument(element) : element; + const scrollElement = domUtils.isHtmlElement(element) ? domUtils.findDocument(element) : element; const originalScroll = { - left: utilsAdapter.style.getScrollLeft(scrollElement), - top: utilsAdapter.style.getScrollTop(scrollElement), + left: styleUtils.getScrollLeft(scrollElement), + top: styleUtils.getScrollTop(scrollElement), }; left = Math.max(left, 0); top = Math.max(top, 0); - let scrollPromise = scrollAdapter.controller.waitForScroll(scrollElement); + let scrollPromise = scrollController.waitForScroll(scrollElement); - utilsAdapter.style.setScrollLeft(scrollElement, left); - utilsAdapter.style.setScrollTop(scrollElement, top); + styleUtils.setScrollLeft(scrollElement, left); + styleUtils.setScrollTop(scrollElement, top); if (!ScrollAutomation._isScrollValuesChanged(scrollElement, originalScroll)) { // @ts-ignore scrollPromise.cancel(); - return scrollAdapter.PromiseCtor.resolve(); + return Promise.resolve(); } scrollPromise = scrollPromise.then(() => { @@ -185,11 +179,11 @@ export default class ScrollAutomation { left === parentDimensions.scroll.left && top === parentDimensions.scroll.top; } - private _scrollToChild (parent: Element, child: Element, offsets: AxisValues): Promise { + private _scrollToChild (parent: HTMLElement, child: HTMLElement, offsets: AxisValues): Promise { const parentDimensions = positionUtils.getClientDimensions(parent); const childDimensions = positionUtils.getClientDimensions(child); - const windowWidth = utilsAdapter.style.getInnerWidth(window); - const windowHeight = utilsAdapter.style.getInnerHeight(window); + const windowWidth = styleUtils.getInnerWidth(window); + const windowHeight = styleUtils.getInnerHeight(window); let scrollPos = parentDimensions.scroll; let needScroll = !this._isChildFullyVisible(parentDimensions, childDimensions, offsets); @@ -218,7 +212,7 @@ export default class ScrollAutomation { private _scrollElement (): Promise { if (!hasScroll(this._element)) - return scrollAdapter.PromiseCtor.resolve(); + return Promise.resolve(); const elementDimensions = positionUtils.getClientDimensions(this._element); const scroll = this._getScrollToPoint(elementDimensions, this._offsets, this._maxScrollMargin); @@ -226,16 +220,16 @@ export default class ScrollAutomation { return this._setScroll(this._element, scroll); } - private _scrollParents (): Promise { + private _scrollParents (): Promise> { const parents = getScrollableParents(this._element); let currentChild = this._element; - const scrollLeft = utilsAdapter.style.getScrollLeft(currentChild); - const scrollTop = utilsAdapter.style.getScrollTop(currentChild); + const scrollLeft = styleUtils.getScrollLeft(currentChild); + const scrollTop = styleUtils.getScrollTop(currentChild); const currentOffset = AxisValues.create(this._offsets).sub(new AxisValues(scrollLeft, scrollTop).round()); let childDimensions = null; let parentDimensions = null; - const scrollParentsPromise = promiseUtils.times(parents.length, i => { + const scrollParentsPromise = promiseUtils.times(parents.length, (i: number) => { return this._scrollToChild(parents[i], currentChild, currentOffset) .then(() => { childDimensions = positionUtils.getClientDimensions(currentChild); @@ -256,8 +250,8 @@ export default class ScrollAutomation { maxScrollMargin: this._maxScrollMargin, }; - if (!utilsAdapter.sendRequestToFrame) - return scrollParentsPromise.then(() => state as ScrollResultProxyless); + if (!sendRequestToFrame) + return scrollParentsPromise.then(() => state); return scrollParentsPromise .then(() => { @@ -267,13 +261,13 @@ export default class ScrollAutomation { state.cmd = ScrollAutomation.SCROLL_REQUEST_CMD; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, consistent-return - return utilsAdapter.sendRequestToFrame!(state, ScrollAutomation.SCROLL_RESPONSE_CMD, window.parent); + return sendRequestToFrame!(state, ScrollAutomation.SCROLL_RESPONSE_CMD, window.parent); }) .then(() => this._scrollWasPerformed); } private static _getFixedAncestorOrSelf (element: Element): Node | null { - return utilsAdapter.dom.findParent(element, true, isFixedElement); + return domUtils.findParent(element, true, styleUtils.isFixedElement); } private _isTargetElementObscuredInPoint (point: AxisValues): boolean { @@ -287,7 +281,7 @@ export default class ScrollAutomation { return !!fixedElement && !fixedElement.contains(this._element); } - public run (): Promise { + public run (): Promise> { return this._scrollElement() .then(() => this._scrollParents()); } diff --git a/src/client/core/scroll/types.d.ts b/src/client/core/scroll/types.d.ts deleted file mode 100644 index 76fb88d28fa..00000000000 --- a/src/client/core/scroll/types.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ScrollCoreAdapter { - PromiseCtor: typeof Promise; - controller: { - waitForScroll (scrollElement: any): Promise; - }; -} diff --git a/src/client/core/shared-adapter-initializer.ts b/src/client/core/shared-adapter-initializer.ts deleted file mode 100644 index 48bbb58fc30..00000000000 --- a/src/client/core/shared-adapter-initializer.ts +++ /dev/null @@ -1,29 +0,0 @@ -// @ts-ignore -import hammerhead from './deps/hammerhead'; -import { initializeAdapter } from '../../shared/adapter/index'; -import * as position from './utils/position'; -import * as style from './utils/style'; -import * as event from './utils/event'; -import { MouseClickStrategyEmpty } from '../../shared/actions/automations/click/mouse-click-strategy-base'; - -const { Promise } = hammerhead; - - -initializeAdapter({ - position, style, event, - // NOTE: this functions are unnecessary in the core - getElementExceptUI: () => Promise.resolve(), - scroll: () => Promise.resolve(), - createEventSequence: () => {}, - sendRequestToFrame: () => Promise.resolve(), - ensureMouseEventAfterScroll: () => Promise.resolve(), - - automations: { - click: { - createMouseClickStrategy: () => new MouseClickStrategyEmpty(), - }, - - _ensureWindowAndCursorForLegacyTests () { // eslint-disable-line no-empty-function - }, - }, -}); diff --git a/src/client/core/utils/delay.js b/src/client/core/utils/delay.js deleted file mode 100644 index 70d433ae736..00000000000 --- a/src/client/core/utils/delay.js +++ /dev/null @@ -1,9 +0,0 @@ -import hammerhead from '../deps/hammerhead'; - -const Promise = hammerhead.Promise; -const nativeMethods = hammerhead.nativeMethods; - - -export default function (ms) { - return new Promise(resolve => nativeMethods.setTimeout.call(window, resolve, ms)); -} diff --git a/src/client/core/utils/delay.ts b/src/client/core/utils/delay.ts new file mode 100644 index 00000000000..bf121845f43 --- /dev/null +++ b/src/client/core/utils/delay.ts @@ -0,0 +1,9 @@ +import hammerhead from '../deps/hammerhead'; + +const Promise = hammerhead.Promise; +const nativeMethods = hammerhead.nativeMethods; + + +export default function (ms: number): Promise { + return new Promise((resolve: () => void) => nativeMethods.setTimeout.call(window, resolve, ms)); +} diff --git a/src/client/core/utils/dom.js b/src/client/core/utils/dom.js index 749156c10af..a86ba6673f0 100644 --- a/src/client/core/utils/dom.js +++ b/src/client/core/utils/dom.js @@ -504,6 +504,3 @@ export function isDocumentElement (el) { return el === document.documentElement; } -export function isIframeWindow () { - return false; -} diff --git a/src/shared/utils/event-emitter.ts b/src/client/core/utils/event-emitter.ts similarity index 93% rename from src/shared/utils/event-emitter.ts rename to src/client/core/utils/event-emitter.ts index 0525fd2b3ea..bced8f6aa24 100644 --- a/src/shared/utils/event-emitter.ts +++ b/src/client/core/utils/event-emitter.ts @@ -1,6 +1,6 @@ -import { Dictionary } from '../../configuration/interfaces'; +import { Dictionary } from '../../../configuration/interfaces'; // @ts-ignore -import { nativeMethods } from '../../client/driver/deps/hammerhead'; +import { nativeMethods } from '../../driver/deps/hammerhead'; type Listener = (...args: any[]) => void; diff --git a/src/shared/utils/position.ts b/src/client/core/utils/get-line-by.ts similarity index 100% rename from src/shared/utils/position.ts rename to src/client/core/utils/get-line-by.ts diff --git a/src/shared/utils/next-tick.ts b/src/client/core/utils/next-tick.ts similarity index 100% rename from src/shared/utils/next-tick.ts rename to src/client/core/utils/next-tick.ts diff --git a/src/shared/actions/utils/offsets.ts b/src/client/core/utils/offsets.ts similarity index 66% rename from src/shared/actions/utils/offsets.ts rename to src/client/core/utils/offsets.ts index 6eb1c201635..ba1919646d4 100644 --- a/src/shared/actions/utils/offsets.ts +++ b/src/client/core/utils/offsets.ts @@ -1,4 +1,4 @@ -import { adapter } from '../../adapter'; +import * as positionUtils from './position'; function calcOffset (size: number): number { const offset = size / 2; @@ -6,17 +6,16 @@ function calcOffset (size: number): number { return offset < 1 ? 0 : Math.round(offset); } -export async function getDefaultAutomationOffsets (element: any): Promise<{ offsetX: number; offsetY: number }> { - const rect = await adapter.position.getElementRectangle(element); - +export function getDefaultAutomationOffsets (element: any): { offsetX: number; offsetY: number } { + const rect = positionUtils.getElementRectangle(element); const offsetX = calcOffset(rect.width); const offsetY = calcOffset(rect.height); return { offsetX, offsetY }; } -export async function getOffsetOptions (element: any, offsetX?: number, offsetY?: number): Promise<{ offsetX: number; offsetY: number }> { - const defaultOffsets = await getDefaultAutomationOffsets(element); +export function getOffsetOptions (element: any, offsetX?: number, offsetY?: number): { offsetX: number; offsetY: number } { + const defaultOffsets = getDefaultAutomationOffsets(element); offsetX = typeof offsetX === 'number' ? Math.round(offsetX) : defaultOffsets.offsetX; offsetY = typeof offsetY === 'number' ? Math.round(offsetY) : defaultOffsets.offsetY; @@ -24,7 +23,7 @@ export async function getOffsetOptions (element: any, offsetX?: number, offsetY? if (offsetX > 0 && offsetY > 0) return { offsetX, offsetY }; - const dimensions = await adapter.position.getClientDimensions(element); + const dimensions = positionUtils.getClientDimensions(element); const width = Math.round(Math.max(element.scrollWidth, dimensions.width)); const height = Math.round(Math.max(element.scrollHeight, dimensions.height)); const maxX = dimensions.scrollbar.right + dimensions.border.left + dimensions.border.right + width; diff --git a/src/client/core/utils/position.js b/src/client/core/utils/position.js deleted file mode 100644 index e48616b32af..00000000000 --- a/src/client/core/utils/position.js +++ /dev/null @@ -1,158 +0,0 @@ -import hammerhead from '../deps/hammerhead'; -import * as styleUtils from './style'; -import * as domUtils from './dom'; -import * as shared from './shared/position'; -import AxisValues from '../../../shared/utils/values/axis-values'; - -export { isElementVisible, isIframeVisible } from './shared/visibility'; - -export const getElementRectangle = hammerhead.utils.position.getElementRectangle; -export const getOffsetPosition = hammerhead.utils.position.getOffsetPosition; -export const offsetToClientCoords = hammerhead.utils.position.offsetToClientCoords; -export const getClientDimensions = shared.getClientDimensions; -export const getElementFromPoint = shared.getElementFromPoint; -export const calcRelativePosition = shared.calcRelativePosition; - -export function getIframeClientCoordinates (iframe) { - const { left, top } = getOffsetPosition(iframe); - const clientPosition = offsetToClientCoords({ x: left, y: top }); - const iframeBorders = styleUtils.getBordersWidth(iframe); - const iframePadding = styleUtils.getElementPadding(iframe); - const iframeRectangleLeft = clientPosition.x + iframeBorders.left + iframePadding.left; - const iframeRectangleTop = clientPosition.y + iframeBorders.top + iframePadding.top; - - return { - left: iframeRectangleLeft, - top: iframeRectangleTop, - right: iframeRectangleLeft + styleUtils.getWidth(iframe), - bottom: iframeRectangleTop + styleUtils.getHeight(iframe), - }; -} - -export function containsOffset (el, offsetX, offsetY) { - const dimensions = getClientDimensions(el); - const width = Math.max(el.scrollWidth, dimensions.width); - const height = Math.max(el.scrollHeight, dimensions.height); - const maxX = dimensions.scrollbar.right + dimensions.border.left + dimensions.border.right + width; - const maxY = dimensions.scrollbar.bottom + dimensions.border.top + dimensions.border.bottom + height; - - return (typeof offsetX === 'undefined' || offsetX >= 0 && maxX >= offsetX) && - (typeof offsetY === 'undefined' || offsetY >= 0 && maxY >= offsetY); -} - -export function getEventAbsoluteCoordinates (ev) { - const el = ev.target || ev.srcElement; - const pageCoordinates = getEventPageCoordinates(ev); - const curDocument = domUtils.findDocument(el); - let xOffset = 0; - let yOffset = 0; - - if (domUtils.isElementInIframe(curDocument.documentElement)) { - const currentIframe = domUtils.getIframeByElement(curDocument); - - if (currentIframe) { - const iframeOffset = getOffsetPosition(currentIframe); - const iframeBorders = styleUtils.getBordersWidth(currentIframe); - - xOffset = iframeOffset.left + iframeBorders.left; - yOffset = iframeOffset.top + iframeBorders.top; - } - } - - return { - x: pageCoordinates.x + xOffset, - y: pageCoordinates.y + yOffset, - }; -} - -export function getEventPageCoordinates (ev) { - const curCoordObject = /^touch/.test(ev.type) && ev.targetTouches ? ev.targetTouches[0] || ev.changedTouches[0] : ev; - - const bothPageCoordinatesAreZero = curCoordObject.pageX === 0 && curCoordObject.pageY === 0; - const notBothClientCoordinatesAreZero = curCoordObject.clientX !== 0 || curCoordObject.clientY !== 0; - - if ((curCoordObject.pageX === null || bothPageCoordinatesAreZero && notBothClientCoordinatesAreZero) && - curCoordObject.clientX !== null) { - const currentDocument = domUtils.findDocument(ev.target || ev.srcElement); - const html = currentDocument.documentElement; - const body = currentDocument.body; - - return { - x: Math.round(curCoordObject.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - - (html.clientLeft || 0)), - y: Math.round(curCoordObject.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - - (html.clientTop || 0)), - }; - } - return { - x: Math.round(curCoordObject.pageX), - y: Math.round(curCoordObject.pageY), - }; -} - -export function getIframePointRelativeToParentFrame (pos, iframeWin) { - const iframe = domUtils.findIframeByWindow(iframeWin); - const iframeOffset = getOffsetPosition(iframe); - const iframeBorders = styleUtils.getBordersWidth(iframe); - const iframePadding = styleUtils.getElementPadding(iframe); - - return offsetToClientCoords({ - x: pos.x + iframeOffset.left + iframeBorders.left + iframePadding.left, - y: pos.y + iframeOffset.top + iframeBorders.top + iframePadding.top, - }); -} - -export function clientToOffsetCoord (coords, currentDocument) { - const doc = currentDocument || document; - - return { - x: coords.x + styleUtils.getScrollLeft(doc), - y: coords.y + styleUtils.getScrollTop(doc), - }; -} - -export function findCenter (el) { - const rectangle = getElementRectangle(el); - - return { - x: Math.round(rectangle.left + rectangle.width / 2), - y: Math.round(rectangle.top + rectangle.height / 2), - }; -} - -export function getClientPosition (el) { - const { left, top } = getOffsetPosition(el); - - const clientCoords = offsetToClientCoords({ x: left, y: top }); - - clientCoords.x = Math.round(clientCoords.x); - clientCoords.y = Math.round(clientCoords.y); - - return clientCoords; -} - -export function getElementClientRectangle (el) { - const rect = getElementRectangle(el); - const clientPos = offsetToClientCoords({ - x: rect.left, - y: rect.top, - }); - - return { - height: rect.height, - left: clientPos.x, - top: clientPos.y, - width: rect.width, - }; -} - -export function isInRectangle ({ x, y }, rectangle) { - return x >= rectangle.left && x <= rectangle.right && y >= rectangle.top && y <= rectangle.bottom; -} - -export function getWindowPosition () { - const x = window.screenLeft || window.screenX; - const y = window.screenTop || window.screenY; - - return new AxisValues(x, y); -} diff --git a/src/client/core/utils/position.ts b/src/client/core/utils/position.ts new file mode 100644 index 00000000000..0f4b7629213 --- /dev/null +++ b/src/client/core/utils/position.ts @@ -0,0 +1,228 @@ +import hammerhead from '../deps/hammerhead'; +import * as styleUtils from './style'; +import * as domUtils from './dom'; +import AxisValues, { AxisValuesData } from './values/axis-values'; +import BoundaryValues, { BoundaryValuesData } from './values/boundary-values'; +import Dimensions from './values/dimensions'; + + +export const getElementRectangle = hammerhead.utils.position.getElementRectangle; +export const getOffsetPosition = hammerhead.utils.position.getOffsetPosition; +export const offsetToClientCoords = hammerhead.utils.position.offsetToClientCoords; + +export function getClientDimensions (target: HTMLElement): Dimensions { + const isHtmlElement = domUtils.isHtmlElement(target); + const body = isHtmlElement ? target.getElementsByTagName('body')[0] : null; + const elementRect = target.getBoundingClientRect(); + const elBorders = BoundaryValues.create(styleUtils.getBordersWidth(target)); + const elScroll = styleUtils.getElementScroll(target); + const isElementInIframe = domUtils.isElementInIframe(target); + const isCompatMode = target.ownerDocument.compatMode === 'BackCompat'; + const elPosition = isHtmlElement ? new AxisValues(0, 0) : AxisValues.create(elementRect); + + let elHeight = elementRect.height; + let elWidth = elementRect.width; + + if (isHtmlElement) { + if (body && isCompatMode) { + elHeight = body.clientHeight; + elWidth = body.clientWidth; + } + else { + elHeight = target.clientHeight; + elWidth = target.clientWidth; + } + } + + if (isElementInIframe) { + const iframeElement = domUtils.getIframeByElement(target); + + if (iframeElement) { + const iframeOffset = getOffsetPosition(iframeElement); + const clientOffset = offsetToClientCoords(AxisValues.create(iframeOffset)); + const iframeBorders = styleUtils.getBordersWidth(iframeElement); + + elPosition.add(clientOffset).add(AxisValues.create(iframeBorders)); + + if (isHtmlElement) + elBorders.add(iframeBorders); + } + } + + const hasRightScrollbar = !isHtmlElement && styleUtils.getInnerWidth(target) !== target.clientWidth; + const hasBottomScrollbar = !isHtmlElement && styleUtils.getInnerHeight(target) !== target.clientHeight; + + const scrollbar = { + right: hasRightScrollbar ? domUtils.getScrollbarSize() : 0, + bottom: hasBottomScrollbar ? domUtils.getScrollbarSize() : 0, + }; + + return new Dimensions(elWidth, elHeight, elPosition, elBorders, elScroll, scrollbar); +} + +export function getElementFromPoint ({ x, y }: AxisValuesData): Element | null { + // @ts-ignore + const ieFn = document.getElementFromPoint; + const func = ieFn || document.elementFromPoint; + + let el: Element | null = null; + + try { + // Permission denied to access property 'getElementFromPoint' error in iframe + el = func.call(document, x, y); + } + catch { + return null; + } + + //NOTE: elementFromPoint returns null when is's a border of an iframe + if (el === null) + el = func.call(document, x - 1, y - 1); + + while (el && el.shadowRoot && el.shadowRoot.elementFromPoint) { + const shadowEl = el.shadowRoot.elementFromPoint(x, y); + + if (!shadowEl || el === shadowEl) + break; + + el = shadowEl; + } + + return el; +} + +export function calcRelativePosition (dimensions: Dimensions, toDimensions: Dimensions): BoundaryValuesData { + const pos = BoundaryValues.create({ + top: dimensions.top - toDimensions.top, + left: dimensions.left - toDimensions.left, + right: toDimensions.right - dimensions.right, + bottom: toDimensions.bottom - dimensions.bottom, + }); + + return pos.sub(toDimensions.border).sub(toDimensions.scrollbar).round(Math.ceil, Math.floor); +} + +export function getIframeClientCoordinates (iframe: HTMLIFrameElement): BoundaryValues { + const { left, top } = getOffsetPosition(iframe); + const clientPosition = offsetToClientCoords({ x: left, y: top }); + const iframeBorders = styleUtils.getBordersWidth(iframe); + const iframePadding = styleUtils.getElementPadding(iframe); + const iframeRectangleLeft = clientPosition.x + iframeBorders.left + iframePadding.left; + const iframeRectangleTop = clientPosition.y + iframeBorders.top + iframePadding.top; + + return new BoundaryValues(iframeRectangleTop, + iframeRectangleLeft + styleUtils.getWidth(iframe), + iframeRectangleTop + styleUtils.getHeight(iframe), + iframeRectangleLeft); +} + +export function containsOffset (el: HTMLElement, offsetX?: number, offsetY?: number): boolean { + const dimensions = getClientDimensions(el); + const width = Math.max(el.scrollWidth, dimensions.width); + const height = Math.max(el.scrollHeight, dimensions.height); + const maxX = dimensions.scrollbar.right + dimensions.border.left + dimensions.border.right + width; + const maxY = dimensions.scrollbar.bottom + dimensions.border.top + dimensions.border.bottom + height; + + return (typeof offsetX === 'undefined' || offsetX >= 0 && maxX >= offsetX) && + (typeof offsetY === 'undefined' || offsetY >= 0 && maxY >= offsetY); +} + +export function getIframePointRelativeToParentFrame (pos: AxisValues, iframeWin: Window): AxisValues { + const iframe = domUtils.findIframeByWindow(iframeWin); + const iframeOffset = getOffsetPosition(iframe); + const iframeBorders = styleUtils.getBordersWidth(iframe); + const iframePadding = styleUtils.getElementPadding(iframe); + + return offsetToClientCoords({ + x: pos.x + iframeOffset.left + iframeBorders.left + iframePadding.left, + y: pos.y + iframeOffset.top + iframeBorders.top + iframePadding.top, + }); +} + +export function findCenter (el: HTMLElement): AxisValues { + const rectangle = getElementRectangle(el); + + return new AxisValues( + Math.round(rectangle.left + rectangle.width / 2), + Math.round(rectangle.top + rectangle.height / 2), + ); +} + +export function getClientPosition (el: HTMLElement): any { + const { left, top } = getOffsetPosition(el); + + const clientCoords = offsetToClientCoords({ x: left, y: top }); + + clientCoords.x = Math.round(clientCoords.x); + clientCoords.y = Math.round(clientCoords.y); + + return clientCoords; +} + +export function isInRectangle ({ x, y }: AxisValues, rectangle: BoundaryValues): boolean { + return x >= rectangle.left && x <= rectangle.right && y >= rectangle.top && y <= rectangle.bottom; +} + +export function getWindowPosition (): AxisValues { + const x = window.screenLeft || window.screenX; + const y = window.screenTop || window.screenY; + + return new AxisValues(x, y); +} + +export function isIframeVisible (el: Node): boolean { + return !hiddenUsingStyles(el as HTMLElement); +} + +function hiddenUsingStyles (el: HTMLElement): boolean { + return styleUtils.get(el, 'visibility') === 'hidden' || + styleUtils.get(el, 'display') === 'none'; +} + +function hiddenByRectangle (el: HTMLElement): boolean { + const elementRectangle = getElementRectangle(el); + + return elementRectangle.width === 0 || + elementRectangle.height === 0; +} + +export function isElementVisible (el: Node): boolean { + if (domUtils.isTextNode(el)) + return !styleUtils.isNotVisibleNode(el); + + if (!domUtils.isContentEditableElement(el) && + !domUtils.isSVGElement(el) && + hiddenByRectangle(el as HTMLElement)) + return false; + + if (domUtils.isMapElement(el)) { + const mapContainer = domUtils.getMapContainer(domUtils.closest(el, 'map')); + + return mapContainer ? isElementVisible(mapContainer) : false; + } + + if (styleUtils.isSelectVisibleChild(el)) { + const select = domUtils.getSelectParent(el) as HTMLSelectElement; + const childRealIndex = domUtils.getChildVisibleIndex(select, el); + const realSelectSizeValue = styleUtils.getSelectElementSize(select); + const topVisibleIndex = Math.max(styleUtils.getScrollTop(select) / styleUtils.getOptionHeight(select), 0); + const bottomVisibleIndex = topVisibleIndex + realSelectSizeValue - 1; + const optionVisibleIndex = Math.max(childRealIndex - topVisibleIndex, 0); + + return optionVisibleIndex >= topVisibleIndex && optionVisibleIndex <= bottomVisibleIndex; + } + + if (domUtils.isSVGElement(el)) { + const hiddenParent = domUtils.findParent(el, true, (parent: Node) => { + return hiddenUsingStyles(parent as unknown as HTMLElement); + }); + + if (!hiddenParent) + return !hiddenByRectangle(el as unknown as HTMLElement); + + return false; + } + + return styleUtils.hasDimensions(el as HTMLElement) && !hiddenUsingStyles(el as unknown as HTMLElement); +} + diff --git a/src/client/core/utils/shared/scroll.ts b/src/client/core/utils/scroll.ts similarity index 70% rename from src/client/core/utils/shared/scroll.ts rename to src/client/core/utils/scroll.ts index f37bf07a843..5f8d78ef980 100644 --- a/src/client/core/utils/shared/scroll.ts +++ b/src/client/core/utils/scroll.ts @@ -1,14 +1,15 @@ -import adapter from './adapter/index'; -import AxisValues from '../../../../shared/utils/values/axis-values'; +import AxisValues from './values/axis-values'; // @ts-ignore -import { utils } from '../../deps/hammerhead'; +import { utils, nativeMethods } from '../deps/hammerhead'; +import * as domUtils from './dom'; +import * as styleUtils from './style'; const SCROLLABLE_OVERFLOW_STYLE_RE = /auto|scroll|hidden/i; const DEFAULT_IE_SCROLLABLE_OVERFLOW_STYLE_VALUE = 'visible'; function getScrollable (el: Element): AxisValues { - const overflowX = adapter.style.get(el, 'overflowX') as string; - const overflowY = adapter.style.get(el, 'overflowY') as string; + const overflowX = styleUtils.get(el, 'overflowX') as string; + const overflowY = styleUtils.get(el, 'overflowY') as string; let scrollableHorizontally = SCROLLABLE_OVERFLOW_STYLE_RE.test(overflowX); let scrollableVertically = SCROLLABLE_OVERFLOW_STYLE_RE.test(overflowY); @@ -24,12 +25,12 @@ function getScrollable (el: Element): AxisValues { } function hasBodyScroll (el: HTMLBodyElement): boolean { - const overflowX = adapter.style.get(el, 'overflowX') as string; - const overflowY = adapter.style.get(el, 'overflowY') as string; + const overflowX = styleUtils.get(el, 'overflowX') as string; + const overflowY = styleUtils.get(el, 'overflowY') as string; const scrollableHorizontally = SCROLLABLE_OVERFLOW_STYLE_RE.test(overflowX); const scrollableVertically = SCROLLABLE_OVERFLOW_STYLE_RE.test(overflowY); - const documentElement = adapter.dom.findDocument(el).documentElement; + const documentElement = domUtils.findDocument(el).documentElement; let bodyScrollHeight = el.scrollHeight; @@ -45,8 +46,8 @@ function hasBodyScroll (el: HTMLBodyElement): boolean { } function hasHTMLElementScroll (el: HTMLHtmlElement): boolean { - const overflowX = adapter.style.get(el, 'overflowX') as string; - const overflowY = adapter.style.get(el, 'overflowY') as string; + const overflowX = styleUtils.get(el, 'overflowX') as string; + const overflowY = styleUtils.get(el, 'overflowY') as string; //T303226 if (overflowX === 'hidden' && overflowY === 'hidden') @@ -74,11 +75,11 @@ function hasHTMLElementScroll (el: HTMLHtmlElement): boolean { } export function hasScroll (el: Element): boolean { - if (adapter.dom.isBodyElement(el)) - return hasBodyScroll(el); + if (domUtils.isBodyElement(el)) + return hasBodyScroll(el as HTMLBodyElement); - if (adapter.dom.isHtmlElement(el)) - return hasHTMLElementScroll(el); + if (domUtils.isHtmlElement(el)) + return hasHTMLElementScroll(el as HTMLHtmlElement); const scrollable = getScrollable(el); @@ -91,18 +92,18 @@ export function hasScroll (el: Element): boolean { return hasHorizontalScroll || hasVerticalScroll; } -export function getScrollableParents (element: Element): Element[] { - const parentsArray = adapter.dom.getParents(element); +export function getScrollableParents (element: HTMLElement): HTMLElement[] { + const parentsArray = domUtils.getParents(element); - if (adapter.dom.isElementInIframe(element)) { - const iframe = adapter.dom.getIframeByElement(element); + if (domUtils.isElementInIframe(element)) { + const iframe = domUtils.getIframeByElement(element); if (iframe) { - const iFrameParents = adapter.dom.getParents(iframe); + const iFrameParents = domUtils.getParents(iframe); parentsArray.concat(iFrameParents); } } - return adapter.nativeMethods.arrayFilter.call(parentsArray, hasScroll); + return nativeMethods.arrayFilter.call(parentsArray, hasScroll); } diff --git a/src/client/core/utils/service.js b/src/client/core/utils/service.js index 199eeb13a71..7b96293a98d 100644 --- a/src/client/core/utils/service.js +++ b/src/client/core/utils/service.js @@ -1,5 +1,5 @@ import hammerhead from '../deps/hammerhead'; -import SharedEventEmitter from '../../../shared/utils/event-emitter'; +import SharedEventEmitter from './event-emitter'; export const EventEmitter = SharedEventEmitter; diff --git a/src/client/core/utils/shared/adapter/index.ts b/src/client/core/utils/shared/adapter/index.ts deleted file mode 100644 index 9b5148007fc..00000000000 --- a/src/client/core/utils/shared/adapter/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { CoreUtilsAdapter } from '../types'; - -// @ts-ignore -const adapter: CoreUtilsAdapter = {}; - -export default adapter; - -export function initializeAdapter (initializer: CoreUtilsAdapter): void { - if (initializer.nativeMethods.objectAssign) { - initializer.nativeMethods.objectAssign(adapter, initializer); - - return; - } - - const keys = initializer.nativeMethods.objectKeys(initializer) as (keyof CoreUtilsAdapter)[]; - - for (const key of keys) - // @ts-ignore - adapter[key] = initializer[key]; -} diff --git a/src/client/core/utils/shared/adapter/initializer.ts b/src/client/core/utils/shared/adapter/initializer.ts deleted file mode 100644 index 161c6d93c48..00000000000 --- a/src/client/core/utils/shared/adapter/initializer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-ignore -import { nativeMethods, utils } from '../../../deps/hammerhead'; -import * as dom from '../../dom'; -import * as position from '../../position'; -import * as style from '../../style'; -import { CoreUtilsAdapter } from '../types'; -import sendRequestToFrame from '../../send-request-to-frame'; - -const browser = utils.browser; - -const initializer: CoreUtilsAdapter = { nativeMethods, browser, dom, position, style, sendRequestToFrame }; - -export default initializer; diff --git a/src/client/core/utils/shared/position.ts b/src/client/core/utils/shared/position.ts deleted file mode 100644 index 2a8274710cf..00000000000 --- a/src/client/core/utils/shared/position.ts +++ /dev/null @@ -1,96 +0,0 @@ -import adapter from './adapter/index'; -import BoundaryValues, { BoundaryValuesData } from '../../../../shared/utils/values/boundary-values'; -import Dimensions from '../../../../shared/utils/values/dimensions'; -import AxisValues, { AxisValuesData } from '../../../../shared/utils/values/axis-values'; - -export function getClientDimensions (target: Element): Dimensions { - const isHtmlElement = adapter.dom.isHtmlElement(target); - const body = isHtmlElement ? target.getElementsByTagName('body')[0] : null; - const elementRect = target.getBoundingClientRect(); - const elBorders = BoundaryValues.create(adapter.style.getBordersWidth(target)); - const elScroll = adapter.style.getElementScroll(target); - const isElementInIframe = adapter.dom.isElementInIframe(target); - const isCompatMode = target.ownerDocument.compatMode === 'BackCompat'; - const elPosition = isHtmlElement ? new AxisValues(0, 0) : AxisValues.create(elementRect); - - let elHeight = elementRect.height; - let elWidth = elementRect.width; - - if (isHtmlElement) { - if (body && isCompatMode) { - elHeight = body.clientHeight; - elWidth = body.clientWidth; - } - else { - elHeight = target.clientHeight; - elWidth = target.clientWidth; - } - } - - if (isElementInIframe) { - const iframeElement = adapter.dom.getIframeByElement(target); - - if (iframeElement) { - const iframeOffset = adapter.position.getOffsetPosition(iframeElement); - const clientOffset = adapter.position.offsetToClientCoords(AxisValues.create(iframeOffset)); - const iframeBorders = adapter.style.getBordersWidth(iframeElement); - - elPosition.add(clientOffset).add(AxisValues.create(iframeBorders)); - - if (isHtmlElement) - elBorders.add(iframeBorders); - } - } - - const hasRightScrollbar = !isHtmlElement && adapter.style.getInnerWidth(target) !== target.clientWidth; - const hasBottomScrollbar = !isHtmlElement && adapter.style.getInnerHeight(target) !== target.clientHeight; - - const scrollbar = { - right: hasRightScrollbar ? adapter.dom.getScrollbarSize() : 0, - bottom: hasBottomScrollbar ? adapter.dom.getScrollbarSize() : 0, - }; - - return new Dimensions(elWidth, elHeight, elPosition, elBorders, elScroll, scrollbar); -} - -export function getElementFromPoint ({ x, y }: AxisValuesData): Element | null { - // @ts-ignore - const ieFn = document.getElementFromPoint; - const func = ieFn || document.elementFromPoint; - - let el: Element | null = null; - - try { - // Permission denied to access property 'getElementFromPoint' error in iframe - el = func.call(document, x, y); - } - catch { - return null; - } - - //NOTE: elementFromPoint returns null when is's a border of an iframe - if (el === null) - el = func.call(document, x - 1, y - 1); - - while (el && el.shadowRoot && el.shadowRoot.elementFromPoint) { - const shadowEl = el.shadowRoot.elementFromPoint(x, y); - - if (!shadowEl || el === shadowEl) - break; - - el = shadowEl; - } - - return el; -} - -export function calcRelativePosition (dimensions: Dimensions, toDimensions: Dimensions): BoundaryValuesData { - const pos = BoundaryValues.create({ - top: dimensions.top - toDimensions.top, - left: dimensions.left - toDimensions.left, - right: toDimensions.right - dimensions.right, - bottom: toDimensions.bottom - dimensions.bottom, - }); - - return pos.sub(toDimensions.border).sub(toDimensions.scrollbar).round(Math.ceil, Math.floor); -} diff --git a/src/client/core/utils/shared/style.ts b/src/client/core/utils/shared/style.ts deleted file mode 100644 index ed79273e97d..00000000000 --- a/src/client/core/utils/shared/style.ts +++ /dev/null @@ -1,25 +0,0 @@ -import adapter from './adapter/index'; - - -const isVisibilityHiddenNode = function (node: Node): boolean { - return !!adapter.dom.findParent(node, true, (ancestor: Node) => - adapter.dom.isElementNode(ancestor) && adapter.style.get(ancestor, 'visibility') === 'hidden'); -}; - -const isHiddenNode = function (node: Node): boolean { - return !!adapter.dom.findParent(node, true, (ancestor: Node) => - adapter.dom.isElementNode(ancestor) && adapter.style.get(ancestor, 'display') === 'none'); -}; - -export function isNotVisibleNode (node: Node): boolean { - return !adapter.dom.isRenderedNode(node) || isHiddenNode(node) || isVisibilityHiddenNode(node); -} - -export function hasDimensions (el: HTMLElement): boolean { - //NOTE: it's like jquery ':visible' selector (http://blog.jquery.com/2009/02/20/jquery-1-3-2-released/) - return el && !(el.offsetHeight <= 0 && el.offsetWidth <= 0); -} - -export function isFixedElement (node: Node): boolean { - return adapter.dom.isElementNode(node) && adapter.style.get(node, 'position') === 'fixed'; -} diff --git a/src/client/core/utils/shared/types.d.ts b/src/client/core/utils/shared/types.d.ts deleted file mode 100644 index 5fcc136c510..00000000000 --- a/src/client/core/utils/shared/types.d.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { BoundaryValuesData } from '../../../../shared/utils/values/boundary-values'; -import { AxisValuesData, LeftTopValues } from '../../../../shared/utils/values/axis-values'; -import { Dictionary } from '../../../../configuration/interfaces'; - -export interface ElementRectangle { - height: number; - left: number; - top: number; - width: number; -} - -export interface NativeMethods { - Function: typeof Function; - Node: typeof Node; - objectKeys: ObjectConstructor['keys']; - objectAssign: ObjectConstructor['assign']; - objectGetPrototypeOf: ObjectConstructor['getPrototypeOf']; - objectToString: Object['toString']; // eslint-disable-line @typescript-eslint/ban-types - Promise: typeof Promise; - dateNow: DateConstructor['now']; - isArray: ArrayConstructor['isArray']; - arrayFilter: any[]['filter']; - NodeList: typeof NodeList; - HTMLCollection: typeof HTMLCollection; - setTimeout: Window['setTimeout']; - elementClass: typeof Element; - svgElementClass: typeof SVGElement; - closest: Element['closest']; - getAttribute: Element['getAttribute']; - querySelector: HTMLElement['querySelector']; - querySelectorAll: HTMLElement['querySelectorAll']; - scrollTo: Window['scrollTo']; -} - -export interface CoreUtilsAdapter { - nativeMethods: NativeMethods; - browser: { - isIE?: boolean; - isChrome?: boolean; - isFirefox?: boolean; - isSafari?: boolean; - }; - - dom: { - isTextNode (el: unknown): boolean; - isMapElement (el: unknown): el is HTMLMapElement | HTMLAreaElement; - isSVGElement (el: unknown): el is SVGElement; - isContentEditableElement (el: unknown): boolean; - closest (el: Element, selector: string): Element | null; - getMapContainer (el: Element | null): Element | null; - getSelectParent (el: Element): HTMLSelectElement | null; - getChildVisibleIndex (select: HTMLSelectElement, child: Node): number; - findParent (node: Node, includeSelf?: boolean, predicate?: (el: Node) => boolean): Node | null; - isElementNode (el: Node): el is Element; - isBodyElement (el: unknown): el is HTMLBodyElement; - isHtmlElement (el: unknown): el is HTMLHtmlElement; - isRenderedNode (node: Node): boolean; - findDocument (el: Node): Document; - isElementInIframe (el: Element | Document, currentDocument?: Document): boolean; - getIframeByElement (el: Element | Document): HTMLFrameElement | HTMLIFrameElement | null; - getParents (el: Element, selector?: string): Element[]; - getScrollbarSize (): number; - }; - - style: { - get (el: Node, property: keyof CSSStyleDeclaration): string | null; - isSelectVisibleChild (el: Node): el is HTMLElement; - getScrollTop (el: Window | Document | Element | null): number; - getOptionHeight (el: Element): number; - getSelectElementSize (select: HTMLSelectElement): number; - getBordersWidth (el: Element): BoundaryValuesData; - getElementScroll (el: Window | Document | Element): LeftTopValues; - getInnerWidth (el: Element | Window | Document | null): number; - getInnerHeight (el: Element | Window | Document | null): number; - getScrollLeft (el: Window | Document | Element | null): number; - setScrollLeft (el: Element | Document | Window, value: number): void; - setScrollTop (el: Element | Document | Window, value: number): void; - }; - - position: { - getElementRectangle (el: Node): ElementRectangle; - getOffsetPosition (el: Element | Document, roundFn?: (n: number) => number): LeftTopValues; - offsetToClientCoords (coords: AxisValuesData, currentDocument?: Document): AxisValuesData; - }; - - sendRequestToFrame: null | ((msg: Dictionary, responseCmd: string, receiverWindow: Window) => Promise>); -} diff --git a/src/client/core/utils/shared/visibility.ts b/src/client/core/utils/shared/visibility.ts deleted file mode 100644 index 3eeb6ca6a61..00000000000 --- a/src/client/core/utils/shared/visibility.ts +++ /dev/null @@ -1,58 +0,0 @@ -import adapter from './adapter/index'; -import { isNotVisibleNode, hasDimensions } from './style'; - -export function isIframeVisible (el: Node): boolean { - return !hiddenUsingStyles(el as HTMLElement); -} - -function hiddenUsingStyles (el: HTMLElement): boolean { - return adapter.style.get(el, 'visibility') === 'hidden' || - adapter.style.get(el, 'display') === 'none'; -} - -function hiddenByRectangle (el: HTMLElement): boolean { - const elementRectangle = adapter.position.getElementRectangle(el); - - return elementRectangle.width === 0 || - elementRectangle.height === 0; -} - -export function isElementVisible (el: Node): boolean { - if (adapter.dom.isTextNode(el)) - return !isNotVisibleNode(el); - - if (!adapter.dom.isContentEditableElement(el) && - !adapter.dom.isSVGElement(el) && - hiddenByRectangle(el as HTMLElement)) - return false; - - if (adapter.dom.isMapElement(el)) { - const mapContainer = adapter.dom.getMapContainer(adapter.dom.closest(el, 'map')); - - return mapContainer ? isElementVisible(mapContainer) : false; - } - - if (adapter.style.isSelectVisibleChild(el)) { - const select = adapter.dom.getSelectParent(el) as HTMLSelectElement; - const childRealIndex = adapter.dom.getChildVisibleIndex(select, el); - const realSelectSizeValue = adapter.style.getSelectElementSize(select); - const topVisibleIndex = Math.max(adapter.style.getScrollTop(select) / adapter.style.getOptionHeight(select), 0); - const bottomVisibleIndex = topVisibleIndex + realSelectSizeValue - 1; - const optionVisibleIndex = Math.max(childRealIndex - topVisibleIndex, 0); - - return optionVisibleIndex >= topVisibleIndex && optionVisibleIndex <= bottomVisibleIndex; - } - - if (adapter.dom.isSVGElement(el)) { - const hiddenParent = adapter.dom.findParent(el, true, (parent: Node) => { - return hiddenUsingStyles(parent as unknown as HTMLElement); - }); - - if (!hiddenParent) - return !hiddenByRectangle(el as unknown as HTMLElement); - - return false; - } - - return hasDimensions(el as HTMLElement) && !hiddenUsingStyles(el as unknown as HTMLElement); -} diff --git a/src/client/core/utils/style.js b/src/client/core/utils/style.js index 0a9f9e1db3d..70215b72126 100644 --- a/src/client/core/utils/style.js +++ b/src/client/core/utils/style.js @@ -1,9 +1,6 @@ import hammerhead from '../deps/hammerhead'; -import BoundaryValues from '../../../shared/utils/values/boundary-values'; - -export * from './shared/style'; -export { hasScroll } from './shared/scroll'; - +import BoundaryValues from './values/boundary-values'; +import * as domUtils from './dom'; const styleUtils = hammerhead.utils.style; @@ -57,3 +54,26 @@ export function getViewportDimensions () { export function getWindowDimensions (window) { return new BoundaryValues(0, getWidth(window), getHeight(window), 0); } + +function isVisibilityHiddenNode (node) { + return !!domUtils.findParent(node, true, ancestor => + domUtils.isElementNode(ancestor) && styleUtils.get(ancestor, 'visibility') === 'hidden'); +} + +function isHiddenNode (node) { + return !!domUtils.findParent(node, true, ancestor => + domUtils.isElementNode(ancestor) && styleUtils.get(ancestor, 'display') === 'none'); +} + +export function isNotVisibleNode (node) { + return !domUtils.isRenderedNode(node) || isHiddenNode(node) || isVisibilityHiddenNode(node); +} + +export function hasDimensions (el) { + //NOTE: it's like jquery ':visible' selector (http://blog.jquery.com/2009/02/20/jquery-1-3-2-released/) + return el && !(el.offsetHeight <= 0 && el.offsetWidth <= 0); +} + +export function isFixedElement (node) { + return domUtils.isElementNode(node) && styleUtils.get(node, 'position') === 'fixed'; +} diff --git a/src/shared/utils/values/axis-values.ts b/src/client/core/utils/values/axis-values.ts similarity index 100% rename from src/shared/utils/values/axis-values.ts rename to src/client/core/utils/values/axis-values.ts diff --git a/src/shared/utils/values/boundary-values.ts b/src/client/core/utils/values/boundary-values.ts similarity index 100% rename from src/shared/utils/values/boundary-values.ts rename to src/client/core/utils/values/boundary-values.ts diff --git a/src/shared/utils/values/dimensions.ts b/src/client/core/utils/values/dimensions.ts similarity index 100% rename from src/shared/utils/values/dimensions.ts rename to src/client/core/utils/values/dimensions.ts diff --git a/src/client/driver/command-executors/actions-initializer.ts b/src/client/driver/command-executors/action-executor/actions-initializer.ts similarity index 94% rename from src/client/driver/command-executors/actions-initializer.ts rename to src/client/driver/command-executors/action-executor/actions-initializer.ts index 4fd08e7b7ba..c0f86e8462c 100644 --- a/src/client/driver/command-executors/actions-initializer.ts +++ b/src/client/driver/command-executors/action-executor/actions-initializer.ts @@ -1,10 +1,10 @@ -import ActionExecutor from '../../../shared/actions/action-executor'; +import ActionExecutor from './index'; import { // @ts-ignore domUtils, // @ts-ignore contentEditable, // @ts-ignore parseKeySequence, -} from '../deps/testcafe-core'; +} from '../../deps/testcafe-core'; import { // @ts-ignore calculateSelectTextArguments, // @ts-ignore @@ -24,7 +24,7 @@ import { // @ts-ignore SetScroll as SetScrollAutomation, // @ts-ignore ScrollIntoView as ScrollIntoViewAutomation, // @ts-ignore cursor, -} from '../deps/testcafe-automation'; +} from '../../deps/testcafe-automation'; import { ActionIncorrectKeysError, @@ -34,11 +34,11 @@ import { ActionRootContainerNotFoundError, ActionElementNotTextAreaError, ActionElementIsNotFileInputError, -} from '../../../shared/errors/index'; +} from '../../../../shared/errors'; -import COMMAND_TYPE from '../../../test-run/commands/type'; -import { ActionCommandBase } from '../../../test-run/commands/base'; -import { Automation } from '../../../shared/actions/types'; +import COMMAND_TYPE from '../../../../test-run/commands/type'; +import { ActionCommandBase } from '../../../../test-run/commands/base'; +import { Automation } from '../../../automation/types'; ActionExecutor.ACTIONS_HANDLERS[COMMAND_TYPE.dispatchEvent] = { diff --git a/src/shared/utils/elements-retriever.ts b/src/client/driver/command-executors/action-executor/elements-retriever.ts similarity index 71% rename from src/shared/utils/elements-retriever.ts rename to src/client/driver/command-executors/action-executor/elements-retriever.ts index ed3ad4c054b..50e4d62d66c 100644 --- a/src/shared/utils/elements-retriever.ts +++ b/src/client/driver/command-executors/action-executor/elements-retriever.ts @@ -1,24 +1,24 @@ -import { ExecuteSelectorCommand } from '../../test-run/commands/observation'; -import { ExecuteSelectorFn } from '../types'; -import NODE_TYPE_DESCRIPTIONS from '../utils/node-type-descriptions'; +import { ExecuteSelectorCommand } from '../../../../test-run/commands/observation'; +import { ExecuteSelectorFn } from '../../../../shared/types'; +import NODE_TYPE_DESCRIPTIONS from '../../node-type-descriptions'; import { ActionSelectorMatchesWrongNodeTypeError, ActionAdditionalSelectorMatchesWrongNodeTypeError, -} from '../../shared/errors'; -import { getInvisibleErrorCtor, getNotFoundErrorCtor } from '../errors/selector-error-ctor-callback'; +} from '../../../../shared/errors'; +import { getInvisibleErrorCtor, getNotFoundErrorCtor } from '../../../../shared/errors/selector-error-ctor-callback'; // @ts-ignore -import { nativeMethods, Promise } from '../../client/driver/deps/hammerhead'; +import { nativeMethods, Promise } from '../../deps/hammerhead'; // @ts-ignore -import { domUtils } from '../../client/driver/deps/testcafe-core'; +import { domUtils } from '../../deps/testcafe-core'; -export default class ElementsRetriever { +export default class ElementsRetriever { private readonly _globalSelectorTimeout: number; private readonly _ensureElementsStartTime: number; - private readonly _executeSelectorFn: ExecuteSelectorFn; - private readonly _elements: T[]; + private readonly _executeSelectorFn: ExecuteSelectorFn; + private readonly _elements: HTMLElement[]; private _ensureElementsPromise: Promise; - public constructor (globalSelectorTimeout: number, executeSelectorFn: ExecuteSelectorFn) { + public constructor (globalSelectorTimeout: number, executeSelectorFn: ExecuteSelectorFn) { this._globalSelectorTimeout = globalSelectorTimeout; this._ensureElementsStartTime = nativeMethods.dateNow(); this._ensureElementsPromise = Promise.resolve(); @@ -34,7 +34,7 @@ export default class ElementsRetriever { notFound: getNotFoundErrorCtor(elementName), }, this._ensureElementsStartTime); }) - .then((el: T) => { + .then((el: HTMLElement) => { if (!domUtils.isDomElement(el)) { const nodeType = (el as unknown as { nodeType: number }).nodeType; const nodeTypeStr = NODE_TYPE_DESCRIPTIONS[nodeType]; @@ -50,7 +50,7 @@ export default class ElementsRetriever { } - public getElements (): Promise { + public getElements (): Promise { return this._ensureElementsPromise.then(() => this._elements); } } diff --git a/src/shared/actions/action-executor.ts b/src/client/driver/command-executors/action-executor/index.ts similarity index 84% rename from src/shared/actions/action-executor.ts rename to src/client/driver/command-executors/action-executor/index.ts index 54abded1b10..26085119d89 100644 --- a/src/shared/actions/action-executor.ts +++ b/src/client/driver/command-executors/action-executor/index.ts @@ -1,22 +1,22 @@ -import { adapter } from '../adapter'; -import EventEmitter from '../utils/event-emitter'; -import ComplexBarrier from '../../client/driver/barriers/complex-barrier'; -import delay from '../utils/delay'; -import { whilst } from '../utils/promise'; -import { ActionCommandBase } from '../../test-run/commands/base'; -import { Dictionary } from '../../configuration/interfaces'; -import AUTOMATION_ERROR_TYPES from '../errors/automation-errors'; -import { ActionElementIsInvisibleError } from '../../shared/errors'; -import { ExecuteSelectorFn } from '../types'; -import ElementsRetriever from '../utils/elements-retriever'; -import { Automation, AutomationHandler } from './types'; +import EventEmitter from '../../../core/utils/event-emitter'; +import ComplexBarrier from '../../barriers/complex-barrier'; +import delay from '../../../core/utils/delay'; +import { whilst } from '../../../core/utils/promise'; +import { ActionCommandBase } from '../../../../test-run/commands/base'; +import { Dictionary } from '../../../../configuration/interfaces'; +import AUTOMATION_ERROR_TYPES from '../../../../shared/errors/automation-errors'; +import { ActionElementIsInvisibleError } from '../../../../shared/errors'; +import { ExecuteSelectorFn } from '../../../../shared/types'; +import ElementsRetriever from './elements-retriever'; +import { Automation, AutomationHandler } from '../../../automation/types'; // @ts-ignore -import { nativeMethods, Promise } from '../../client/driver/deps/hammerhead'; +import { nativeMethods, Promise } from '../../deps/hammerhead'; +import { getOffsetOptions } from '../../../core/utils/offsets'; const MAX_DELAY_AFTER_EXECUTION = 2000; const CHECK_ELEMENT_IN_AUTOMATIONS_INTERVAL = 250; -export default class ActionExecutor extends EventEmitter { +export default class ActionExecutor extends EventEmitter { public static readonly EXECUTION_STARTED_EVENT = 'execution-started'; public static readonly WAITING_FOR_ELEMENT_EVENT = 'waiting-for-elements'; public static readonly ACTIONS_HANDLERS: Dictionary = {}; @@ -24,12 +24,12 @@ export default class ActionExecutor extends EventEmitter { private readonly _command: ActionCommandBase; private readonly _globalSelectorTimeout: number; private readonly _commandSelectorTimeout: number; - private readonly _executeSelectorFn: ExecuteSelectorFn; - private _elements: T[]; + private readonly _executeSelectorFn: ExecuteSelectorFn; + private _elements: HTMLElement[]; private _executionStartTime: number; - private _targetElement: T | null; + private _targetElement: HTMLElement | null; - public constructor (command: ActionCommandBase, globalSelectorTimeout: number, testSpeed: number, executeSelectorFn: ExecuteSelectorFn) { + public constructor (command: ActionCommandBase, globalSelectorTimeout: number, testSpeed: number, executeSelectorFn: ExecuteSelectorFn) { super(); this._command = command; @@ -90,7 +90,7 @@ export default class ActionExecutor extends EventEmitter { } return elsRetriever.getElements() - .then((elements: T[]) => { + .then((elements: HTMLElement[]) => { this._elements = elements; }); } @@ -109,7 +109,7 @@ export default class ActionExecutor extends EventEmitter { // @ts-ignore TODO if (this._elements.length && opts && 'offsetX' in opts && 'offsetY' in opts) { // @ts-ignore - const { offsetX, offsetY } = await adapter.getOffsetOptions(this._elements[0], opts.offsetX, opts.offsetY); + const { offsetX, offsetY } = getOffsetOptions(this._elements[0], opts.offsetX, opts.offsetY); // @ts-ignore TODO opts.offsetX = offsetX; @@ -175,7 +175,7 @@ export default class ActionExecutor extends EventEmitter { }); } - public execute (barriers: ComplexBarrier): Promise { + public execute (barriers: ComplexBarrier): Promise { this._executionStartTime = nativeMethods.dateNow(); try { diff --git a/src/client/driver/command-executors/browser-manipulation/ensure-crop-options.js b/src/client/driver/command-executors/browser-manipulation/ensure-crop-options.js index ba45861c660..bd5073296d8 100644 --- a/src/client/driver/command-executors/browser-manipulation/ensure-crop-options.js +++ b/src/client/driver/command-executors/browser-manipulation/ensure-crop-options.js @@ -117,7 +117,7 @@ export default async function ensureCropOptions (element, options) { if (!hasScrollTargetY) options.scrollTargetY = determineScrollPoint(options.crop.top, options.crop.bottom, viewportDimensions.height); - const { offsetX, offsetY } = await getOffsetOptions(element, options.scrollTargetX, options.scrollTargetY); + const { offsetX, offsetY } = getOffsetOptions(element, options.scrollTargetX, options.scrollTargetY); options.scrollTargetX = offsetX; options.scrollTargetY = offsetY; diff --git a/src/client/driver/command-executors/client-functions/selector-executor/node-snapshots.ts b/src/client/driver/command-executors/client-functions/selector-executor/node-snapshots.ts index c1d5e73bb2e..5b7e4867b88 100644 --- a/src/client/driver/command-executors/client-functions/selector-executor/node-snapshots.ts +++ b/src/client/driver/command-executors/client-functions/selector-executor/node-snapshots.ts @@ -6,8 +6,7 @@ import { import { Dictionary } from '../../../../../configuration/interfaces'; // @ts-ignore import { utils } from '../../../deps/hammerhead'; -// @ts-ignore -import { positionUtils } from '../../../deps/testcafe-core'; +import { isElementVisible } from '../../../../core/utils/position'; const nodeSnapshotPropertyInitializers = { @@ -64,7 +63,7 @@ export class NodeSnapshot extends BaseSnapshot { // Element const elementSnapshotPropertyInitializers = { tagName: (element: Element) => element.tagName.toLowerCase(), - visible: (element: Element) => positionUtils.isElementVisible(element), + visible: (element: Element) => isElementVisible(element), focused: (element: Element) => utils.dom.getActiveElement() === element, attributes: (element: Element) => { diff --git a/src/client/driver/command-executors/client-functions/selector-executor/utils.ts b/src/client/driver/command-executors/client-functions/selector-executor/utils.ts index 00e0db4dfbb..e0861e91bd0 100644 --- a/src/client/driver/command-executors/client-functions/selector-executor/utils.ts +++ b/src/client/driver/command-executors/client-functions/selector-executor/utils.ts @@ -1,14 +1,15 @@ // @ts-ignore import { nativeMethods, utils } from '../../../deps/hammerhead'; // @ts-ignore -import { domUtils, positionUtils } from '../../../deps/testcafe-core'; +import { domUtils } from '../../../deps/testcafe-core'; // @ts-ignore import { selectElement } from '../../../deps/testcafe-ui'; +import { isElementVisible, isIframeVisible } from '../../../../core/utils/position'; export function visible (el: Node): boolean { if (domUtils.isIframeElement(el)) - return positionUtils.isIframeVisible(el); + return isIframeVisible(el); if (!utils.dom.isDomElement(el) && !utils.dom.isTextNode(el)) return false; @@ -16,7 +17,7 @@ export function visible (el: Node): boolean { if (domUtils.isOptionElement(el) || domUtils.getTagName(el as Element) === 'optgroup') return selectElement.isOptionElementVisible(el); - return positionUtils.isElementVisible(el); + return isElementVisible(el); } export function isNodeCollection (obj: unknown): obj is HTMLCollection | NodeList { diff --git a/src/client/driver/driver.js b/src/client/driver/driver.js index 5180336de09..3a6ccd63249 100644 --- a/src/client/driver/driver.js +++ b/src/client/driver/driver.js @@ -86,8 +86,7 @@ import ChildIframeDriverLink from './driver-link/iframe/child'; import createReplicator from './command-executors/client-functions/replicator/index'; import SelectorNodeTransform from './command-executors/client-functions/replicator/transforms/selector-node-transform'; - -import ActionExecutor from '../../shared/actions/action-executor'; +import ActionExecutor from './command-executors/action-executor'; import executeManipulationCommand from './command-executors/browser-manipulation'; import executeNavigateToCommand from './command-executors/execute-navigate-to'; import { @@ -116,7 +115,7 @@ import createErrorCtorCallback, { getInvisibleErrorCtor, getNotFoundErrorCtor, } from '../../shared/errors/selector-error-ctor-callback'; -import './command-executors/actions-initializer'; +import './command-executors/action-executor/actions-initializer'; const settings = hammerhead.settings; diff --git a/src/client/driver/index.js b/src/client/driver/index.js index 493f73241ec..665aa81505b 100644 --- a/src/client/driver/index.js +++ b/src/client/driver/index.js @@ -1,5 +1,3 @@ -// NOTE: Initializer should be the first -import './shared-adapter-initializer'; import hammerhead from './deps/hammerhead'; import Driver from './driver'; import IframeDriver from './iframe-driver'; diff --git a/src/shared/utils/node-type-descriptions.ts b/src/client/driver/node-type-descriptions.ts similarity index 100% rename from src/shared/utils/node-type-descriptions.ts rename to src/client/driver/node-type-descriptions.ts diff --git a/src/client/driver/shared-adapter-initializer.ts b/src/client/driver/shared-adapter-initializer.ts deleted file mode 100644 index d5732719065..00000000000 --- a/src/client/driver/shared-adapter-initializer.ts +++ /dev/null @@ -1,32 +0,0 @@ -import hammerhead from './deps/hammerhead'; -import testCafeAutomation from './deps/testcafe-automation'; -import testCafeCore from './deps/testcafe-core'; -import { initializeAdapter } from '../../shared/adapter/index'; -import { MouseClickStrategyEmpty } from '../../shared/actions/automations/click/mouse-click-strategy-base'; - -const { Promise } = hammerhead; -const { positionUtils: position, styleUtils: style, eventUtils: event } = testCafeCore; -const { getOffsetOptions } = testCafeAutomation; - - -initializeAdapter({ - getOffsetOptions: getOffsetOptions, - - position, style, event, - - // NOTE: this functions are unnecessary in the driver - getElementExceptUI: () => Promise.resolve(), - scroll: () => Promise.resolve(), - createEventSequence: () => {}, - sendRequestToFrame: () => Promise.resolve(), - ensureMouseEventAfterScroll: () => Promise.resolve(), - - automations: { - click: { - createMouseClickStrategy: () => new MouseClickStrategyEmpty(), - }, - - _ensureWindowAndCursorForLegacyTests () { // eslint-disable-line no-empty-function - }, - }, -}); diff --git a/src/client/driver/utils/ensure-elements.js b/src/client/driver/utils/ensure-elements.js index 8d48a860d16..9c43c6f4ca7 100644 --- a/src/client/driver/utils/ensure-elements.js +++ b/src/client/driver/utils/ensure-elements.js @@ -2,7 +2,7 @@ import { Promise, nativeMethods } from '../deps/hammerhead'; import { domUtils } from '../deps/testcafe-core'; -import NODE_TYPE_DESCRIPTIONS from '../../../shared/utils/node-type-descriptions'; +import NODE_TYPE_DESCRIPTIONS from '../node-type-descriptions'; import SelectorExecutor from '../command-executors/client-functions/selector-executor'; diff --git a/src/client/ui/status-bar/progress-bar/indeterminate-indicator.js b/src/client/ui/status-bar/progress-bar/indeterminate-indicator.js index 1200bdbdf59..3b1eee139e4 100644 --- a/src/client/ui/status-bar/progress-bar/indeterminate-indicator.js +++ b/src/client/ui/status-bar/progress-bar/indeterminate-indicator.js @@ -1,6 +1,6 @@ import hammerhead from '../../deps/hammerhead'; import testCafeCore from '../../deps/testcafe-core'; -import { getLineYByXCoord } from '../../../../shared/utils/position'; +import { getLineYByXCoord } from '../../../core/utils/get-line-by'; const shadowUI = hammerhead.shadowUI; const nativeMethods = hammerhead.nativeMethods; diff --git a/src/compiler/test-file/api-based.js b/src/compiler/test-file/api-based.js index 95132b04b94..d4d5e7c1d77 100644 --- a/src/compiler/test-file/api-based.js +++ b/src/compiler/test-file/api-based.js @@ -13,7 +13,7 @@ import Fixture from '../../api/structure/fixture'; import Test from '../../api/structure/test'; import { TestCompilationError, APIError } from '../../errors/runtime'; import stackCleaningHook from '../../errors/stack-cleaning-hook'; -import NODE_MODULES from '../../shared/node-modules-folder-name'; +import NODE_MODULES from '../../utils/node-modules-folder-name'; import cacheProxy from './cache-proxy'; import exportableLib from '../../api/exportable-lib'; import TEST_FILE_TEMP_VARIABLE_NAME from './test-file-temp-variable-name'; diff --git a/src/errors/process-test-fn-error.js b/src/errors/process-test-fn-error.js index 3b5f2b3d937..70bac3fe19c 100644 --- a/src/errors/process-test-fn-error.js +++ b/src/errors/process-test-fn-error.js @@ -3,7 +3,7 @@ import { getCallsiteForError } from './get-callsite'; import { APIError } from './runtime'; import TestCafeErrorList from './error-list'; import INTERNAL_MODULES_PREFIX from './internal-modules-prefix'; -import NODE_MODULES from '../shared/node-modules-folder-name'; +import NODE_MODULES from '../utils/node-modules-folder-name'; import { UncaughtErrorInTestCode, diff --git a/src/live/file-watcher/index.js b/src/live/file-watcher/index.js index 3fe5b78423c..8ed7d0e51b1 100644 --- a/src/live/file-watcher/index.js +++ b/src/live/file-watcher/index.js @@ -1,7 +1,7 @@ import EventEmitter from 'events'; import fs from 'fs'; import ModulesGraph from './modules-graph'; -import NODE_MODULES from '../../shared/node-modules-folder-name'; +import NODE_MODULES from '../../utils/node-modules-folder-name'; import toPosixPath from '../../utils/to-posix-path'; const WATCH_LOCKED_TIMEOUT = 700; diff --git a/src/live/file-watcher/modules-graph.js b/src/live/file-watcher/modules-graph.js index 94365a2e078..611427eb17a 100644 --- a/src/live/file-watcher/modules-graph.js +++ b/src/live/file-watcher/modules-graph.js @@ -1,5 +1,5 @@ import { Graph } from 'graphlib'; -import NODE_MODULES from '../../shared/node-modules-folder-name'; +import NODE_MODULES from '../../utils/node-modules-folder-name'; export default class ModulesGraph { constructor () { diff --git a/src/shared/actions/automations/click/mouse-click-strategy-base.ts b/src/shared/actions/automations/click/mouse-click-strategy-base.ts deleted file mode 100644 index 33b72f14546..00000000000 --- a/src/shared/actions/automations/click/mouse-click-strategy-base.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { MouseEventArgs } from '../visible-element-automation'; - -export abstract class MouseClickStrategyBase { - public abstract mousedown (eventArgs: MouseEventArgs): Promise; - - public abstract mouseup (element: E, eventArgs: MouseEventArgs): Promise>; -} - -export class MouseClickStrategyEmpty extends MouseClickStrategyBase { - public constructor () { - super(); - } - - public mousedown (): Promise { - throw new Error('not implemented'); - } - - public mouseup (): Promise> { - throw new Error('not implemented'); - } -} diff --git a/src/shared/actions/utils/get-automation-point.ts b/src/shared/actions/utils/get-automation-point.ts deleted file mode 100644 index 8e8f0ce0f1b..00000000000 --- a/src/shared/actions/utils/get-automation-point.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { adapter } from '../../adapter'; -import AxisValues, { AxisValuesData } from '../../utils/values/axis-values'; -// @ts-ignore -import { Promise, utils } from '../../../client/core/deps/hammerhead'; -// @ts-ignore -import * as domUtils from '../../../client/core/utils/dom'; - -export default function getAutomationPoint (element: E, offset: AxisValuesData): Promise> { - return Promise.resolve(domUtils.isDocumentElement(element)) - .then((isDocEl: boolean) => { - if (isDocEl) - return new AxisValues(0, 0); - - const roundFn = utils.browser.isFirefox ? Math.ceil : Math.round; - - return Promise.resolve(adapter.position.getOffsetPosition(element, roundFn)) - .then((elementOffset: any) => AxisValues.create(elementOffset)); - }) - .then((point: any) => point.add(offset)); -} diff --git a/src/shared/actions/utils/get-device-point.ts b/src/shared/actions/utils/get-device-point.ts deleted file mode 100644 index 8d906e61fcd..00000000000 --- a/src/shared/actions/utils/get-device-point.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { adapter } from '../../adapter'; -import AxisValues from '../../utils/values/axis-values'; -// @ts-ignore -import { Promise } from '../../../client/driver/deps/hammerhead'; - -export default async function getDevicePoint (clientPoint: AxisValues): Promise | null> { - if (!clientPoint) - return null; - - return Promise.resolve(adapter.position.getWindowPosition()) - .then((windowPosition: any) => { - const screenLeft = windowPosition.x; - const screenTop = windowPosition.y; - const x = screenLeft + clientPoint.x; - const y = screenTop + clientPoint.y; - - return new AxisValues(x, y); - }); -} diff --git a/src/shared/actions/utils/is-window-iframe.ts b/src/shared/actions/utils/is-window-iframe.ts deleted file mode 100644 index 04e528ae6ea..00000000000 --- a/src/shared/actions/utils/is-window-iframe.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { SharedWindow } from '../../types'; - -export default function isIframeWindow (window: SharedWindow): boolean { - return !window.parent || window.parent !== window; -} diff --git a/src/shared/actions/utils/screen-point-to-client.ts b/src/shared/actions/utils/screen-point-to-client.ts deleted file mode 100644 index 948edbbde98..00000000000 --- a/src/shared/actions/utils/screen-point-to-client.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { adapter } from '../../adapter'; -import AxisValues, { AxisValuesData } from '../../utils/values/axis-values'; - -export default async function convertToClient (element: any, point: AxisValuesData): Promise> { - const elementScroll = await adapter.style.getElementScroll(element); - const hasScroll = await adapter.style.hasScroll(element); - - if (!/html/i.test(element.tagName) && hasScroll) { - point.x -= elementScroll.left; - point.y -= elementScroll.top; - } - - return adapter.position.offsetToClientCoords(point); -} diff --git a/src/shared/adapter/index.ts b/src/shared/adapter/index.ts deleted file mode 100644 index 530f07b5df0..00000000000 --- a/src/shared/adapter/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SharedAdapter } from '../types'; - -// @ts-ignore -export const adapter: SharedAdapter = { }; - -export function initializeAdapter (initializer: SharedAdapter): void { - // eslint-disable-next-line no-restricted-globals - const keys = Object.keys(initializer) as (keyof SharedAdapter)[]; - - for (const key of keys) - // @ts-ignore - adapter[key] = initializer[key]; -} diff --git a/src/shared/types.d.ts b/src/shared/types.d.ts index cae827b12cf..65368e984bb 100644 --- a/src/shared/types.d.ts +++ b/src/shared/types.d.ts @@ -1,12 +1,6 @@ /* global globalThis */ import { ExecuteSelectorCommand } from '../test-run/commands/observation'; -import { ScrollOptions } from '../test-run/commands/options'; -import AxisValues, { AxisValuesData, LeftTopValues } from './utils/values/axis-values'; -import BoundaryValues, { BoundaryValuesData } from './utils/values/boundary-values'; -import { ElementRectangle } from '../client/core/utils/shared/types'; -import Dimensions from './utils/values/dimensions'; -import { MouseClickStrategyBase } from './actions/automations/click/mouse-click-strategy-base'; export interface NativeMethods { setTimeout: typeof globalThis.setTimeout; @@ -20,53 +14,6 @@ export interface NativeMethods { dateNow: DateConstructor['now']; } -type SharedFnResult = T | Promise; - -export interface SharedAdapter { - getOffsetOptions?: (el: any, offsetX: number, offsetY: number) => { offsetX: number; offsetY: number }; - scroll: (el: any, scrollOptions: ScrollOptions) => Promise; - - getElementExceptUI: (point: AxisValuesData, underTopShadowUIElement?: boolean) => Promise; - - position: { - getElementFromPoint: (point: AxisValuesData) => SharedFnResult; - containsOffset: (el: any, offsetX?: number, offsetY?: number) => SharedFnResult; - getIframeClientCoordinates: (el: any) => SharedFnResult; - getClientPosition: (el: any) => SharedFnResult>; - getOffsetPosition: (el: any, roundFn?: (n: number) => number) => SharedFnResult>; - getWindowPosition: () => SharedFnResult>; - getClientDimensions (el: any): SharedFnResult; - getElementRectangle (el: any): SharedFnResult; - offsetToClientCoords (point: AxisValuesData): SharedFnResult>; - }; - - style: { - getWindowDimensions: (win: any) => SharedFnResult; - getElementScroll: (el: any) => SharedFnResult>; - hasScroll: (el: any) => SharedFnResult; - }; - - event: { - BUTTONS_PARAMETER: { - noButton: number; - leftButton: number; - rightButton: number; - }; - }; - - createEventSequence: (dragAndDropEnabled: boolean, firstMovingStepOccured: boolean, options: any) => any; - sendRequestToFrame (msg: any, MOVE_RESPONSE_CMD: string, activeWindow: SharedWindow): SharedFnResult; - ensureMouseEventAfterScroll: (currentElement: any, element: any, wasScrolled: boolean) => SharedFnResult; - - automations: { - click: { - createMouseClickStrategy: (element: any, caretPos: number) => MouseClickStrategyBase; - }; - - _ensureWindowAndCursorForLegacyTests (automation: any): void; - }; -} - export interface ClientRequestEmitter { onRequestSend: (fn: (req: R) => void) => void; onRequestCompleted: (fn: (req: R) => void) => void; @@ -92,6 +39,3 @@ interface AutomationErrorCtors { export type ExecuteSelectorFn = (selector: ExecuteSelectorCommand, errCtors: AutomationErrorCtors, startTime: number) => Promise; -export interface SharedWindow { - parent: SharedWindow | null; -} diff --git a/src/shared/utils/delay.ts b/src/shared/utils/delay.ts deleted file mode 100644 index 28dfef9d7fc..00000000000 --- a/src/shared/utils/delay.ts +++ /dev/null @@ -1,8 +0,0 @@ -// @ts-ignore -import { nativeMethods, Promise } from '../../client/driver/deps/hammerhead'; - -export default function delay (ms: number): Promise { - const setTimeout = nativeMethods.setTimeout; - - return new Promise((resolve: () => void) => setTimeout(resolve, ms)); -} diff --git a/src/shared/utils/promise.ts b/src/shared/utils/promise.ts deleted file mode 100644 index e90f2bf26d3..00000000000 --- a/src/shared/utils/promise.ts +++ /dev/null @@ -1,9 +0,0 @@ -export async function whilst (condition: () => boolean, iterator: () => Promise): Promise { - while (condition()) - await iterator(); -} - -export async function times (n: number, iterator: (n: number) => Promise): Promise { - for (let i = 0; i < n; i++) - await iterator(i); -} diff --git a/src/shared/node-modules-folder-name.ts b/src/utils/node-modules-folder-name.ts similarity index 100% rename from src/shared/node-modules-folder-name.ts rename to src/utils/node-modules-folder-name.ts diff --git a/test/client/fixtures/automation/automations-test.js b/test/client/fixtures/automation/automations-test.js index 404a2f3caf5..3c8bd1e89f9 100644 --- a/test/client/fixtures/automation/automations-test.js +++ b/test/client/fixtures/automation/automations-test.js @@ -218,25 +218,25 @@ $(document).ready(function () { }; const runClickAutomation = function (el, options, callback) { - return getOffsetOptions(el, options.offsetX, options.offsetY) - .then(function (offsets) { - const clickOptions = new ClickOptions({ - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - caretPos: options.caretPos, - - modifiers: { - ctrl: options.ctrl, - alt: options.ctrl, - shift: options.shift, - meta: options.meta, - }, - }); + const offsets = getOffsetOptions(el, options.offsetX, options.offsetY); - const clickAutomation = new ClickAutomation(el, clickOptions, window, cursor); + const clickOptions = new ClickOptions({ + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + caretPos: options.caretPos, + + modifiers: { + ctrl: options.ctrl, + alt: options.ctrl, + shift: options.shift, + meta: options.meta, + }, + }); - return clickAutomation.run(); - }) + const clickAutomation = new ClickAutomation(el, clickOptions, window, cursor); + + clickAutomation + .run() .then(callback); }; @@ -302,6 +302,7 @@ $(document).ready(function () { .then(function () { equal(clickCount, 2); equal(dblclickCount, 1); + startNext(); }); }); @@ -319,7 +320,10 @@ $(document).ready(function () { dragAutomation .run() .then(function () { - deepEqual(position.findCenter($draggable[0]), pointTo); + deepEqual( + JSON.stringify(position.findCenter($draggable[0])), + JSON.stringify(pointTo)); + startNext(); }); }); @@ -613,6 +617,7 @@ $(document).ready(function () { }); module('Regression'); + asyncTest('T191234 - Press Enter key on a textbox element doesn\'t raise report\'s element updating during test running', function () { const input = createTextInput()[0]; const keys = 'enter'; diff --git a/test/client/fixtures/automation/click-test.js b/test/client/fixtures/automation/click-test.js index 4ba507c9c8d..cdd55e3d418 100644 --- a/test/client/fixtures/automation/click-test.js +++ b/test/client/fixtures/automation/click-test.js @@ -664,15 +664,14 @@ $(document).ready(function () { el.addEventListener('click', handler); - return getOffsetOptions($el[0], 20, 20) - .then(function (offsets) { - const click = new ClickAutomation($el[0], new ClickOptions({ - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - }), window, cursor); - - return click.run(); - }) + const offsets = getOffsetOptions($el[0], 20, 20); + + const click = new ClickAutomation($el[0], new ClickOptions({ + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + }), window, cursor); + + click.run() .then(function () { el.removeEventListener('click', handler); @@ -702,15 +701,14 @@ $(document).ready(function () { el.addEventListener('click', handler); - return getOffsetOptions($el[0], -20, -20) - .then(function (offsets) { - const click = new ClickAutomation($el[0], new ClickOptions({ - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - }), window, cursor); + const offsets = getOffsetOptions($el[0], -20, -20); - return click.run(); - }) + const click = new ClickAutomation($el[0], new ClickOptions({ + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + }), window, cursor); + + click.run() .then(function () { el.removeEventListener('click', handler); diff --git a/test/client/fixtures/automation/focus-blur-change-test.js b/test/client/fixtures/automation/focus-blur-change-test.js index 304e8eb64e2..5e485af00b0 100644 --- a/test/client/fixtures/automation/focus-blur-change-test.js +++ b/test/client/fixtures/automation/focus-blur-change-test.js @@ -149,39 +149,37 @@ const getArrayDiff = function (a, b) { }; const runClickAutomation = function (el, options) { - return getOffsetOptions(el, options.offsetX, options.offsetY) - .then(function (offsets) { - const clickOptions = new ClickOptions({ - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - caretPos: options.caretPos, - - modifiers: { - ctrl: options.ctrl, - alt: options.ctrl, - shift: options.shift, - meta: options.meta, - }, - }); + const offsets = getOffsetOptions(el, options.offsetX, options.offsetY); + + const clickOptions = new ClickOptions({ + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + caretPos: options.caretPos, + + modifiers: { + ctrl: options.ctrl, + alt: options.ctrl, + shift: options.shift, + meta: options.meta, + }, + }); - const clickAutomation = new ClickAutomation(el, clickOptions, window, cursor); + const clickAutomation = new ClickAutomation(el, clickOptions, window, cursor); - return clickAutomation.run(); - }); + return clickAutomation.run(); }; const runTypeAutomation = function (element, text) { - return getOffsetOptions(element) - .then(function (offsets) { - const typeOptions = new TypeOptions({ - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - }); + const offsets = getOffsetOptions(element); + + const typeOptions = new TypeOptions({ + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + }); - const typeAutomation = new TypeAutomation(element, text, typeOptions); + const typeAutomation = new TypeAutomation(element, text, typeOptions); - return typeAutomation.run(); - }); + return typeAutomation.run(); }; //tests diff --git a/test/client/fixtures/automation/regression-test.js b/test/client/fixtures/automation/regression-test.js index c340c8a93fe..07e255fabdc 100644 --- a/test/client/fixtures/automation/regression-test.js +++ b/test/client/fixtures/automation/regression-test.js @@ -169,68 +169,65 @@ $(document).ready(function () { }; const runClickAutomation = function (el, options, callback) { - return getOffsetOptions(el, options.offsetX, options.offsetY) - .then(function (offsets) { - const clickOptions = new ClickOptions({ - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - caretPos: options.caretPos, - - modifiers: { - ctrl: options.ctrl, - alt: options.ctrl, - shift: options.shift, - meta: options.meta, - }, - }); + const offsets = getOffsetOptions(el, options.offsetX, options.offsetY); + + const clickOptions = new ClickOptions({ + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + caretPos: options.caretPos, + + modifiers: { + ctrl: options.ctrl, + alt: options.ctrl, + shift: options.shift, + meta: options.meta, + }, + }); - const clickAutomation = new ClickAutomation(el, clickOptions, window, cursor); + const clickAutomation = new ClickAutomation(el, clickOptions, window, cursor); - return clickAutomation - .run() - .then(callback); - }); + return clickAutomation + .run() + .then(callback); }; const runDblClickAutomation = function (el, options, callback) { - return getOffsetOptions(el, options.offsetX, options.offsetY) - .then(function (offsets) { - const clickOptions = new ClickOptions({ - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - caretPos: options.caretPos, - - modifiers: { - ctrl: options.ctrl, - alt: options.ctrl, - shift: options.shift, - meta: options.meta, - }, - }); + const offsets = getOffsetOptions(el, options.offsetX, options.offsetY); + + const clickOptions = new ClickOptions({ + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + caretPos: options.caretPos, + + modifiers: { + ctrl: options.ctrl, + alt: options.ctrl, + shift: options.shift, + meta: options.meta, + }, + }); - const dblClickAutomation = new DblClickAutomation(el, clickOptions); + const dblClickAutomation = new DblClickAutomation(el, clickOptions); - return dblClickAutomation - .run() - .then(callback); - }); + return dblClickAutomation + .run() + .then(callback); }; const runTypeAutomation = function (element, text, options) { - return getOffsetOptions(element) - .then(function (offsets) { - const typeOptions = new TypeOptions({ - caretPos: options.caretPos, - replace: options.replace, - paste: options.paste, - offsetX: offsets.offsetX, - offsetY: offsets.offsetY, - }); + const offsets = getOffsetOptions(element); + + const typeOptions = new TypeOptions({ + caretPos: options.caretPos, + replace: options.replace, + paste: options.paste, + offsetX: offsets.offsetX, + offsetY: offsets.offsetY, + }); - const typeAutomation = new TypeAutomation(element, text, typeOptions); + const typeAutomation = new TypeAutomation(element, text, typeOptions); - return typeAutomation.run(); - }); + return typeAutomation.run(); }; QUnit.testDone(function () { diff --git a/test/client/fixtures/automation/select-element-test.js b/test/client/fixtures/automation/select-element-test.js index c652470636f..31e257c95df 100644 --- a/test/client/fixtures/automation/select-element-test.js +++ b/test/client/fixtures/automation/select-element-test.js @@ -122,27 +122,26 @@ $(document).ready(function () { }; const runDblClickAutomation = function (el, options, callback) { - return getOffsetOptions(el, options.offsetX, options.offsetY) - .then(function (offsets) { - const clickOptions = new ClickOptions(); - - clickOptions.offsetX = offsets.offsetX; - clickOptions.offsetY = offsets.offsetY; - clickOptions.caretPos = options.caretPos; - - clickOptions.modifiers = { - ctrl: options.ctrl, - alt: options.ctrl, - shift: options.shift, - meta: options.meta, - }; - - const dblClickAutomation = new DblClickAutomation(el, clickOptions); - - return dblClickAutomation - .run() - .then(callback); - }); + const offsets = getOffsetOptions(el, options.offsetX, options.offsetY); + + const clickOptions = new ClickOptions(); + + clickOptions.offsetX = offsets.offsetX; + clickOptions.offsetY = offsets.offsetY; + clickOptions.caretPos = options.caretPos; + + clickOptions.modifiers = { + ctrl: options.ctrl, + alt: options.ctrl, + shift: options.shift, + meta: options.meta, + }; + + const dblClickAutomation = new DblClickAutomation(el, clickOptions); + + return dblClickAutomation + .run() + .then(callback); }; const preventDefault = function (e) { @@ -409,30 +408,29 @@ $(document).ready(function () { }; const runClickAutomation = function (el, options, callback) { - return getOffsetOptions(el, options.offsetX, options.offsetY) - .then(function (offsets) { - const clickOptions = new ClickOptions(); - - clickOptions.offsetX = offsets.offsetX; - clickOptions.offsetY = offsets.offsetY; - clickOptions.caretPos = options.caretPos; - - clickOptions.modifiers = { - ctrl: options.ctrl, - alt: options.ctrl, - shift: options.shift, - meta: options.meta, - }; - - const clickAutomation = /opt/i.test(el.tagName) ? - new SelectChildClickAutomation(el, clickOptions) : - new ClickAutomation(el, clickOptions, window, cursor); - - return clickAutomation - .run() - .then(function () { - callback(); - }); + const offsets = getOffsetOptions(el, options.offsetX, options.offsetY); + + const clickOptions = new ClickOptions(); + + clickOptions.offsetX = offsets.offsetX; + clickOptions.offsetY = offsets.offsetY; + clickOptions.caretPos = options.caretPos; + + clickOptions.modifiers = { + ctrl: options.ctrl, + alt: options.ctrl, + shift: options.shift, + meta: options.meta, + }; + + const clickAutomation = /opt/i.test(el.tagName) ? + new SelectChildClickAutomation(el, clickOptions) : + new ClickAutomation(el, clickOptions, window, cursor); + + return clickAutomation + .run() + .then(function () { + callback(); }); }; diff --git a/test/client/fixtures/core/style-utils-test.js b/test/client/fixtures/core/style-utils-test.js index dbb5cb03263..88753979305 100644 --- a/test/client/fixtures/core/style-utils-test.js +++ b/test/client/fixtures/core/style-utils-test.js @@ -1,5 +1,5 @@ const testCafeCore = window.getTestCafeModule('testCafeCore'); -const styleUtils = testCafeCore.styleUtils; +const scrollUtils = testCafeCore.scrollUtils; test('hasScroll (GH-2511)', function () { const div = document.createElement('div'); @@ -11,11 +11,11 @@ test('hasScroll (GH-2511)', function () { // Vertical scroll div.setAttribute('style', 'width: 200px; height: 20px; overflow-x: auto; border: 1px solid black;'); - ok(styleUtils.hasScroll(div), 'vertical scroll'); + ok(scrollUtils.hasScroll(div), 'vertical scroll'); // Horizontal scroll div.setAttribute('style', 'width: 50px; height: 150px; overflow-y: auto; border: 1px solid black;'); - ok(styleUtils.hasScroll(div), 'horizontal scroll'); + ok(scrollUtils.hasScroll(div), 'horizontal scroll'); div.parentNode.removeChild(div); });