1 | import _ from 'lodash';
|
2 | import logger from './logger';
|
3 | import { processCapabilities, PROTOCOLS } from 'appium-base-driver';
|
4 | import findRoot from 'find-root';
|
5 |
|
6 | const W3C_APPIUM_PREFIX = 'appium';
|
7 |
|
8 | function 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 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constraints = {}, defaultCapabilities = {}) {
|
46 |
|
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 |
|
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 |
|
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 |
|
99 | if (hasJSONWPCaps) {
|
100 | protocol = MJSONWP;
|
101 | desiredCaps = jsonwpCapabilities;
|
102 | processedJsonwpCapabilities = removeW3CPrefixes({...desiredCaps});
|
103 | }
|
104 |
|
105 |
|
106 | if (hasW3CCaps) {
|
107 | protocol = W3C;
|
108 |
|
109 |
|
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 |
|
153 | processedW3CCapabilities = {
|
154 | alwaysMatch: {...insertAppiumPrefixes(desiredCaps)},
|
155 | firstMatch: [{}],
|
156 | };
|
157 | }
|
158 |
|
159 | return {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol};
|
160 | }
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 | function 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 |
|
205 |
|
206 |
|
207 | function insertAppiumPrefixes (caps) {
|
208 |
|
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 |
|
232 | function 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 |
|
244 | function removeW3CPrefix (key) {
|
245 | const colonPos = key.indexOf(':');
|
246 | return colonPos > 0 && key.length > colonPos ? key.substring(colonPos + 1) : key;
|
247 | }
|
248 |
|
249 | function getPackageVersion (pkgName) {
|
250 | const pkgInfo = require(`${pkgName}/package.json`) || {};
|
251 | return pkgInfo.version;
|
252 | }
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | function 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 |
|
288 | const rootDir = findRoot(__dirname);
|
289 |
|
290 | export {
|
291 | inspectObject, parseCapsForInnerDriver, insertAppiumPrefixes, rootDir,
|
292 | getPackageVersion, pullSettings,
|
293 | };
|