appium 2.0.0-beta.23 → 2.0.0-beta.26
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 +1 -2
- package/build/lib/appium-config.schema.json +278 -0
- package/build/lib/appium.js +62 -71
- package/build/lib/cli/args.js +31 -53
- 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 +10 -20
- 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 +67 -30
- 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 +64 -92
- package/build/lib/schema/appium-config-schema.js +2 -4
- package/build/lib/schema/arg-spec.js +14 -15
- package/build/lib/schema/cli-args.js +8 -16
- 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 +136 -41
- package/build/lib/utils.js +13 -64
- package/lib/appium.js +74 -55
- package/lib/cli/args.js +36 -37
- 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 +7 -15
- package/lib/cli/plugin-command.js +1 -2
- package/lib/config-file.js +12 -15
- package/lib/config.js +111 -36
- 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 +110 -96
- package/lib/schema/arg-spec.js +11 -5
- package/lib/schema/cli-args.js +7 -30
- package/lib/schema/keywords.js +1 -1
- package/lib/schema/schema.js +206 -48
- package/lib/utils.js +27 -58
- package/package.json +29 -21
- package/{postinstall.js → scripts/postinstall.js} +1 -1
- package/types/types.d.ts +72 -28
- 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 -16
- 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 -159
- package/build/test/driver-e2e-specs.js +0 -435
- package/build/test/driver-specs.js +0 -321
- 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 -504
- 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 -314
- 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 -431
- package/build/test/schema/schema-specs.js +0 -389
- package/build/test/utils-specs.js +0 -266
- 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 -11
package/lib/cli/parser.js
CHANGED
|
@@ -2,21 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { fs } from '@appium/support';
|
|
4
4
|
import { ArgumentParser } from 'argparse';
|
|
5
|
-
import B from 'bluebird';
|
|
6
5
|
import _ from 'lodash';
|
|
7
6
|
import path from 'path';
|
|
8
|
-
import { DRIVER_TYPE, PLUGIN_TYPE } from '../
|
|
7
|
+
import { DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND } from '../constants';
|
|
9
8
|
import { finalizeSchema, getArgSpec, hasArgSpec } from '../schema';
|
|
10
|
-
import { rootDir } from '../
|
|
9
|
+
import { rootDir } from '../config';
|
|
11
10
|
import {
|
|
12
|
-
driverConfig,
|
|
13
11
|
getExtensionArgs,
|
|
14
|
-
getServerArgs
|
|
15
|
-
pluginConfig
|
|
12
|
+
getServerArgs
|
|
16
13
|
} from './args';
|
|
17
14
|
|
|
18
|
-
export const SERVER_SUBCOMMAND = 'server';
|
|
19
|
-
|
|
20
15
|
/**
|
|
21
16
|
* If the parsed args do not contain any of these values, then we
|
|
22
17
|
* will automatially inject the `server` subcommand.
|
|
@@ -146,7 +141,7 @@ class ArgParser {
|
|
|
146
141
|
return _.reduce(
|
|
147
142
|
args,
|
|
148
143
|
(unpacked, value, key) => {
|
|
149
|
-
if (hasArgSpec(key)) {
|
|
144
|
+
if (!_.isUndefined(value) && hasArgSpec(key)) {
|
|
150
145
|
const {dest} = /** @type {import('../schema/arg-spec').ArgSpec} */(getArgSpec(key));
|
|
151
146
|
_.set(unpacked, dest, value);
|
|
152
147
|
} else {
|
|
@@ -258,19 +253,16 @@ class ArgParser {
|
|
|
258
253
|
}
|
|
259
254
|
|
|
260
255
|
/**
|
|
261
|
-
* Creates a {@link ArgParser} instance
|
|
262
|
-
* beforehand, and finalizes the config schema.
|
|
256
|
+
* Creates a {@link ArgParser} instance; finalizes the config schema.
|
|
263
257
|
*
|
|
264
258
|
* @constructs ArgParser
|
|
265
259
|
* @param {boolean} [debug] - If `true`, throw instead of exit upon parsing error
|
|
266
|
-
* @returns {
|
|
260
|
+
* @returns {ArgParser}
|
|
267
261
|
*/
|
|
268
|
-
|
|
269
|
-
await B.all([driverConfig.read(), pluginConfig.read()]);
|
|
262
|
+
function getParser (debug) {
|
|
270
263
|
finalizeSchema();
|
|
271
264
|
|
|
272
265
|
return new ArgParser(debug);
|
|
273
266
|
}
|
|
274
267
|
|
|
275
|
-
export default getParser;
|
|
276
268
|
export { getParser, ArgParser };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import ExtensionCommand from './extension-command';
|
|
3
|
-
import { PLUGIN_TYPE } from '../
|
|
4
|
-
import { KNOWN_PLUGINS } from '../plugins';
|
|
3
|
+
import { PLUGIN_TYPE, KNOWN_PLUGINS } from '../constants';
|
|
5
4
|
|
|
6
5
|
const REQ_PLUGIN_FIELDS = ['pluginName', 'mainClass'];
|
|
7
6
|
|
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
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-console */
|
|
1
4
|
import _ from 'lodash';
|
|
2
|
-
import {
|
|
5
|
+
import { system, fs } from '@appium/support';
|
|
3
6
|
import axios from 'axios';
|
|
4
7
|
import { exec } from 'teen_process';
|
|
5
|
-
import { rootDir } from './utils';
|
|
6
8
|
import logger from './logger';
|
|
7
9
|
import semver from 'semver';
|
|
8
10
|
import findUp from 'find-up';
|
|
9
|
-
import {
|
|
11
|
+
import { getDefaultsForSchema, getAllArgSpecs } from './schema/schema';
|
|
10
12
|
|
|
11
13
|
const npmPackage = fs.readPackageJsonFrom(__dirname);
|
|
12
14
|
|
|
@@ -22,7 +24,7 @@ const BUILD_INFO = {
|
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
function getNodeVersion () {
|
|
25
|
-
return semver.coerce(process.version);
|
|
27
|
+
return /** @type {import('semver').SemVer} */(semver.coerce(process.version));
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
async function updateBuildInfo (useGithubApiFallback = false) {
|
|
@@ -42,7 +44,7 @@ async function updateBuildInfo (useGithubApiFallback = false) {
|
|
|
42
44
|
*
|
|
43
45
|
* This is needed because Appium cannot assume `package.json` and `.git` are in the same
|
|
44
46
|
* directory. Monorepos, see?
|
|
45
|
-
* @returns {string|
|
|
47
|
+
* @returns {Promise<string|undefined>} Path to dir or `undefined` if not found
|
|
46
48
|
*/
|
|
47
49
|
async function findGitRoot () {
|
|
48
50
|
return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
|
|
@@ -80,6 +82,11 @@ async function getGitRev (useGithubApiFallback = false) {
|
|
|
80
82
|
return null;
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} commitSha
|
|
87
|
+
* @param {boolean} [useGithubApiFallback]
|
|
88
|
+
* @returns {Promise<number?>}
|
|
89
|
+
*/
|
|
83
90
|
async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
|
|
84
91
|
const gitRoot = await findGitRoot();
|
|
85
92
|
if (gitRoot) {
|
|
@@ -144,37 +151,49 @@ function warnNodeDeprecations () {
|
|
|
144
151
|
// }
|
|
145
152
|
}
|
|
146
153
|
|
|
147
|
-
async function
|
|
154
|
+
async function showBuildInfo () {
|
|
148
155
|
await updateBuildInfo(true);
|
|
149
156
|
console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
|
|
150
157
|
}
|
|
151
158
|
|
|
152
|
-
|
|
153
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Returns k/v pairs of server arguments which are _not_ the defaults.
|
|
161
|
+
* @param {ParsedArgs} parsedArgs
|
|
162
|
+
* @returns {Partial<ParsedArgs>}
|
|
163
|
+
*/
|
|
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 }>} */({}));
|
|
154
179
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
180
|
+
return flattened;
|
|
181
|
+
};
|
|
158
182
|
|
|
159
|
-
|
|
160
|
-
return _.isArray(defaultsFromSchema[dest]);
|
|
161
|
-
}
|
|
183
|
+
const args = flatten(parsedArgs);
|
|
162
184
|
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
}
|
|
185
|
+
// hopefully these function names are descriptive enough
|
|
186
|
+
const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest].value !== typeof defaultsFromSchema[dest];
|
|
166
187
|
|
|
167
|
-
|
|
168
|
-
return _.difference(args[dest], defaultsFromSchema[dest]).length > 0;
|
|
169
|
-
}
|
|
188
|
+
const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
|
|
170
189
|
|
|
171
|
-
|
|
172
|
-
return args[dest] !== defaultsFromSchema[dest];
|
|
173
|
-
}
|
|
190
|
+
const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest].value);
|
|
174
191
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
192
|
+
const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest].value, defaultsFromSchema[dest])), 0);
|
|
193
|
+
|
|
194
|
+
const valuesDiffer = /** @param {string} dest */(dest) => args[dest].value !== defaultsFromSchema[dest];
|
|
195
|
+
|
|
196
|
+
const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
|
|
178
197
|
|
|
179
198
|
// note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
|
|
180
199
|
|
|
@@ -183,8 +202,8 @@ function getNonDefaultServerArgs (parser, args) {
|
|
|
183
202
|
arraysDiffer
|
|
184
203
|
]);
|
|
185
204
|
|
|
186
|
-
const
|
|
187
|
-
_.negate(defaultValueIsArray),
|
|
205
|
+
const defaultValueNotArrayAndValuesDiffer = _.overEvery([
|
|
206
|
+
_.negate(defaultValueIsArray), valuesDiffer
|
|
188
207
|
]);
|
|
189
208
|
|
|
190
209
|
/**
|
|
@@ -197,8 +216,7 @@ function getNonDefaultServerArgs (parser, args) {
|
|
|
197
216
|
* - if so, and the default is an array:
|
|
198
217
|
* - ensures the args value is an array
|
|
199
218
|
* - ensures the args values do not differ from the default values
|
|
200
|
-
* @
|
|
201
|
-
* @returns {boolean}
|
|
219
|
+
* @type {(dest: string) => boolean}
|
|
202
220
|
*/
|
|
203
221
|
const isNotDefault = _.overEvery([
|
|
204
222
|
defaultIsDefined,
|
|
@@ -208,26 +226,83 @@ function getNonDefaultServerArgs (parser, args) {
|
|
|
208
226
|
defaultValueIsArray,
|
|
209
227
|
argValueNotArrayOrArraysDiffer
|
|
210
228
|
]),
|
|
211
|
-
|
|
229
|
+
defaultValueNotArrayAndValuesDiffer
|
|
212
230
|
])
|
|
213
231
|
]);
|
|
214
232
|
|
|
215
|
-
const defaultsFromSchema =
|
|
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
|
|
|
242
|
+
/**
|
|
243
|
+
* Compacts an object for {@link showConfig}:
|
|
244
|
+
* 1. Removes `subcommand` key/value
|
|
245
|
+
* 2. Removes `undefined` values
|
|
246
|
+
* 3. Removes empty objects (but not `false` values)
|
|
247
|
+
* Does not operate recursively.
|
|
248
|
+
*/
|
|
249
|
+
const compactConfig = _.partial(
|
|
250
|
+
_.omitBy,
|
|
251
|
+
_,
|
|
252
|
+
(value, key) => key === 'subcommand' || _.isUndefined(value) || (_.isObject(value) && _.isEmpty(value))
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Shows a breakdown of the current config after CLI params, config file loaded & defaults applied.
|
|
257
|
+
*
|
|
258
|
+
* The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
|
|
259
|
+
* but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
|
|
260
|
+
*
|
|
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
|
|
265
|
+
*/
|
|
266
|
+
function showConfig (nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
|
|
267
|
+
console.log('Appium Configuration\n');
|
|
268
|
+
console.log('from defaults:\n');
|
|
269
|
+
console.dir(compactConfig(defaults));
|
|
270
|
+
if (configResult.config) {
|
|
271
|
+
console.log(`\nfrom config file at ${configResult.filepath}:\n`);
|
|
272
|
+
console.dir(compactConfig(configResult.config));
|
|
273
|
+
} else {
|
|
274
|
+
console.log(`\n(no configuration file loaded)`);
|
|
275
|
+
}
|
|
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));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @param {string} tmpDir
|
|
288
|
+
*/
|
|
220
289
|
async function validateTmpDir (tmpDir) {
|
|
221
290
|
try {
|
|
222
|
-
await mkdirp(tmpDir);
|
|
291
|
+
await fs.mkdirp(tmpDir);
|
|
223
292
|
} catch (e) {
|
|
224
293
|
throw new Error(`We could not ensure that the temp dir you specified ` +
|
|
225
294
|
`(${tmpDir}) exists. Please make sure it's writeable.`);
|
|
226
295
|
}
|
|
227
296
|
}
|
|
228
297
|
|
|
298
|
+
const rootDir = fs.findRoot(__dirname);
|
|
299
|
+
|
|
229
300
|
export {
|
|
230
|
-
getBuildInfo, checkNodeOk,
|
|
301
|
+
getBuildInfo, checkNodeOk, showBuildInfo,
|
|
231
302
|
warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
|
|
232
|
-
getGitRev, APPIUM_VER, updateBuildInfo
|
|
303
|
+
getGitRev, APPIUM_VER, updateBuildInfo, showConfig, rootDir
|
|
233
304
|
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @typedef {import('../types/types').ParsedArgs} ParsedArgs
|
|
308
|
+
*/
|
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
|
+
|