/*

Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license

*/
/**
@class Siesta.Test.UserAgent.Mouse

This is a mixin, providing the mouse events simulation functionality.
*/


Role('Siesta.Test.UserAgent.Mouse', {

    requires        : [
        //'simulateEvent', 'getSimulateEventsWith', 'normalizeElement', 'isTextInput'
    ],

    has: {
        // backward-compat only - just reference to a `this.simulator.currentPosition`
        currentPosition                 : null,

        /**
         *  @cfg {Boolean} moveCursorBetweenPoints True to move the mouse cursor between for example two clicks on
         *  separate elements (for better visual experience)
         */
        moveCursorBetweenPoints         : true,


        enableUnreachableClickWarning   : true,

        autoScrollElementsIntoView      : true,
        delayAfterScrollIntoView        : 200
    },


    methods: {

        // not used anymore? previously been used in RootCause, as "instance" mouse move
        setCursorPosition : function (x, y, callback) {
            this.doMouseMove(this.simulator.currentPosition, [ x, y ], callback, null, null, { moveKind : 'instant' });
        },


        moveMouseAlongPath : function () {
            return this.moveCursorAlongPath.apply(this, arguments);
        },

        /**
         * This method sequentially moves a cursor through a series of "path points".
         *
         * A path point can be one of the following:
         *
         * - An object `{ target : 'query', offset : [ x, y ] }`, where 'query' is any valid
         * {@link Siesta.Test.ActionTarget} query. This object will specify an absolute point on the page.
         * - A more compact notation with single letter properties `{ t : 'query', o : [ x, y ] }`
         * - An object `{ target : [ x, y ] }`, where 'x' and 'y' specifies an aboslute point on the page.
         * - An object `{ by : [ dx, dy ] }`, where `by` is an array, with a relative delta from previous point.
         * - An array with 3 elements: `[ 'query', x, y ]`, specifying an absolute point on the page, using
         * 'query' and 'x' 'y' offset.
         * - An array with 1 element: `[ [ x, y ] ]`, which is in turn an array, specifying an absolute
         * point on the page, using 'x' and 'y' page coordinates.
         * - An array with 2 elements: `[ dx, dy ]`, specifying a relative delta from previous point
         *
         * This method is generally intended to be used by {@link Siesta.Recorder.Recorder} with enabled
         * {@link Siesta.Recorder.Recorder#recordMouseMovePath recordMouseMovePath} option. That option can generate
         * a substantial amount of data, thus there is a focus on having a compact notation for it.
         *
         * @param {Array[path point]} pathArray An array of "path points"
         * @param {Function} callback A function to call after visiting all points
         *
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        moveCursorAlongPath : function (pathArray, callback) {
            var me          = this

            var queue       = new Siesta.Util.Queue({
                deferer         : this.originalSetTimeout,
                deferClearer    : this.originalClearTimeout,

                interval        : 0,

                processor       : function (data) {
                    var pathPoint       = data.pathPoint

                    var target, offset, isDelta

                    if (me.typeOf(pathPoint) == 'Array') {
                        if (me.typeOf(pathPoint[ 0 ]) == 'Array') {
                            target      = pathPoint[ 0 ]
                        } else if (me.typeOf(pathPoint[ 0 ]) == 'String') {
                            target      = pathPoint[ 0 ]
                            offset      = pathPoint.length == 3 ? [ pathPoint[ 1 ], pathPoint[ 2 ] ] : null
                        } else {
                            isDelta     = true
                            target      = pathPoint
                        }

                    } else {
                        if (pathPoint.by) {
                            isDelta     = true
                            target      = pathPoint.by
                        } else {
                            target      = pathPoint.t || pathPoint.target
                            offset      = pathPoint.o || pathPoint.offset
                        }
                    }

                    if (isDelta)
                        me.moveMouseBy(target, data.next, me)
                    else
                        me.moveMouseTo(target, data.next, me, offset)
                }
            })

            Joose.A.each(pathArray, function (pathPoint) {
                queue.addStep({
                    isAsync     : true,

                    pathPoint   : pathPoint
                })
            })

            return new Promise(function (resolve, _reject) {
                queue.run(function () {
                    me.processCallbackFromTest(callback, null, me)

                    resolve()
                })
            })
        },


        /**
         * This method will simulate a mouse move to an xy-coordinate or an element (the center of it)
         *
         * @param {Siesta.Test.ActionTarget} target Target point to move the mouse to.
         * @param {Function} [callback] To run this method async, provide a callback method to be called after the operation is completed.
         * @param {Object} [scope] the scope for the callback
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "50%"] to click in the center.
         * @param {Object} [waitForTarget] *private* True to wait for the target to exist before moving mouse
         * @param {Object} [options] Any options to use for the simulated DOM event
         *
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        moveMouseTo : function (target, callback, scope, offset, waitForTarget, options) {
            if (!target) throw new Error('Trying to call `moveMouseTo` without a target')

            // TODO this method should also accept an options object, so user can for example hold CTRL key during mouse operation

            if (waitForTarget !== false) {
                return this.waitForTargetAndSyncMousePosition(target, offset, function () {
                    callback && callback.call(scope || this);
                }, [], false, undefined, options);
            } else {
                var me          = this

                return new Promise(function (resolve, _reject) {
                    // skip warning about clicking in an unreachable point of the element at me step
                    // when mouse position is not yet updated
                    // potentially the element will become reachable when the mouse is moved to the required point
                    var data        = me.getNormalizedTopElementInfo(target, true, 'moveMouseTo', offset);

                    if (data) {
                        me.syncCursor(data.globalXY, function () {
                            callback && callback.call(scope || me);

                            resolve()
                        }, options);
                    } else {
                        // No point in continuing
                        callback && callback.call(scope || me);

                        resolve()
                    }
                })
            }
        },


        /**
         * Alias for moveMouseTo, this method will simulate a mouse move to an xy-coordinate or an element (the center of it)
         *
         * @param {Siesta.Test.ActionTarget} target Target point to move the mouse to.
         * @param {Function} [callback] To run this method async, provide a callback method to be called after the operation is completed.
         * @param {Object} [scope] the scope for the callback
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "50%"] to click in the center.
         *
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        moveCursorTo : function (target, callback, scope, offset) {
            return this.moveMouseTo.apply(this, arguments);
        },

        /**
         * This method will simulate a mouse move by an x a y delta amount
         * @param {Array} delta The delta x and y distance to move, e.g. [20, 20] for 20px down/right, or [0, 10] for just 10px down.
         * @param {Function} [callback] To run this method async, provide a callback method to be called after the operation is completed.
         * @param {Object} [scope] the scope for the callback
         * @param {Object} [options] Any options to use for the simulated DOM events
         *
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        moveMouseBy : function (delta, callback, scope, options) {
            return this.moveCursorBy.apply(this, arguments);
        },

        /**
         * This method will simulate a mouse move by an x and y delta amount
         * @param {Array} delta The delta x and y distance to move, e.g. [20, 20] for 20px down/right, or [0, -10] for 10px up.
         * @param {Function} [callback] To run this method async, provide a callback method to be called after the operation is completed.
         * @param {Object} [scope] the scope for the callback
         * @param {Object} [options] Any options to use for the simulated DOM events
         *
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        moveCursorBy : function (delta, callback, scope, options) {
            if (!delta) {
                throw 'Trying to call moveCursorBy without relative distances';
            }

            // Normalize target
            var target = [
                this.simulator.currentPosition[ 0 ] + delta[ 0 ],
                this.simulator.currentPosition[ 1 ] + delta[ 1 ]
            ];

            return this.doMouseMove(this.simulator.currentPosition, target, callback, scope, undefined, options);
        },


        // private, to be removed in the future?
        // we don't support source `xy` coordinates in native simulator
        doMouseMove : function (xy, xy2, callback, scope, params, options) {
            var promise         = this.simulator.simulateMouseMove(xy2[ 0 ], xy2[ 1 ], options, params)

            return this.runPromiseAsync(promise, 'mousemove', callback, scope)
        },


        // candidate for removal, only used in 2 places, feels redundant with "doMouseMouse"
        syncCursor : function (toXY, callback, options) {
            var fromXY      = this.simulator.currentPosition;

            if (toXY[ 0 ] !== fromXY[ 0 ] || toXY[ 1 ] !== fromXY[ 1 ]) {

                this.doMouseMove(fromXY, toXY, callback, this, undefined, options);
            } else
                // already aligned
                callback && callback();
        },


        runPromiseAsync : function (promise, actionDesc, callback, callbackScope, errback) {
            var me          = this

            var async       = this.beginAsync(null, function () {
                me.fail(me.formatString("Action `{actionDesc}` failed to complete within {time}ms", { actionDesc : actionDesc, time : this.defaultTimeout }))

                return true
            })

            return promise.then(function (result) {
                me.endAsync(async)

                me.processCallbackFromTest(callback, result !== undefined ? [ result ] : [], callbackScope || me)
            }, function (exception) {
                me.endAsync(async)

                if (errback)
                    errback(exception)
                else if (!me.isFinished()) {
                    me.fail("Command failed: " + exception)

                    me.processCallbackFromTest(callback, [], callbackScope || me)
                }
            })
        },


        // This method is called before mouse interactions (the "method" param, along with its "args") to assure that target is visible and reachable.
        // It also handles cases where the target is moved or made unreachable while the cursor is moving towards it.
        // In such unusual cases, a wait is added and the method calls itself to start over
        waitForTargetAndSyncMousePosition : function (target, offset, method, args, waitForTargetTop, syncMousePosition, options) {
            var originalXY
            var targetIsNotAPoint                   = this.typeOf(target) != 'Array'
            var oldSuppressPassedWaitForAssertion   = this.suppressPassedWaitForAssertion

            this.suppressPassedWaitForAssertion     = true

            var me                                  = this

            return new Promise(function (resolve, reject) {
                me.chain(
                    { waitForAnimations : [] },

                    // Initial wait for target to be
                    // 1. in the dom,
                    // 2. visible
                    targetIsNotAPoint && { waitForElementVisible : target },

                    me.autoScrollElementsIntoView
                        ?
                            function (next) {
                                if (me.scrollTargetIntoView(target, offset) === true)
                                    // me "waitFor" has been added because of Ext6 behaviour (see https://www.assembla.com/spaces/bryntum/tickets/2211#/activity/ticket:)
                                    // Ext6 listens to scroll event on grid view and sets the "pointer-event : none" on the grid view el in the handler
                                    // Problem happens during click.
                                     // Seems, depending from browser engine, "scroll" event may be fired after slight delay, already after the "mousedown"
                                    // even has been fired, then, with "pointer-events" none on grid view, grid container becomes a top element
                                    // and further `mouseup` and `click` happens on it, instead of original element
                                    // the "pointer-event" style is reset back in the another ExtJS handler, which is buffered for 100ms
                                    // so we need to wait > 100ms, waiting for 200ms
                                    // potential race condition
                                    me.waitFor(me.delayAfterScrollIntoView, next)
                                else
                                    next()
                            }
                        :
                            null,

                    function (next) {
                        var data    = me.getNormalizedTopElementInfo(target, true, method.toString(), offset)

                        // No target available, possibly page is reloading. Go back to waiting for something to appear
                        if (!data) {
                            me.waitForTarget(target, function () {
                                var data    = me.getNormalizedTopElementInfo(target, true, method.toString(), offset)

                                originalXY  = data.globalXY

                                if (me.moveCursorBetweenPoints && syncMousePosition !== false) {
                                    me.syncCursor(originalXY, next, options)
                                } else {
                                    next()
                                }
                            })

                            return
                        }

                        originalXY  = data.globalXY;

                        if (me.moveCursorBetweenPoints && syncMousePosition !== false) {
                            me.syncCursor(originalXY, next, options)
                        } else {
                            next()
                        }
                    },

                    // After moving cursor, we again wait as something might have changed in the while we moved the cursor
                    waitForTargetTop !== false && targetIsNotAPoint && function (next) {
                        me.waitForTarget(target, next, me, null, offset)
                    },

                    function (_next) {
                        var data    = me.getNormalizedTopElementInfo(target, true, method.toString(), offset)
                        var newXY   = data && data.globalXY

                        // If target has moved or disappeared, start over after a short wait
                        if (targetIsNotAPoint && (!data || originalXY[0] !== newXY[0] || originalXY[1] !== newXY[1])) {
                            me.diag(Siesta.Resource('Siesta.Test.Browser','targetMoved'))

                            me.waitFor(100, function() {
                                me.waitForTargetAndSyncMousePosition(target, offset, method, args, waitForTargetTop, syncMousePosition).then(resolve, reject)
                            });
                        } else {
                            me.suppressPassedWaitForAssertion = oldSuppressPassedWaitForAssertion;

                            // Here we're done - call original method if its a function
                            method && method.apply && method.apply(me, args)

                            resolve()
                        }
                    }
                )
                // eof chain
            })
            // eof promise
        },


        getCursorPagePosition : function () {
            return [
                this.viewportXtoPageX(this.simulator.currentPosition[ 0 ]),
                this.viewportYtoPageY(this.simulator.currentPosition[ 1 ])
            ]
        },


        // el - the target
        // callback
        // scope
        // options for the events emitted
        // actionName (string) - the method of simulator to call (will return promise)
        // offset
        // performTargetCheck, true to waitFor target appearing - false to avoid
        genericMouseAction : function (el, callback, scope, options, actionName, offset, waitForTargetAndSyncMousePosition) {
            var me          = this

            if (this.typeOf(el) == 'Function') {
                scope       = callback
                callback    = el
                el          = null
            }

            waitForTargetAndSyncMousePosition = waitForTargetAndSyncMousePosition && Boolean(el);

            el              = el || this.getCursorPagePosition()

            var targetCheckPromise = waitForTargetAndSyncMousePosition !== false ? this.waitForTargetAndSyncMousePosition(el, offset, actionName, undefined, undefined, undefined, options) : Promise.resolve()

            return me.runPromiseAsync(targetCheckPromise.then(function () {
                options         = options ? Joose.O.copy(options) : {}

                // skip warning about clicking in an unreachable point of the element at me step
                // when mouse position is not yet updated
                // potentially the element will become reachable when the mouse is moved to the required point
                var data        = me.getNormalizedTopElementInfo(el, true, actionName, offset)

                if (!data) {
                    // No point in continuing
                    return
                }

                // marking data as preliminary, indicating that it should be updated before the click
                data.originalEl     = el
                data.method         = actionName
                data.offset         = offset

                options.clientX     = options.clientX != null ? options.clientX : data.localXY[ 0 ]
                options.clientY     = options.clientY != null ? options.clientY : data.localXY[ 1 ]

                options.testUniqueId = me.uniqueId

                return me.simulator[ actionName ](data, options);
            }), 'generic click', callback, scope)
        },



        /**
         * This method will simulate a mouse click in the center of the specified DOM element,
         * or at current cursor position if no target is provided.
         *
         * Note, that it will first calculate the central point of the specified element and then
         * will pick the top-most DOM element from that point. For example, if you will provide a grid row as the `el`,
         * then click will happen on top of the central cell, and then will bubble to the row itself.
         * In most cases this is the desired behavior.
         *
         * Example:
         *
         *      t.click(t.getFirstRow(grid), function () { ... })
         *
         * The 1st argument for this method can be omitted. In this case, Siesta will use the current cursor position:
         *
         *      t.click(function () { ... })
         *
         * This method returns a `Promise` which is resolved once the click has completed:
         *
         *      t.click('#someEl').then(function () {
         *          return t.click('#anotherEl')
         *      }).then(function () {
         *          return t.click('#yetAnotherEl')
         *      })
         *
         * See also {@link Siesta.Test#chain chain} method for slimer chaining notation.
         *
         * @param {Siesta.Test.ActionTarget} [el] One of the {@link Siesta.Test.ActionTarget} values to convert to DOM element
         * @param {Function} [callback] A function to call after the click
         * @param {Object} [scope] The scope for the callback
         * @param {Object} [options] Any options to use for the simulated DOM event
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"]
         * to click in the center horizontally and 2px from the bottom edge.
         * @return {Promise} Returns a promise resolved once the click has completed
         */
        click : function (el, callback, scope, options, offset, waitForTarget) {
            return this.genericMouseAction(el, callback, scope, options, 'simulateMouseClick', offset, waitForTarget)
        },


        /**
         * This method will simulate a mouse right click in the center of the specified DOM/Ext element,
         * or at current cursor position if no target is provided.
         *
         * Note, that it will first calculate the centeral point of the specified element and then
         * will pick the top-most DOM element from that point. For example, if you will provide a grid row as the `el`,
         * then click will happen on top of the central cell, and then will bubble to the row itself.
         * In most cases this is the desired behavior.
         *
         * Example:
         *
         *      t.rightClick(t.getFirstRow(grid), function () { ... })
         *
         * The 1st argument for this method can be omitted. In this case, Siesta will use the current cursor position:
         *
         *      t.rightClick(function () { ... })
         *
         * This method returns a `Promise` which is resolved once the right click has completed
         *
         * @param {Siesta.Test.ActionTarget} [el] One of the {@link Siesta.Test.ActionTarget} values to convert to DOM element
         * @param {Function} [callback] A function to call after click.
         * @param {Object} [scope] The scope for the callback
         * @param {Object} [options] Any options to use for the simulated DOM event
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"]
         * to click in the center horizontally and 2px from the bottom edge.
         * @return {Promise} Returns a promise resolved once the right click has completed
         */
        rightClick : function (el, callback, scope, options, offset, waitForTarget) {
            return this.genericMouseAction(el, callback, scope, options, 'simulateRightClick', offset, waitForTarget)
        },


        /**
         * This method will simulate a mouse double click in the center of the specified DOM/Ext element,
         * or at current cursor position if no target is provided.
         *
         * Note, that it will first calculate the centeral point of the specified element and then
         * will pick the top-most DOM element from that point. For example, if you will provide a grid row as the `el`,
         * then click will happen on top of the central cell, and then will bubble to the row itself.
         * In most cases this is the desired behavior.
         *
         * Example:
         *
         *      t.doubleClick(t.getFirstRow(grid), function () { ... })
         *
         * The 1st argument for this method can be omitted. In this case, Siesta will use the current cursor position:
         *
         *      t.doubleClick(function () { ... })
         *
         * This method returns a `Promise` which is resolved once the double click has completed
         *
         * @param {Siesta.Test.ActionTarget} [el] One of the {@link Siesta.Test.ActionTarget} values to convert to DOM element
         * @param {Function} [callback] A function to call after click.
         * @param {Object} [scope] The scope for the callback
         * @param {Object} [options] Any options to use for the simulated DOM event
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"]
         * to click in the center horizontally and 2px from the bottom edge.
         * @return {Promise} Returns a promise resolved once the double click has completed
         */
        doubleClick : function (el, callback, scope, options, offset, waitForTarget) {
            return this.genericMouseAction(el, callback, scope, options, 'simulateDoubleClick', offset, waitForTarget)
        },


        /**
         * This method will simulate a mousedown event in the center of the specified DOM element,
         * or at current cursor position if no target is provided.
         *
         * @param {Siesta.Test.ActionTarget} el
         * @param {Object} options any extra options used to configure the DOM event
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"]
         * to click in the center horizontally and 2px from the bottom edge.
         * @param {Function} [callback] A function to call after mousedown.
         * @param {Object} [scope] The scope for the callback
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        mouseDown : function (el, options, offset, callback, scope, performTargetCheck) {
            return this.genericMouseAction(el, callback, scope, options, 'simulateMouseDown', offset, performTargetCheck);
        },


        /**
         * This method will simulate a mousedown event in the center of the specified DOM element,
         * or at current cursor position if no target is provided.
         *
         * @param {Siesta.Test.ActionTarget} el
         * @param {Object} options any extra options used to configure the DOM event
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"]
         * to click in the center horizontally and 2px from the bottom edge.
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        mouseUp : function (el, options, offset, callback, scope) {
            return this.genericMouseAction(el, callback, scope, options, 'simulateMouseUp', offset, true);
        },


        /**
         * This method will simulate a drag and drop operation between either two points or two DOM elements.
         *
         * @param {Siesta.Test.ActionTarget} source {@link Siesta.Test.ActionTarget} value for the drag starting point
         * @param {Siesta.Test.ActionTarget} target {@link Siesta.Test.ActionTarget} value for the drag end point
         * @param {Function} [callback] To run this method async, provide a callback method to be called after the drag operation is completed.
         * @param {Object} [scope] the scope for the callback
         * @param {Object} options any extra options used to configure the DOM event
         * @param {Boolean} dragOnly true to skip the mouseup and not finish the drop operation.
         * @param {Array} [sourceOffset] An X,Y offset relative to the source. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         * @param {Array} [targetOffset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        dragTo : function (source, target, callback, scope, options, dragOnly, sourceOffset, targetOffset) {
            var me              = this;

            if (!target) throw new Error('No drag target defined');

            source              = source || me.getCursorPagePosition()

            return new Promise(function (resolve, reject) {

                me.chain(
                    { mouseDown : source, offset : sourceOffset, options : options },

                    function (next) {
                        var data    = me.getNormalizedTopElementInfo(target, true, 'dragTo', targetOffset);

                        target      = data.globalXY;

                        next();
                    },

                    { moveCursorTo : function () { return target }, options : options },

                    dragOnly ? null : { mouseUp : null, options : options },

                    function () {
                        me.processCallbackFromTest(callback, null, scope || me);

                        resolve()
                    }
                );
            })
        },


        /**
         * This method will simulate a drag and drop operation from a point (or DOM element) and move by a delta.
         *
         * @param {Siesta.Test.ActionTarget} source {@link Siesta.Test.ActionTarget} value as the drag starting point
         * @param {Array} delta The amount to drag from the source coordinate, expressed as [x,y]. E.g. [50, 10] will drag 50px to the right and 10px down.
         * @param {Function} [callback] To run this method async, provide a callback method to be called after the drag operation is completed.
         * @param {Object} [scope] the scope for the callback
         * @param {Object} options any extra options used to configure the DOM event
         * @param {Boolean} dragOnly true to skip the mouseup and not finish the drop operation.
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        dragBy : function (source, delta, callback, scope, options, dragOnly, offset) {
            var me              = this;

            if (!delta) throw new Error('No drag delta defined');

            source              = source || this.getCursorPagePosition()

            return new Promise(function (resolve, reject) {
                me.chain(
                    { mouseDown : source, offset : offset, options : options },

                    { moveCursorBy : delta, options : options },

                    dragOnly ? null : { mouseUp : null, options : options },

                    function () {
                        me.processCallbackFromTest(callback, null, scope || me);

                        resolve()
                    }
                );
            })
        },


        /**
         * This method will simulate a wheel event on the specified DOM element.
         *
         * @param {Siesta.Test.ActionTarget} [el] One of the {@link Siesta.Test.ActionTarget} values to convert to a DOM element
         * @param {Function} [callback] A function to call after the action.
         * @param {Object} [scope] The scope for the callback
         * @param {Object} [options] Any options to use for the simulated DOM event
         * @param {Object} options any extra options used to configure the DOM event
         * @param {Object} options.deltaX a double representing the horizontal scroll amount.
         * @param {Object} options.deltaY a double representing the vertical scroll amount. Not supported in native events simulation.
         * @param {Object} options.deltaZ a double representing scroll amount for the z-axis. Not supported in native events simulation.
         * @param {Array} [offset] An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         * @return {Promise} Returns a promise resolved once the action has completed
         */
        wheel : function (el, callback, scope, options, offset, waitForTarget) {
            return this.genericMouseAction(el, callback, scope, options, 'simulateMouseWheel', offset, waitForTarget)
        }
    }
});