appium 2.0.0-beta.2 → 2.0.0-beta.20
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/README.md +9 -9
- package/build/lib/appium-config.schema.json +0 -0
- package/build/lib/appium.js +157 -53
- package/build/lib/cli/argparse-actions.js +104 -0
- package/build/lib/cli/args.js +115 -279
- package/build/lib/cli/driver-command.js +11 -1
- package/build/lib/cli/extension-command.js +60 -8
- package/build/lib/cli/extension.js +30 -7
- package/build/lib/cli/npm.js +17 -14
- package/build/lib/cli/parser.js +152 -89
- package/build/lib/cli/plugin-command.js +11 -1
- package/build/lib/cli/utils.js +29 -3
- package/build/lib/config-file.js +141 -0
- package/build/lib/config.js +76 -61
- package/build/lib/driver-config.js +42 -19
- package/build/lib/drivers.js +8 -4
- package/build/lib/ext-config-io.js +165 -0
- package/build/lib/extension-config.js +130 -61
- package/build/lib/grid-register.js +22 -24
- package/build/lib/logger.js +3 -3
- package/build/lib/logsink.js +11 -13
- package/build/lib/main.js +197 -77
- package/build/lib/plugin-config.js +20 -10
- package/build/lib/plugins.js +4 -2
- 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 +44 -99
- package/lib/appium-config.schema.json +277 -0
- package/lib/appium.js +201 -65
- package/lib/cli/argparse-actions.js +77 -0
- package/lib/cli/args.js +174 -375
- package/lib/cli/driver-command.js +4 -0
- package/lib/cli/extension-command.js +70 -5
- package/lib/cli/extension.js +25 -5
- package/lib/cli/npm.js +18 -12
- package/lib/cli/parser.js +254 -79
- package/lib/cli/plugin-command.js +4 -0
- package/lib/cli/utils.js +21 -1
- package/lib/config-file.js +227 -0
- package/lib/config.js +109 -62
- package/lib/driver-config.js +66 -11
- package/lib/drivers.js +4 -1
- package/lib/ext-config-io.js +287 -0
- package/lib/extension-config.js +225 -67
- package/lib/grid-register.js +27 -24
- package/lib/logger.js +1 -1
- package/lib/logsink.js +10 -7
- package/lib/main.js +211 -77
- package/lib/plugin-config.js +34 -5
- package/lib/plugins.js +1 -0
- 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 +42 -88
- package/package.json +55 -80
- package/postinstall.js +71 -0
- package/types/appium-config.d.ts +197 -0
- package/types/types.d.ts +201 -0
- package/CHANGELOG.md +0 -3515
- package/build/lib/cli/parser-helpers.js +0 -82
- package/lib/cli/parser-helpers.js +0 -79
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import betterAjvErrors from '@sidvind/better-ajv-errors';
|
|
4
|
+
import { lilconfig } from 'lilconfig';
|
|
5
|
+
import _ from 'lodash';
|
|
6
|
+
import yaml from 'yaml';
|
|
7
|
+
import { getSchema, validate } from './schema/schema';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* lilconfig loader to handle `.yaml` files
|
|
11
|
+
* @type {import('lilconfig').LoaderSync}
|
|
12
|
+
*/
|
|
13
|
+
function yamlLoader (filepath, content) {
|
|
14
|
+
return yaml.parse(content);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A cache of the raw config file (a JSON string) at a filepath.
|
|
19
|
+
* This is used for better error reporting.
|
|
20
|
+
* Note that config files needn't be JSON, but it helps if they are.
|
|
21
|
+
* @type {Map<string,RawJson>}
|
|
22
|
+
*/
|
|
23
|
+
const rawConfig = new Map();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Custom JSON loader that caches the raw config file (for use with `better-ajv-errors`).
|
|
27
|
+
* If it weren't for this cache, this would be unnecessary.
|
|
28
|
+
* @type {import('lilconfig').LoaderSync}
|
|
29
|
+
*/
|
|
30
|
+
function jsonLoader (filepath, content) {
|
|
31
|
+
rawConfig.set(filepath, content);
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Loads a config file from an explicit path
|
|
37
|
+
* @param {LilconfigAsyncSearcher} lc - lilconfig instance
|
|
38
|
+
* @param {string} filepath - Path to config file
|
|
39
|
+
* @returns {Promise<import('lilconfig').LilconfigResult>}
|
|
40
|
+
*/
|
|
41
|
+
async function loadConfigFile (lc, filepath) {
|
|
42
|
+
try {
|
|
43
|
+
// removing "await" will cause any rejection to _not_ be caught in this block!
|
|
44
|
+
return await lc.load(filepath);
|
|
45
|
+
} catch (/** @type {unknown} */err) {
|
|
46
|
+
if (/** @type {NodeJS.ErrnoException} */(err).code === 'ENOENT') {
|
|
47
|
+
/** @type {NodeJS.ErrnoException} */(err).message = `Config file not found at user-provided path: ${filepath}`;
|
|
48
|
+
throw err;
|
|
49
|
+
} else if (err instanceof SyntaxError) {
|
|
50
|
+
// generally invalid JSON
|
|
51
|
+
err.message = `Config file at user-provided path ${filepath} is invalid:\n${err.message}`;
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Searches for a config file
|
|
60
|
+
* @param {LilconfigAsyncSearcher} lc - lilconfig instance
|
|
61
|
+
* @returns {Promise<import('lilconfig').LilconfigResult>}
|
|
62
|
+
*/
|
|
63
|
+
async function searchConfigFile (lc) {
|
|
64
|
+
return await lc.search();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Given an array of errors and the result of loading a config file, generate a
|
|
69
|
+
* helpful string for the user.
|
|
70
|
+
*
|
|
71
|
+
* - If `opts` contains a `json` property, this should be the original JSON
|
|
72
|
+
* _string_ of the config file. This is only applicable if the config file
|
|
73
|
+
* was in JSON format. If present, it will associate line numbers with errors.
|
|
74
|
+
* - If `errors` happens to be empty, this will throw.
|
|
75
|
+
* @param {import('ajv').ErrorObject[]} errors - Non-empty array of errors. Required.
|
|
76
|
+
* @param {ReadConfigFileResult['config']|any} [config] -
|
|
77
|
+
* Configuration & metadata
|
|
78
|
+
* @param {FormatConfigErrorsOptions} [opts]
|
|
79
|
+
* @throws {TypeError} If `errors` is empty
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
export function formatErrors (errors = [], config = {}, opts = {}) {
|
|
83
|
+
if (errors && !errors.length) {
|
|
84
|
+
throw new TypeError('Array of errors must be non-empty');
|
|
85
|
+
}
|
|
86
|
+
return betterAjvErrors(getSchema(opts.schemaId), config, errors, {
|
|
87
|
+
json: opts.json,
|
|
88
|
+
format: 'cli',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Given an optional path, read a config file. Validates the config file.
|
|
94
|
+
*
|
|
95
|
+
* Call {@link validate} if you already have a config object.
|
|
96
|
+
* @param {string} [filepath] - Path to config file, if we have one
|
|
97
|
+
* @param {ReadConfigFileOptions} [opts] - Options
|
|
98
|
+
* @public
|
|
99
|
+
* @returns {Promise<ReadConfigFileResult>} Contains config and filepath, if found, and any errors
|
|
100
|
+
*/
|
|
101
|
+
export async function readConfigFile (filepath, opts = {}) {
|
|
102
|
+
const lc = lilconfig('appium', {
|
|
103
|
+
loaders: {
|
|
104
|
+
'.yaml': yamlLoader,
|
|
105
|
+
'.yml': yamlLoader,
|
|
106
|
+
'.json': jsonLoader,
|
|
107
|
+
noExt: jsonLoader,
|
|
108
|
+
},
|
|
109
|
+
packageProp: 'appiumConfig'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = filepath
|
|
113
|
+
? await loadConfigFile(lc, filepath)
|
|
114
|
+
: await searchConfigFile(lc);
|
|
115
|
+
|
|
116
|
+
if (result && !result.isEmpty && result.filepath) {
|
|
117
|
+
const {normalize = true, pretty = true} = opts;
|
|
118
|
+
try {
|
|
119
|
+
/** @type {ReadConfigFileResult} */
|
|
120
|
+
let configResult;
|
|
121
|
+
const errors = validate(result.config);
|
|
122
|
+
if (_.isEmpty(errors)) {
|
|
123
|
+
configResult = {...result, errors};
|
|
124
|
+
} else {
|
|
125
|
+
const reason = formatErrors(errors, result.config, {
|
|
126
|
+
json: rawConfig.get(result.filepath),
|
|
127
|
+
pretty,
|
|
128
|
+
});
|
|
129
|
+
configResult = reason
|
|
130
|
+
? {...result, errors, reason}
|
|
131
|
+
: {...result, errors};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (normalize) {
|
|
135
|
+
// normalize (to camel case) all top-level property names of the config file
|
|
136
|
+
configResult.config = normalizeConfig(
|
|
137
|
+
/** @type {AppiumConfig} */ (configResult.config),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return configResult;
|
|
142
|
+
} finally {
|
|
143
|
+
// clean up the raw config file cache, which is only kept to better report errors.
|
|
144
|
+
rawConfig.delete(result.filepath);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result ?? {};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Convert schema property names to either a) the value of the `appiumCliDest` property, if any; or b) camel-case
|
|
152
|
+
* @param {AppiumConfig} config - Configuration object
|
|
153
|
+
* @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys).
|
|
154
|
+
*/
|
|
155
|
+
function normalizeConfig (config) {
|
|
156
|
+
const schema = getSchema();
|
|
157
|
+
/**
|
|
158
|
+
* @param {AppiumConfig} config
|
|
159
|
+
* @param {string} [section] - Keypath (lodash `_.get()` style) to section of config. If omitted, assume root Appium config schema
|
|
160
|
+
* @returns Normalized section of config
|
|
161
|
+
*/
|
|
162
|
+
const normalize = (config, section) => {
|
|
163
|
+
const obj = _.isUndefined(section) ? config : _.get(config, section, config);
|
|
164
|
+
|
|
165
|
+
const mappedObj = _.mapKeys(obj, (__, prop) =>
|
|
166
|
+
schema.properties[prop]?.appiumCliDest ?? _.camelCase(prop),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return _.mapValues(mappedObj, (value, property) => {
|
|
170
|
+
const nextSection = section ? `${section}.${property}` : property;
|
|
171
|
+
return isSchemaTypeObject(value) ? normalize(config, nextSection) : value;
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Returns `true` if the schema prop references an object, or if it's an object itself
|
|
177
|
+
* @param {import('ajv').SchemaObject|object} schema - Referencing schema object
|
|
178
|
+
*/
|
|
179
|
+
const isSchemaTypeObject = (schema) => Boolean(schema.properties);
|
|
180
|
+
|
|
181
|
+
return normalize(config);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Result of calling {@link readConfigFile}.
|
|
186
|
+
* @typedef {Object} ReadConfigFileResult
|
|
187
|
+
* @property {import('ajv').ErrorObject[]} [errors] - Validation errors
|
|
188
|
+
* @property {string} [filepath] - The path to the config file, if found
|
|
189
|
+
* @property {boolean} [isEmpty] - If `true`, the config file exists but is empty
|
|
190
|
+
* @property {AppiumConfig} [config] - The parsed configuration
|
|
191
|
+
* @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
|
+
*/
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Options for {@link readConfigFile}.
|
|
196
|
+
* @typedef {Object} ReadConfigFileOptions
|
|
197
|
+
* @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
|
+
*/
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* This is an `AsyncSearcher` which is inexplicably _not_ exported by the `lilconfig` type definition.
|
|
203
|
+
* @typedef {ReturnType<import('lilconfig')["lilconfig"]>} LilconfigAsyncSearcher
|
|
204
|
+
*/
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The contents of an Appium config file. Generated from schema
|
|
208
|
+
* @typedef {import('../types/types').AppiumConfig} AppiumConfig
|
|
209
|
+
*/
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The contents of an Appium config file with camelcased property names (and using `appiumCliDest` value if present). Generated from {@link AppiumConfig}
|
|
213
|
+
* @typedef {import('../types/types').NormalizedAppiumConfig} NormalizedAppiumConfig
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The string should be a raw JSON string.
|
|
218
|
+
* @typedef {string} RawJson
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Options for {@link formatErrors}.
|
|
223
|
+
* @typedef {Object} FormatConfigErrorsOptions
|
|
224
|
+
* @property {import('./config-file').RawJson} [json] - Raw JSON config (as string)
|
|
225
|
+
* @property {boolean} [pretty=true] - Whether to format errors as a CLI-friendly string
|
|
226
|
+
* @property {string} [schemaId] - Specific ID of a prop; otherwise entire schema
|
|
227
|
+
*/
|
package/lib/config.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import
|
|
3
|
-
import { mkdirp, fs, system } from 'appium-support';
|
|
2
|
+
import { mkdirp, system, fs } from '@appium/support';
|
|
4
3
|
import axios from 'axios';
|
|
5
4
|
import { exec } from 'teen_process';
|
|
6
5
|
import { rootDir } from './utils';
|
|
7
6
|
import logger from './logger';
|
|
8
7
|
import semver from 'semver';
|
|
8
|
+
import {
|
|
9
|
+
StoreDeprecatedDefaultCapabilityAction, DEFAULT_CAPS_ARG,
|
|
10
|
+
} from './cli/argparse-actions';
|
|
11
|
+
import findUp from 'find-up';
|
|
12
|
+
import { getDefaultsFromSchema } from './schema/schema';
|
|
9
13
|
|
|
14
|
+
const npmPackage = fs.readPackageJsonFrom(__dirname);
|
|
10
15
|
|
|
11
|
-
const npmPackage = require(path.resolve(rootDir, 'package.json'));
|
|
12
16
|
const APPIUM_VER = npmPackage.version;
|
|
13
17
|
const MIN_NODE_VERSION = npmPackage.engines.node;
|
|
14
18
|
|
|
@@ -24,6 +28,11 @@ function getNodeVersion () {
|
|
|
24
28
|
return semver.coerce(process.version);
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
function isSubClass (candidateClass, superClass) {
|
|
32
|
+
return _.isFunction(superClass) && _.isFunction(candidateClass)
|
|
33
|
+
&& (candidateClass.prototype instanceof superClass || candidateClass === superClass);
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
async function updateBuildInfo (useGithubApiFallback = false) {
|
|
28
37
|
const sha = await getGitRev(useGithubApiFallback);
|
|
29
38
|
if (!sha) {
|
|
@@ -36,11 +45,23 @@ async function updateBuildInfo (useGithubApiFallback = false) {
|
|
|
36
45
|
}
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Finds the Git metadata dir (see `GIT_META_ROOT`)
|
|
50
|
+
*
|
|
51
|
+
* This is needed because Appium cannot assume `package.json` and `.git` are in the same
|
|
52
|
+
* directory. Monorepos, see?
|
|
53
|
+
* @returns {string|void} Path to dir or `undefined` if not found
|
|
54
|
+
*/
|
|
55
|
+
async function findGitRoot () {
|
|
56
|
+
return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
async function getGitRev (useGithubApiFallback = false) {
|
|
40
|
-
|
|
60
|
+
const gitRoot = await findGitRoot();
|
|
61
|
+
if (gitRoot) {
|
|
41
62
|
try {
|
|
42
63
|
const {stdout} = await exec(GIT_BINARY, ['rev-parse', 'HEAD'], {
|
|
43
|
-
cwd:
|
|
64
|
+
cwd: gitRoot
|
|
44
65
|
});
|
|
45
66
|
return stdout.trim();
|
|
46
67
|
} catch (ign) {}
|
|
@@ -68,10 +89,11 @@ async function getGitRev (useGithubApiFallback = false) {
|
|
|
68
89
|
}
|
|
69
90
|
|
|
70
91
|
async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
|
|
71
|
-
|
|
92
|
+
const gitRoot = await findGitRoot();
|
|
93
|
+
if (gitRoot) {
|
|
72
94
|
try {
|
|
73
95
|
const {stdout} = await exec(GIT_BINARY, ['show', '-s', '--format=%ci', commitSha], {
|
|
74
|
-
cwd:
|
|
96
|
+
cwd: gitRoot
|
|
75
97
|
});
|
|
76
98
|
return stdout.trim();
|
|
77
99
|
} catch (ign) {}
|
|
@@ -135,65 +157,89 @@ async function showConfig () {
|
|
|
135
157
|
console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
|
|
136
158
|
}
|
|
137
159
|
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
160
|
+
function getNonDefaultServerArgs (parser, args) {
|
|
161
|
+
// hopefully these function names are descriptive enough
|
|
162
|
+
|
|
163
|
+
function typesDiffer (dest) {
|
|
164
|
+
return typeof args[dest] !== typeof defaultsFromSchema[dest];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function defaultValueIsArray (dest) {
|
|
168
|
+
return _.isArray(defaultsFromSchema[dest]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function argsValueIsArray (dest) {
|
|
172
|
+
return _.isArray(args[dest]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function arraysDiffer (dest) {
|
|
176
|
+
return _.difference(args[dest], defaultsFromSchema[dest]).length > 0;
|
|
145
177
|
}
|
|
146
|
-
return nonDefaults;
|
|
147
|
-
}
|
|
148
178
|
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
179
|
+
function valuesUnequal (dest) {
|
|
180
|
+
return args[dest] !== defaultsFromSchema[dest];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function defaultIsDefined (dest) {
|
|
184
|
+
return !_.isUndefined(defaultsFromSchema[dest]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
|
|
188
|
+
|
|
189
|
+
const argValueNotArrayOrArraysDiffer = _.overSome([
|
|
190
|
+
_.negate(argsValueIsArray),
|
|
191
|
+
arraysDiffer
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
const defaultValueNotArrayAndValuesUnequal = _.overEvery([
|
|
195
|
+
_.negate(defaultValueIsArray), valuesUnequal
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* This used to be a hideous conditional, but it's broken up into a hideous function instead.
|
|
200
|
+
* hopefully this makes things a little more understandable.
|
|
201
|
+
* - checks if the default value is defined
|
|
202
|
+
* - if so, and the default is not an array:
|
|
203
|
+
* - ensures the types are the same
|
|
204
|
+
* - ensures the values are equal
|
|
205
|
+
* - if so, and the default is an array:
|
|
206
|
+
* - ensures the args value is an array
|
|
207
|
+
* - ensures the args values do not differ from the default values
|
|
208
|
+
* @param {string} dest - argument name (`dest` value)
|
|
209
|
+
* @returns {boolean}
|
|
210
|
+
*/
|
|
211
|
+
const isNotDefault = _.overEvery([
|
|
212
|
+
defaultIsDefined,
|
|
213
|
+
_.overSome([
|
|
214
|
+
typesDiffer,
|
|
215
|
+
_.overEvery([
|
|
216
|
+
defaultValueIsArray,
|
|
217
|
+
argValueNotArrayOrArraysDiffer
|
|
218
|
+
]),
|
|
219
|
+
defaultValueNotArrayAndValuesUnequal
|
|
220
|
+
])
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
const defaultsFromSchema = getDefaultsFromSchema();
|
|
224
|
+
|
|
225
|
+
return _.pickBy(args, (__, key) => isNotDefault(key));
|
|
153
226
|
}
|
|
154
227
|
|
|
155
|
-
function
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
[
|
|
160
|
-
|
|
161
|
-
['forceIphone', 'forceIpad'],
|
|
162
|
-
['deviceName', 'defaultDevice']
|
|
163
|
-
];
|
|
164
|
-
|
|
165
|
-
for (let exSet of exclusives) {
|
|
166
|
-
let numFoundInArgs = 0;
|
|
167
|
-
for (let opt of exSet) {
|
|
168
|
-
if (_.has(args, opt) && args[opt]) {
|
|
169
|
-
numFoundInArgs++;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
if (numFoundInArgs > 1) {
|
|
173
|
-
throw new Error(`You can't pass in more than one argument from the ` +
|
|
174
|
-
`set ${JSON.stringify(exSet)}, since they are ` +
|
|
175
|
-
`mutually exclusive`);
|
|
228
|
+
function getDeprecatedArgs (parser, args) {
|
|
229
|
+
// go through the server command line arguments and figure
|
|
230
|
+
// out which of the ones used are deprecated
|
|
231
|
+
return parser.rawArgs.reduce((acc, [[name], {dest, default: defaultValue, action}]) => {
|
|
232
|
+
if (!args[dest] || args[dest] === defaultValue) {
|
|
233
|
+
return acc;
|
|
176
234
|
}
|
|
177
|
-
}
|
|
178
235
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
chromedriverPort: checkValidPort,
|
|
184
|
-
robotPort: checkValidPort,
|
|
185
|
-
backendRetries: (r) => r >= 0
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const nonDefaultArgs = getNonDefaultArgs(parser, args);
|
|
189
|
-
|
|
190
|
-
for (let [arg, validator] of _.toPairs(validations)) {
|
|
191
|
-
if (_.has(nonDefaultArgs, arg)) {
|
|
192
|
-
if (!validator(args[arg], arg)) {
|
|
193
|
-
throw new Error(`Invalid argument for param ${arg}: ${args[arg]}`);
|
|
194
|
-
}
|
|
236
|
+
if (action?.deprecated_for) {
|
|
237
|
+
acc[name] = action.deprecated_for;
|
|
238
|
+
} else if (isSubClass(action, StoreDeprecatedDefaultCapabilityAction)) {
|
|
239
|
+
acc[name] = DEFAULT_CAPS_ARG;
|
|
195
240
|
}
|
|
196
|
-
|
|
241
|
+
return acc;
|
|
242
|
+
}, {});
|
|
197
243
|
}
|
|
198
244
|
|
|
199
245
|
async function validateTmpDir (tmpDir) {
|
|
@@ -206,7 +252,8 @@ async function validateTmpDir (tmpDir) {
|
|
|
206
252
|
}
|
|
207
253
|
|
|
208
254
|
export {
|
|
209
|
-
getBuildInfo,
|
|
210
|
-
warnNodeDeprecations, validateTmpDir,
|
|
211
|
-
getGitRev,
|
|
255
|
+
getBuildInfo, checkNodeOk, showConfig,
|
|
256
|
+
warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
|
|
257
|
+
getGitRev, APPIUM_VER, updateBuildInfo,
|
|
258
|
+
getDeprecatedArgs
|
|
212
259
|
};
|
package/lib/driver-config.js
CHANGED
|
@@ -1,25 +1,74 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import _ from 'lodash';
|
|
2
|
-
import ExtensionConfig
|
|
4
|
+
import ExtensionConfig from './extension-config';
|
|
5
|
+
import { DRIVER_TYPE } from './ext-config-io';
|
|
3
6
|
|
|
4
7
|
export default class DriverConfig extends ExtensionConfig {
|
|
5
|
-
|
|
8
|
+
/**
|
|
9
|
+
* A mapping of `APPIUM_HOME` values to {@link DriverConfig} instances.
|
|
10
|
+
* Each `APPIUM_HOME` should only have one associated `DriverConfig` instance.
|
|
11
|
+
* @type {Record<string,DriverConfig>}
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
static _instances = {};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Call {@link DriverConfig.getInstance} instead.
|
|
18
|
+
* @private
|
|
19
|
+
* @param {string} appiumHome
|
|
20
|
+
* @param {(...args: any[]) => void} [logFn]
|
|
21
|
+
*/
|
|
22
|
+
constructor (appiumHome, logFn) {
|
|
6
23
|
super(appiumHome, DRIVER_TYPE, logFn);
|
|
24
|
+
/** @type {Set<string>} */
|
|
25
|
+
this.knownAutomationNames = new Set();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async read () {
|
|
29
|
+
this.knownAutomationNames.clear();
|
|
30
|
+
return await super.read();
|
|
7
31
|
}
|
|
8
32
|
|
|
9
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Creates or gets an instance of {@link DriverConfig} based value of `appiumHome`
|
|
35
|
+
* @param {string} appiumHome - `APPIUM_HOME` path
|
|
36
|
+
* @param {(...args: any[]) => void} [logFn] - Optional logging function
|
|
37
|
+
* @returns {DriverConfig}
|
|
38
|
+
*/
|
|
39
|
+
static getInstance (appiumHome, logFn) {
|
|
40
|
+
const instance = DriverConfig._instances[appiumHome] ?? new DriverConfig(appiumHome, logFn);
|
|
41
|
+
DriverConfig._instances[appiumHome] = instance;
|
|
42
|
+
return instance;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param {object} extData
|
|
48
|
+
* @param {string} extName
|
|
49
|
+
* @returns {import('./extension-config').Problem[]}
|
|
50
|
+
*/
|
|
51
|
+
// eslint-disable-next-line no-unused-vars
|
|
52
|
+
getConfigProblems (extData, extName) {
|
|
10
53
|
const problems = [];
|
|
11
|
-
const
|
|
12
|
-
const {platformNames, automationName} = driver;
|
|
54
|
+
const {platformNames, automationName} = extData;
|
|
13
55
|
|
|
14
56
|
if (!_.isArray(platformNames)) {
|
|
15
57
|
problems.push({
|
|
16
|
-
err: 'Missing or incorrect supported
|
|
58
|
+
err: 'Missing or incorrect supported platformNames list.',
|
|
17
59
|
val: platformNames
|
|
18
60
|
});
|
|
19
61
|
} else {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
62
|
+
if (_.isEmpty(platformNames)) {
|
|
63
|
+
problems.push({
|
|
64
|
+
err: 'Empty platformNames list.',
|
|
65
|
+
val: platformNames
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
for (const pName of platformNames) {
|
|
69
|
+
if (!_.isString(pName)) {
|
|
70
|
+
problems.push({err: 'Incorrectly formatted platformName.', val: pName});
|
|
71
|
+
}
|
|
23
72
|
}
|
|
24
73
|
}
|
|
25
74
|
}
|
|
@@ -28,17 +77,23 @@ export default class DriverConfig extends ExtensionConfig {
|
|
|
28
77
|
problems.push({err: 'Missing or incorrect automationName', val: automationName});
|
|
29
78
|
}
|
|
30
79
|
|
|
31
|
-
if (
|
|
80
|
+
if (this.knownAutomationNames.has(automationName)) {
|
|
32
81
|
problems.push({
|
|
33
82
|
err: 'Multiple drivers claim support for the same automationName',
|
|
34
83
|
val: automationName
|
|
35
84
|
});
|
|
36
85
|
}
|
|
37
|
-
|
|
86
|
+
|
|
87
|
+
// should we retain the name at the end of this function, once we've checked there are no problems?
|
|
88
|
+
this.knownAutomationNames.add(automationName);
|
|
38
89
|
|
|
39
90
|
return problems;
|
|
40
91
|
}
|
|
41
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} driverName
|
|
95
|
+
* @param {object} extData
|
|
96
|
+
*/
|
|
42
97
|
extensionDesc (driverName, {version, automationName}) {
|
|
43
98
|
return `${driverName}@${version} (automationName '${automationName}')`;
|
|
44
99
|
}
|
package/lib/drivers.js
CHANGED
|
@@ -12,9 +12,12 @@ const KNOWN_DRIVERS = {
|
|
|
12
12
|
youiengine: 'appium-youiengine-driver',
|
|
13
13
|
windows: 'appium-windows-driver',
|
|
14
14
|
mac: 'appium-mac-driver',
|
|
15
|
+
mac2: 'appium-mac2-driver',
|
|
15
16
|
espresso: 'appium-espresso-driver',
|
|
16
17
|
tizen: 'appium-tizen-driver',
|
|
17
18
|
flutter: 'appium-flutter-driver',
|
|
19
|
+
safari: 'appium-safari-driver',
|
|
20
|
+
gecko: 'appium-geckodriver',
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
function getDriverBySupport (drivers, matchAutomationName, matchPlatformName) {
|
|
@@ -64,7 +67,7 @@ function findMatchingDriver (config, {automationName, platformName}) {
|
|
|
64
67
|
if (!driver) {
|
|
65
68
|
throw new Error(`MainClass ${mainClass} did not result in a driver object`);
|
|
66
69
|
}
|
|
67
|
-
return {driver, version};
|
|
70
|
+
return {driver, version, driverName};
|
|
68
71
|
} catch (err) {
|
|
69
72
|
const msg = `Could not find a driver for automationName ` +
|
|
70
73
|
`'${automationName}' and platformName ${platformName}'. ` +
|