appium 2.0.0-beta.18 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/lib/appium-config.schema.json +0 -0
- package/build/lib/appium.js +84 -69
- package/build/lib/cli/argparse-actions.js +1 -1
- package/build/lib/cli/args.js +87 -223
- package/build/lib/cli/extension-command.js +2 -2
- package/build/lib/cli/extension.js +14 -6
- package/build/lib/cli/parser.js +142 -106
- package/build/lib/cli/utils.js +1 -1
- package/build/lib/config-file.js +141 -0
- package/build/lib/config.js +42 -64
- package/build/lib/driver-config.js +41 -20
- package/build/lib/drivers.js +1 -1
- package/build/lib/ext-config-io.js +165 -0
- package/build/lib/extension-config.js +110 -60
- package/build/lib/grid-register.js +19 -21
- package/build/lib/logsink.js +1 -1
- package/build/lib/main.js +135 -72
- package/build/lib/plugin-config.js +17 -8
- package/build/lib/schema/appium-config-schema.js +252 -0
- package/build/lib/schema/arg-spec.js +120 -0
- package/build/lib/schema/cli-args.js +173 -0
- package/build/lib/schema/cli-transformers.js +76 -0
- package/build/lib/schema/index.js +36 -0
- package/build/lib/schema/keywords.js +62 -0
- package/build/lib/schema/schema.js +357 -0
- package/build/lib/utils.js +26 -35
- package/lib/appium-config.schema.json +277 -0
- package/lib/appium.js +99 -75
- package/lib/cli/args.js +138 -335
- package/lib/cli/extension-command.js +7 -6
- package/lib/cli/extension.js +12 -4
- package/lib/cli/parser.js +248 -96
- package/lib/config-file.js +227 -0
- package/lib/config.js +71 -61
- package/lib/driver-config.js +66 -11
- package/lib/ext-config-io.js +287 -0
- package/lib/extension-config.js +209 -66
- package/lib/grid-register.js +24 -21
- package/lib/main.js +139 -68
- package/lib/plugin-config.js +32 -2
- package/lib/schema/appium-config-schema.js +286 -0
- package/lib/schema/arg-spec.js +218 -0
- package/lib/schema/cli-args.js +273 -0
- package/lib/schema/cli-transformers.js +123 -0
- package/lib/schema/index.js +2 -0
- package/lib/schema/keywords.js +119 -0
- package/lib/schema/schema.js +577 -0
- package/lib/utils.js +29 -52
- package/package.json +17 -11
- package/types/appium-config.d.ts +197 -0
- package/types/types.d.ts +201 -0
- package/LICENSE +0 -201
- package/build/lib/cli/parser-helpers.js +0 -106
- package/lib/cli/parser-helpers.js +0 -106
package/lib/extension-config.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import _ from 'lodash';
|
|
2
|
-
import log from './logger';
|
|
3
|
-
import { fs, mkdirp } from '@appium/support';
|
|
4
|
-
import path from 'path';
|
|
5
4
|
import os from 'os';
|
|
6
|
-
import
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import resolveFrom from 'resolve-from';
|
|
7
|
+
import { getExtConfigIOInstance } from './ext-config-io';
|
|
8
|
+
import log from './logger';
|
|
9
|
+
import { ALLOWED_SCHEMA_EXTENSIONS, isAllowedSchemaFileExtension, registerSchema } from './schema/schema';
|
|
7
10
|
|
|
8
|
-
const DRIVER_TYPE = 'driver';
|
|
9
|
-
const PLUGIN_TYPE = 'plugin';
|
|
10
11
|
const DEFAULT_APPIUM_HOME = path.resolve(os.homedir(), '.appium');
|
|
11
|
-
|
|
12
|
-
const CONFIG_FILE_NAME = 'extensions.yaml';
|
|
13
|
-
const CONFIG_SCHEMA_REV = 2;
|
|
12
|
+
const APPIUM_HOME = process.env.APPIUM_HOME || DEFAULT_APPIUM_HOME;
|
|
14
13
|
|
|
15
14
|
const INSTALL_TYPE_NPM = 'npm';
|
|
16
15
|
const INSTALL_TYPE_LOCAL = 'local';
|
|
@@ -23,27 +22,44 @@ const INSTALL_TYPES = [
|
|
|
23
22
|
INSTALL_TYPE_NPM
|
|
24
23
|
];
|
|
25
24
|
|
|
26
|
-
|
|
27
25
|
export default class ExtensionConfig {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* @param {string} appiumHome - `APPIUM_HOME`
|
|
29
|
+
* @param {ExtensionType} extensionType - Type of extension
|
|
30
|
+
* @param {(...args: any[]) => void} [logFn]
|
|
31
|
+
*/
|
|
32
|
+
constructor (appiumHome, extensionType, logFn) {
|
|
33
|
+
const logger = _.isFunction(logFn) ? logFn : log.error.bind(log);
|
|
34
|
+
/** @type {string} */
|
|
32
35
|
this.appiumHome = appiumHome;
|
|
33
|
-
|
|
36
|
+
/** @type {Record<string,object>} */
|
|
34
37
|
this.installedExtensions = {};
|
|
38
|
+
/** @type {import('./ext-config-io').ExtensionConfigIO} */
|
|
39
|
+
this.io = getExtConfigIOInstance(appiumHome);
|
|
40
|
+
/** @type {ExtensionType} */
|
|
35
41
|
this.extensionType = extensionType;
|
|
36
|
-
|
|
37
|
-
this.
|
|
38
|
-
|
|
42
|
+
/** @type {'drivers'|'plugins'} */
|
|
43
|
+
this.configKey = `${extensionType}s`; // todo use template type
|
|
44
|
+
/**
|
|
45
|
+
* @type {(...args: any[])=>void}
|
|
46
|
+
*/
|
|
47
|
+
this.log = logger;
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Checks extensions for problems
|
|
52
|
+
* @template ExtData
|
|
53
|
+
* @param {ExtData[]} exts - Array of extData objects
|
|
54
|
+
* @returns {ExtData[]}
|
|
55
|
+
*/
|
|
41
56
|
validate (exts) {
|
|
42
57
|
const foundProblems = {};
|
|
43
58
|
for (const [extName, extData] of _.toPairs(exts)) {
|
|
44
59
|
foundProblems[extName] = [
|
|
45
|
-
...this.getGenericConfigProblems(extData),
|
|
46
|
-
...this.getConfigProblems(extData)
|
|
60
|
+
...this.getGenericConfigProblems(extData, extName),
|
|
61
|
+
...this.getConfigProblems(extData, extName),
|
|
62
|
+
...this.getSchemaProblems(extData, extName)
|
|
47
63
|
];
|
|
48
64
|
}
|
|
49
65
|
|
|
@@ -64,7 +80,7 @@ export default class ExtensionConfig {
|
|
|
64
80
|
|
|
65
81
|
if (!_.isEmpty(problemSummaries)) {
|
|
66
82
|
this.log(`Appium encountered one or more errors while validating ` +
|
|
67
|
-
`the ${this.configKey} extension file (${this.
|
|
83
|
+
`the ${this.configKey} extension file (${this.io.filepath}):`);
|
|
68
84
|
for (const summary of problemSummaries) {
|
|
69
85
|
this.log(summary);
|
|
70
86
|
}
|
|
@@ -73,8 +89,52 @@ export default class ExtensionConfig {
|
|
|
73
89
|
return exts;
|
|
74
90
|
}
|
|
75
91
|
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
/**
|
|
93
|
+
* @param {object} extData
|
|
94
|
+
* @param {string} extName
|
|
95
|
+
* @returns {Problem[]}
|
|
96
|
+
*/
|
|
97
|
+
getSchemaProblems (extData, extName) {
|
|
98
|
+
const problems = [];
|
|
99
|
+
const {schema: argSchemaPath} = extData;
|
|
100
|
+
if (argSchemaPath) {
|
|
101
|
+
if (_.isString(argSchemaPath)) {
|
|
102
|
+
if (isAllowedSchemaFileExtension(argSchemaPath)) {
|
|
103
|
+
try {
|
|
104
|
+
this.readExtensionSchema(extName, extData);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
problems.push({err: `Unable to register schema at path ${argSchemaPath}; ${err.message}`, val: argSchemaPath});
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
problems.push({
|
|
110
|
+
err: `Schema file has unsupported extension. Allowed: ${[...ALLOWED_SCHEMA_EXTENSIONS].join(', ')}`,
|
|
111
|
+
val: argSchemaPath
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} else if (_.isPlainObject(argSchemaPath)) {
|
|
115
|
+
try {
|
|
116
|
+
this.readExtensionSchema(extName, extData);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
problems.push({err: `Unable to register embedded schema; ${err.message}`, val: argSchemaPath});
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
problems.push({
|
|
122
|
+
err: 'Incorrectly formatted schema field; must be a path to a schema file or a schema object.',
|
|
123
|
+
val: argSchemaPath
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return problems;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {object} extData
|
|
132
|
+
* @param {string} extName
|
|
133
|
+
* @returns {Problem[]}
|
|
134
|
+
*/
|
|
135
|
+
// eslint-disable-next-line no-unused-vars
|
|
136
|
+
getGenericConfigProblems (extData, extName) {
|
|
137
|
+
const {version, pkgName, installSpec, installType, installPath, mainClass} = extData;
|
|
78
138
|
const problems = [];
|
|
79
139
|
|
|
80
140
|
if (!_.isString(version)) {
|
|
@@ -104,62 +164,48 @@ export default class ExtensionConfig {
|
|
|
104
164
|
return problems;
|
|
105
165
|
}
|
|
106
166
|
|
|
107
|
-
|
|
167
|
+
/**
|
|
168
|
+
* @param {object} extData
|
|
169
|
+
* @param {string} extName
|
|
170
|
+
* @returns {Problem[]}
|
|
171
|
+
*/
|
|
172
|
+
// eslint-disable-next-line no-unused-vars
|
|
173
|
+
getConfigProblems (extData, extName) {
|
|
108
174
|
// shoud override this method if special validation is necessary for this extension type
|
|
109
175
|
return [];
|
|
110
176
|
}
|
|
111
177
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// so make sure we at least have an empty section for it
|
|
116
|
-
this.yamlData[PLUGIN_TYPE] = {};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
178
|
+
/**
|
|
179
|
+
* @returns {Promise<typeof this.installedExtensions>}
|
|
180
|
+
*/
|
|
120
181
|
async read () {
|
|
121
|
-
await
|
|
122
|
-
|
|
123
|
-
this.yamlData = YAML.parse(await fs.readFile(this.configFile, 'utf8'));
|
|
124
|
-
this.applySchemaMigrations();
|
|
125
|
-
|
|
126
|
-
// set the list of drivers the user has installed
|
|
127
|
-
this.installedExtensions = this.validate(this.yamlData[this.configKey]);
|
|
128
|
-
} catch (err) {
|
|
129
|
-
if (await fs.exists(this.configFile)) {
|
|
130
|
-
// if the file exists and we couldn't parse it, that's a problem
|
|
131
|
-
throw new Error(`Appium had trouble loading the extension installation ` +
|
|
132
|
-
`cache file (${this.configFile}). Ensure it exists and is ` +
|
|
133
|
-
`readable. Specific error: ${err.message}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// if the config file doesn't exist, try to write an empty one, to make
|
|
137
|
-
// sure we actually have write privileges, and complain if we don't
|
|
138
|
-
try {
|
|
139
|
-
await this.write();
|
|
140
|
-
} catch {
|
|
141
|
-
throw new Error(`Appium could not read or write from the Appium Home directory ` +
|
|
142
|
-
`(${this.appiumHome}). Please ensure it is writable.`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
182
|
+
const extensions = await this.io.read(this.extensionType);
|
|
183
|
+
this.installedExtensions = this.validate(extensions);
|
|
145
184
|
return this.installedExtensions;
|
|
146
185
|
}
|
|
147
186
|
|
|
148
|
-
|
|
187
|
+
/**
|
|
188
|
+
* @returns {Promise<boolean>}
|
|
189
|
+
*/
|
|
149
190
|
async write () {
|
|
150
|
-
|
|
151
|
-
...this.yamlData,
|
|
152
|
-
schemaRev: CONFIG_SCHEMA_REV,
|
|
153
|
-
[this.configKey]: this.installedExtensions
|
|
154
|
-
};
|
|
155
|
-
await fs.writeFile(this.configFile, YAML.stringify(newYamlData), 'utf8');
|
|
191
|
+
return await this.io.write();
|
|
156
192
|
}
|
|
157
193
|
|
|
194
|
+
/**
|
|
195
|
+
* @param {string} extName
|
|
196
|
+
* @param {object} extData
|
|
197
|
+
* @returns {Promise<void>}
|
|
198
|
+
*/
|
|
158
199
|
async addExtension (extName, extData) {
|
|
159
200
|
this.installedExtensions[extName] = extData;
|
|
160
201
|
await this.write();
|
|
161
202
|
}
|
|
162
203
|
|
|
204
|
+
/**
|
|
205
|
+
* @param {string} extName
|
|
206
|
+
* @param {object} extData
|
|
207
|
+
* @returns {Promise<void>}
|
|
208
|
+
*/
|
|
163
209
|
async updateExtension (extName, extData) {
|
|
164
210
|
this.installedExtensions[extName] = {
|
|
165
211
|
...this.installedExtensions[extName],
|
|
@@ -168,6 +214,10 @@ export default class ExtensionConfig {
|
|
|
168
214
|
await this.write();
|
|
169
215
|
}
|
|
170
216
|
|
|
217
|
+
/**
|
|
218
|
+
* @param {string} extName
|
|
219
|
+
* @returns {Promise<void>}
|
|
220
|
+
*/
|
|
171
221
|
async removeExtension (extName) {
|
|
172
222
|
delete this.installedExtensions[extName];
|
|
173
223
|
await this.write();
|
|
@@ -187,20 +237,41 @@ export default class ExtensionConfig {
|
|
|
187
237
|
}
|
|
188
238
|
}
|
|
189
239
|
|
|
190
|
-
|
|
191
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Returns a string describing the extension. Subclasses must implement.
|
|
242
|
+
* @param {string} extName - Extension name
|
|
243
|
+
* @param {object} extData - Extension data
|
|
244
|
+
* @returns {string}
|
|
245
|
+
* @abstract
|
|
246
|
+
*/
|
|
247
|
+
// eslint-disable-next-line no-unused-vars
|
|
248
|
+
extensionDesc (extName, extData) {
|
|
249
|
+
throw new Error('This must be implemented in a subclass');
|
|
192
250
|
}
|
|
193
251
|
|
|
252
|
+
/**
|
|
253
|
+
* @param {string} extName
|
|
254
|
+
* @returns {string}
|
|
255
|
+
*/
|
|
194
256
|
getExtensionRequirePath (extName) {
|
|
195
257
|
const {pkgName, installPath} = this.installedExtensions[extName];
|
|
196
258
|
return path.resolve(this.appiumHome, installPath, 'node_modules', pkgName);
|
|
197
259
|
}
|
|
198
260
|
|
|
261
|
+
/**
|
|
262
|
+
* @param {string} extName
|
|
263
|
+
* @returns {string}
|
|
264
|
+
*/
|
|
199
265
|
getInstallPath (extName) {
|
|
200
266
|
const {installPath} = this.installedExtensions[extName];
|
|
201
267
|
return path.resolve(this.appiumHome, installPath);
|
|
202
268
|
}
|
|
203
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Loads extension and returns its main class
|
|
272
|
+
* @param {string} extName
|
|
273
|
+
* @returns {(...args: any[]) => object }
|
|
274
|
+
*/
|
|
204
275
|
require (extName) {
|
|
205
276
|
const {mainClass} = this.installedExtensions[extName];
|
|
206
277
|
const reqPath = this.getExtensionRequirePath(extName);
|
|
@@ -212,12 +283,84 @@ export default class ExtensionConfig {
|
|
|
212
283
|
return require(reqPath)[mainClass];
|
|
213
284
|
}
|
|
214
285
|
|
|
286
|
+
/**
|
|
287
|
+
* @param {string} extName
|
|
288
|
+
* @returns {boolean}
|
|
289
|
+
*/
|
|
215
290
|
isInstalled (extName) {
|
|
216
291
|
return _.includes(Object.keys(this.installedExtensions), extName);
|
|
217
292
|
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Intended to be called by corresponding instance methods of subclass.
|
|
296
|
+
* @private
|
|
297
|
+
* @param {string} appiumHome
|
|
298
|
+
* @param {ExtensionType} extType
|
|
299
|
+
* @param {string} extName - Extension name (unique to its type)
|
|
300
|
+
* @param {ExtData} extData - Extension config
|
|
301
|
+
* @returns {import('ajv').SchemaObject|undefined}
|
|
302
|
+
*/
|
|
303
|
+
static _readExtensionSchema (appiumHome, extType, extName, extData) {
|
|
304
|
+
const {installPath, pkgName, schema: argSchemaPath} = extData;
|
|
305
|
+
if (!argSchemaPath) {
|
|
306
|
+
throw new TypeError(
|
|
307
|
+
`No \`schema\` property found in config for ${extType} ${pkgName} -- why is this function being called?`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
let moduleObject;
|
|
311
|
+
if (_.isString(argSchemaPath)) {
|
|
312
|
+
const schemaPath = resolveFrom(
|
|
313
|
+
path.resolve(appiumHome, installPath),
|
|
314
|
+
// this path sep is fine because `resolveFrom` uses Node's module resolution
|
|
315
|
+
path.normalize(`${pkgName}/${argSchemaPath}`),
|
|
316
|
+
);
|
|
317
|
+
moduleObject = require(schemaPath);
|
|
318
|
+
} else {
|
|
319
|
+
moduleObject = argSchemaPath;
|
|
320
|
+
}
|
|
321
|
+
// this sucks. default exports should be destroyed
|
|
322
|
+
const schema = moduleObject.__esModule
|
|
323
|
+
? moduleObject.default
|
|
324
|
+
: moduleObject;
|
|
325
|
+
registerSchema(extType, extName, schema);
|
|
326
|
+
return schema;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* If an extension provides a schema, this will load the schema and attempt to
|
|
331
|
+
* register it with the schema registrar.
|
|
332
|
+
* @param {string} extName - Name of extension
|
|
333
|
+
* @param {ExtData} extData - Extension data
|
|
334
|
+
* @returns {import('ajv').SchemaObject|undefined}
|
|
335
|
+
*/
|
|
336
|
+
readExtensionSchema (extName, extData) {
|
|
337
|
+
return ExtensionConfig._readExtensionSchema(this.appiumHome, this.extensionType, extName, extData);
|
|
338
|
+
}
|
|
218
339
|
}
|
|
219
340
|
|
|
341
|
+
export { DRIVER_TYPE, PLUGIN_TYPE } from './ext-config-io';
|
|
220
342
|
export {
|
|
221
343
|
INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_LOCAL, INSTALL_TYPE_GITHUB,
|
|
222
|
-
INSTALL_TYPES, DEFAULT_APPIUM_HOME,
|
|
344
|
+
INSTALL_TYPES, DEFAULT_APPIUM_HOME, APPIUM_HOME
|
|
223
345
|
};
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Config problem
|
|
349
|
+
* @typedef {Object} Problem
|
|
350
|
+
* @property {string} err - Error message
|
|
351
|
+
* @property {any} val - Associated value
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Alias
|
|
356
|
+
* @typedef {import('./ext-config-io').ExtensionType} ExtensionType
|
|
357
|
+
*/
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Extension data (pulled from config YAML)
|
|
361
|
+
* @typedef {Object} ExtData
|
|
362
|
+
* @property {string|import('ajv').SchemaObject} [schema] - Optional schema path if the ext defined it
|
|
363
|
+
* @property {string} pkgName - Package name
|
|
364
|
+
* @property {string} installPath - Actually looks more like a module identifier? Resolved from `APPIUM_HOME`
|
|
365
|
+
*/
|
|
366
|
+
|
package/lib/grid-register.js
CHANGED
|
@@ -9,20 +9,31 @@ const hubUri = (config) => {
|
|
|
9
9
|
return `${protocol}://${config.hubHost}:${config.hubPort}`;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Registers a new node with a selenium grid
|
|
14
|
+
* @param {string|object} data - Path or object representing selenium grid node config file
|
|
15
|
+
* @param {string} addr - Bind to this address
|
|
16
|
+
* @param {number} port - Bind to this port
|
|
17
|
+
* @param {string} basePath - Base path for the grid
|
|
18
|
+
*/
|
|
19
|
+
async function registerNode (data, addr, port, basePath) {
|
|
20
|
+
let configFilePath;
|
|
21
|
+
if (_.isString(data)) {
|
|
22
|
+
configFilePath = data;
|
|
23
|
+
try {
|
|
24
|
+
data = await fs.readFile(data, 'utf-8');
|
|
25
|
+
} catch (err) {
|
|
26
|
+
logger.error(`Unable to load node configuration file ${configFilePath} to register with grid: ${err.message}`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
data = JSON.parse(data);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
logger.errorAndThrow(`Syntax error in node configuration file ${configFilePath}: ${err.message}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
19
35
|
}
|
|
20
36
|
|
|
21
|
-
// Check presence of data before posting it to the selenium grid
|
|
22
|
-
if (!data) {
|
|
23
|
-
logger.error('No data found in the node configuration file to send to the grid');
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
37
|
postRequest(data, addr, port, basePath);
|
|
27
38
|
}
|
|
28
39
|
|
|
@@ -39,15 +50,7 @@ async function registerToGrid (postOptions, configHolder) {
|
|
|
39
50
|
}
|
|
40
51
|
}
|
|
41
52
|
|
|
42
|
-
function postRequest (
|
|
43
|
-
// parse json to get hub host and port
|
|
44
|
-
let configHolder;
|
|
45
|
-
try {
|
|
46
|
-
configHolder = JSON.parse(data);
|
|
47
|
-
} catch (err) {
|
|
48
|
-
logger.errorAndThrow(`Syntax error in node configuration file: ${err.message}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
53
|
+
function postRequest (configHolder, addr, port, basePath) {
|
|
51
54
|
// Move Selenium 3 configuration properties to configuration object
|
|
52
55
|
if (!_.has(configHolder, 'configuration')) {
|
|
53
56
|
let configuration = {};
|