appium 2.0.0-beta.17 → 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/build/lib/appium-config.schema.json +0 -0
- package/build/lib/appium.js +84 -69
- package/build/lib/cli/argparse-actions.js +1 -1
- package/build/lib/cli/args.js +87 -223
- package/build/lib/cli/extension-command.js +2 -2
- package/build/lib/cli/extension.js +14 -6
- package/build/lib/cli/parser.js +142 -106
- package/build/lib/cli/utils.js +1 -1
- package/build/lib/config-file.js +141 -0
- package/build/lib/config.js +42 -64
- package/build/lib/driver-config.js +41 -20
- package/build/lib/drivers.js +1 -1
- package/build/lib/ext-config-io.js +165 -0
- package/build/lib/extension-config.js +110 -60
- package/build/lib/grid-register.js +19 -21
- package/build/lib/logsink.js +1 -1
- package/build/lib/main.js +135 -72
- package/build/lib/plugin-config.js +17 -8
- package/build/lib/schema/appium-config-schema.js +252 -0
- package/build/lib/schema/arg-spec.js +120 -0
- package/build/lib/schema/cli-args.js +173 -0
- package/build/lib/schema/cli-transformers.js +76 -0
- package/build/lib/schema/index.js +36 -0
- package/build/lib/schema/keywords.js +62 -0
- package/build/lib/schema/schema.js +357 -0
- package/build/lib/utils.js +26 -35
- package/lib/appium-config.schema.json +277 -0
- package/lib/appium.js +99 -75
- package/lib/cli/args.js +138 -335
- package/lib/cli/extension-command.js +7 -6
- package/lib/cli/extension.js +12 -4
- package/lib/cli/parser.js +248 -96
- package/lib/config-file.js +227 -0
- package/lib/config.js +71 -61
- package/lib/driver-config.js +66 -11
- package/lib/ext-config-io.js +287 -0
- package/lib/extension-config.js +209 -66
- package/lib/grid-register.js +24 -21
- package/lib/main.js +139 -68
- package/lib/plugin-config.js +32 -2
- package/lib/schema/appium-config-schema.js +286 -0
- package/lib/schema/arg-spec.js +218 -0
- package/lib/schema/cli-args.js +273 -0
- package/lib/schema/cli-transformers.js +123 -0
- package/lib/schema/index.js +2 -0
- package/lib/schema/keywords.js +119 -0
- package/lib/schema/schema.js +577 -0
- package/lib/utils.js +29 -52
- package/package.json +16 -11
- package/types/appium-config.d.ts +197 -0
- package/types/types.d.ts +201 -0
- package/build/lib/cli/parser-helpers.js +0 -106
- package/lib/cli/parser-helpers.js +0 -106
package/lib/appium.js
CHANGED
|
@@ -2,10 +2,10 @@ import _ from 'lodash';
|
|
|
2
2
|
import log from './logger';
|
|
3
3
|
import { getBuildInfo, updateBuildInfo, APPIUM_VER } from './config';
|
|
4
4
|
import { findMatchingDriver } from './drivers';
|
|
5
|
-
import { BaseDriver, errors, isSessionCommand
|
|
6
|
-
|
|
5
|
+
import { BaseDriver, errors, isSessionCommand,
|
|
6
|
+
CREATE_SESSION_COMMAND } from '@appium/base-driver';
|
|
7
7
|
import AsyncLock from 'async-lock';
|
|
8
|
-
import {
|
|
8
|
+
import { parseCapsForInnerDriver, pullSettings } from './utils';
|
|
9
9
|
import { util } from '@appium/support';
|
|
10
10
|
|
|
11
11
|
const desiredCapabilityConstraints = {
|
|
@@ -39,7 +39,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
39
39
|
// the main Appium Driver has no new command timeout
|
|
40
40
|
this.newCommandTimeoutMs = 0;
|
|
41
41
|
|
|
42
|
-
this.args =
|
|
42
|
+
this.args = {...args};
|
|
43
43
|
|
|
44
44
|
// Access to sessions list must be guarded with a Semaphore, because
|
|
45
45
|
// it might be changed by other async calls at any time
|
|
@@ -59,6 +59,9 @@ class AppiumDriver extends BaseDriver {
|
|
|
59
59
|
updateBuildInfo();
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** @type {DriverConfig|undefined} */
|
|
63
|
+
driverConfig;
|
|
64
|
+
|
|
62
65
|
/**
|
|
63
66
|
* Cancel commands queueing for the umbrella Appium driver
|
|
64
67
|
*/
|
|
@@ -113,21 +116,16 @@ class AppiumDriver extends BaseDriver {
|
|
|
113
116
|
|
|
114
117
|
/**
|
|
115
118
|
* Validate and assign CLI args for a driver or plugin
|
|
116
|
-
*
|
|
119
|
+
*
|
|
120
|
+
* If the extension has provided a schema, validation has already happened.
|
|
121
|
+
* @param {import('./ext-config-io').ExtensionType} extType 'driver' or 'plugin'
|
|
117
122
|
* @param {string} extName the name of the extension
|
|
118
|
-
* @param {ObjectConstructor} extClass the class of the extension
|
|
119
123
|
* @param {Object} extInstance the driver or plugin instance
|
|
120
124
|
*/
|
|
121
|
-
assignCliArgsToExtension (extType, extName,
|
|
122
|
-
const cliArgs =
|
|
125
|
+
assignCliArgsToExtension (extType, extName, extInstance) {
|
|
126
|
+
const cliArgs = this.args[extType]?.[extName];
|
|
123
127
|
if (!_.isEmpty(cliArgs)) {
|
|
124
|
-
if (!_.has(extClass, 'argsConstraints')) {
|
|
125
|
-
throw new Error(`You sent in CLI args for the ${extName} ${extType}, but it ` +
|
|
126
|
-
`does not define any`);
|
|
127
|
-
}
|
|
128
|
-
validateExtensionArgs(cliArgs, extClass.argsConstraints);
|
|
129
128
|
extInstance.cliArgs = cliArgs;
|
|
130
|
-
log.debug(`Set CLI arguments on ${extName} ${extType} instance: ${JSON.stringify(cliArgs)}`);
|
|
131
129
|
}
|
|
132
130
|
}
|
|
133
131
|
|
|
@@ -213,7 +211,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
213
211
|
|
|
214
212
|
// Likewise, any driver-specific CLI args that were passed in should be assigned directly to
|
|
215
213
|
// the driver so that they cannot be mimicked by a malicious user sending in capabilities
|
|
216
|
-
this.assignCliArgsToExtension('driver', driverName,
|
|
214
|
+
this.assignCliArgsToExtension('driver', driverName, driverInstance);
|
|
217
215
|
|
|
218
216
|
|
|
219
217
|
// This assignment is required for correct web sockets functionality inside the driver
|
|
@@ -278,34 +276,31 @@ class AppiumDriver extends BaseDriver {
|
|
|
278
276
|
}
|
|
279
277
|
|
|
280
278
|
attachUnexpectedShutdownHandler (driver, innerSessionId) {
|
|
281
|
-
const
|
|
282
|
-
log.warn(`
|
|
279
|
+
const onShutdown = (cause = new Error('Unknown error')) => {
|
|
280
|
+
log.warn(`Ending session, cause was '${cause.message}'`);
|
|
281
|
+
|
|
282
|
+
if (this.sessionPlugins[innerSessionId]) {
|
|
283
|
+
for (const plugin of this.sessionPlugins[innerSessionId]) {
|
|
284
|
+
if (_.isFunction(plugin.onUnexpectedShutdown)) {
|
|
285
|
+
log.debug(`Plugin ${plugin.name} defines an unexpected shutdown handler; calling it now`);
|
|
286
|
+
try {
|
|
287
|
+
plugin.onUnexpectedShutdown(driver, cause);
|
|
288
|
+
} catch (e) {
|
|
289
|
+
log.warn(`Got an error when running plugin ${plugin.name} shutdown handler: ${e}`);
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
log.debug(`Plugin ${plugin.name} does not define an unexpected shutdown handler`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
283
297
|
log.info(`Removing session '${innerSessionId}' from our master session list`);
|
|
284
298
|
delete this.sessions[innerSessionId];
|
|
285
299
|
delete this.sessionPlugins[innerSessionId];
|
|
286
300
|
};
|
|
287
301
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// TODO: Remove this block after all the drivers use base driver above v 5.0.0
|
|
291
|
-
// Remove the session on unexpected shutdown, so that we are in a position
|
|
292
|
-
// to open another session later on.
|
|
293
|
-
driver.onUnexpectedShutdown
|
|
294
|
-
// eslint-disable-next-line promise/prefer-await-to-then
|
|
295
|
-
.then(() => {
|
|
296
|
-
// if we get here, we've had an unexpected shutdown, so error
|
|
297
|
-
throw new Error('Unexpected shutdown');
|
|
298
|
-
})
|
|
299
|
-
.catch((e) => {
|
|
300
|
-
// if we cancelled the unexpected shutdown promise, that means we
|
|
301
|
-
// no longer care about it, and can safely ignore it
|
|
302
|
-
if (!(e instanceof B.CancellationError)) {
|
|
303
|
-
removeSessionFromMasterList(e);
|
|
304
|
-
}
|
|
305
|
-
}); // this is a cancellable promise
|
|
306
|
-
} else if (_.isFunction(driver.onUnexpectedShutdown)) {
|
|
307
|
-
// since base driver v 5.0.0
|
|
308
|
-
driver.onUnexpectedShutdown(removeSessionFromMasterList);
|
|
302
|
+
if (_.isFunction(driver.onUnexpectedShutdown)) {
|
|
303
|
+
driver.onUnexpectedShutdown(onShutdown);
|
|
309
304
|
} else {
|
|
310
305
|
log.warn(`Failed to attach the unexpected shutdown listener. ` +
|
|
311
306
|
`Is 'onUnexpectedShutdown' method available for '${driver.constructor.name}'?`);
|
|
@@ -393,19 +388,17 @@ class AppiumDriver extends BaseDriver {
|
|
|
393
388
|
* @returns {Array} - array of plugin instances
|
|
394
389
|
*/
|
|
395
390
|
pluginsForSession (sessionId = null) {
|
|
396
|
-
let plugins = [];
|
|
397
391
|
if (sessionId) {
|
|
398
392
|
if (!this.sessionPlugins[sessionId]) {
|
|
399
393
|
this.sessionPlugins[sessionId] = this.createPluginInstances();
|
|
400
394
|
}
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
plugins = this.sessionlessPlugins;
|
|
395
|
+
return this.sessionPlugins[sessionId];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (_.isEmpty(this.sessionlessPlugins)) {
|
|
399
|
+
this.sessionlessPlugins = this.createPluginInstances();
|
|
407
400
|
}
|
|
408
|
-
return
|
|
401
|
+
return this.sessionlessPlugins;
|
|
409
402
|
}
|
|
410
403
|
|
|
411
404
|
/**
|
|
@@ -425,16 +418,11 @@ class AppiumDriver extends BaseDriver {
|
|
|
425
418
|
.filter((p) => _.isFunction(p[cmd]) || _.isFunction(p.handle));
|
|
426
419
|
}
|
|
427
420
|
|
|
428
|
-
createPluginInstances (
|
|
429
|
-
if (!sessionId) {
|
|
430
|
-
sessionId = 'sessionless';
|
|
431
|
-
}
|
|
432
|
-
sessionId = _.truncate(sessionId, {length: 11});
|
|
421
|
+
createPluginInstances () {
|
|
433
422
|
return this.pluginClasses.map((PluginClass) => {
|
|
434
|
-
const
|
|
435
|
-
const name = `${generalName} (${sessionId})`;
|
|
423
|
+
const name = PluginClass.pluginName;
|
|
436
424
|
const plugin = new PluginClass(name);
|
|
437
|
-
this.assignCliArgsToExtension('plugin',
|
|
425
|
+
this.assignCliArgsToExtension('plugin', name, plugin);
|
|
438
426
|
return plugin;
|
|
439
427
|
});
|
|
440
428
|
}
|
|
@@ -453,6 +441,15 @@ class AppiumDriver extends BaseDriver {
|
|
|
453
441
|
const isUmbrellaCmd = !isGetStatus && isAppiumDriverCommand(cmd);
|
|
454
442
|
const isSessionCmd = !isGetStatus && !isUmbrellaCmd;
|
|
455
443
|
|
|
444
|
+
// if a plugin override proxying for this command and that is why we are here instead of just
|
|
445
|
+
// letting the protocol proxy the command entirely, determine that, get the request object for
|
|
446
|
+
// use later on, then clean up the args
|
|
447
|
+
const reqForProxy = _.last(args)?.reqForProxy;
|
|
448
|
+
if (reqForProxy) {
|
|
449
|
+
args.pop();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
456
453
|
// first do some error checking. If we're requesting a session command execution, then make
|
|
457
454
|
// sure that session actually exists on the session driver, and set the session driver itself
|
|
458
455
|
let sessionId = null;
|
|
@@ -494,6 +491,18 @@ class AppiumDriver extends BaseDriver {
|
|
|
494
491
|
// if we make it here, we know that the default behavior is handled
|
|
495
492
|
cmdHandledBy.default = true;
|
|
496
493
|
|
|
494
|
+
if (reqForProxy) {
|
|
495
|
+
// we would have proxied this command had a plugin not handled it, so the default behavior
|
|
496
|
+
// is to do the proxy and retrieve the result internally so it can be passed to the plugin
|
|
497
|
+
// in case it calls 'await next()'. This requires that the driver have defined
|
|
498
|
+
// 'proxyCommand' and not just 'proxyReqRes'.
|
|
499
|
+
if (!dstSession.proxyCommand) {
|
|
500
|
+
throw new NoDriverProxyCommandError();
|
|
501
|
+
}
|
|
502
|
+
return await dstSession.proxyCommand(reqForProxy.originalUrl, reqForProxy.method,
|
|
503
|
+
reqForProxy.body);
|
|
504
|
+
}
|
|
505
|
+
|
|
497
506
|
if (isGetStatus) {
|
|
498
507
|
return await this.getStatus();
|
|
499
508
|
}
|
|
@@ -516,7 +525,18 @@ class AppiumDriver extends BaseDriver {
|
|
|
516
525
|
|
|
517
526
|
// if we had plugins, make sure to log out the helpful report about which plugins ended up
|
|
518
527
|
// handling the command and which didn't
|
|
519
|
-
|
|
528
|
+
this.logPluginHandlerReport(plugins, {cmd, cmdHandledBy});
|
|
529
|
+
|
|
530
|
+
// And finally, if the command was createSession, we want to migrate any plugins which were
|
|
531
|
+
// previously sessionless to use the new sessionId, so that plugins can share state between
|
|
532
|
+
// their createSession method and other instance methods
|
|
533
|
+
if (cmd === CREATE_SESSION_COMMAND && this.sessionlessPlugins.length && !res.error) {
|
|
534
|
+
const sessionId = _.first(res.value);
|
|
535
|
+
log.info(`Promoting ${this.sessionlessPlugins.length} sessionless plugins to be attached ` +
|
|
536
|
+
`to session ID ${sessionId}`);
|
|
537
|
+
this.sessionPlugins[sessionId] = this.sessionlessPlugins;
|
|
538
|
+
this.sessionlessPlugins = [];
|
|
539
|
+
}
|
|
520
540
|
|
|
521
541
|
return res;
|
|
522
542
|
}
|
|
@@ -546,7 +566,11 @@ class AppiumDriver extends BaseDriver {
|
|
|
546
566
|
return next;
|
|
547
567
|
}
|
|
548
568
|
|
|
549
|
-
logPluginHandlerReport ({cmd, cmdHandledBy}) {
|
|
569
|
+
logPluginHandlerReport (plugins, {cmd, cmdHandledBy}) {
|
|
570
|
+
if (!plugins.length) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
550
574
|
// at the end of the day, we have an object representing which plugins ended up getting
|
|
551
575
|
// their code run as part of handling this command. Because plugins can choose *not* to
|
|
552
576
|
// pass control to other plugins or to the default driver behavior, this is information
|
|
@@ -556,7 +580,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
556
580
|
const didHandle = Object.keys(cmdHandledBy).filter((k) => cmdHandledBy[k]);
|
|
557
581
|
const didntHandle = Object.keys(cmdHandledBy).filter((k) => !cmdHandledBy[k]);
|
|
558
582
|
if (didntHandle.length > 0) {
|
|
559
|
-
log.info(`Command '${cmd}' was not handled by the following
|
|
583
|
+
log.info(`Command '${cmd}' was *not* handled by the following behaviours or plugins, even ` +
|
|
560
584
|
`though they were registered to handle it: ${JSON.stringify(didntHandle)}. The ` +
|
|
561
585
|
`command *was* handled by these: ${JSON.stringify(didHandle)}.`);
|
|
562
586
|
}
|
|
@@ -586,24 +610,6 @@ class AppiumDriver extends BaseDriver {
|
|
|
586
610
|
return res;
|
|
587
611
|
}
|
|
588
612
|
|
|
589
|
-
// We want to override basedriver's proxy avoidance detection since plugins might want to avoid
|
|
590
|
-
// proxying in order to handle a command, so we use this function to make an extra check whether
|
|
591
|
-
// plugins might want to do this.
|
|
592
|
-
proxyRouteIsAvoided (sessionId, method, url, body) {
|
|
593
|
-
if (super.proxyRouteIsAvoided(sessionId, method, url, body)) {
|
|
594
|
-
return true;
|
|
595
|
-
}
|
|
596
|
-
// if even just one plugin needs to avoid proxying, do so
|
|
597
|
-
for (const plugin of this.pluginsForSession(sessionId)) {
|
|
598
|
-
if (plugin.shouldAvoidProxy?.(method, url, body)) {
|
|
599
|
-
log.info(`Request to ${url} would normally be proxied, but plugin ${plugin.name} wants ` +
|
|
600
|
-
`to handle it internally. We'll avoid proxying in this case.`);
|
|
601
|
-
return true;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
return false;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
613
|
proxyActive (sessionId) {
|
|
608
614
|
const dstSession = this.sessions[sessionId];
|
|
609
615
|
return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
|
|
@@ -626,4 +632,22 @@ function isAppiumDriverCommand (cmd) {
|
|
|
626
632
|
return !isSessionCommand(cmd) || cmd === 'deleteSession';
|
|
627
633
|
}
|
|
628
634
|
|
|
635
|
+
/**
|
|
636
|
+
* Thrown when Appium tried to proxy a command using a driver's `proxyCommand` method but the
|
|
637
|
+
* method did not exist
|
|
638
|
+
*/
|
|
639
|
+
export class NoDriverProxyCommandError extends Error {
|
|
640
|
+
/**
|
|
641
|
+
* @type {Readonly<string>}
|
|
642
|
+
*/
|
|
643
|
+
code = 'APPIUMERR_NO_DRIVER_PROXYCOMMAND';
|
|
644
|
+
|
|
645
|
+
constructor () {
|
|
646
|
+
super(`The default behavior for this command was to proxy, but the driver ` +
|
|
647
|
+
`did not have the 'proxyCommand' method defined. To fully support ` +
|
|
648
|
+
`plugins, drivers should have 'proxyCommand' set to a jwpProxy object's ` +
|
|
649
|
+
`'command()' method, in addition to the normal 'proxyReqRes'`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
629
653
|
export { AppiumDriver };
|