UNPKG

9.26 kBJavaScriptView Raw
1import _ from 'lodash';
2import logger from './logger';
3import { processCapabilities, PROTOCOLS } from 'appium-base-driver';
4import findRoot from 'find-root';
5
6const W3C_APPIUM_PREFIX = 'appium';
7
8function inspectObject (args) {
9 function getValueArray (obj, indent = ' ') {
10 if (!_.isObject(obj)) {
11 return [obj];
12 }
13
14 let strArr = ['{'];
15 for (let [arg, value] of _.toPairs(obj)) {
16 if (!_.isObject(value)) {
17 strArr.push(`${indent} ${arg}: ${value}`);
18 } else {
19 value = getValueArray(value, `${indent} `);
20 strArr.push(`${indent} ${arg}: ${value.shift()}`);
21 strArr.push(...value);
22 }
23 }
24 strArr.push(`${indent}}`);
25 return strArr;
26 }
27 for (let [arg, value] of _.toPairs(args)) {
28 value = getValueArray(value);
29 logger.info(` ${arg}: ${value.shift()}`);
30 for (let val of value) {
31 logger.info(val);
32 }
33 }
34}
35
36/**
37 * Takes the caps that were provided in the request and translates them
38 * into caps that can be used by the inner drivers.
39 *
40 * @param {Object} jsonwpCapabilities
41 * @param {Object} w3cCapabilities
42 * @param {Object} constraints
43 * @param {Object} defaultCapabilities
44 */
45function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constraints = {}, defaultCapabilities = {}) {
46 // Check if the caller sent JSONWP caps, W3C caps, or both
47 const hasW3CCaps = _.isPlainObject(w3cCapabilities) &&
48 (_.has(w3cCapabilities, 'alwaysMatch') || _.has(w3cCapabilities, 'firstMatch'));
49 const hasJSONWPCaps = _.isPlainObject(jsonwpCapabilities);
50 let protocol = null;
51 let desiredCaps = {};
52 let processedW3CCapabilities = null;
53 let processedJsonwpCapabilities = null;
54
55 if (!hasJSONWPCaps && !hasW3CCaps) {
56 return {
57 protocol: PROTOCOLS.W3C,
58 error: new Error('Either JSONWP or W3C capabilities should be provided'),
59 };
60 }
61
62 const {W3C, MJSONWP} = PROTOCOLS;
63
64 // Make sure we don't mutate the original arguments
65 jsonwpCapabilities = _.cloneDeep(jsonwpCapabilities);
66 w3cCapabilities = _.cloneDeep(w3cCapabilities);
67 defaultCapabilities = _.cloneDeep(defaultCapabilities);
68
69 if (!_.isEmpty(defaultCapabilities)) {
70 if (hasW3CCaps) {
71 const {firstMatch = [], alwaysMatch = {}} = w3cCapabilities;
72 for (const [defaultCapKey, defaultCapValue] of _.toPairs(defaultCapabilities)) {
73 let isCapAlreadySet = false;
74 for (const firstMatchEntry of firstMatch) {
75 if (_.has(removeW3CPrefixes(firstMatchEntry), removeW3CPrefix(defaultCapKey))) {
76 isCapAlreadySet = true;
77 break;
78 }
79 }
80 isCapAlreadySet = isCapAlreadySet || _.has(removeW3CPrefixes(alwaysMatch), removeW3CPrefix(defaultCapKey));
81 if (isCapAlreadySet) {
82 continue;
83 }
84
85 // Only add the default capability if it is not overridden
86 if (_.isEmpty(firstMatch)) {
87 w3cCapabilities.firstMatch = [{[defaultCapKey]: defaultCapValue}];
88 } else {
89 firstMatch[0][defaultCapKey] = defaultCapValue;
90 }
91 }
92 }
93 if (hasJSONWPCaps) {
94 jsonwpCapabilities = Object.assign({}, removeW3CPrefixes(defaultCapabilities), jsonwpCapabilities);
95 }
96 }
97
98 // Get MJSONWP caps
99 if (hasJSONWPCaps) {
100 protocol = MJSONWP;
101 desiredCaps = jsonwpCapabilities;
102 processedJsonwpCapabilities = removeW3CPrefixes({...desiredCaps});
103 }
104
105 // Get W3C caps
106 if (hasW3CCaps) {
107 protocol = W3C;
108 // Call the process capabilities algorithm to find matching caps on the W3C
109 // (see: https://github.com/jlipps/simple-wd-spec#processing-capabilities)
110 let isFixingNeededForW3cCaps = false;
111 try {
112 desiredCaps = processCapabilities(w3cCapabilities, constraints, true);
113 } catch (error) {
114 if (!hasJSONWPCaps) {
115 return {
116 desiredCaps,
117 processedJsonwpCapabilities,
118 processedW3CCapabilities,
119 protocol,
120 error,
121 };
122 }
123 logger.info(`Could not parse W3C capabilities: ${error.message}`);
124 isFixingNeededForW3cCaps = true;
125 }
126
127 if (hasJSONWPCaps && !isFixingNeededForW3cCaps) {
128 const differingKeys = _.difference(_.keys(processedJsonwpCapabilities), _.keys(removeW3CPrefixes(desiredCaps)));
129 if (!_.isEmpty(differingKeys)) {
130 logger.info(`The following capabilities were provided in the JSONWP desired capabilities that are missing ` +
131 `in W3C capabilities: ${JSON.stringify(differingKeys)}`);
132 isFixingNeededForW3cCaps = true;
133 }
134 }
135
136 if (isFixingNeededForW3cCaps && hasJSONWPCaps) {
137 logger.info('Trying to fix W3C capabilities by merging them with JSONWP caps');
138 w3cCapabilities = fixW3cCapabilities(w3cCapabilities, jsonwpCapabilities);
139 try {
140 desiredCaps = processCapabilities(w3cCapabilities, constraints, true);
141 } catch (error) {
142 logger.warn(`Could not parse fixed W3C capabilities: ${error.message}. Falling back to JSONWP protocol`);
143 return {
144 desiredCaps: processedJsonwpCapabilities,
145 processedJsonwpCapabilities,
146 processedW3CCapabilities: null,
147 protocol: MJSONWP,
148 };
149 }
150 }
151
152 // Create a new w3c capabilities payload that contains only the matching caps in `alwaysMatch`
153 processedW3CCapabilities = {
154 alwaysMatch: {...insertAppiumPrefixes(desiredCaps)},
155 firstMatch: [{}],
156 };
157 }
158
159 return {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol};
160}
161
162/**
163 * This helper method tries to fix corrupted W3C capabilities by
164 * merging them to existing JSONWP capabilities.
165 *
166 * @param {Object} w3cCaps W3C capabilities
167 * @param {Object} jsonwpCaps JSONWP capabilities
168 * @returns {Object} Fixed W3C capabilities
169 */
170function fixW3cCapabilities (w3cCaps, jsonwpCaps) {
171 const result = {
172 firstMatch: w3cCaps.firstMatch || [],
173 alwaysMatch: w3cCaps.alwaysMatch || {},
174 };
175 const keysToInsert = _.keys(jsonwpCaps);
176 const removeMatchingKeys = (match) => {
177 _.pull(keysToInsert, match);
178 const colonIndex = match.indexOf(':');
179 if (colonIndex >= 0 && match.length > colonIndex) {
180 _.pull(keysToInsert, match.substring(colonIndex + 1));
181 }
182 if (keysToInsert.includes(`${W3C_APPIUM_PREFIX}:${match}`)) {
183 _.pull(keysToInsert, `${W3C_APPIUM_PREFIX}:${match}`);
184 }
185 };
186
187 for (const firstMatchEntry of result.firstMatch) {
188 for (const pair of _.toPairs(firstMatchEntry)) {
189 removeMatchingKeys(pair[0]);
190 }
191 }
192
193 for (const pair of _.toPairs(result.alwaysMatch)) {
194 removeMatchingKeys(pair[0]);
195 }
196
197 for (const key of keysToInsert) {
198 result.alwaysMatch[key] = jsonwpCaps[key];
199 }
200 return result;
201}
202
203/**
204 * Takes a capabilities objects and prefixes capabilities with `appium:`
205 * @param {Object} caps Desired capabilities object
206 */
207function insertAppiumPrefixes (caps) {
208 // Standard, non-prefixed capabilities (see https://www.w3.org/TR/webdriver/#dfn-table-of-standard-capabilities)
209 const STANDARD_CAPS = [
210 'browserName',
211 'browserVersion',
212 'platformName',
213 'acceptInsecureCerts',
214 'pageLoadStrategy',
215 'proxy',
216 'setWindowRect',
217 'timeouts',
218 'unhandledPromptBehavior'
219 ];
220
221 let prefixedCaps = {};
222 for (let [name, value] of _.toPairs(caps)) {
223 if (STANDARD_CAPS.includes(name) || name.includes(':')) {
224 prefixedCaps[name] = value;
225 } else {
226 prefixedCaps[`${W3C_APPIUM_PREFIX}:${name}`] = value;
227 }
228 }
229 return prefixedCaps;
230}
231
232function removeW3CPrefixes (caps) {
233 if (!_.isPlainObject(caps)) {
234 return caps;
235 }
236
237 const fixedCaps = {};
238 for (let [name, value] of _.toPairs(caps)) {
239 fixedCaps[removeW3CPrefix(name)] = value;
240 }
241 return fixedCaps;
242}
243
244function removeW3CPrefix (key) {
245 const colonPos = key.indexOf(':');
246 return colonPos > 0 && key.length > colonPos ? key.substring(colonPos + 1) : key;
247}
248
249function getPackageVersion (pkgName) {
250 const pkgInfo = require(`${pkgName}/package.json`) || {};
251 return pkgInfo.version;
252}
253
254/**
255 * Pulls the initial values of Appium settings from the given capabilities argument.
256 * Each setting item must satisfy the following format:
257 * `setting[setting_name]: setting_value`
258 * The capabilities argument itself gets mutated, so it does not contain parsed
259 * settings anymore to avoid further parsing issues.
260 * Check
261 * https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md
262 * for more details on the available settings.
263 *
264 * @param {?Object} caps - Capabilities dictionary. It is mutated if
265 * one or more settings have been pulled from it
266 * @returns {Object} - An empty dictionary if the given caps contains no
267 * setting items or a dictionary containing parsed Appium setting names along with
268 * their values.
269 */
270function pullSettings (caps) {
271 if (!_.isPlainObject(caps) || _.isEmpty(caps)) {
272 return {};
273 }
274
275 const result = {};
276 for (const [key, value] of _.toPairs(caps)) {
277 const match = /\bsettings\[(\S+)\]$/.exec(key);
278 if (!match) {
279 continue;
280 }
281
282 result[match[1]] = value;
283 delete caps[key];
284 }
285 return result;
286}
287
288const rootDir = findRoot(__dirname);
289
290export {
291 inspectObject, parseCapsForInnerDriver, insertAppiumPrefixes, rootDir,
292 getPackageVersion, pullSettings,
293};