🏡 index : quine-global/webdriverio.git

import type { TouchAction, TouchActions } from '../types.js'

/**
 * Constants around commands
 */
const TOUCH_ACTIONS = ['press', 'longPress', 'tap', 'moveTo', 'wait', 'release']
const POS_ACTIONS = TOUCH_ACTIONS.slice(0, 4)
const ACCEPTED_OPTIONS = ['x', 'y', 'element']
export const SCRIPT_PREFIX = '/* __wdio script__ */'
export const SCRIPT_SUFFIX = '/* __wdio script end__ */'

/**
 * These scripts are loaded by Esbuild at build time and injected into the bundle
 */
declare const WDIO_RESQ_SCRIPT: string
export const resqScript = WDIO_RESQ_SCRIPT

interface FormattedTouchAction extends Omit<TouchAction, 'element'> {
    element?: string
}

interface FormattedActions {
    action: string
    options?: FormattedTouchAction
}

export const formatArgs = function (
    scope: WebdriverIO.Browser | WebdriverIO.Element,
    actions: TouchActions[]
): FormattedActions[] {
    return actions.map((action: TouchAction) => {
        if (Array.isArray(action)) {
            return formatArgs(scope, action) as unknown as FormattedActions
        }

        if (typeof action === 'string') {
            action = { action }
        }

        const formattedAction: FormattedActions = {
            action: action.action,
            options: {} as FormattedTouchAction
        }

        /**
         * don't propagate for actions that don't require element options
         */
        const actionElement = action.element && typeof (action.element as unknown as WebdriverIO.Element).elementId === 'string'
            ? (action.element as unknown as WebdriverIO.Element).elementId
            : (scope as WebdriverIO.Element).elementId
        if (POS_ACTIONS.includes(action.action) && formattedAction.options && actionElement) {
            formattedAction.options.element = actionElement
        }

        if (formattedAction.options && typeof action.x === 'number' && isFinite(action.x)) {
            formattedAction.options.x = action.x
        }
        if (formattedAction.options && typeof action.y === 'number' && isFinite(action.y)) {
            formattedAction.options.y = action.y
        }
        if (formattedAction.options && action.ms) {
            formattedAction.options.ms = action.ms
        }

        /**
         * remove options property if empty
         */
        if (formattedAction.options && Object.keys(formattedAction.options).length === 0) {
            delete formattedAction.options
        }

        return formattedAction
    })
}

/**
 * Make sure action has proper options before sending command to Appium.
 *
 * @param  {Object} params  touchAction parameters
 * @return null
 */
export const validateParameters = (params: FormattedActions) => {
    const options = Object.keys(params.options || {})
    if (params.action === 'release' && options.length !== 0) {
        throw new Error(
            'action "release" doesn\'t accept any options ' +
            `("${options.join('", "')}" found)`
        )
    }

    if (
        params.action === 'wait' &&
        (options.includes('x') || options.includes('y'))
    ) {
        throw new Error('action "wait" doesn\'t accept x or y options')
    }

    if (POS_ACTIONS.includes(params.action)) {
        for (const option in params.options) {
            if (!ACCEPTED_OPTIONS.includes(option)) {
                throw new Error(`action "${params.action}" doesn't accept "${option}" as option`)
            }
        }

        if (options.length === 0) {
            throw new Error(
                `Touch actions like "${params.action}" need at least some kind of ` +
                'position information like "element", "x" or "y" options, you\'ve none given.'
            )
        }
    }
}

export const touchAction = function (
    this: WebdriverIO.Browser | WebdriverIO.Element,
    actions: TouchActions
) {
    if (!this.multiTouchPerform || !this.touchPerform) {
        throw new Error('touchAction can be used with Appium only.')
    }
    if (!Array.isArray(actions)) {
        actions = [actions as TouchAction]
    }

    const formattedAction = formatArgs(this, actions)
    const protocolCommand = Array.isArray(actions[0])
        // cast old JSONWP
        ? this.multiTouchPerform.bind(this) as unknown as (actions: object[]) => Promise<void>
        : this.touchPerform.bind(this)
    formattedAction.forEach((params) => validateParameters(params))
    return protocolCommand(formattedAction)
}