appium 2.0.0-beta.21 → 2.0.0-beta.25
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/LICENSE +201 -0
- package/README.md +1 -2
- package/build/check-npm-pack-files.js +23 -0
- package/build/commands-yml/parse.js +319 -0
- package/build/commands-yml/validator.js +130 -0
- package/build/index.js +19 -0
- package/build/lib/appium.js +22 -7
- package/build/lib/cli/args.js +13 -15
- package/build/lib/cli/npm.js +27 -16
- package/build/lib/cli/parser.js +7 -3
- package/build/lib/config-file.js +4 -7
- package/build/lib/config.js +57 -48
- package/build/lib/extension-config.js +1 -1
- package/build/lib/main.js +28 -28
- package/build/lib/plugin-config.js +2 -2
- package/build/lib/plugins.js +4 -2
- package/build/lib/schema/appium-config-schema.js +3 -2
- package/build/lib/schema/arg-spec.js +5 -3
- package/build/lib/schema/cli-args.js +25 -16
- package/build/lib/schema/keywords.js +14 -4
- package/build/lib/schema/schema.js +86 -9
- package/build/lib/utils.js +16 -36
- package/build/postinstall.js +90 -0
- package/build/test/cli/cli-e2e-specs.js +221 -0
- package/build/test/cli/cli-helpers.js +86 -0
- package/build/test/cli/cli-specs.js +71 -0
- package/build/test/cli/fixtures/test-driver/package.json +27 -0
- package/build/test/cli/schema-args-specs.js +48 -0
- package/build/test/cli/schema-e2e-specs.js +47 -0
- package/build/test/config-e2e-specs.js +112 -0
- package/build/test/config-file-e2e-specs.js +191 -0
- package/build/test/config-file-specs.js +281 -0
- package/build/test/config-specs.js +258 -0
- package/build/test/driver-e2e-specs.js +435 -0
- package/build/test/driver-specs.js +386 -0
- package/build/test/ext-config-io-specs.js +181 -0
- package/build/test/extension-config-specs.js +365 -0
- package/build/test/fixtures/allow-feat.txt +5 -0
- package/build/test/fixtures/caps.json +3 -0
- package/build/test/fixtures/config/allow-insecure.txt +3 -0
- package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +5 -0
- package/build/test/fixtures/config/appium.config.bad.json +32 -0
- package/build/test/fixtures/config/appium.config.ext-good.json +9 -0
- package/build/test/fixtures/config/appium.config.ext-unknown-props.json +10 -0
- package/build/test/fixtures/config/appium.config.good.js +40 -0
- package/build/test/fixtures/config/appium.config.good.json +33 -0
- package/build/test/fixtures/config/appium.config.good.yaml +30 -0
- package/build/test/fixtures/config/appium.config.invalid.json +31 -0
- package/build/test/fixtures/config/appium.config.security-array.json +5 -0
- package/build/test/fixtures/config/appium.config.security-delimited.json +5 -0
- package/build/test/fixtures/config/appium.config.security-path.json +5 -0
- package/build/test/fixtures/config/driver-fake.config.json +8 -0
- package/build/test/fixtures/config/nodeconfig.json +3 -0
- package/build/test/fixtures/config/plugin-fake.config.json +0 -0
- package/build/test/fixtures/default-args.js +35 -0
- package/build/test/fixtures/deny-feat.txt +5 -0
- package/build/test/fixtures/driver.schema.js +20 -0
- package/build/test/fixtures/extensions.yaml +27 -0
- package/build/test/fixtures/flattened-schema.js +532 -0
- package/build/test/fixtures/plugin.schema.js +20 -0
- package/build/test/fixtures/schema-with-extensions.js +28 -0
- package/build/test/grid-register-specs.js +74 -0
- package/build/test/helpers.js +75 -0
- package/build/test/logger-specs.js +76 -0
- package/build/test/npm-specs.js +20 -0
- package/build/test/parser-specs.js +319 -0
- package/build/test/plugin-e2e-specs.js +316 -0
- package/build/test/schema/arg-spec-specs.js +70 -0
- package/build/test/schema/cli-args-specs.js +408 -0
- package/build/test/schema/schema-specs.js +407 -0
- package/build/test/utils-specs.js +288 -0
- package/index.js +11 -0
- package/lib/appium-config.schema.json +2 -1
- package/lib/appium.js +51 -8
- package/lib/cli/args.js +17 -14
- package/lib/cli/npm.js +68 -6
- package/lib/cli/parser.js +5 -2
- package/lib/config-file.js +9 -12
- package/lib/config.js +104 -56
- package/lib/extension-config.js +1 -1
- package/lib/main.js +94 -40
- package/lib/plugin-config.js +1 -1
- package/lib/plugins.js +2 -0
- package/lib/schema/appium-config-schema.js +1 -0
- package/lib/schema/arg-spec.js +13 -3
- package/lib/schema/cli-args.js +22 -34
- package/lib/schema/keywords.js +20 -4
- package/lib/schema/schema.js +150 -24
- package/lib/utils.js +28 -29
- package/package.json +9 -14
- package/types/types.d.ts +5 -0
- package/build/lib/cli/argparse-actions.js +0 -104
- package/lib/cli/argparse-actions.js +0 -77
package/lib/cli/parser.js
CHANGED
|
@@ -146,7 +146,7 @@ class ArgParser {
|
|
|
146
146
|
return _.reduce(
|
|
147
147
|
args,
|
|
148
148
|
(unpacked, value, key) => {
|
|
149
|
-
if (hasArgSpec(key)) {
|
|
149
|
+
if (!_.isUndefined(value) && hasArgSpec(key)) {
|
|
150
150
|
const {dest} = /** @type {import('../schema/arg-spec').ArgSpec} */(getArgSpec(key));
|
|
151
151
|
_.set(unpacked, dest, value);
|
|
152
152
|
} else {
|
|
@@ -165,7 +165,10 @@ class ArgParser {
|
|
|
165
165
|
*/
|
|
166
166
|
static _patchExit (parser) {
|
|
167
167
|
parser.exit = (code, msg) => {
|
|
168
|
-
|
|
168
|
+
if (code) {
|
|
169
|
+
throw new Error(msg);
|
|
170
|
+
}
|
|
171
|
+
process.exit();
|
|
169
172
|
};
|
|
170
173
|
}
|
|
171
174
|
|
package/lib/config-file.js
CHANGED
|
@@ -113,10 +113,9 @@ export async function readConfigFile (filepath, opts = {}) {
|
|
|
113
113
|
? await loadConfigFile(lc, filepath)
|
|
114
114
|
: await searchConfigFile(lc);
|
|
115
115
|
|
|
116
|
-
if (result && !result
|
|
117
|
-
const {
|
|
116
|
+
if (result?.filepath && !result?.isEmpty) {
|
|
117
|
+
const {pretty = true} = opts;
|
|
118
118
|
try {
|
|
119
|
-
/** @type {ReadConfigFileResult} */
|
|
120
119
|
let configResult;
|
|
121
120
|
const errors = validate(result.config);
|
|
122
121
|
if (_.isEmpty(errors)) {
|
|
@@ -131,12 +130,10 @@ export async function readConfigFile (filepath, opts = {}) {
|
|
|
131
130
|
: {...result, errors};
|
|
132
131
|
}
|
|
133
132
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
configResult.config
|
|
137
|
-
|
|
138
|
-
);
|
|
139
|
-
}
|
|
133
|
+
// normalize (to camel case) all top-level property names of the config file
|
|
134
|
+
configResult.config = normalizeConfig(
|
|
135
|
+
/** @type {AppiumConfig} */ (configResult.config),
|
|
136
|
+
);
|
|
140
137
|
|
|
141
138
|
return configResult;
|
|
142
139
|
} finally {
|
|
@@ -152,11 +149,12 @@ export async function readConfigFile (filepath, opts = {}) {
|
|
|
152
149
|
* @param {AppiumConfig} config - Configuration object
|
|
153
150
|
* @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys).
|
|
154
151
|
*/
|
|
155
|
-
function normalizeConfig (config) {
|
|
152
|
+
export function normalizeConfig (config) {
|
|
156
153
|
const schema = getSchema();
|
|
157
154
|
/**
|
|
158
155
|
* @param {AppiumConfig} config
|
|
159
156
|
* @param {string} [section] - Keypath (lodash `_.get()` style) to section of config. If omitted, assume root Appium config schema
|
|
157
|
+
* @todo Rewrite as a loop
|
|
160
158
|
* @returns Normalized section of config
|
|
161
159
|
*/
|
|
162
160
|
const normalize = (config, section) => {
|
|
@@ -187,7 +185,7 @@ function normalizeConfig (config) {
|
|
|
187
185
|
* @property {import('ajv').ErrorObject[]} [errors] - Validation errors
|
|
188
186
|
* @property {string} [filepath] - The path to the config file, if found
|
|
189
187
|
* @property {boolean} [isEmpty] - If `true`, the config file exists but is empty
|
|
190
|
-
* @property {
|
|
188
|
+
* @property {NormalizedAppiumConfig} [config] - The parsed configuration
|
|
191
189
|
* @property {string|betterAjvErrors.IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print.
|
|
192
190
|
*/
|
|
193
191
|
|
|
@@ -195,7 +193,6 @@ function normalizeConfig (config) {
|
|
|
195
193
|
* Options for {@link readConfigFile}.
|
|
196
194
|
* @typedef {Object} ReadConfigFileOptions
|
|
197
195
|
* @property {boolean} [pretty=true] If `false`, do not use color and fancy formatting in the `reason` property of the {@link ReadConfigFileResult}. The value of `reason` is then suitable for machine-reading.
|
|
198
|
-
* @property {boolean} [normalize=true] If `false`, do not normalize key names to camel case.
|
|
199
196
|
*/
|
|
200
197
|
|
|
201
198
|
/**
|
package/lib/config.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-console */
|
|
1
4
|
import _ from 'lodash';
|
|
2
5
|
import { mkdirp, system, fs } from '@appium/support';
|
|
3
6
|
import axios from 'axios';
|
|
@@ -5,11 +8,8 @@ import { exec } from 'teen_process';
|
|
|
5
8
|
import { rootDir } from './utils';
|
|
6
9
|
import logger from './logger';
|
|
7
10
|
import semver from 'semver';
|
|
8
|
-
import {
|
|
9
|
-
StoreDeprecatedDefaultCapabilityAction, DEFAULT_CAPS_ARG,
|
|
10
|
-
} from './cli/argparse-actions';
|
|
11
11
|
import findUp from 'find-up';
|
|
12
|
-
import {
|
|
12
|
+
import { getDefaultsForSchema, getAllArgSpecs } from './schema/schema';
|
|
13
13
|
|
|
14
14
|
const npmPackage = fs.readPackageJsonFrom(__dirname);
|
|
15
15
|
|
|
@@ -25,12 +25,7 @@ const BUILD_INFO = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
function getNodeVersion () {
|
|
28
|
-
return semver.coerce(process.version);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function isSubClass (candidateClass, superClass) {
|
|
32
|
-
return _.isFunction(superClass) && _.isFunction(candidateClass)
|
|
33
|
-
&& (candidateClass.prototype instanceof superClass || candidateClass === superClass);
|
|
28
|
+
return /** @type {import('semver').SemVer} */(semver.coerce(process.version));
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
async function updateBuildInfo (useGithubApiFallback = false) {
|
|
@@ -50,7 +45,7 @@ async function updateBuildInfo (useGithubApiFallback = false) {
|
|
|
50
45
|
*
|
|
51
46
|
* This is needed because Appium cannot assume `package.json` and `.git` are in the same
|
|
52
47
|
* directory. Monorepos, see?
|
|
53
|
-
* @returns {string|
|
|
48
|
+
* @returns {Promise<string|undefined>} Path to dir or `undefined` if not found
|
|
54
49
|
*/
|
|
55
50
|
async function findGitRoot () {
|
|
56
51
|
return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
|
|
@@ -88,6 +83,11 @@ async function getGitRev (useGithubApiFallback = false) {
|
|
|
88
83
|
return null;
|
|
89
84
|
}
|
|
90
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} commitSha
|
|
88
|
+
* @param {boolean} [useGithubApiFallback]
|
|
89
|
+
* @returns {Promise<number?>}
|
|
90
|
+
*/
|
|
91
91
|
async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
|
|
92
92
|
const gitRoot = await findGitRoot();
|
|
93
93
|
if (gitRoot) {
|
|
@@ -152,37 +152,49 @@ function warnNodeDeprecations () {
|
|
|
152
152
|
// }
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
async function
|
|
155
|
+
async function showBuildInfo () {
|
|
156
156
|
await updateBuildInfo(true);
|
|
157
157
|
console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Returns k/v pairs of server arguments which are _not_ the defaults.
|
|
162
|
+
* @param {ParsedArgs} parsedArgs
|
|
163
|
+
* @returns {Partial<ParsedArgs>}
|
|
164
|
+
*/
|
|
165
|
+
function getNonDefaultServerArgs (parsedArgs) {
|
|
166
|
+
/**
|
|
167
|
+
* Flattens parsed args into a single level object for comparison with
|
|
168
|
+
* flattened defaults across server args and extension args.
|
|
169
|
+
* @param {ParsedArgs} args
|
|
170
|
+
* @returns {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>}
|
|
171
|
+
*/
|
|
172
|
+
const flatten = (args) => {
|
|
173
|
+
const argSpecs = getAllArgSpecs();
|
|
174
|
+
const flattened = _.reduce([...argSpecs.values()], (acc, argSpec) => {
|
|
175
|
+
if (_.has(args, argSpec.dest)) {
|
|
176
|
+
acc[argSpec.dest] = {value: _.get(args, argSpec.dest), argSpec};
|
|
177
|
+
}
|
|
178
|
+
return acc;
|
|
179
|
+
}, /** @type {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>} */({}));
|
|
162
180
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
181
|
+
return flattened;
|
|
182
|
+
};
|
|
166
183
|
|
|
167
|
-
|
|
168
|
-
return _.isArray(defaultsFromSchema[dest]);
|
|
169
|
-
}
|
|
184
|
+
const args = flatten(parsedArgs);
|
|
170
185
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
}
|
|
186
|
+
// hopefully these function names are descriptive enough
|
|
187
|
+
const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest].value !== typeof defaultsFromSchema[dest];
|
|
174
188
|
|
|
175
|
-
|
|
176
|
-
return _.difference(args[dest], defaultsFromSchema[dest]).length > 0;
|
|
177
|
-
}
|
|
189
|
+
const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
|
|
178
190
|
|
|
179
|
-
|
|
180
|
-
return args[dest] !== defaultsFromSchema[dest];
|
|
181
|
-
}
|
|
191
|
+
const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest].value);
|
|
182
192
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
193
|
+
const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest].value, defaultsFromSchema[dest])), 0);
|
|
194
|
+
|
|
195
|
+
const valuesDiffer = /** @param {string} dest */(dest) => args[dest].value !== defaultsFromSchema[dest];
|
|
196
|
+
|
|
197
|
+
const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
|
|
186
198
|
|
|
187
199
|
// note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
|
|
188
200
|
|
|
@@ -191,8 +203,8 @@ function getNonDefaultServerArgs (parser, args) {
|
|
|
191
203
|
arraysDiffer
|
|
192
204
|
]);
|
|
193
205
|
|
|
194
|
-
const
|
|
195
|
-
_.negate(defaultValueIsArray),
|
|
206
|
+
const defaultValueNotArrayAndValuesDiffer = _.overEvery([
|
|
207
|
+
_.negate(defaultValueIsArray), valuesDiffer
|
|
196
208
|
]);
|
|
197
209
|
|
|
198
210
|
/**
|
|
@@ -205,8 +217,7 @@ function getNonDefaultServerArgs (parser, args) {
|
|
|
205
217
|
* - if so, and the default is an array:
|
|
206
218
|
* - ensures the args value is an array
|
|
207
219
|
* - ensures the args values do not differ from the default values
|
|
208
|
-
* @
|
|
209
|
-
* @returns {boolean}
|
|
220
|
+
* @type {(dest: string) => boolean}
|
|
210
221
|
*/
|
|
211
222
|
const isNotDefault = _.overEvery([
|
|
212
223
|
defaultIsDefined,
|
|
@@ -216,32 +227,66 @@ function getNonDefaultServerArgs (parser, args) {
|
|
|
216
227
|
defaultValueIsArray,
|
|
217
228
|
argValueNotArrayOrArraysDiffer
|
|
218
229
|
]),
|
|
219
|
-
|
|
230
|
+
defaultValueNotArrayAndValuesDiffer
|
|
220
231
|
])
|
|
221
232
|
]);
|
|
222
233
|
|
|
223
|
-
const defaultsFromSchema =
|
|
234
|
+
const defaultsFromSchema = getDefaultsForSchema(true);
|
|
224
235
|
|
|
225
|
-
return _.
|
|
236
|
+
return _.reduce(
|
|
237
|
+
_.pickBy(args, (__, key) => isNotDefault(key)),
|
|
238
|
+
// explodes the flattened object back into nested one
|
|
239
|
+
(acc, {value, argSpec}) => _.set(acc, argSpec.dest, value), /** @type {Partial<ParsedArgs>} */({})
|
|
240
|
+
);
|
|
226
241
|
}
|
|
227
242
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Compacts an object for {@link showConfig}:
|
|
245
|
+
* 1. Removes `subcommand` key/value
|
|
246
|
+
* 2. Removes `undefined` values
|
|
247
|
+
* 3. Removes empty objects (but not `false` values)
|
|
248
|
+
* Does not operate recursively.
|
|
249
|
+
*/
|
|
250
|
+
const compactConfig = _.partial(
|
|
251
|
+
_.omitBy,
|
|
252
|
+
_,
|
|
253
|
+
(value, key) => key === 'subcommand' || _.isUndefined(value) || (_.isObject(value) && _.isEmpty(value))
|
|
254
|
+
);
|
|
235
255
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Shows a breakdown of the current config after CLI params, config file loaded & defaults applied.
|
|
258
|
+
*
|
|
259
|
+
* The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
|
|
260
|
+
* but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
|
|
261
|
+
*
|
|
262
|
+
* @param {Partial<ParsedArgs>} nonDefaultPreConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
|
|
263
|
+
* @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file. _Must_ be normalized
|
|
264
|
+
* @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
|
|
265
|
+
* @param {ParsedArgs} parsedArgs - Entire parsed args object
|
|
266
|
+
*/
|
|
267
|
+
function showConfig (nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
|
|
268
|
+
console.log('Appium Configuration\n');
|
|
269
|
+
console.log('from defaults:\n');
|
|
270
|
+
console.dir(compactConfig(defaults));
|
|
271
|
+
if (configResult.config) {
|
|
272
|
+
console.log(`\nfrom config file at ${configResult.filepath}:\n`);
|
|
273
|
+
console.dir(compactConfig(configResult.config));
|
|
274
|
+
} else {
|
|
275
|
+
console.log(`\n(no configuration file loaded)`);
|
|
276
|
+
}
|
|
277
|
+
if (_.isEmpty(nonDefaultPreConfigParsedArgs)) {
|
|
278
|
+
console.log(`\n(no CLI parameters provided)`);
|
|
279
|
+
} else {
|
|
280
|
+
console.log('\nvia CLI or function call:\n');
|
|
281
|
+
console.dir(compactConfig(nonDefaultPreConfigParsedArgs));
|
|
282
|
+
}
|
|
283
|
+
console.log('\nfinal configuration:\n');
|
|
284
|
+
console.dir(compactConfig(parsedArgs));
|
|
243
285
|
}
|
|
244
286
|
|
|
287
|
+
/**
|
|
288
|
+
* @param {string} tmpDir
|
|
289
|
+
*/
|
|
245
290
|
async function validateTmpDir (tmpDir) {
|
|
246
291
|
try {
|
|
247
292
|
await mkdirp(tmpDir);
|
|
@@ -252,8 +297,11 @@ async function validateTmpDir (tmpDir) {
|
|
|
252
297
|
}
|
|
253
298
|
|
|
254
299
|
export {
|
|
255
|
-
getBuildInfo, checkNodeOk,
|
|
300
|
+
getBuildInfo, checkNodeOk, showBuildInfo,
|
|
256
301
|
warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
|
|
257
|
-
getGitRev, APPIUM_VER, updateBuildInfo,
|
|
258
|
-
getDeprecatedArgs
|
|
302
|
+
getGitRev, APPIUM_VER, updateBuildInfo, showConfig
|
|
259
303
|
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @typedef {import('../types/types').ParsedArgs} ParsedArgs
|
|
307
|
+
*/
|
package/lib/extension-config.js
CHANGED
|
@@ -270,7 +270,7 @@ export default class ExtensionConfig {
|
|
|
270
270
|
/**
|
|
271
271
|
* Loads extension and returns its main class
|
|
272
272
|
* @param {string} extName
|
|
273
|
-
* @returns {(
|
|
273
|
+
* @returns {import('type-fest').Class<unknown>}
|
|
274
274
|
*/
|
|
275
275
|
require (extName) {
|
|
276
276
|
const {mainClass} = this.installedExtensions[extName];
|
package/lib/main.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// transpile:main
|
|
4
4
|
// @ts-check
|
|
5
5
|
|
|
6
|
+
import logger from './logger'; // logger needs to remain first of imports
|
|
6
7
|
// @ts-ignore
|
|
7
8
|
import { routeConfiguringFunction as makeRouter, server as baseServer } from '@appium/base-driver';
|
|
8
9
|
import { logger as logFactory, util } from '@appium/support';
|
|
@@ -12,14 +13,13 @@ import { AppiumDriver } from './appium';
|
|
|
12
13
|
import { driverConfig, pluginConfig, USE_ALL_PLUGINS } from './cli/args';
|
|
13
14
|
import { runExtensionCommand } from './cli/extension';
|
|
14
15
|
import { default as getParser, SERVER_SUBCOMMAND } from './cli/parser';
|
|
15
|
-
import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs,
|
|
16
|
+
import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showBuildInfo, validateTmpDir, warnNodeDeprecations, showConfig } from './config';
|
|
16
17
|
import { readConfigFile } from './config-file';
|
|
17
18
|
import { DRIVER_TYPE, PLUGIN_TYPE } from './extension-config';
|
|
18
19
|
import registerNode from './grid-register';
|
|
19
|
-
import logger from './logger'; // logger needs to remain first of imports
|
|
20
20
|
import { init as logsinkInit } from './logsink';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { getDefaultsForSchema } from './schema/schema';
|
|
22
|
+
import { inspect } from './utils';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
*
|
|
@@ -32,14 +32,12 @@ async function preflightChecks (args, throwInsteadOfExit = false) {
|
|
|
32
32
|
if (args.longStacktrace) {
|
|
33
33
|
require('longjohn').async_trace_limit = -1;
|
|
34
34
|
}
|
|
35
|
-
if (args.
|
|
36
|
-
await
|
|
35
|
+
if (args.showBuildInfo) {
|
|
36
|
+
await showBuildInfo();
|
|
37
37
|
process.exit(0);
|
|
38
38
|
}
|
|
39
39
|
warnNodeDeprecations();
|
|
40
40
|
|
|
41
|
-
validate(args);
|
|
42
|
-
|
|
43
41
|
if (args.tmpDir) {
|
|
44
42
|
await validateTmpDir(args.tmpDir);
|
|
45
43
|
}
|
|
@@ -53,18 +51,24 @@ async function preflightChecks (args, throwInsteadOfExit = false) {
|
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @param {Partial<ParsedArgs>} args
|
|
56
|
+
*/
|
|
56
57
|
function logNonDefaultArgsWarning (args) {
|
|
57
58
|
logger.info('Non-default server args:');
|
|
58
|
-
|
|
59
|
+
inspect(args);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
function logDefaultCapabilitiesWarning (caps) {
|
|
62
63
|
logger.info('Default capabilities, which will be added to each request ' +
|
|
63
64
|
'unless overridden by desired capabilities:');
|
|
64
|
-
|
|
65
|
+
inspect(caps);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
/**
|
|
69
|
+
* @param {ParsedArgs} args
|
|
70
|
+
*/
|
|
71
|
+
async function logStartupInfo (args) {
|
|
68
72
|
let welcome = `Welcome to Appium v${APPIUM_VER}`;
|
|
69
73
|
let appiumRev = await getGitRev();
|
|
70
74
|
if (appiumRev) {
|
|
@@ -72,7 +76,7 @@ async function logStartupInfo (parser, args) {
|
|
|
72
76
|
}
|
|
73
77
|
logger.info(welcome);
|
|
74
78
|
|
|
75
|
-
let showArgs = getNonDefaultServerArgs(
|
|
79
|
+
let showArgs = getNonDefaultServerArgs(args);
|
|
76
80
|
if (_.size(showArgs)) {
|
|
77
81
|
logNonDefaultArgsWarning(showArgs);
|
|
78
82
|
}
|
|
@@ -86,6 +90,12 @@ async function logStartupInfo (parser, args) {
|
|
|
86
90
|
// }
|
|
87
91
|
}
|
|
88
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Logs the address and port the server is listening on
|
|
95
|
+
* @param {string} address - Address
|
|
96
|
+
* @param {number} port - Port
|
|
97
|
+
* @returns {void}
|
|
98
|
+
*/
|
|
89
99
|
function logServerPort (address, port) {
|
|
90
100
|
let logMessage = `Appium REST http interface listener started on ` +
|
|
91
101
|
`${address}:${port}`;
|
|
@@ -94,13 +104,13 @@ function logServerPort (address, port) {
|
|
|
94
104
|
|
|
95
105
|
/**
|
|
96
106
|
* Find any plugin name which has been installed, and which has been requested for activation by
|
|
97
|
-
* using the --plugins flag, and turn each one into its class, so we can send them as objects
|
|
98
|
-
* the server init. We also want to send/assign them to the umbrella driver so it can use them
|
|
99
|
-
* wrap command execution
|
|
107
|
+
* using the --use-plugins flag, and turn each one into its class, so we can send them as objects
|
|
108
|
+
* to the server init. We also want to send/assign them to the umbrella driver so it can use them
|
|
109
|
+
* to wrap command execution
|
|
100
110
|
*
|
|
101
111
|
* @param {Object} args - argparser parsed dict
|
|
102
112
|
* @param {import('./plugin-config').default} pluginConfig - a plugin extension config
|
|
103
|
-
* @returns {
|
|
113
|
+
* @returns {PluginExtensionClass[]}
|
|
104
114
|
*/
|
|
105
115
|
function getActivePlugins (args, pluginConfig) {
|
|
106
116
|
return _.compact(Object.keys(pluginConfig.installedExtensions).filter((pluginName) =>
|
|
@@ -109,7 +119,7 @@ function getActivePlugins (args, pluginConfig) {
|
|
|
109
119
|
).map((pluginName) => {
|
|
110
120
|
try {
|
|
111
121
|
logger.info(`Attempting to load plugin ${pluginName}...`);
|
|
112
|
-
const PluginClass = /** @type {
|
|
122
|
+
const PluginClass = /** @type {PluginExtensionClass} */(pluginConfig.require(pluginName));
|
|
113
123
|
|
|
114
124
|
PluginClass.pluginName = pluginName; // store the plugin name on the class so it can be used later
|
|
115
125
|
return PluginClass;
|
|
@@ -144,10 +154,22 @@ function getActiveDrivers (args, driverConfig) {
|
|
|
144
154
|
}));
|
|
145
155
|
}
|
|
146
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Gets a list of `updateServer` functions from all extensions
|
|
159
|
+
* @param {DriverExtensionClass[]} driverClasses
|
|
160
|
+
* @param {PluginExtensionClass[]} pluginClasses
|
|
161
|
+
* @returns {StaticExtMembers['updateServer'][]}
|
|
162
|
+
*/
|
|
147
163
|
function getServerUpdaters (driverClasses, pluginClasses) {
|
|
148
|
-
return [...driverClasses, ...pluginClasses]
|
|
164
|
+
return _.compact(_.map([...driverClasses, ...pluginClasses], 'updateServer'));
|
|
149
165
|
}
|
|
150
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Makes a big `MethodMap` from all the little `MethodMap`s in the extensions
|
|
169
|
+
* @param {DriverExtensionClass[]} driverClasses
|
|
170
|
+
* @param {PluginExtensionClass[]} pluginClasses
|
|
171
|
+
* @returns {import('@appium/base-driver').MethodMap}
|
|
172
|
+
*/
|
|
151
173
|
function getExtraMethodMap (driverClasses, pluginClasses) {
|
|
152
174
|
return [...driverClasses, ...pluginClasses].reduce(
|
|
153
175
|
(map, klass) => ({...map, ...klass.newMethodMap}),
|
|
@@ -160,19 +182,30 @@ function getExtraMethodMap (driverClasses, pluginClasses) {
|
|
|
160
182
|
*
|
|
161
183
|
* Use this to get at the configuration schema.
|
|
162
184
|
*
|
|
185
|
+
* If `args` contains a non-empty `subcommand` which is not `server`, this function
|
|
186
|
+
* will resolve with an empty object.
|
|
187
|
+
*
|
|
188
|
+
* @param {ParsedArgs} [args] - Parsed args
|
|
189
|
+
* @returns {Promise<Partial<{appiumDriver: AppiumDriver, parsedArgs: ParsedArgs}>>}
|
|
163
190
|
* @example
|
|
164
191
|
* import {init, getSchema} from 'appium';
|
|
165
192
|
* const options = {}; // config object
|
|
166
193
|
* await init(options);
|
|
167
194
|
* const schema = getSchema(); // entire config schema including plugins and drivers
|
|
168
|
-
* @param {ParsedArgs} [args] - Parsed args
|
|
169
|
-
* @returns {Promise<{parser: import('./cli/parser').ArgParser} & Partial<{appiumDriver: AppiumDriver, parsedArgs: ParsedArgs}>>}
|
|
170
195
|
*/
|
|
171
196
|
async function init (args) {
|
|
172
197
|
const parser = await getParser();
|
|
173
198
|
let throwInsteadOfExit = false;
|
|
174
199
|
/** @type {ParsedArgs} */
|
|
200
|
+
let preConfigParsedArgs;
|
|
201
|
+
/** @type {typeof preConfigParsedArgs & import('type-fest').AsyncReturnType<readConfigFile>['config']} */
|
|
175
202
|
let parsedArgs;
|
|
203
|
+
/**
|
|
204
|
+
* This is a definition (instead of declaration) because TS can't figure out
|
|
205
|
+
* the value will be defined when it's used.
|
|
206
|
+
* @type {ReturnType<getDefaultsForSchema>}
|
|
207
|
+
*/
|
|
208
|
+
let defaults = {};
|
|
176
209
|
if (args) {
|
|
177
210
|
// if we have a containing package instead of running as a CLI process,
|
|
178
211
|
// that package might not appreciate us calling 'process.exit' willy-
|
|
@@ -182,35 +215,41 @@ async function init (args) {
|
|
|
182
215
|
// but remove it since it's not a real server arg per se
|
|
183
216
|
delete args.throwInsteadOfExit;
|
|
184
217
|
}
|
|
185
|
-
|
|
218
|
+
preConfigParsedArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
|
|
186
219
|
} else {
|
|
187
220
|
// otherwise parse from CLI
|
|
188
|
-
|
|
221
|
+
preConfigParsedArgs = parser.parseArgs();
|
|
189
222
|
}
|
|
190
223
|
|
|
191
|
-
const configResult = await readConfigFile(
|
|
224
|
+
const configResult = await readConfigFile(preConfigParsedArgs.configFile);
|
|
192
225
|
|
|
193
226
|
if (!_.isEmpty(configResult.errors)) {
|
|
194
227
|
throw new Error(`Errors in config file ${configResult.filepath}:\n ${configResult.reason ?? configResult.errors}`);
|
|
195
228
|
}
|
|
196
229
|
|
|
230
|
+
|
|
197
231
|
// merge config and apply defaults.
|
|
198
232
|
// the order of precendece is:
|
|
199
233
|
// 1. command line args
|
|
200
234
|
// 2. config file
|
|
201
235
|
// 3. defaults from config file.
|
|
202
|
-
if (
|
|
236
|
+
if (preConfigParsedArgs.subcommand === SERVER_SUBCOMMAND) {
|
|
237
|
+
defaults = getDefaultsForSchema(false);
|
|
238
|
+
|
|
203
239
|
parsedArgs = _.defaultsDeep(
|
|
204
|
-
|
|
240
|
+
preConfigParsedArgs,
|
|
205
241
|
configResult.config?.server,
|
|
206
|
-
|
|
242
|
+
defaults
|
|
207
243
|
);
|
|
208
|
-
}
|
|
209
244
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
245
|
+
if (preConfigParsedArgs.showConfig) {
|
|
246
|
+
showConfig(getNonDefaultServerArgs(preConfigParsedArgs), configResult, defaults, parsedArgs);
|
|
247
|
+
return {};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
} else {
|
|
251
|
+
parsedArgs = preConfigParsedArgs;
|
|
252
|
+
}
|
|
214
253
|
|
|
215
254
|
await logsinkInit(parsedArgs);
|
|
216
255
|
|
|
@@ -218,11 +257,11 @@ async function init (args) {
|
|
|
218
257
|
// but instead pass control to the driver CLI
|
|
219
258
|
if (parsedArgs.subcommand === DRIVER_TYPE) {
|
|
220
259
|
await runExtensionCommand(parsedArgs, parsedArgs.subcommand, driverConfig);
|
|
221
|
-
return {
|
|
260
|
+
return {};
|
|
222
261
|
}
|
|
223
262
|
if (parsedArgs.subcommand === PLUGIN_TYPE) {
|
|
224
263
|
await runExtensionCommand(parsedArgs, parsedArgs.subcommand, pluginConfig);
|
|
225
|
-
return {
|
|
264
|
+
return {};
|
|
226
265
|
}
|
|
227
266
|
|
|
228
267
|
if (parsedArgs.logFilters) {
|
|
@@ -243,16 +282,17 @@ async function init (args) {
|
|
|
243
282
|
appiumDriver.driverConfig = driverConfig;
|
|
244
283
|
await preflightChecks(parsedArgs, throwInsteadOfExit);
|
|
245
284
|
|
|
246
|
-
return {
|
|
285
|
+
return {appiumDriver, parsedArgs};
|
|
247
286
|
}
|
|
248
287
|
|
|
249
288
|
/**
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* @
|
|
289
|
+
* Initializes Appium's config. Starts server if appropriate and resolves the
|
|
290
|
+
* server instance if so; otherwise resolves w/ `undefined`.
|
|
291
|
+
* @param {ParsedArgs} [args] - Arguments from CLI or otherwise
|
|
292
|
+
* @returns {Promise<import('express').Express|undefined>}
|
|
253
293
|
*/
|
|
254
294
|
async function main (args) {
|
|
255
|
-
const {
|
|
295
|
+
const {appiumDriver, parsedArgs} = await init(args);
|
|
256
296
|
|
|
257
297
|
if (!appiumDriver || !parsedArgs) {
|
|
258
298
|
// if this branch is taken, we've run a different subcommand, so there's nothing
|
|
@@ -264,7 +304,7 @@ async function main (args) {
|
|
|
264
304
|
// set the active plugins on the umbrella driver so it can use them for commands
|
|
265
305
|
appiumDriver.pluginClasses = pluginClasses;
|
|
266
306
|
|
|
267
|
-
await logStartupInfo(
|
|
307
|
+
await logStartupInfo(parsedArgs);
|
|
268
308
|
let routeConfiguringFunction = makeRouter(appiumDriver);
|
|
269
309
|
|
|
270
310
|
const driverClasses = getActiveDrivers(parsedArgs, driverConfig);
|
|
@@ -298,7 +338,6 @@ async function main (args) {
|
|
|
298
338
|
'to visit sites which could maliciously try to start Appium ' +
|
|
299
339
|
'sessions on your machine');
|
|
300
340
|
}
|
|
301
|
-
// @ts-ignore
|
|
302
341
|
appiumDriver.server = server;
|
|
303
342
|
try {
|
|
304
343
|
// configure as node on grid, if necessary
|
|
@@ -335,6 +374,9 @@ async function main (args) {
|
|
|
335
374
|
return server;
|
|
336
375
|
}
|
|
337
376
|
|
|
377
|
+
// NOTE: this is here for backwards compat for any scripts referencing `main.js` directly
|
|
378
|
+
// (more specifically, `build/lib/main.js`)
|
|
379
|
+
// the executable is now `../index.js`, so that module will typically be `require.main`.
|
|
338
380
|
if (require.main === module) {
|
|
339
381
|
asyncify(main);
|
|
340
382
|
}
|
|
@@ -348,3 +390,15 @@ export { readConfigFile } from './config-file';
|
|
|
348
390
|
/**
|
|
349
391
|
* @typedef {import('../types/types').ParsedArgs} ParsedArgs
|
|
350
392
|
*/
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @typedef {import('./appium').PluginExtensionClass} PluginExtensionClass
|
|
396
|
+
*/
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @typedef {import('./appium').DriverExtensionClass} DriverExtensionClass
|
|
400
|
+
*/
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* @typedef {import('./appium').StaticExtMembers} StaticExtMembers
|
|
404
|
+
*/
|
package/lib/plugin-config.js
CHANGED
|
@@ -57,7 +57,7 @@ export default class PluginConfig extends ExtensionConfig {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (_.isEmpty(activeNames)) {
|
|
60
|
-
log.info('No plugins activated. Use the --plugins flag with names of plugins to activate');
|
|
60
|
+
log.info('No plugins activated. Use the --use-plugins flag with names of plugins to activate');
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
package/lib/plugins.js
CHANGED
|
@@ -212,6 +212,7 @@ const schema = /** @type {const} */ ({
|
|
|
212
212
|
'Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it\'s not the case if a client could potentially break out of the session sandbox. Specific features can be overridden by using "deny-insecure"',
|
|
213
213
|
title: 'relaxed-security config',
|
|
214
214
|
type: 'boolean',
|
|
215
|
+
appiumCliDest: 'relaxedSecurityEnabled'
|
|
215
216
|
},
|
|
216
217
|
'session-override': {
|
|
217
218
|
default: false,
|