mirror of
https://github.com/appium/appium.git
synced 2026-01-24 19:28:59 -06:00
298 lines
9.4 KiB
JavaScript
298 lines
9.4 KiB
JavaScript
import _ from 'lodash';
|
|
import logger from './logger';
|
|
import { processCapabilities, PROTOCOLS } from 'appium-base-driver';
|
|
import findRoot from 'find-root';
|
|
|
|
const W3C_APPIUM_PREFIX = 'appium';
|
|
|
|
function inspectObject (args) {
|
|
function getValueArray (obj, indent = ' ') {
|
|
if (!_.isObject(obj)) {
|
|
return [obj];
|
|
}
|
|
|
|
let strArr = ['{'];
|
|
for (let [arg, value] of _.toPairs(obj)) {
|
|
if (!_.isObject(value)) {
|
|
strArr.push(`${indent} ${arg}: ${value}`);
|
|
} else {
|
|
value = getValueArray(value, `${indent} `);
|
|
strArr.push(`${indent} ${arg}: ${value.shift()}`);
|
|
strArr.push(...value);
|
|
}
|
|
}
|
|
strArr.push(`${indent}}`);
|
|
return strArr;
|
|
}
|
|
for (let [arg, value] of _.toPairs(args)) {
|
|
value = getValueArray(value);
|
|
logger.info(` ${arg}: ${value.shift()}`);
|
|
for (let val of value) {
|
|
logger.info(val);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes the caps that were provided in the request and translates them
|
|
* into caps that can be used by the inner drivers.
|
|
*
|
|
* @param {Object} jsonwpCapabilities
|
|
* @param {Object} w3cCapabilities
|
|
* @param {Object} constraints
|
|
* @param {Object} defaultCapabilities
|
|
*/
|
|
function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constraints = {}, defaultCapabilities = {}) {
|
|
// Check if the caller sent JSONWP caps, W3C caps, or both
|
|
const hasW3CCaps = _.isPlainObject(w3cCapabilities) &&
|
|
(_.has(w3cCapabilities, 'alwaysMatch') || _.has(w3cCapabilities, 'firstMatch'));
|
|
const hasJSONWPCaps = _.isPlainObject(jsonwpCapabilities);
|
|
let protocol = null;
|
|
let desiredCaps = {};
|
|
let processedW3CCapabilities = null;
|
|
let processedJsonwpCapabilities = null;
|
|
|
|
if (!hasJSONWPCaps && !hasW3CCaps) {
|
|
return {
|
|
protocol: PROTOCOLS.W3C,
|
|
error: new Error('Either JSONWP or W3C capabilities should be provided'),
|
|
};
|
|
}
|
|
|
|
const {W3C, MJSONWP} = PROTOCOLS;
|
|
|
|
// Make sure we don't mutate the original arguments
|
|
jsonwpCapabilities = _.cloneDeep(jsonwpCapabilities);
|
|
w3cCapabilities = _.cloneDeep(w3cCapabilities);
|
|
defaultCapabilities = _.cloneDeep(defaultCapabilities);
|
|
|
|
if (!_.isEmpty(defaultCapabilities)) {
|
|
if (hasW3CCaps) {
|
|
for (const [defaultCapKey, defaultCapValue] of _.toPairs(defaultCapabilities)) {
|
|
let isCapAlreadySet = false;
|
|
// Check if the key is already present in firstMatch entries
|
|
for (const firstMatchEntry of (w3cCapabilities.firstMatch || [])) {
|
|
if (_.isPlainObject(firstMatchEntry)
|
|
&& _.has(removeAppiumPrefixes(firstMatchEntry), removeAppiumPrefix(defaultCapKey))) {
|
|
isCapAlreadySet = true;
|
|
break;
|
|
}
|
|
}
|
|
// Check if the key is already present in alwaysMatch entries
|
|
isCapAlreadySet = isCapAlreadySet || (_.isPlainObject(w3cCapabilities.alwaysMatch)
|
|
&& _.has(removeAppiumPrefixes(w3cCapabilities.alwaysMatch), removeAppiumPrefix(defaultCapKey)));
|
|
if (isCapAlreadySet) {
|
|
// Skip if the key is already present in the provided caps
|
|
continue;
|
|
}
|
|
|
|
// Only add the default capability if it is not overridden
|
|
if (_.isEmpty(w3cCapabilities.firstMatch)) {
|
|
w3cCapabilities.firstMatch = [{[defaultCapKey]: defaultCapValue}];
|
|
} else {
|
|
w3cCapabilities.firstMatch[0][defaultCapKey] = defaultCapValue;
|
|
}
|
|
}
|
|
}
|
|
if (hasJSONWPCaps) {
|
|
jsonwpCapabilities = Object.assign({}, removeAppiumPrefixes(defaultCapabilities), jsonwpCapabilities);
|
|
}
|
|
}
|
|
|
|
// Get MJSONWP caps
|
|
if (hasJSONWPCaps) {
|
|
protocol = MJSONWP;
|
|
desiredCaps = jsonwpCapabilities;
|
|
processedJsonwpCapabilities = {...jsonwpCapabilities};
|
|
}
|
|
|
|
// Get W3C caps
|
|
if (hasW3CCaps) {
|
|
protocol = W3C;
|
|
// Call the process capabilities algorithm to find matching caps on the W3C
|
|
// (see: https://github.com/jlipps/simple-wd-spec#processing-capabilities)
|
|
let isFixingNeededForW3cCaps = false;
|
|
try {
|
|
desiredCaps = processCapabilities(w3cCapabilities, constraints, true);
|
|
} catch (error) {
|
|
if (!hasJSONWPCaps) {
|
|
return {
|
|
desiredCaps,
|
|
processedJsonwpCapabilities,
|
|
processedW3CCapabilities,
|
|
protocol,
|
|
error,
|
|
};
|
|
}
|
|
logger.info(`Could not parse W3C capabilities: ${error.message}`);
|
|
isFixingNeededForW3cCaps = true;
|
|
}
|
|
|
|
if (hasJSONWPCaps && !isFixingNeededForW3cCaps) {
|
|
const differingKeys = _.difference(_.keys(removeAppiumPrefixes(processedJsonwpCapabilities)), _.keys(removeAppiumPrefixes(desiredCaps)));
|
|
if (!_.isEmpty(differingKeys)) {
|
|
logger.info(`The following capabilities were provided in the JSONWP desired capabilities that are missing ` +
|
|
`in W3C capabilities: ${JSON.stringify(differingKeys)}`);
|
|
isFixingNeededForW3cCaps = true;
|
|
}
|
|
}
|
|
|
|
if (isFixingNeededForW3cCaps && hasJSONWPCaps) {
|
|
logger.info('Trying to fix W3C capabilities by merging them with JSONWP caps');
|
|
w3cCapabilities = fixW3cCapabilities(w3cCapabilities, jsonwpCapabilities);
|
|
try {
|
|
desiredCaps = processCapabilities(w3cCapabilities, constraints, true);
|
|
} catch (error) {
|
|
logger.warn(`Could not parse fixed W3C capabilities: ${error.message}. Falling back to JSONWP protocol`);
|
|
return {
|
|
desiredCaps: processedJsonwpCapabilities,
|
|
processedJsonwpCapabilities,
|
|
processedW3CCapabilities: null,
|
|
protocol: MJSONWP,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Create a new w3c capabilities payload that contains only the matching caps in `alwaysMatch`
|
|
processedW3CCapabilities = {
|
|
alwaysMatch: {...insertAppiumPrefixes(desiredCaps)},
|
|
firstMatch: [{}],
|
|
};
|
|
}
|
|
|
|
return {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol};
|
|
}
|
|
|
|
/**
|
|
* This helper method tries to fix corrupted W3C capabilities by
|
|
* merging them to existing JSONWP capabilities.
|
|
*
|
|
* @param {Object} w3cCaps W3C capabilities
|
|
* @param {Object} jsonwpCaps JSONWP capabilities
|
|
* @returns {Object} Fixed W3C capabilities
|
|
*/
|
|
function fixW3cCapabilities (w3cCaps, jsonwpCaps) {
|
|
const result = {
|
|
firstMatch: w3cCaps.firstMatch || [],
|
|
alwaysMatch: w3cCaps.alwaysMatch || {},
|
|
};
|
|
const keysToInsert = _.keys(jsonwpCaps);
|
|
const removeMatchingKeys = (match) => {
|
|
_.pull(keysToInsert, match);
|
|
const colonIndex = match.indexOf(':');
|
|
if (colonIndex >= 0 && match.length > colonIndex) {
|
|
_.pull(keysToInsert, match.substring(colonIndex + 1));
|
|
}
|
|
if (keysToInsert.includes(`${W3C_APPIUM_PREFIX}:${match}`)) {
|
|
_.pull(keysToInsert, `${W3C_APPIUM_PREFIX}:${match}`);
|
|
}
|
|
};
|
|
|
|
for (const firstMatchEntry of result.firstMatch) {
|
|
for (const pair of _.toPairs(firstMatchEntry)) {
|
|
removeMatchingKeys(pair[0]);
|
|
}
|
|
}
|
|
|
|
for (const pair of _.toPairs(result.alwaysMatch)) {
|
|
removeMatchingKeys(pair[0]);
|
|
}
|
|
|
|
for (const key of keysToInsert) {
|
|
result.alwaysMatch[key] = jsonwpCaps[key];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Takes a capabilities objects and prefixes capabilities with `appium:`
|
|
* @param {Object} caps Desired capabilities object
|
|
*/
|
|
function insertAppiumPrefixes (caps) {
|
|
// Standard, non-prefixed capabilities (see https://www.w3.org/TR/webdriver/#dfn-table-of-standard-capabilities)
|
|
const STANDARD_CAPS = [
|
|
'browserName',
|
|
'browserVersion',
|
|
'platformName',
|
|
'acceptInsecureCerts',
|
|
'pageLoadStrategy',
|
|
'proxy',
|
|
'setWindowRect',
|
|
'timeouts',
|
|
'unhandledPromptBehavior'
|
|
];
|
|
|
|
let prefixedCaps = {};
|
|
for (let [name, value] of _.toPairs(caps)) {
|
|
if (STANDARD_CAPS.includes(name) || name.includes(':')) {
|
|
prefixedCaps[name] = value;
|
|
} else {
|
|
prefixedCaps[`${W3C_APPIUM_PREFIX}:${name}`] = value;
|
|
}
|
|
}
|
|
return prefixedCaps;
|
|
}
|
|
|
|
function removeAppiumPrefixes (caps) {
|
|
if (!_.isPlainObject(caps)) {
|
|
return caps;
|
|
}
|
|
|
|
const fixedCaps = {};
|
|
for (let [name, value] of _.toPairs(caps)) {
|
|
fixedCaps[removeAppiumPrefix(name)] = value;
|
|
}
|
|
return fixedCaps;
|
|
}
|
|
|
|
function removeAppiumPrefix (key) {
|
|
const prefix = `${W3C_APPIUM_PREFIX}:`;
|
|
return _.startsWith(key, prefix) ? key.substring(prefix.length) : key;
|
|
}
|
|
|
|
function getPackageVersion (pkgName) {
|
|
const pkgInfo = require(`${pkgName}/package.json`) || {};
|
|
return pkgInfo.version;
|
|
}
|
|
|
|
/**
|
|
* Pulls the initial values of Appium settings from the given capabilities argument.
|
|
* Each setting item must satisfy the following format:
|
|
* `setting[setting_name]: setting_value`
|
|
* The capabilities argument itself gets mutated, so it does not contain parsed
|
|
* settings anymore to avoid further parsing issues.
|
|
* Check
|
|
* https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md
|
|
* for more details on the available settings.
|
|
*
|
|
* @param {?Object} caps - Capabilities dictionary. It is mutated if
|
|
* one or more settings have been pulled from it
|
|
* @returns {Object} - An empty dictionary if the given caps contains no
|
|
* setting items or a dictionary containing parsed Appium setting names along with
|
|
* their values.
|
|
*/
|
|
function pullSettings (caps) {
|
|
if (!_.isPlainObject(caps) || _.isEmpty(caps)) {
|
|
return {};
|
|
}
|
|
|
|
const result = {};
|
|
for (const [key, value] of _.toPairs(caps)) {
|
|
const match = /\bsettings\[(\S+)\]$/.exec(key);
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
|
|
result[match[1]] = value;
|
|
delete caps[key];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const rootDir = findRoot(__dirname);
|
|
|
|
export {
|
|
inspectObject, parseCapsForInnerDriver, insertAppiumPrefixes, rootDir,
|
|
getPackageVersion, pullSettings, removeAppiumPrefixes,
|
|
};
|