UNPKG

webdriverio-workflo

Version:

This is a customized version of webdriverio for use with workflo framework.

293 lines (258 loc) 10.5 kB
/** * * The Touch Action API provides the basis of all gestures that can be automated in Appium. * It is currently only available to native apps and can not be used to interact with webapps. * At its core is the ability to chain together _ad hoc_ individual actions, which will then be * applied to an element in the application on the device. The basic actions that can be used are: * * - press (pass selector or (x,y) or both) * - longPress (pass selector or (x,y) or both) * - tap (pass selector or (x,y) or both) * - moveTo (pass selector or (x,y) or both) * - wait (pass ms (as milliseconds)) * - release (no arguments) * * If you use the touchAction command with a selector you don't need to pass the selector to each * action. It will be propagated by the internally (if no x or y parameters are given). * * <example> :touchAction.js it('should do a touch gesture', function () { var screen = $('//UITextbox'); // simple touch action on element screen.touchAction('tap'); // same as browser.touchAction('//UITextbox', 'tap') // simple touch action using x y variables browser.touchAction({ action: 'tap', x: 300, y:200 }) // simple touch action using selector and x y variables // tap location is 30px right and 20px down relative from the center of the element browser.touchAction({ action: 'tap', x: 30, y:20, selector: '//UIAApplication[1]/UIAElement[2]' }) // multi action on an element (drag&drop) screen.touchAction([ 'press', { action: 'moveTo', x: 200, y: 0 }, 'release' ]) // same as browser.touchAction('//UITextbox', [ 'press', { action: 'moveTo', x: 200, y: 0}, 'release' ]) // multi action using x y variables // moveTo location is relative from the starting coordinate browser.touchAction([ { action: 'press', x: 20, y: 550 }, { action: 'moveTo', x: 0, y: -500}, 'release' ]) // drag&drop to element screen.touchAction([ 'press', { action: 'moveTo', selector: '//UIAApplication[1]/UIAElement[2]' }, 'release' ]) }); :multiTouchAction.js it('should do a multitouch gesture', function () { // drag&drop with two fingers 200px down browser.touchAction([ [{action: 'press', x: 10, y: 10}, { action: 'moveTo', x: 0, y: 200 }, 'release'], [{action: 'press', x: 100, y: 10}, { action: 'moveTo', x: 0, y: 200 }, 'release'] ]) }) * </example> * * @param {String} selector selector to execute the touchAction on * @param {String} action action to execute * * @see https://saucelabs.com/blog/appium-sauce-labs-bootcamp-chapter-2-touch-actions * @type mobile * @for android, ios * @uses mobile/performTouchAction, mobile/performMultiAction * */ const TOUCH_ACTIONS = ['press', 'longPress', 'tap', 'moveTo', 'wait', 'release'] const POS_ACTIONS = TOUCH_ACTIONS.slice(0, -2) const ACCEPTED_OPTIONS = ['x', 'y', 'selector', 'element'] export default function touchAction (selector, actions) { if (typeof selector !== 'string' || TOUCH_ACTIONS.indexOf(selector) > -1) { actions = selector selector = this.lastResult } if (!Array.isArray(actions)) { actions = [actions] } /** * check if multiAction */ if (Array.isArray(actions[0])) { actions = formatArgs(selector, actions) return Promise.all(getSelectors.call(this, actions, true)).then((jsonElements) => { actions = replaceSelectorsById(actions, jsonElements) return this.performMultiAction({ actions }) }) } actions = formatArgs(selector, actions) return Promise.all(getSelectors.call(this, actions)).then((jsonElements) => { actions = replaceSelectorsById(actions, jsonElements) return this.performTouchAction({ actions }) }) } /** * helper to determine if action has proper option arguments * ('press', 'longPress', 'tap', 'moveTo' need at least some kind of position information) * @param {String} action name of action * @param {Object} options action options * @return {Boolean} True if don't need any options or has a position option */ let hasValidActionOptions = function (action, options) { return POS_ACTIONS.indexOf(action) < 0 || (POS_ACTIONS.indexOf(action) > -1 && Object.keys(options).length > 0) } let formatArgs = function (selector, actions) { return actions.map((action) => { if (Array.isArray(action)) { return formatArgs(selector, action) } const formattedAction = { action: action.action, options: {} } /** * propagate selector or element to options object */ if ( selector && // selector is given as string `e.g. browser.touchAction(selector, 'tap')` typeof selector === 'string' && // don't propagate for actions that don't require element options POS_ACTIONS.indexOf(typeof action === 'string' ? action : formattedAction.action) > -1 && // don't propagate if user has x and y set !(isFinite(action.x) && isFinite(action.y)) ) { formattedAction.options.selector = selector } else if ( selector && // selector is given by previous command // e.g. $(selector).touchAction('tap') selector.value && // don't propagate for actions that don't require element options POS_ACTIONS.indexOf(typeof action === 'string' ? action : formattedAction.action) > -1 && // don't propagate if user has x and y set !(isFinite(action.x) && isFinite(action.y)) ) { formattedAction.options.element = selector.value.ELEMENT } if (typeof action === 'string') { if (!hasValidActionOptions(action, formattedAction.options)) { throw new Error( `Touch action "${action}" doesn't have proper options. Make sure certain actions like ` + `${POS_ACTIONS.join(', ')} have position options like "selector", "x" or "y".` ) } formattedAction.action = action /** * remove options property if empty */ if (Object.keys(formattedAction.options).length === 0) { delete formattedAction.options } return formattedAction } if (isFinite(action.x)) formattedAction.options.x = action.x if (isFinite(action.y)) formattedAction.options.y = action.y if (action.ms) formattedAction.options.ms = action.ms if (action.selector && POS_ACTIONS.indexOf(formattedAction.action) > -1) { formattedAction.options.selector = action.selector } if (action.element) { formattedAction.options.element = action.element delete formattedAction.options.selector } /** * remove options property if empty */ if (Object.keys(formattedAction.options).length === 0) { delete formattedAction.options } /** * option check * make sure action has proper options before sending command to Appium */ if (formattedAction.action === 'release' && formattedAction.options) { throw new Error( 'action "release" doesn\'t accept any options ' + `("${Object.keys(formattedAction.options).join('", "')}" found)` ) } else if ( formattedAction.action === 'wait' && (Object.keys(formattedAction.options).indexOf('x') > -1 || Object.keys(formattedAction.options).indexOf('y') > -1) ) { throw new Error('action "wait" doesn\'t accept x, y options') } else if (POS_ACTIONS.indexOf(formattedAction.action) > -1) { for (const option in formattedAction.options) { if (ACCEPTED_OPTIONS.indexOf(option) === -1) { throw new Error(`action "${formattedAction.action}" doesn't accept "${option}" as option`) } } if (Object.keys(formattedAction.options || {}).length === 0) { throw new Error( `Touch actions like "${formattedAction.action}" need at least some kind of ` + 'position information like "selector", "x" or "y" options, you\'ve none given.' ) } } return formattedAction }) } let getSelectors = function (actions, isMultiAction = false) { let queriedSelectors = [] /** * flatten actions array */ if (isMultiAction) { actions = [].concat.apply([], actions) } return actions /** * map down to list of selectors */ .map((action) => action.options && action.options.selector) /** * filter actions without selector and unique selectors */ .filter((selector) => { const res = Boolean(selector) && queriedSelectors.indexOf(selector) === -1 queriedSelectors.push(selector) return res }) /** * call element command on selectors */ .map((selector) => this.element(selector)) } /** * replaces selector action properties with element ids after they got fetched * @param {Object[]} actions list of actions * @param {Object[]} elements list of fetched elements * @return {Object[]} list of actions with proper element ids */ let replaceSelectorsById = function (actions, elements) { return actions.map((action) => { if (Array.isArray(action)) { return replaceSelectorsById(action, elements) } if (!action.options || !action.options.selector) { return action } elements.forEach((element) => { if (action.options.selector === element.selector) { action.options.element = element.value.ELEMENT delete action.options.selector } }) return action }) }