@appium/base-driver
Version:
Base driver class for Appium drivers
227 lines • 10.8 kB
JavaScript
"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 {
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 {Array} 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 {Object} body Request body
*/
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 [response, resBody];
}
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('/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('/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);
}
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);
}
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);
}
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);
}
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 {?string|object} body
* @returns The proxyfying result as [response, responseBody] tuple
*/
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;
//# sourceMappingURL=protocol-converter.js.map