    /*

    Siesta 5.3.0
    Copyright(c) 2009-2019 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.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) {
                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) {
                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, performTargetCheck) {
                var me          = this

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

                el              = el || this.getCursorPagePosition()

                var targetCheckPromise = performTargetCheck !== 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)
            }
        }
    });