appium 2.0.0-beta.24 → 2.0.0-beta.27
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 +278 -0
- package/build/lib/appium.js +45 -66
- package/build/lib/cli/args.js +19 -39
- package/build/lib/cli/driver-command.js +5 -9
- package/build/lib/cli/extension-command.js +73 -64
- package/build/lib/cli/extension.js +10 -23
- package/build/lib/cli/parser.js +9 -19
- package/build/lib/cli/plugin-command.js +5 -9
- package/build/lib/cli/utils.js +2 -4
- package/build/lib/config-file.js +5 -10
- package/build/lib/config.js +50 -20
- package/build/lib/constants.js +60 -0
- package/build/lib/extension/driver-config.js +190 -0
- package/build/lib/extension/extension-config.js +297 -0
- package/build/lib/extension/index.js +77 -0
- package/build/lib/extension/manifest.js +246 -0
- package/build/lib/extension/package-changed.js +68 -0
- package/build/lib/extension/plugin-config.js +87 -0
- package/build/lib/grid-register.js +2 -4
- package/build/lib/logger.js +2 -4
- package/build/lib/logsink.js +2 -4
- package/build/lib/main.js +42 -71
- package/build/lib/schema/appium-config-schema.js +2 -4
- package/build/lib/schema/arg-spec.js +11 -14
- package/build/lib/schema/cli-args.js +2 -4
- package/build/lib/schema/cli-transformers.js +2 -4
- package/build/lib/schema/index.js +2 -4
- package/build/lib/schema/keywords.js +2 -4
- package/build/lib/schema/schema.js +61 -37
- package/build/lib/utils.js +1 -32
- package/lib/appium.js +50 -68
- package/lib/cli/args.js +19 -23
- package/lib/cli/driver-command.js +10 -2
- package/lib/cli/extension-command.js +216 -135
- package/lib/cli/extension.js +7 -15
- package/lib/cli/parser.js +6 -14
- package/lib/cli/plugin-command.js +1 -2
- package/lib/config-file.js +12 -15
- package/lib/config.js +55 -24
- package/lib/constants.js +79 -0
- package/lib/extension/driver-config.js +230 -0
- package/lib/extension/extension-config.js +459 -0
- package/lib/extension/index.js +103 -0
- package/lib/extension/manifest.js +590 -0
- package/lib/extension/package-changed.js +64 -0
- package/lib/extension/plugin-config.js +111 -0
- package/lib/grid-register.js +4 -4
- package/lib/main.js +57 -93
- package/lib/schema/arg-spec.js +3 -3
- package/lib/schema/cli-args.js +1 -0
- package/lib/schema/keywords.js +1 -1
- package/lib/schema/schema.js +67 -29
- package/lib/utils.js +2 -32
- package/package.json +29 -21
- package/{postinstall.js → scripts/postinstall.js} +1 -1
- package/types/types.d.ts +70 -31
- package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
- package/build/check-npm-pack-files.js +0 -23
- package/build/commands-yml/parse.js +0 -319
- package/build/commands-yml/validator.js +0 -130
- package/build/index.js +0 -19
- package/build/lib/cli/npm.js +0 -220
- package/build/lib/driver-config.js +0 -100
- package/build/lib/drivers.js +0 -100
- package/build/lib/ext-config-io.js +0 -165
- package/build/lib/extension-config.js +0 -320
- package/build/lib/plugin-config.js +0 -69
- package/build/lib/plugins.js +0 -18
- package/build/postinstall.js +0 -90
- package/build/test/cli/cli-e2e-specs.js +0 -221
- package/build/test/cli/cli-helpers.js +0 -86
- package/build/test/cli/cli-specs.js +0 -71
- package/build/test/cli/fixtures/test-driver/package.json +0 -27
- package/build/test/cli/schema-args-specs.js +0 -48
- package/build/test/cli/schema-e2e-specs.js +0 -47
- package/build/test/config-e2e-specs.js +0 -112
- package/build/test/config-file-e2e-specs.js +0 -209
- package/build/test/config-file-specs.js +0 -281
- package/build/test/config-specs.js +0 -246
- package/build/test/driver-e2e-specs.js +0 -435
- package/build/test/driver-specs.js +0 -386
- package/build/test/ext-config-io-specs.js +0 -181
- package/build/test/extension-config-specs.js +0 -365
- package/build/test/fixtures/allow-feat.txt +0 -5
- package/build/test/fixtures/caps.json +0 -3
- package/build/test/fixtures/config/allow-insecure.txt +0 -3
- package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +0 -5
- package/build/test/fixtures/config/appium.config.bad.json +0 -32
- package/build/test/fixtures/config/appium.config.ext-good.json +0 -9
- package/build/test/fixtures/config/appium.config.ext-unknown-props.json +0 -10
- package/build/test/fixtures/config/appium.config.good.js +0 -40
- package/build/test/fixtures/config/appium.config.good.json +0 -33
- package/build/test/fixtures/config/appium.config.good.yaml +0 -30
- package/build/test/fixtures/config/appium.config.invalid.json +0 -31
- package/build/test/fixtures/config/appium.config.security-array.json +0 -5
- package/build/test/fixtures/config/appium.config.security-delimited.json +0 -5
- package/build/test/fixtures/config/appium.config.security-path.json +0 -5
- package/build/test/fixtures/config/driver-fake.config.json +0 -8
- package/build/test/fixtures/config/nodeconfig.json +0 -3
- package/build/test/fixtures/config/plugin-fake.config.json +0 -0
- package/build/test/fixtures/default-args.js +0 -35
- package/build/test/fixtures/deny-feat.txt +0 -5
- package/build/test/fixtures/driver.schema.js +0 -20
- package/build/test/fixtures/extensions.yaml +0 -27
- package/build/test/fixtures/flattened-schema.js +0 -532
- package/build/test/fixtures/plugin.schema.js +0 -20
- package/build/test/fixtures/schema-with-extensions.js +0 -28
- package/build/test/grid-register-specs.js +0 -74
- package/build/test/helpers.js +0 -75
- package/build/test/logger-specs.js +0 -76
- package/build/test/npm-specs.js +0 -20
- package/build/test/parser-specs.js +0 -319
- package/build/test/plugin-e2e-specs.js +0 -316
- package/build/test/schema/arg-spec-specs.js +0 -70
- package/build/test/schema/cli-args-specs.js +0 -408
- package/build/test/schema/schema-specs.js +0 -407
- package/build/test/utils-specs.js +0 -288
- package/lib/cli/npm.js +0 -251
- package/lib/driver-config.js +0 -101
- package/lib/drivers.js +0 -84
- package/lib/ext-config-io.js +0 -287
- package/lib/extension-config.js +0 -366
- package/lib/plugin-config.js +0 -63
- package/lib/plugins.js +0 -13
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) => {
|
|
@@ -183,19 +181,18 @@ function normalizeConfig (config) {
|
|
|
183
181
|
|
|
184
182
|
/**
|
|
185
183
|
* Result of calling {@link readConfigFile}.
|
|
186
|
-
* @typedef
|
|
184
|
+
* @typedef ReadConfigFileResult
|
|
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
|
|
|
194
192
|
/**
|
|
195
193
|
* Options for {@link readConfigFile}.
|
|
196
|
-
* @typedef
|
|
194
|
+
* @typedef 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
|
/**
|
|
@@ -220,7 +217,7 @@ function normalizeConfig (config) {
|
|
|
220
217
|
|
|
221
218
|
/**
|
|
222
219
|
* Options for {@link formatErrors}.
|
|
223
|
-
* @typedef
|
|
220
|
+
* @typedef FormatConfigErrorsOptions
|
|
224
221
|
* @property {import('./config-file').RawJson} [json] - Raw JSON config (as string)
|
|
225
222
|
* @property {boolean} [pretty=true] - Whether to format errors as a CLI-friendly string
|
|
226
223
|
* @property {string} [schemaId] - Specific ID of a prop; otherwise entire schema
|
package/lib/config.js
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint-disable no-console */
|
|
4
4
|
import _ from 'lodash';
|
|
5
|
-
import {
|
|
5
|
+
import { system, fs } from '@appium/support';
|
|
6
6
|
import axios from 'axios';
|
|
7
7
|
import { exec } from 'teen_process';
|
|
8
|
-
import { rootDir } from './utils';
|
|
9
8
|
import logger from './logger';
|
|
10
9
|
import semver from 'semver';
|
|
11
10
|
import findUp from 'find-up';
|
|
12
|
-
import { getDefaultsForSchema } from './schema/schema';
|
|
11
|
+
import { getDefaultsForSchema, getAllArgSpecs } from './schema/schema';
|
|
13
12
|
|
|
14
13
|
const npmPackage = fs.readPackageJsonFrom(__dirname);
|
|
15
14
|
|
|
@@ -159,21 +158,40 @@ async function showBuildInfo () {
|
|
|
159
158
|
|
|
160
159
|
/**
|
|
161
160
|
* Returns k/v pairs of server arguments which are _not_ the defaults.
|
|
162
|
-
* @param {ParsedArgs}
|
|
161
|
+
* @param {ParsedArgs} parsedArgs
|
|
163
162
|
* @returns {Partial<ParsedArgs>}
|
|
164
163
|
*/
|
|
165
|
-
function getNonDefaultServerArgs (
|
|
166
|
-
|
|
164
|
+
function getNonDefaultServerArgs (parsedArgs) {
|
|
165
|
+
/**
|
|
166
|
+
* Flattens parsed args into a single level object for comparison with
|
|
167
|
+
* flattened defaults across server args and extension args.
|
|
168
|
+
* @param {ParsedArgs} args
|
|
169
|
+
* @returns {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>}
|
|
170
|
+
*/
|
|
171
|
+
const flatten = (args) => {
|
|
172
|
+
const argSpecs = getAllArgSpecs();
|
|
173
|
+
const flattened = _.reduce([...argSpecs.values()], (acc, argSpec) => {
|
|
174
|
+
if (_.has(args, argSpec.dest)) {
|
|
175
|
+
acc[argSpec.dest] = {value: _.get(args, argSpec.dest), argSpec};
|
|
176
|
+
}
|
|
177
|
+
return acc;
|
|
178
|
+
}, /** @type {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>} */({}));
|
|
179
|
+
|
|
180
|
+
return flattened;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const args = flatten(parsedArgs);
|
|
167
184
|
|
|
168
|
-
|
|
185
|
+
// hopefully these function names are descriptive enough
|
|
186
|
+
const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest].value !== typeof defaultsFromSchema[dest];
|
|
169
187
|
|
|
170
188
|
const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
|
|
171
189
|
|
|
172
|
-
const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest]);
|
|
190
|
+
const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest].value);
|
|
173
191
|
|
|
174
|
-
const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest], defaultsFromSchema[dest])), 0);
|
|
192
|
+
const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest].value, defaultsFromSchema[dest])), 0);
|
|
175
193
|
|
|
176
|
-
const valuesDiffer = /** @param {string} dest */(dest) => args[dest] !== defaultsFromSchema[dest];
|
|
194
|
+
const valuesDiffer = /** @param {string} dest */(dest) => args[dest].value !== defaultsFromSchema[dest];
|
|
177
195
|
|
|
178
196
|
const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
|
|
179
197
|
|
|
@@ -212,9 +230,13 @@ function getNonDefaultServerArgs (args) {
|
|
|
212
230
|
])
|
|
213
231
|
]);
|
|
214
232
|
|
|
215
|
-
const defaultsFromSchema = getDefaultsForSchema();
|
|
233
|
+
const defaultsFromSchema = getDefaultsForSchema(true);
|
|
216
234
|
|
|
217
|
-
return _.
|
|
235
|
+
return _.reduce(
|
|
236
|
+
_.pickBy(args, (__, key) => isNotDefault(key)),
|
|
237
|
+
// explodes the flattened object back into nested one
|
|
238
|
+
(acc, {value, argSpec}) => _.set(acc, argSpec.dest, value), /** @type {Partial<ParsedArgs>} */({})
|
|
239
|
+
);
|
|
218
240
|
}
|
|
219
241
|
|
|
220
242
|
/**
|
|
@@ -236,22 +258,29 @@ const compactConfig = _.partial(
|
|
|
236
258
|
* The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
|
|
237
259
|
* but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
|
|
238
260
|
*
|
|
239
|
-
* @param {
|
|
240
|
-
* @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file
|
|
241
|
-
* @param {
|
|
261
|
+
* @param {Partial<ParsedArgs>} nonDefaultPreConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
|
|
262
|
+
* @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file. _Must_ be normalized
|
|
263
|
+
* @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
|
|
264
|
+
* @param {ParsedArgs} parsedArgs - Entire parsed args object
|
|
242
265
|
*/
|
|
243
|
-
function showConfig (
|
|
266
|
+
function showConfig (nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
|
|
244
267
|
console.log('Appium Configuration\n');
|
|
268
|
+
console.log('from defaults:\n');
|
|
269
|
+
console.dir(compactConfig(defaults));
|
|
245
270
|
if (configResult.config) {
|
|
246
|
-
console.log(
|
|
271
|
+
console.log(`\nfrom config file at ${configResult.filepath}:\n`);
|
|
247
272
|
console.dir(compactConfig(configResult.config));
|
|
248
273
|
} else {
|
|
249
|
-
console.log(
|
|
274
|
+
console.log(`\n(no configuration file loaded)`);
|
|
250
275
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
276
|
+
if (_.isEmpty(nonDefaultPreConfigParsedArgs)) {
|
|
277
|
+
console.log(`\n(no CLI parameters provided)`);
|
|
278
|
+
} else {
|
|
279
|
+
console.log('\nvia CLI or function call:\n');
|
|
280
|
+
console.dir(compactConfig(nonDefaultPreConfigParsedArgs));
|
|
281
|
+
}
|
|
282
|
+
console.log('\nfinal configuration:\n');
|
|
283
|
+
console.dir(compactConfig(parsedArgs));
|
|
255
284
|
}
|
|
256
285
|
|
|
257
286
|
/**
|
|
@@ -259,17 +288,19 @@ function showConfig (preConfigParsedArgs, configResult, defaults) {
|
|
|
259
288
|
*/
|
|
260
289
|
async function validateTmpDir (tmpDir) {
|
|
261
290
|
try {
|
|
262
|
-
await mkdirp(tmpDir);
|
|
291
|
+
await fs.mkdirp(tmpDir);
|
|
263
292
|
} catch (e) {
|
|
264
293
|
throw new Error(`We could not ensure that the temp dir you specified ` +
|
|
265
294
|
`(${tmpDir}) exists. Please make sure it's writeable.`);
|
|
266
295
|
}
|
|
267
296
|
}
|
|
268
297
|
|
|
298
|
+
const rootDir = fs.findRoot(__dirname);
|
|
299
|
+
|
|
269
300
|
export {
|
|
270
301
|
getBuildInfo, checkNodeOk, showBuildInfo,
|
|
271
302
|
warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
|
|
272
|
-
getGitRev, APPIUM_VER, updateBuildInfo, showConfig
|
|
303
|
+
getGitRev, APPIUM_VER, updateBuildInfo, showConfig, rootDir
|
|
273
304
|
};
|
|
274
305
|
|
|
275
306
|
/**
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The name of the extension type for drivers
|
|
7
|
+
*/
|
|
8
|
+
export const DRIVER_TYPE = 'driver';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The name of the extension type for plugins
|
|
12
|
+
*/
|
|
13
|
+
export const PLUGIN_TYPE = 'plugin';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `server` command of the `appium` CLI
|
|
17
|
+
*/
|
|
18
|
+
export const SERVER_SUBCOMMAND = 'server';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The value of `--use-plugins` if _all_ plugins should be loaded
|
|
22
|
+
*/
|
|
23
|
+
export const USE_ALL_PLUGINS = 'all';
|
|
24
|
+
|
|
25
|
+
// This is a map of plugin names to npm packages representing those plugins.
|
|
26
|
+
// The plugins in this list will be available to the CLI so users can just
|
|
27
|
+
// type 'appium plugin install 'name'', rather than having to specify the full
|
|
28
|
+
// npm package. I.e., these are the officially recognized plugins.
|
|
29
|
+
export const KNOWN_PLUGINS = Object.freeze(
|
|
30
|
+
/** @type {const} */ ({
|
|
31
|
+
images: '@appium/images-plugin',
|
|
32
|
+
'execute-driver': '@appium/execute-driver-plugin',
|
|
33
|
+
'relaxed-caps': '@appium/relaxed-caps-plugin',
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// This is a map of driver names to npm packages representing those drivers.
|
|
38
|
+
// The drivers in this list will be available to the CLI so users can just
|
|
39
|
+
// type 'appium driver install 'name'', rather than having to specify the full
|
|
40
|
+
// npm package. I.e., these are the officially recognized drivers.
|
|
41
|
+
export const KNOWN_DRIVERS = Object.freeze(
|
|
42
|
+
/** @type {const} */ ({
|
|
43
|
+
uiautomator2: 'appium-uiautomator2-driver',
|
|
44
|
+
xcuitest: 'appium-xcuitest-driver',
|
|
45
|
+
youiengine: 'appium-youiengine-driver',
|
|
46
|
+
windows: 'appium-windows-driver',
|
|
47
|
+
mac: 'appium-mac-driver',
|
|
48
|
+
mac2: 'appium-mac2-driver',
|
|
49
|
+
espresso: 'appium-espresso-driver',
|
|
50
|
+
tizen: 'appium-tizen-driver',
|
|
51
|
+
flutter: 'appium-flutter-driver',
|
|
52
|
+
safari: 'appium-safari-driver',
|
|
53
|
+
gecko: 'appium-geckodriver',
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Relative path to directory containing any Appium internal files
|
|
59
|
+
*/
|
|
60
|
+
export const CACHE_DIR_RELATIVE_PATH = path.join(
|
|
61
|
+
'node_modules',
|
|
62
|
+
'.cache',
|
|
63
|
+
'appium',
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Relative path to hashfile (from `APPIUM_HOME`) of consuming project's `package.json` (if it exists)
|
|
68
|
+
*/
|
|
69
|
+
export const PKG_HASHFILE_RELATIVE_PATH = path.join(
|
|
70
|
+
CACHE_DIR_RELATIVE_PATH,
|
|
71
|
+
'package.hash',
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
export const EXT_SUBCOMMAND_LIST = 'list';
|
|
76
|
+
export const EXT_SUBCOMMAND_INSTALL = 'install';
|
|
77
|
+
export const EXT_SUBCOMMAND_UNINSTALL = 'uninstall';
|
|
78
|
+
export const EXT_SUBCOMMAND_UPDATE = 'update';
|
|
79
|
+
export const EXT_SUBCOMMAND_RUN = 'run';
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import { DRIVER_TYPE } from '../constants';
|
|
5
|
+
import log from '../logger';
|
|
6
|
+
import { ExtensionConfig } from './extension-config';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @extends {ExtensionConfig<DriverType>}
|
|
10
|
+
*/
|
|
11
|
+
export class DriverConfig extends ExtensionConfig {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A set of unique automation names used by drivers.
|
|
15
|
+
* @type {Set<string>}
|
|
16
|
+
*/
|
|
17
|
+
knownAutomationNames;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A mapping of {@link Manifest} instances to {@link DriverConfig} instances.
|
|
21
|
+
*
|
|
22
|
+
* `Manifest` and `ExtensionConfig` have a one-to-many relationship; each `Manifest` should be associated with a `DriverConfig` and a `PluginConfig`; no more, no less.
|
|
23
|
+
*
|
|
24
|
+
* This variable tracks the `Manifest`-to-`DriverConfig` portion.
|
|
25
|
+
*
|
|
26
|
+
* @type {WeakMap<Manifest,DriverConfig>}
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
static _instances = new WeakMap();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Call {@link DriverConfig.create} instead.
|
|
33
|
+
* @private
|
|
34
|
+
* @param {import('./manifest').Manifest} manifest - Manifest instance
|
|
35
|
+
* @param {DriverConfigOptions} [opts]
|
|
36
|
+
*/
|
|
37
|
+
constructor (manifest, {logFn, extData} = {}) {
|
|
38
|
+
super(DRIVER_TYPE, manifest, logFn);
|
|
39
|
+
|
|
40
|
+
this.knownAutomationNames = new Set();
|
|
41
|
+
|
|
42
|
+
if (extData) {
|
|
43
|
+
this.validate(extData);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new {@link DriverConfig} instance for a {@link Manifest} instance.
|
|
49
|
+
*
|
|
50
|
+
* @param {Manifest} manifest
|
|
51
|
+
* @param {DriverConfigOptions} [opts]
|
|
52
|
+
* @throws If `manifest` already associated with a `DriverConfig`
|
|
53
|
+
* @returns {DriverConfig}
|
|
54
|
+
*/
|
|
55
|
+
static create (manifest, {extData, logFn} = {}) {
|
|
56
|
+
const instance = new DriverConfig(manifest, {logFn, extData});
|
|
57
|
+
if (DriverConfig.getInstance(manifest)) {
|
|
58
|
+
throw new Error(`Manifest with APPIUM_HOME ${manifest.appiumHome} already has a DriverConfig; use DriverConfig.getInstance() to retrieve it.`);
|
|
59
|
+
}
|
|
60
|
+
DriverConfig._instances.set(manifest, instance);
|
|
61
|
+
return instance;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns a DriverConfig associated with a Manifest
|
|
66
|
+
* @param {Manifest} manifest
|
|
67
|
+
* @returns {DriverConfig|undefined}
|
|
68
|
+
*/
|
|
69
|
+
static getInstance (manifest) {
|
|
70
|
+
return DriverConfig._instances.get(manifest);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Checks extensions for problems
|
|
75
|
+
* @param {ExtRecord<DriverType>} exts
|
|
76
|
+
*/
|
|
77
|
+
validate (exts) {
|
|
78
|
+
this.knownAutomationNames.clear();
|
|
79
|
+
return super.validate(exts);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {ManifestDriverData} extData
|
|
84
|
+
* @returns {import('./extension-config').Problem[]}
|
|
85
|
+
*/
|
|
86
|
+
getConfigProblems (extData) {
|
|
87
|
+
const problems = [];
|
|
88
|
+
const {platformNames, automationName} = extData;
|
|
89
|
+
|
|
90
|
+
if (!_.isArray(platformNames)) {
|
|
91
|
+
problems.push({
|
|
92
|
+
err: 'Missing or incorrect supported platformNames list.',
|
|
93
|
+
val: platformNames
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
if (_.isEmpty(platformNames)) {
|
|
97
|
+
problems.push({
|
|
98
|
+
err: 'Empty platformNames list.',
|
|
99
|
+
val: platformNames
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
for (const pName of platformNames) {
|
|
103
|
+
if (!_.isString(pName)) {
|
|
104
|
+
problems.push({err: 'Incorrectly formatted platformName.', val: pName});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!_.isString(automationName)) {
|
|
111
|
+
problems.push({err: 'Missing or incorrect automationName', val: automationName});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (this.knownAutomationNames.has(automationName)) {
|
|
115
|
+
problems.push({
|
|
116
|
+
err: 'Multiple drivers claim support for the same automationName',
|
|
117
|
+
val: automationName
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// should we retain the name at the end of this function, once we've checked there are no problems?
|
|
122
|
+
this.knownAutomationNames.add(automationName);
|
|
123
|
+
|
|
124
|
+
return problems;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {ExtName<DriverType>} driverName
|
|
129
|
+
* @param {ManifestDriverData} extData
|
|
130
|
+
* @returns {string}
|
|
131
|
+
*/
|
|
132
|
+
extensionDesc (driverName, {version, automationName}) {
|
|
133
|
+
return `${driverName}@${version} (automationName '${automationName}')`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Given capabilities, find a matching driver within the config. Load its class and return it along with version and driver name.
|
|
138
|
+
* @param { { automationName: string, platformName: string } } caps
|
|
139
|
+
* @returns { { driver: import('./manifest').DriverClass, version: string, driverName: string } }
|
|
140
|
+
*/
|
|
141
|
+
findMatchingDriver ({automationName, platformName}) {
|
|
142
|
+
if (!_.isString(platformName)) {
|
|
143
|
+
throw new Error('You must include a platformName capability');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!_.isString(automationName)) {
|
|
147
|
+
throw new Error('You must include an automationName capability');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
log.info(`Attempting to find matching driver for automationName ` +
|
|
151
|
+
`'${automationName}' and platformName '${platformName}'`);
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const {
|
|
155
|
+
driverName,
|
|
156
|
+
mainClass,
|
|
157
|
+
version,
|
|
158
|
+
} = this._getDriverBySupport(automationName, platformName);
|
|
159
|
+
log.info(`The '${driverName}' driver was installed and matched caps.`);
|
|
160
|
+
log.info(`Will require it at ${this.getInstallPath(driverName)}`);
|
|
161
|
+
const driver = this.require(driverName);
|
|
162
|
+
if (!driver) {
|
|
163
|
+
throw new Error(`Driver '${driverName}' did not export a class with name '${mainClass}'. Contact the author of the driver!`);
|
|
164
|
+
}
|
|
165
|
+
return {driver, version, driverName};
|
|
166
|
+
} catch (err) {
|
|
167
|
+
const msg = `Could not find a driver for automationName ` +
|
|
168
|
+
`'${automationName}' and platformName ${platformName}'. ` +
|
|
169
|
+
`Have you installed a driver that supports those ` +
|
|
170
|
+
`capabilities? Run 'appium driver list --installed' to see. ` +
|
|
171
|
+
`(Lower-level error: ${err.message})`;
|
|
172
|
+
throw new Error(msg);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Given an automation name and platform name, find a suitable driver and return its extension data.
|
|
178
|
+
* @param {string} matchAutomationName
|
|
179
|
+
* @param {string} matchPlatformName
|
|
180
|
+
* @returns {ManifestDriverData & { driverName: string } }
|
|
181
|
+
*/
|
|
182
|
+
_getDriverBySupport (matchAutomationName, matchPlatformName) {
|
|
183
|
+
const drivers = this.installedExtensions;
|
|
184
|
+
for (const [driverName, driverData] of _.toPairs(drivers)) {
|
|
185
|
+
const {automationName, platformNames} = driverData;
|
|
186
|
+
const aNameMatches = automationName.toLowerCase() === matchAutomationName.toLowerCase();
|
|
187
|
+
const pNameMatches = _.includes(platformNames.map(_.toLower),
|
|
188
|
+
matchPlatformName.toLowerCase());
|
|
189
|
+
|
|
190
|
+
if (aNameMatches && pNameMatches) {
|
|
191
|
+
return {driverName, ...driverData};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (aNameMatches) {
|
|
195
|
+
throw new Error(`Driver '${driverName}' supports automationName ` +
|
|
196
|
+
`'${automationName}', but Appium could not find ` +
|
|
197
|
+
`support for platformName '${matchPlatformName}'. Supported ` +
|
|
198
|
+
`platformNames are: ` +
|
|
199
|
+
JSON.stringify(platformNames));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
throw new Error(`Could not find installed driver to support given caps`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @typedef DriverConfigOptions
|
|
209
|
+
* @property {import('./extension-config').ExtensionLogFn} [logFn] - Optional logging function
|
|
210
|
+
* @property {ManifestData['drivers']} [extData] - Extension data
|
|
211
|
+
*/
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @typedef {import('./manifest').ExternalData<DriverType>} ExternalDriverData
|
|
215
|
+
* @typedef {import('./manifest').ManifestDriverData} ManifestDriverData
|
|
216
|
+
* @typedef {import('./manifest').ManifestData} ManifestData
|
|
217
|
+
* @typedef {import('./manifest').DriverType} DriverType
|
|
218
|
+
* @typedef {import('./manifest').Manifest} Manifest
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @template T
|
|
223
|
+
* @typedef {import('./extension-config').ExtRecord<T>} ExtRecord
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @template T
|
|
228
|
+
* @typedef {import('./extension-config').ExtName<T>} ExtName
|
|
229
|
+
*/
|
|
230
|
+
|