UNPKG

@appium/base-driver

Version:

Base driver class for Appium drivers

270 lines 12.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.COMMAND_URLS_CONFLICTS = void 0; const lodash_1 = __importDefault(require("lodash")); const support_1 = require("@appium/support"); const helpers_1 = require("../basedriver/helpers"); const constants_1 = require("../constants"); exports.COMMAND_URLS_CONFLICTS = [ { commandNames: ['execute', 'executeAsync'], jsonwpConverter: (url) => url.replace(/\/execute.*/, url.includes('async') ? '/execute_async' : '/execute'), w3cConverter: (url) => url.replace(/\/execute.*/, url.includes('async') ? '/execute/async' : '/execute/sync'), }, { commandNames: ['getElementScreenshot'], jsonwpConverter: (url) => url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'), w3cConverter: (url) => url.replace(/\/screenshot\/([^/]+)/, '/element/$1/screenshot'), }, { commandNames: ['getWindowHandles', 'getWindowHandle'], jsonwpConverter(url) { return /\/window$/.test(url) ? url.replace(/\/window$/, '/window_handle') : url.replace(/\/window\/handle(s?)$/, '/window_handle$1'); }, w3cConverter(url) { return /\/window_handle$/.test(url) ? url.replace(/\/window_handle$/, '/window') : url.replace(/\/window_handles$/, '/window/handles'); }, }, { commandNames: ['getProperty'], jsonwpConverter: (w3cUrl) => { const w3cPropertyRegex = /\/element\/([^/]+)\/property\/([^/]+)/; const jsonwpUrl = w3cUrl.replace(w3cPropertyRegex, '/element/$1/attribute/$2'); return jsonwpUrl; }, w3cConverter: (jsonwpUrl) => jsonwpUrl, // Don't convert JSONWP URL to W3C. W3C accepts /attribute and /property }, ]; const { MJSONWP, W3C } = constants_1.PROTOCOLS; const DEFAULT_LOG = support_1.logger.getLogger('Protocol Converter'); class ProtocolConverter { /** * * @param {ProxyFunction} proxyFunc * @param {import('@appium/types').AppiumLogger | null} [log=null] */ constructor(proxyFunc, log = null) { this.proxyFunc = proxyFunc; this._downstreamProtocol = null; this._log = log; } get log() { return this._log ?? DEFAULT_LOG; } set downstreamProtocol(value) { this._downstreamProtocol = value; } get downstreamProtocol() { return this._downstreamProtocol; } /** * W3C /timeouts can take as many as 3 timeout types at once, MJSONWP /timeouts only takes one * at a time. So if we're using W3C and proxying to MJSONWP and there's more than one timeout type * provided in the request, we need to do 3 proxies and combine the result * * @param {Object} body Request body * @return {Object[]} Array of W3C + MJSONWP compatible timeout objects */ getTimeoutRequestObjects(body) { if (this.downstreamProtocol === W3C && lodash_1.default.has(body, 'ms') && lodash_1.default.has(body, 'type')) { const typeToW3C = (x) => (x === 'page load' ? 'pageLoad' : x); return [ { [typeToW3C(body.type)]: body.ms, }, ]; } if (this.downstreamProtocol === MJSONWP && (!lodash_1.default.has(body, 'ms') || !lodash_1.default.has(body, 'type'))) { const typeToJSONWP = (x) => (x === 'pageLoad' ? 'page load' : x); return (lodash_1.default.toPairs(body) // Only transform the entry if ms value is a valid positive float number .filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`)) .map(function (pair) { return { type: typeToJSONWP(pair[0]), ms: pair[1], }; })); } return [body]; } /** * Proxy an array of timeout objects and merge the result * @param {string} url Endpoint url * @param {string} method Endpoint method * @param {import('@appium/types').HTTPBody} body Request body * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} */ async proxySetTimeouts(url, method, body) { let response, resBody; const timeoutRequestObjects = this.getTimeoutRequestObjects(body); this.log.debug(`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`); for (const timeoutObj of timeoutRequestObjects) { [response, resBody] = await this.proxyFunc(url, method, timeoutObj); // If we got a non-MJSONWP response, return the result, nothing left to do if (this.downstreamProtocol !== MJSONWP) { return [response, resBody]; } // If we got an error, return the error right away if (response.statusCode >= 400) { return [response, resBody]; } // ...Otherwise, continue to the next timeouts call } return [/** @type {import('@appium/types').ProxyResponse} */ (response), resBody]; } /** * * @param {string} url * @param {string} method * @param {import('@appium/types').HTTPBody} body * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} */ async proxySetWindow(url, method, body) { const bodyObj = support_1.util.safeJsonParse(body); if (lodash_1.default.isPlainObject(bodyObj)) { if (this.downstreamProtocol === W3C && lodash_1.default.has(bodyObj, 'name') && !lodash_1.default.has(bodyObj, 'handle')) { this.log.debug(`Copied 'name' value '${ /** @type {import('@appium/types').StringRecord} */(bodyObj).name}' to 'handle' as per W3C spec`); return await this.proxyFunc(url, method, { ... /** @type {import('@appium/types').StringRecord} */(bodyObj), handle: /** @type {import('@appium/types').StringRecord} */ (bodyObj).name, }); } if (this.downstreamProtocol === MJSONWP && lodash_1.default.has(bodyObj, 'handle') && !lodash_1.default.has(bodyObj, 'name')) { this.log.debug(`Copied 'handle' value '${ /** @type {import('@appium/types').StringRecord} */(bodyObj).handle}' to 'name' as per JSONWP spec`); return await this.proxyFunc(url, method, { ... /** @type {import('@appium/types').StringRecord} */(bodyObj), name: /** @type {import('@appium/types').StringRecord} */ (bodyObj).handle, }); } } return await this.proxyFunc(url, method, body); } /** * * @param {string} url * @param {string} method * @param {import('@appium/types').HTTPBody} body * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} */ async proxySetValue(url, method, body) { const bodyObj = support_1.util.safeJsonParse(body); if (lodash_1.default.isPlainObject(bodyObj) && (support_1.util.hasValue(bodyObj.text) || support_1.util.hasValue(bodyObj.value))) { let { text, value } = bodyObj; if (support_1.util.hasValue(text) && !support_1.util.hasValue(value)) { value = lodash_1.default.isString(text) ? [...text] : lodash_1.default.isArray(text) ? text : []; this.log.debug(`Added 'value' property ${JSON.stringify(value)} to 'setValue' request body`); } else if (!support_1.util.hasValue(text) && support_1.util.hasValue(value)) { text = lodash_1.default.isArray(value) ? value.join('') : lodash_1.default.isString(value) ? value : ''; this.log.debug(`Added 'text' property ${JSON.stringify(text)} to 'setValue' request body`); } return await this.proxyFunc(url, method, Object.assign({}, bodyObj, { text, value, })); } return await this.proxyFunc(url, method, body); } /** * * @param {string} url * @param {string} method * @param {import('@appium/types').HTTPBody} body * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} */ async proxySetFrame(url, method, body) { const bodyObj = support_1.util.safeJsonParse(body); return lodash_1.default.has(bodyObj, 'id') && lodash_1.default.isPlainObject(bodyObj.id) ? await this.proxyFunc(url, method, { ...bodyObj, id: (0, helpers_1.duplicateKeys)(bodyObj.id, constants_1.MJSONWP_ELEMENT_KEY, constants_1.W3C_ELEMENT_KEY), }) : await this.proxyFunc(url, method, body); } /** * * @param {string} url * @param {string} method * @param {import('@appium/types').HTTPBody} body * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} */ async proxyPerformActions(url, method, body) { const bodyObj = support_1.util.safeJsonParse(body); return lodash_1.default.isPlainObject(bodyObj) ? await this.proxyFunc(url, method, (0, helpers_1.duplicateKeys)(bodyObj, constants_1.MJSONWP_ELEMENT_KEY, constants_1.W3C_ELEMENT_KEY)) : await this.proxyFunc(url, method, body); } /** * * @param {string} url * @param {string} method * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} */ async proxyReleaseActions(url, method) { return await this.proxyFunc(url, method); } /** * Handle "crossing" endpoints for the case * when upstream and downstream drivers operate different protocols * * @param {string} commandName * @param {string} url * @param {string} method * @param {import('@appium/types').HTTPBody} [body] * @returns {Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} */ async convertAndProxy(commandName, url, method, body) { if (!this.downstreamProtocol) { return await this.proxyFunc(url, method, body); } // Same url, but different arguments switch (commandName) { case 'timeouts': return await this.proxySetTimeouts(url, method, body); case 'setWindow': return await this.proxySetWindow(url, method, body); case 'setValue': return await this.proxySetValue(url, method, body); case 'performActions': return await this.proxyPerformActions(url, method, body); case 'releaseActions': return await this.proxyReleaseActions(url, method); case 'setFrame': return await this.proxySetFrame(url, method, body); default: break; } // Same arguments, but different URLs for (const { commandNames, jsonwpConverter, w3cConverter } of exports.COMMAND_URLS_CONFLICTS) { if (!commandNames.includes(commandName)) { continue; } const rewrittenUrl = this.downstreamProtocol === MJSONWP ? jsonwpConverter(url) : w3cConverter(url); if (rewrittenUrl === url) { this.log.debug(`Did not know how to rewrite the original URL '${url}' ` + `for ${this.downstreamProtocol} protocol`); break; } this.log.info(`Rewrote the original URL '${url}' to '${rewrittenUrl}' ` + `for ${this.downstreamProtocol} protocol`); return await this.proxyFunc(rewrittenUrl, method, body); } // No matches found. Proceed normally return await this.proxyFunc(url, method, body); } } exports.default = ProtocolConverter; /** * @typedef {(url: string, method: string, body?: import('@appium/types').HTTPBody) => Promise<[import('@appium/types').ProxyResponse, import('@appium/types').HTTPBody]>} ProxyFunction */ //# sourceMappingURL=protocol-converter.js.map